Skip to content

Commit

Permalink
Merge pull request #949 from samchon/features/optional-query
Browse files Browse the repository at this point in the history
Features/optional query
  • Loading branch information
samchon authored Jul 8, 2024
2 parents 961de1f + 177ce88 commit 4e4e757
Show file tree
Hide file tree
Showing 34 changed files with 590 additions and 141 deletions.
2 changes: 1 addition & 1 deletion benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"reflect-metadata": "^0.2.2",
"tgrid": "^1.0.2",
"tstl": "^3.0.0",
"typia": "^6.4.0"
"typia": "^6.4.2"
},
"devDependencies": {
"@types/autocannon": "^7.9.0",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@nestia/station",
"version": "3.5.0-dev.20240706",
"version": "3.5.0-dev.20240707",
"description": "Nestia station",
"scripts": {
"build": "node build/index.js",
Expand Down
10 changes: 5 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/core",
"version": "3.5.0-dev.20240706",
"version": "3.5.0-dev.20240707",
"description": "Super-fast validation decorators of NestJS",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -36,7 +36,7 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@nestia/fetcher": "^3.5.0-dev.20240706",
"@nestia/fetcher": "^3.5.0-dev.20240707",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"@samchon/openapi": "^0.3.0",
Expand All @@ -49,16 +49,16 @@
"reflect-metadata": ">=0.1.12",
"rxjs": ">=6.0.3",
"tgrid": "^1.0.0",
"typia": "^6.4.0",
"typia": "^6.4.2",
"ws": "^7.5.3"
},
"peerDependencies": {
"@nestia/fetcher": ">=3.5.0-dev.20240706",
"@nestia/fetcher": ">=3.5.0-dev.20240707",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
"rxjs": ">=6.0.3",
"typia": ">=6.4.0 <7.0.0"
"typia": ">=6.4.2 <7.0.0"
},
"devDependencies": {
"@fastify/multipart": "^8.1.0",
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/programmers/TypedQueryProgrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export namespace TypedQueryProgrammer {
project: IProject,
) => (
modulo: ts.LeftHandSideExpression,
allowOptiona: boolean,
) => (type: ts.Type) => ts.ArrowFunction,
) =>
ts.factory.createObjectLiteralExpression([
Expand All @@ -36,7 +37,10 @@ export namespace TypedQueryProgrammer {
finite: false,
functional: false,
},
})(modulo)(type),
})(
modulo,
true,
)(type),
),
]);

