Skip to content

Commit

Permalink
fix(schema): fix nullable decorator and mixed type usage
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Jul 8, 2024
1 parent 8ac321d commit 9c553d3
Show file tree
Hide file tree
Showing 27 changed files with 1,010 additions and 224 deletions.
118 changes: 115 additions & 3 deletions docs/docs/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,58 @@ class NullableModel {
`returnsCoercedValue` will become true by default in the next major version of Ts.ED.
:::

## Nullable and mixed types <Badge text="7.75.0+"/>

The @@Nullable@@ decorator can be used with Tuple types:

```ts
import {Nullable} from "@tsed/schema";

class Model {
@Nullable(String, Number)
prop: string | number | null;
}
```

Since v7.75.0, when you use @@Nullable@@ decorator combined with other decorators like @@MinLength@@, @@Minimum@@, etc. metadata will be automatically assigned to the right
type. For example, if you add a @@Minimum@@ decorator, it will be assigned to the number type.

```ts
import {Nullable} from "@tsed/schema";

class Model {
@Nullable(String, Number)
@Minimum(0)
@MaxLength(100)
prop: string | number | null;
}
```

Produce a json-schema as follows:

```json
{
"properties": {
"prop": {
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"maxLength": 100
},
{
"type": "number",
"minimum": 0
}
]
}
},
"type": "object"
}
```

## Any

The @@Any@@ decorator is used to allow any types:
Expand All @@ -212,7 +264,30 @@ export class Model {
{
"properties": {
"prop": {
"type": ["integer", "number", "string", "boolean", "array", "object", "null"]
"anyOf": [
{
"type": "null"
},
{
"type": "integer",
"multipleOf": 1
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "boolean"
},
{
"type": "array"
},
{
"type": "object"
}
]
}
},
"type": "object"
Expand All @@ -227,9 +302,10 @@ export class Model {
"properties": {
"prop": {
"nullable": true,
"oneOf": [
"anyOf": [
{
"type": "integer"
"type": "integer",
"multipleOf": 1
},
{
"type": "number"
Expand All @@ -256,6 +332,42 @@ export class Model {
</Tab>
</Tabs>

Since v7.75.0, when you use @@Any@@ decorator combined with other decorators like @@MinLength@@, @@Minimum@@, etc. metadata will be automatically assigned to the right
type. For example, if you add a @@Minimum@@ decorator, it will be assigned to the number type.

```ts
import {Nullable} from "@tsed/schema";

class Model {
@Any(String, Number)
@Minimum(0)
@MaxLength(100)
prop: string | number;
}
```

Produce a json-schema as follows:

```json
{
"properties": {
"prop": {
"oneOf": [
{
"type": "string",
"maxLength": 100
},
{
"type": "number",
"minimum": 0
}
]
}
},
"type": "object"
}
```

## Regular expressions

The @@Pattern@@ decorator is used to restrict a string to a particular regular expression. The regular expression syntax
Expand Down
12 changes: 10 additions & 2 deletions packages/platform/platform-params/src/decorators/useType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import {Type} from "@tsed/core";
import {Any, CollectionOf} from "@tsed/schema";
import {Any, CollectionOf, type JsonParameterStore} from "@tsed/schema";
import {ParamFn} from "./paramFn.js";

function shouldFallBackToAny(entity: JsonParameterStore) {
if (entity.itemSchema.has("allOf") || entity.itemSchema.has("oneOf") || entity.itemSchema.has("oneOf")) {
return false;
}

return entity.isCollection && entity.type === Object && [undefined, "object"].includes(entity.itemSchema.get("type"));
}

/**
* Set the type of the item collection.
*
Expand All @@ -19,7 +27,7 @@ export function UseType(useType: undefined | any | Type<any>) {
return CollectionOf(useType);
}

if (entity.isCollection && entity.type === Object && [undefined, "object"].includes(entity.itemSchema.get("type"))) {
if (shouldFallBackToAny(entity)) {
Any()(...parameters);
}
});
Expand Down
17 changes: 17 additions & 0 deletions packages/specs/schema/src/constants/jsonSchemaProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const MANY_OF_PROPERTIES = ["oneOf", "allOf", "anyOf"];
export const STRING_PROPERTIES = ["minLength", "maxLength", "pattern", "format"];
export const BOOLEAN_PROPERTIES = [];
export const NUMBER_PROPERTIES = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
export const ARRAY_PROPERTIES = ["maxItems", "minItems", "uniqueItems", "items", "contains", "maxContains", "minContains"];
export const OBJECT_PROPERTIES = [
"maxItems",
"minItems",
"uniqueItems",
"items",
"contains",
"maxContains",
"minContains",
"patternProperties",
"dependencies"
];
export const COMMON_PROPERTIES = ["const", "enum"];
13 changes: 10 additions & 3 deletions packages/specs/schema/src/decorators/common/allow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,15 @@ describe("@Allow", () => {
expect(classSchema).toEqual({
properties: {
allow: {
minLength: 1,
type: ["null", "string"]
anyOf: [
{
type: "null"
},
{
minLength: 1,
type: "string"
}
]
}
},
required: ["allow"],
Expand Down Expand Up @@ -108,7 +115,7 @@ describe("@Allow", () => {
},
properties: {
allow: {
oneOf: [
anyOf: [
{
type: "null"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/specs/schema/src/decorators/common/allow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ export function Allow(...values: any[]) {
return useDecorators(
model && Property(model),
JsonEntityFn((store, args) => {
store.schema.allow(...values);

if (store.decoratorType === DecoratorTypes.PARAM) {
(store as JsonParameterStore).required = true;
}

if (store.decoratorType === DecoratorTypes.PROP) {
store.parentSchema.addRequired(store.propertyName);
}

store.schema.allow(...values);
})
);
}
49 changes: 46 additions & 3 deletions packages/specs/schema/src/decorators/common/any.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,30 @@ describe("@Any", () => {
expect(getJsonSchema(Model)).toEqual({
properties: {
prop: {
type: ["null", "integer", "number", "string", "boolean", "array", "object"]
anyOf: [
{
type: "null"
},
{
multipleOf: 1,
type: "integer"
},
{
type: "number"
},
{
type: "string"
},
{
type: "boolean"
},
{
type: "array"
},
{
type: "object"
}
]
}
},
type: "object"
Expand Down Expand Up @@ -47,7 +70,20 @@ describe("@Any", () => {
expect(getJsonSchema(Model)).toEqual({
properties: {
prop: {
type: ["null", "string", "number", "boolean"]
anyOf: [
{
type: "null"
},
{
type: "string"
},
{
type: "number"
},
{
type: "boolean"
}
]
}
},
type: "object"
Expand All @@ -64,7 +100,14 @@ describe("@Any", () => {
expect(getJsonSchema(Model)).toEqual({
properties: {
prop: {
type: ["null", "string"]
anyOf: [
{
type: "null"
},
{
type: "string"
}
]
}
},
type: "object"
Expand Down
Loading

0 comments on commit 9c553d3

Please sign in to comment.