Skip to content

Commit

Permalink
add "map" option for object (#625)
Browse files Browse the repository at this point in the history
* add "map" option for object

* fix error for Deno <v1.23

* remove immediate value for map property

* rename getProperty method

* refactor checking method of rules

* update documents

* update CHANGELOG
  • Loading branch information
shimataro authored Jan 4, 2025
1 parent 3ba2a75 commit 3febbdc
Show file tree
Hide file tree
Showing 38 changed files with 434 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

* `map` rule

### Others

* support Bun>=1.0.0
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@ const schemaObject = { // schema for input
separatedBy: "-",
checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD,
}),
remoteAddr: vs.string({ // IPv4
remoteAddr: vs.string({ // IPv4, mapped to "remote_addr" property
pattern: vs.STRING.PATTERN.IPV4,
map: "remote_addr",
}),
remoteAddrIpv6: vs.string({ // IPv6
remoteAddrIpv6: vs.string({ // IPv6, mapped to "remote-addr-ipv6" property
pattern: vs.STRING.PATTERN.IPV6,
map: "remote-addr-ipv6",
}),
limit: vs.number({ // number, integer, omittable (sets 10 if omitted), >=1 (sets 1 if less), <=100 (sets 100 if greater)
ifUndefined: 10,
Expand Down Expand Up @@ -122,8 +124,8 @@ const input = { // input values
classes: "1,3,abc,4",
skills: "c,c++,javascript,python,,swift,kotlin",
creditCard: "4111-1111-1111-1111",
remoteAddr: "127.0.0.1",
remoteAddrIpv6: "::1",
remote_ddr: "127.0.0.1",
"remote-addr-ipv6": "::1",
limit: "0",
};
const expected = { // should be transformed to this
Expand Down
16 changes: 16 additions & 0 deletions dist-deno/appliers/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { isObject } from "../libs/types.ts";
export interface Rules {
/** map to input property (only available in object) */
map?: string;
}
/**
* check the input has rules of map or not
* @param rules unknown rules
* @returns Yes/No
*/
export function hasMapRules(rules: unknown): rules is Required<Rules> {
if (!isObject(rules)) {
return false;
}
return typeof rules.map === "string";
}
6 changes: 4 additions & 2 deletions dist-deno/libs/applySchemaObjectCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ export function applySchemaObjectCore<S extends SchemaObject>(schemaObject: S, d
const appliedObject: AnyObject = {};
let hasError = false;
for (const key of Object.keys(schemaObject)) {
// A trick in order to call _applyTo() private method from the outside (like "friend")
appliedObject[key] = schemaObject[key]["_applyTo"](data[key], errorHandler, [...keyStack, key]);
const schema = schemaObject[key];
// A trick to call non-public properties/methods from the outside (like "friend" in C++)
const prop = schema["_getPropertyName"](key);
appliedObject[key] = schema["_applyTo"](data[prop], errorHandler, [...keyStack, prop]);
}
if (hasError) {
onFinishWithError();
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/ArraySchema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as transform from "../appliers/transform.ts";
import * as each from "../appliers/array/each.ts";
import * as maxLength from "../appliers/array/maxLength.ts";
import * as minLength from "../appliers/array/minLength.ts";
import * as type from "../appliers/array/type.ts";
import { BaseSchema } from "./BaseSchema.ts";
export type RulesForArray<T> = transform.Rules<T[]> & ifUndefined.Rules<T[]> & ifEmptyString.Rules<T[]> & ifNull.Rules<T[]> & each.Rules<T> & minLength.Rules & maxLength.Rules & type.Rules;
export type RulesForArray<T> = transform.Rules<T[]> & map.Rules & ifUndefined.Rules<T[]> & ifEmptyString.Rules<T[]> & ifNull.Rules<T[]> & each.Rules<T> & minLength.Rules & maxLength.Rules & type.Rules;
export class ArraySchema<T, Tx = never> extends BaseSchema<T[] | Tx> {
constructor(rules: RulesForArray<T>) {
super(rules, [
Expand Down
14 changes: 14 additions & 0 deletions dist-deno/schemaClasses/BaseSchema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { hasMapRules } from "../appliers/map.ts";
import { Key, makeValues, Values } from "../libs/types.ts";
import { ErrorHandler } from "../libs/publicTypes.ts";
import { ValueSchemaError } from "../libs/ValueSchemaError.ts";
Expand Down Expand Up @@ -41,6 +42,19 @@ export class BaseSchema<T = unknown> {
return onError(err as ValueSchemaError);
}
}
/**
* get a "mapped" property
* @param defaultProperty default property
* @returns mapped property
* @see Rules (appliers/map.ts)
* @protected in order to repress TS6133 error
*/
protected _getPropertyName(defaultProperty: string): string {
if (!hasMapRules(this.rules)) {
return defaultProperty;
}
return this.rules.map;
}
}
/**
* default error handler
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/BooleanSchema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as type from "../appliers/boolean/type.ts";
import { BaseSchema } from "./BaseSchema.ts";
export type RulesForBoolean = ifUndefined.Rules<boolean> & ifEmptyString.Rules<boolean> & ifNull.Rules<boolean> & type.Rules;
export type RulesForBoolean = map.Rules & ifUndefined.Rules<boolean> & ifEmptyString.Rules<boolean> & ifNull.Rules<boolean> & type.Rules;
export class BooleanSchema<Tx = never> extends BaseSchema<boolean | Tx> {
constructor(rules: RulesForBoolean) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/DateSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as transform from "../appliers/transform.ts";
import * as iso8601 from "../appliers/date/iso8601.ts";
import * as maxValue from "../appliers/date/maxValue.ts";
Expand All @@ -11,7 +12,7 @@ import { BaseSchema } from "./BaseSchema.ts";
export const DATE = {
UNIXTIME: unixtime.UNIXTIME
} as const;
export type RulesForDate = transform.Rules<Date> & ifUndefined.Rules<Date> & ifEmptyString.Rules<Date> & ifNull.Rules<Date> & iso8601.Rules & maxValue.Rules & minValue.Rules & unixtime.Rules;
export type RulesForDate = transform.Rules<Date> & map.Rules & ifUndefined.Rules<Date> & ifEmptyString.Rules<Date> & ifNull.Rules<Date> & iso8601.Rules & maxValue.Rules & minValue.Rules & unixtime.Rules;
export class DateSchema<Tx = never> extends BaseSchema<Date | Tx> {
constructor(rules: RulesForDate) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/EmailSchema.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as maxLength from "../appliers/email/maxLength.ts";
import * as pattern from "../appliers/email/pattern.ts";
import * as trims from "../appliers/string/trims.ts";
import * as type from "../appliers/string/type.ts";
import { BaseSchema } from "./BaseSchema.ts";
export type RulesForEmail = ifEmptyString.Rules<string> & ifNull.Rules<string> & ifUndefined.Rules<string> & pattern.Rules & maxLength.Rules & trims.Rules & type.Rules;
export type RulesForEmail = map.Rules & ifEmptyString.Rules<string> & ifNull.Rules<string> & ifUndefined.Rules<string> & pattern.Rules & maxLength.Rules & trims.Rules & type.Rules;
export class EmailSchema<Tx = never> extends BaseSchema<string | Tx> {
constructor(rules: RulesForEmail) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/EnumerationSchema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as only from "../appliers/only.ts";
import { BaseSchema } from "./BaseSchema.ts";
export type RulesForEnumeration<E> = ifUndefined.Rules<E> & ifEmptyString.Rules<E> & ifNull.Rules<E> & Required<only.Rules<E>>;
export type RulesForEnumeration<E> = map.Rules & ifUndefined.Rules<E> & ifEmptyString.Rules<E> & ifNull.Rules<E> & Required<only.Rules<E>>;
export class EnumerationSchema<E, Tx = never> extends BaseSchema<E | Tx> {
constructor(rules: RulesForEnumeration<E>) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/NumberSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as only from "../appliers/only.ts";
import * as transform from "../appliers/transform.ts";
import * as acceptsFullWidth from "../appliers/number/acceptsFullWidth.ts";
Expand All @@ -11,7 +12,7 @@ import { BaseSchema } from "./BaseSchema.ts";
export const NUMBER = {
INTEGER: type.INTEGER
} as const;
export type RulesForNumber = transform.Rules<number> & ifUndefined.Rules<number> & ifEmptyString.Rules<number> & ifNull.Rules<number> & only.Rules<number> & acceptsFullWidth.Rules & type.Rules & minValue.Rules & maxValue.Rules;
export type RulesForNumber = transform.Rules<number> & map.Rules & ifUndefined.Rules<number> & ifEmptyString.Rules<number> & ifNull.Rules<number> & only.Rules<number> & acceptsFullWidth.Rules & type.Rules & minValue.Rules & maxValue.Rules;
export class NumberSchema<Tx = never> extends BaseSchema<number | Tx> {
constructor(rules: RulesForNumber) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/NumericStringSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as only from "../appliers/only.ts";
import * as transform from "../appliers/transform.ts";
import * as checksum from "../appliers/numericString/checksum.ts";
Expand All @@ -15,7 +16,7 @@ import { BaseSchema } from "../schemaClasses/BaseSchema.ts";
export const NUMERIC_STRING = {
CHECKSUM_ALGORITHM: checksum.CHECKSUM_ALGORITHM
};
export type RulesForNumericString = transform.Rules<string> & ifEmptyString.Rules<string> & ifNull.Rules<string> & ifUndefined.Rules<string> & only.Rules<string> & checksum.Rules & fullWidthToHalf.Rules & joinsArray.Rules & pattern.Rules & separatedBy.Rules & type.Rules & minLength.Rules & maxLength.Rules;
export type RulesForNumericString = transform.Rules<string> & map.Rules & ifEmptyString.Rules<string> & ifNull.Rules<string> & ifUndefined.Rules<string> & only.Rules<string> & checksum.Rules & fullWidthToHalf.Rules & joinsArray.Rules & pattern.Rules & separatedBy.Rules & type.Rules & minLength.Rules & maxLength.Rules;
export class NumericStringSchema<Tx = never> extends BaseSchema<string | Tx> {
constructor(rules: RulesForNumericString) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/ObjectSchema.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as transform from "../appliers/transform.ts";
import * as schema from "../appliers/object/schema.ts";
import * as type from "../appliers/object/type.ts";
import { AnyObject, ObjectTypeOf, SchemaObject } from "../libs/types.ts";
import { BaseSchema } from "./BaseSchema.ts";
export type RulesForObject<S> = transform.Rules<AnyObject> & ifUndefined.Rules<AnyObject> & ifEmptyString.Rules<AnyObject> & ifNull.Rules<AnyObject> & schema.Rules<S> & type.Rules;
export type RulesForObject<S> = transform.Rules<AnyObject> & map.Rules & ifUndefined.Rules<AnyObject> & ifEmptyString.Rules<AnyObject> & ifNull.Rules<AnyObject> & schema.Rules<S> & type.Rules;
export class ObjectSchema<S extends SchemaObject, Tx = never> extends BaseSchema<ObjectTypeOf<S> | Tx> {
constructor(rules: RulesForObject<S>) {
super(rules, [
Expand Down
3 changes: 2 additions & 1 deletion dist-deno/schemaClasses/StringSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ifEmptyString from "../appliers/ifEmptyString.ts";
import * as ifNull from "../appliers/ifNull.ts";
import * as ifUndefined from "../appliers/ifUndefined.ts";
import * as map from "../appliers/map.ts";
import * as only from "../appliers/only.ts";
import * as transform from "../appliers/transform.ts";
import * as maxLength from "../appliers/string/maxLength.ts";
Expand All @@ -12,7 +13,7 @@ import { BaseSchema } from "./BaseSchema.ts";
export const STRING = {
PATTERN: pattern.PATTERN
} as const;
export type RulesForString = transform.Rules<string> & ifEmptyString.Rules<string> & ifNull.Rules<string> & ifUndefined.Rules<string> & only.Rules<string> & type.Rules & trims.Rules & minLength.Rules & maxLength.Rules & pattern.Rules;
export type RulesForString = transform.Rules<string> & map.Rules & ifEmptyString.Rules<string> & ifNull.Rules<string> & ifUndefined.Rules<string> & only.Rules<string> & type.Rules & trims.Rules & minLength.Rules & maxLength.Rules & pattern.Rules;
export class StringSchema<Tx = never> extends BaseSchema<string | Tx> {
constructor(rules: RulesForString) {
super(rules, [
Expand Down
25 changes: 25 additions & 0 deletions docs/reference/array.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Accepts array or character-separated string (such as comma).
export function array<T>(rules?: RulesForArray<T>): ArraySchema;

type RulesForArray<T> = {
map?: string;
ifUndefined?: T[] | null;
ifEmptyString?: T[] | null;
ifNull?: T[] | null;
Expand Down Expand Up @@ -49,6 +50,30 @@ assert.throws(

## rules

### `map`

Maps the specified value to the properties of the input object.

**NOTE:** This rule is only available in [`object`](./object.md).

```javascript
const schemaObject = {
languageSkills: vs.array({
map: "language_skills",
separatedBy: ",",
each: {
schema: vs.string(),
},
}),
};
const input = { // input values
language_skills: "c,c++,javascript,python,swift,kotlin",
};
assert.deepStrictEqual(
vs.applySchemaObject(schemaObject, input),
{languageSkills: ["c", "c++", "javascript", "python", "swift", "kotlin"]});
```

### `ifUndefined`

Specifies return value when input value is `undefined`.
Expand Down
10 changes: 6 additions & 4 deletions docs/reference/basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@ const schemaObject = { // schema for input
separatedBy: "-",
checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD,
}),
remoteAddr: vs.string({ // IPv4
remoteAddr: vs.string({ // IPv4, mapped to "remote_addr" property
pattern: vs.STRING.PATTERN.IPV4,
map: "remote_addr",
}),
remoteAddrIpv6: vs.string({ // IPv6
remoteAddrIpv6: vs.string({ // IPv6, mapped to "remote-addr-ipv6" property
pattern: vs.STRING.PATTERN.IPV6,
map: "remote-addr-ipv6",
}),
limit: vs.number({ // number, integer, omittable (sets 10 if omitted), >=1 (sets 1 if less), <=100 (sets 100 if greater)
ifUndefined: 10,
Expand Down Expand Up @@ -117,8 +119,8 @@ const input = { // input values
classes: "1,3,abc,4",
skills: "c,c++,javascript,python,,swift,kotlin",
creditCard: "4111-1111-1111-1111",
remoteAddr: "127.0.0.1",
remoteAddrIpv6: "::1",
remote_ddr: "127.0.0.1",
"remote-addr-ipv6": "::1",
limit: "0",
};
const expected = { // should be transformed to this
Expand Down
21 changes: 21 additions & 0 deletions docs/reference/boolean.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type RulesForBoolean = {
strictType?: boolean;
acceptsAllNumbers?: boolean;

map?: string;
ifUndefined?: boolean | null;
ifEmptyString?: boolean | null;
ifNull?: boolean | null;
Expand Down Expand Up @@ -117,6 +118,26 @@ assert.strictEqual(
true);
```

### `map`

Maps the specified value to the properties of the input object.

**NOTE:** This rule is only available in [`object`](./object.md).

```javascript
const schemaObject = {
isAvailable: vs.boolean({
map: "is_available",
}),
};
const input = { // input values
is_available: true,
};
assert.deepStrictEqual(
vs.applySchemaObject(schemaObject, input),
{isAvailable: true});
```

### `ifUndefined`

Specifies return value when input value is `undefined`.
Expand Down
21 changes: 21 additions & 0 deletions docs/reference/date.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type RulesForNumber = {
precision: UNIXTIME.PRECISION;
},

map?: string;
ifUndefined?: Date | null;
ifEmptyString?: Date | null;
ifNull?: Date | null;
Expand Down Expand Up @@ -168,6 +169,26 @@ assert.throws(
{name: "ValueSchemaError", rule: vs.RULE.PATTERN});
```

### `map`

Maps the specified value to the properties of the input object.

**NOTE:** This rule is only available in [`object`](./object.md).

```javascript
const schemaObject = {
startsAt: vs.date({
map: "starts_at",
}),
};
const input = { // input values
starts_at: "2000-01-02T03:04:05.000Z",
};
assert.deepStrictEqual(
vs.applySchemaObject(schemaObject, input),
{startsAt: new Date("2000-01-02T03:04:05.000Z")});
```

### `ifUndefined`

Specifies return value when input value is `undefined`.
Expand Down
Loading

0 comments on commit 3febbdc

Please sign in to comment.