Expand Down
2 changes: 1 addition & 1 deletion packages/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"ts-node": "^10.9.1",
"ts-patch": "^3.2.1",
"typescript": "^5.5.2",
"typia": "^6.4.0"
"typia": "^6.4.2"
},
"dependencies": {
"@nestia/fetcher": "^3.2.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/fetcher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/fetcher",
"version": "3.5.0-dev.20240706",
"version": "3.5.0-dev.20240707",
"description": "Fetcher library of Nestia SDK",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/migrate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"prettier": "^3.2.5",
"tstl": "^3.0.0",
"typescript": "^5.5.3",
"typia": "^6.4.0"
"typia": "^6.4.2"
},
"files": [
"lib",
Expand Down
18 changes: 16 additions & 2 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"name": "@nestia/sdk",
<<<<<<< HEAD
"version": "3.5.0-dev.20240707",
=======
"version": "3.5.0-dev.20240706",
>>>>>>> 961de1f1aeae07cc59cf24e43774df2bc53db2bc
"description": "Nestia SDK and Swagger generator",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -32,8 +36,13 @@
},
"homepage": "https://nestia.io",
"dependencies": {
<<<<<<< HEAD
"@nestia/fetcher": "^3.5.0-dev.20240707",
"@nestia/core": "^3.5.0-dev.20240707",
=======
"@nestia/fetcher": "^3.5.0-dev.20240706",
"@nestia/core": "^3.5.0-dev.20240706",
>>>>>>> 961de1f1aeae07cc59cf24e43774df2bc53db2bc
"@samchon/openapi": "^0.3.0",
"cli": "^1.0.1",
"get-function-location": "^2.0.0",
Expand All @@ -44,16 +53,21 @@
"tsconfck": "^2.0.1",
"tsconfig-paths": "^4.1.1",
"tstl": "^3.0.0",
"typia": "^6.4.0"
"typia": "^6.4.2"
},
"peerDependencies": {
<<<<<<< HEAD
"@nestia/fetcher": ">=3.5.0-dev.20240707",
"@nestia/core": ">=3.5.0-dev.20240707",
=======
"@nestia/fetcher": ">=3.5.0-dev.20240706",
"@nestia/core": ">=3.5.0-dev.20240706",
>>>>>>> 961de1f1aeae07cc59cf24e43774df2bc53db2bc
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
"ts-node": ">=10.6.0",
"typia": ">=6.4.0 <7.0.0"
"typia": ">=6.4.2 <7.0.0"
},
"devDependencies": {
"@nestia/e2e": "^0.4.3",
Expand Down
7 changes: 0 additions & 7 deletions packages/sdk/src/INestiaConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { INestApplication } from "@nestjs/common";
import { OpenApi } from "@samchon/openapi";

import type { INormalizedInput } from "./structures/INormalizedInput";

/**
* Definition for the `nestia.config.ts` file.
*
Expand Down Expand Up @@ -144,11 +142,6 @@ export interface INestiaConfig {
* @default false
*/
json?: boolean;

/**
* @internal
*/
normalized?: INormalizedInput;
}
export namespace INestiaConfig {
/**
Expand Down
38 changes: 28 additions & 10 deletions packages/sdk/src/analyses/TypedHttpOperationAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import path from "path";
import { HashMap } from "tstl";
import ts from "typescript";
import { CommentFactory } from "typia/lib/factories/CommentFactory";
import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
import { MetadataFactory } from "typia/lib/factories/MetadataFactory";

import { IErrorReport } from "../structures/IErrorReport";
import { INestiaProject } from "../structures/INestiaProject";
Expand Down Expand Up @@ -287,16 +289,32 @@ export namespace TypedHttpOperationAnalyzer {
optional === true &&
props.parameter.category === "query" &&
props.parameter.field === undefined
)
errors.push({
file: props.controller.file,
controller: props.controller.name,
function: props.function,
message:
`nestia does not support optional query parameter without field specification. ` +
`Therefore, erase question mark on the "${name}" parameter, ` +
`or re-define re-define parameters for each query parameters.`,
});
) {
const everyPropertiesAreOptional: boolean = (() => {
const res = MetadataFactory.analyze(project.checker)({
escape: false,
constant: true,
absorb: true,
})(new MetadataCollection())(type);
if (res.success === false) return false;
const m = res.data;
return (
m.size() === 1 &&
m.objects.length === 1 &&
m.objects[0]!.properties.every((p) => p.value.optional === true)
);
})();
if (everyPropertiesAreOptional === false)
errors.push({
file: props.controller.file,
controller: props.controller.name,
function: props.function,
message:
`nestia does not support optional query parameter exception field specification or every properties are optional case. ` +
`Therefore, erase question mark on the "${name}" parameter, ` +
`or re-define re-define parameters for each query parameters.`,
});
}

// GET TYPE NAME
const tuple: ITypeTuple | null = ImportAnalyzer.analyze(project.checker)({
Expand Down
21 changes: 19 additions & 2 deletions packages/sdk/src/generates/internal/SdkHttpNamespaceProgrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,14 @@ export namespace SdkHttpNamespaceProgrammer {
IdentifierFactory.parameter(
p.name,
p === props.query
? ts.factory.createTypeReferenceNode(`${route.name}.Query`)
? p.optional
? ts.factory.createUnionTypeNode([
ts.factory.createTypeReferenceNode(
`${route.name}.Query`,
),
ts.factory.createTypeReferenceNode("undefined"),
])
: ts.factory.createTypeReferenceNode(`${route.name}.Query`)
: getType(project)(importer)(p),
),
),
Expand Down Expand Up @@ -409,7 +416,17 @@ export namespace SdkHttpNamespaceProgrammer {
);
};
if (props.query !== undefined && g.query.length === 0)
return out(block(ts.factory.createIdentifier(props.query.name)));
return out(
block(
props.query.optional
? ts.factory.createBinaryExpression(
ts.factory.createIdentifier(props.query.name),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
ts.factory.createObjectLiteralExpression([], false),
)
: ts.factory.createIdentifier(props.query.name),
),
);
return out(
block(
ts.factory.createObjectLiteralExpression(
Expand Down
12 changes: 10 additions & 2 deletions packages/sdk/src/generates/internal/SwaggerSchemaValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ export namespace SwaggerSchemaValidator {
if (meta.objects.length !== 1 || meta.bucket() !== 1)
insert("only one object type is allowed.");
if (meta.nullable === true) insert("query parameters cannot be null.");
if (meta.isRequired() === false)
insert("query parameters cannot be undefined.");
if (meta.isRequired() === false) {
const everyPropertiesAreOptional: boolean =
meta.size() === 1 &&
meta.objects.length === 1 &&
meta.objects[0].properties.every((p) => p.value.optional);
if (everyPropertiesAreOptional === false)
insert(
"query parameters can be optional only when every properties are optional.",
);
}
} else if (
explore.nested !== null &&
explore.nested instanceof MetadataArray
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@
},
"dependencies": {
"@nestia/fetcher": "^3.4.2-dev.20240705",
"typia": "^6.4.0"
"typia": "^6.4.2"
}
}
2 changes: 1 addition & 1 deletion test/features/distribute-assert/packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@
},
"dependencies": {
"@nestia/fetcher": "^3.4.2-dev.20240705",
"typia": "^6.4.0"
"typia": "^6.4.2"
}
}
2 changes: 1 addition & 1 deletion test/features/distribute-json/packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@
},
"dependencies": {
"@nestia/fetcher": "^3.4.2-dev.20240705",
"typia": "^6.4.0"
"typia": "^6.4.2"
}
}
2 changes: 1 addition & 1 deletion test/features/distribute/packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@
},
"dependencies": {
"@nestia/fetcher": "^3.4.2-dev.20240705",
"typia": "^6.4.0"
"typia": "^6.4.2"
}
}
1 change: 1 addition & 0 deletions test/features/query-decompose/swagger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"openapi":"3.1.0","servers":[{"url":"https://github.com/samchon/nestia","description":"insert your server url"}],"info":{"version":"3.5.0-dev.20240707","title":"@nestia/test","description":"Test program of Nestia","license":{"name":"MIT"}},"paths":{"/query/typed":{"get":{"tags":[],"parameters":[{"name":"limit","in":"query","schema":{"type":"number"},"required":false},{"name":"enforce","in":"query","schema":{"type":"boolean"},"required":true},{"name":"values","in":"query","schema":{"type":"array","items":{"type":"string"}},"required":true},{"name":"atomic","in":"query","schema":{"oneOf":[{"type":"null"},{"type":"string"}]},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}},"/query/nest":{"get":{"tags":[],"parameters":[{"name":"limit","in":"query","schema":{"type":"string","pattern":"^([+-]?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)$"},"required":false},{"name":"enforce","in":"query","schema":{"oneOf":[{"const":"false"},{"const":"true"}]},"required":true},{"name":"atomic","in":"query","schema":{"type":"string"},"required":true},{"name":"values","in":"query","schema":{"type":"array","items":{"type":"string"}},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}},"/query/individual":{"get":{"tags":[],"parameters":[{"name":"id","in":"query","schema":{"type":"string"},"description":"","required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/query/composite":{"get":{"tags":[],"parameters":[{"name":"atomic","in":"query","schema":{"type":"string"},"description":"","required":true},{"name":"limit","in":"query","schema":{"type":"number"},"required":false},{"name":"enforce","in":"query","schema":{"type":"boolean"},"required":true},{"name":"values","in":"query","schema":{"type":"array","items":{"type":"string"}},"required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}}},"components":{"schemas":{"IQuery":{"type":"object","properties":{"limit":{"type":"number"},"enforce":{"type":"boolean"},"values":{"type":"array","items":{"type":"string"}},"atomic":{"oneOf":[{"type":"null"},{"type":"string"}]}},"required":["enforce","values","atomic"]},"INestQuery":{"type":"object","properties":{"limit":{"type":"string","pattern":"^([+-]?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)$"},"enforce":{"oneOf":[{"const":"false"},{"const":"true"}]},"atomic":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}},"required":["enforce","atomic","values"]},"OmitIQueryatomic":{"type":"object","properties":{"limit":{"type":"number"},"enforce":{"type":"boolean"},"values":{"type":"array","items":{"type":"string"}}},"required":["enforce","values"],"description":"Construct a type with the properties of T except for those in type K."}},"securitySchemes":{"bearer":{"type":"apiKey","in":"header","name":"Authorization"}}},"tags":[],"x-samchon-emended":true}
15 changes: 15 additions & 0 deletions test/features/query-error-optional/nestia.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { INestiaConfig } from "@nestia/sdk";

export const NESTIA_CONFIG: INestiaConfig = {
input: ["src/controllers"],
output: "src/api",
swagger: {
output: "swagger.json",
security: {
bearer: {
type: "apiKey",
},
},
},
};
export default NESTIA_CONFIG;
28 changes: 28 additions & 0 deletions test/features/query-error-optional/src/Backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import core from "@nestia/core";
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";

export class Backend {
private application_?: INestApplication;

public async open(): Promise<void> {
this.application_ = await NestFactory.create(
await core.EncryptedModule.dynamic(__dirname + "/controllers", {
key: "A".repeat(32),
iv: "B".repeat(16),
}),
{ logger: false },
);
await core.WebSocketAdaptor.upgrade(this.application_);
await this.application_.listen(37_000);
}

public async close(): Promise<void> {
if (this.application_ === undefined) return;

const app = this.application_;
await app.close();

delete this.application_;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { TypedQuery, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";

@Controller("query")
export class QueryController {
@TypedRoute.Get("optional")
public async optional(
@TypedQuery() query?: IOptionalQuery,
): Promise<IOptionalQuery> {
return (
query ?? {
b: 1,
c: false,
}
);
}
}
export interface IOptionalQuery {
a?: string;
b: number;
c: boolean;
}
1 change: 1 addition & 0 deletions test/features/query-error-optional/swagger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"openapi":"3.1.0","servers":[{"url":"https://github.com/samchon/nestia","description":"insert your server url"}],"info":{"version":"3.5.0-dev.20240707","title":"@nestia/test","description":"Test program of Nestia","license":{"name":"MIT"}},"paths":{"/query/typed":{"get":{"tags":[],"parameters":[{"name":"query","in":"query","schema":{"$ref":"#/components/schemas/IQuery"},"description":"","required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}},"/query/optional":{"get":{"tags":[],"parameters":[{"name":"query","in":"query","schema":{"$ref":"#/components/schemas/IOptionalQuery"},"description":"","required":false}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IOptionalQuery"}}}}}}},"/query/nest":{"get":{"tags":[],"parameters":[{"name":"query","in":"query","schema":{"$ref":"#/components/schemas/INestQuery"},"description":"","required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}},"/query/individual":{"get":{"tags":[],"parameters":[{"name":"id","in":"query","schema":{"type":"string"},"description":"","required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/query/composite":{"get":{"tags":[],"parameters":[{"name":"atomic","in":"query","schema":{"type":"string"},"description":"","required":true},{"name":"query","in":"query","schema":{"$ref":"#/components/schemas/OmitIQueryatomic"},"description":"","required":true}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}},"/query/body":{"post":{"tags":[],"parameters":[],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/IQuery"}}},"required":true},"responses":{"201":{"description":"","content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/IQuery"}}}}}}}},"components":{"schemas":{"IQuery":{"type":"object","properties":{"limit":{"type":"number"},"enforce":{"type":"boolean"},"values":{"type":"array","items":{"type":"string"}},"atomic":{"oneOf":[{"type":"null"},{"type":"string"}]}},"required":["enforce","atomic"]},"IOptionalQuery":{"type":"object","properties":{"a":{"type":"string"},"b":{"type":"number"},"c":{"type":"boolean"}}},"INestQuery":{"type":"object","properties":{"limit":{"type":"string","pattern":"^([+-]?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)$"},"enforce":{"oneOf":[{"const":"false"},{"const":"true"}]},"atomic":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}},"required":["enforce","atomic","values"]},"OmitIQueryatomic":{"type":"object","properties":{"limit":{"type":"number"},"enforce":{"type":"boolean"},"values":{"type":"array","items":{"type":"string"}}},"required":["enforce"],"description":"Construct a type with the properties of T except for those in type K."}},"securitySchemes":{"bearer":{"type":"apiKey","in":"header","name":"Authorization"}}},"tags":[],"x-samchon-emended":true}
Loading

0 comments on commit 4e4e757

Please sign in to comment.