@rexeus/typeweaver-types 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2424 -0
- package/dist/lib/InvalidResponseStatusCodeError.ts +21 -0
- package/dist/lib/RequestValidator.ts +43 -0
- package/dist/lib/ResponseValidator.ts +39 -0
- package/dist/lib/assert.ts +5 -0
- package/dist/lib/index.ts +4 -0
- package/dist/templates/Request.ejs +93 -0
- package/dist/templates/RequestValidator.ejs +92 -0
- package/dist/templates/Response.ejs +78 -0
- package/dist/templates/ResponseValidator.ejs +142 -0
- package/dist/templates/SharedResponse.ejs +38 -0
- package/package.json +50 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IHttpResponse } from "@rexeus/typeweaver-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when an HTTP response has an unexpected status code.
|
|
5
|
+
*
|
|
6
|
+
* This error is typically thrown during response validation when the
|
|
7
|
+
* actual status code doesn't match any of the expected status codes
|
|
8
|
+
* defined in the operation specification.
|
|
9
|
+
*
|
|
10
|
+
* The error includes the complete response for debugging and error handling.
|
|
11
|
+
*/
|
|
12
|
+
export class InvalidResponseStatusCodeError extends Error {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new InvalidResponseStatusCodeError.
|
|
15
|
+
*
|
|
16
|
+
* @param response - The HTTP response with unexpected status code
|
|
17
|
+
*/
|
|
18
|
+
constructor(public response: IHttpResponse) {
|
|
19
|
+
super("Invalid response status code");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IHttpRequest,
|
|
3
|
+
IRequestValidator,
|
|
4
|
+
SafeRequestValidationResult,
|
|
5
|
+
} from "@rexeus/typeweaver-core";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for HTTP request validation.
|
|
9
|
+
*
|
|
10
|
+
* This class provides the foundation for request validators that:
|
|
11
|
+
* - Validate headers, body, query parameters, and path parameters
|
|
12
|
+
* - Support both safe (non-throwing) and unsafe (throwing) validation
|
|
13
|
+
* - Integrate with Zod schemas for runtime validation
|
|
14
|
+
* - Provide detailed error information for debugging
|
|
15
|
+
*
|
|
16
|
+
* Implementations should validate all request components and either:
|
|
17
|
+
* - Return validated data (for `validate`)
|
|
18
|
+
* - Return success/error result (for `safeValidate`)
|
|
19
|
+
*/
|
|
20
|
+
export abstract class RequestValidator implements IRequestValidator {
|
|
21
|
+
public constructor() {
|
|
22
|
+
//
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validates a request without throwing errors.
|
|
27
|
+
*
|
|
28
|
+
* @param request - The HTTP request to validate
|
|
29
|
+
* @returns A result object containing either the validated request or error details
|
|
30
|
+
*/
|
|
31
|
+
public abstract safeValidate(
|
|
32
|
+
request: IHttpRequest
|
|
33
|
+
): SafeRequestValidationResult<IHttpRequest>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validates a request and throws if validation fails.
|
|
37
|
+
*
|
|
38
|
+
* @param request - The HTTP request to validate
|
|
39
|
+
* @returns The validated request with proper typing
|
|
40
|
+
* @throws {RequestValidationError} If any part of the request fails validation
|
|
41
|
+
*/
|
|
42
|
+
public abstract validate(request: IHttpRequest): IHttpRequest;
|
|
43
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IHttpResponse,
|
|
3
|
+
IResponseValidator,
|
|
4
|
+
SafeResponseValidationResult,
|
|
5
|
+
} from "@rexeus/typeweaver-core";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for HTTP response validation.
|
|
9
|
+
*
|
|
10
|
+
* This class provides the foundation for response validators that:
|
|
11
|
+
* - Validate response status codes match expected values
|
|
12
|
+
* - Validate response headers and body against schemas
|
|
13
|
+
* - Support both safe (non-throwing) and unsafe (throwing) validation
|
|
14
|
+
* - Integrate with Zod schemas for runtime validation
|
|
15
|
+
*
|
|
16
|
+
* Response validators are typically used in API clients to ensure
|
|
17
|
+
* responses match the expected format before processing.
|
|
18
|
+
*/
|
|
19
|
+
export abstract class ResponseValidator implements IResponseValidator {
|
|
20
|
+
/**
|
|
21
|
+
* Validates a response without throwing errors.
|
|
22
|
+
*
|
|
23
|
+
* @param response - The HTTP response to validate
|
|
24
|
+
* @returns A result object containing either the validated response or error details
|
|
25
|
+
*/
|
|
26
|
+
public abstract safeValidate(
|
|
27
|
+
response: IHttpResponse
|
|
28
|
+
): SafeResponseValidationResult<IHttpResponse>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validates a response and throws if validation fails.
|
|
32
|
+
*
|
|
33
|
+
* @param response - The HTTP response to validate
|
|
34
|
+
* @returns The validated response with proper typing
|
|
35
|
+
* @throws {InvalidResponseStatusCodeError} If status code doesn't match expected
|
|
36
|
+
* @throws {ResponseValidationError} If response structure fails validation
|
|
37
|
+
*/
|
|
38
|
+
public abstract validate(response: IHttpResponse): IHttpResponse;
|
|
39
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import definition from "<%= sourcePath %>";
|
|
2
|
+
import { HttpMethod, type IHttpResponse } from "@rexeus/typeweaver-core";
|
|
3
|
+
import { RequestCommand } from "../lib/clients";
|
|
4
|
+
import { <%= pascalCaseOperationId %>ResponseValidator } from "<%= responseValidationFile %>";
|
|
5
|
+
import {
|
|
6
|
+
type <%= pascalCaseOperationId %>Response,
|
|
7
|
+
<% for (const ownResponse of [...ownSuccessResponses, ...ownErrorResponses]) { %>
|
|
8
|
+
<%= ownResponse.name %>Response,
|
|
9
|
+
<% } %>
|
|
10
|
+
} from "<%= responseFile %>";
|
|
11
|
+
<% for (const sharedResponse of [...sharedSuccessResponses, ...sharedErrorResponses]) { %>
|
|
12
|
+
import { <%= sharedResponse.name %>Response } from "<%= sharedResponse.path %>";
|
|
13
|
+
<% } %>
|
|
14
|
+
|
|
15
|
+
<% if (headerTsType) { %>
|
|
16
|
+
export type I<%= pascalCaseOperationId %>RequestHeader = <%- headerTsType %>
|
|
17
|
+
<% } %>
|
|
18
|
+
|
|
19
|
+
<% if (paramTsType) { %>
|
|
20
|
+
export type I<%= pascalCaseOperationId %>RequestParam = <%- paramTsType %>
|
|
21
|
+
<% } %>
|
|
22
|
+
|
|
23
|
+
<% if (queryTsType) { %>
|
|
24
|
+
export type I<%= pascalCaseOperationId %>RequestQuery = <%- queryTsType %>
|
|
25
|
+
<% } %>
|
|
26
|
+
|
|
27
|
+
<% if (bodyTsType) { %>
|
|
28
|
+
export type I<%= pascalCaseOperationId %>RequestBody = <%- bodyTsType %>
|
|
29
|
+
<% } %>
|
|
30
|
+
|
|
31
|
+
export type I<%= pascalCaseOperationId %>Request = {
|
|
32
|
+
path: string;
|
|
33
|
+
method: HttpMethod.<%= method %>;
|
|
34
|
+
<%= headerTsType ? `header: I${pascalCaseOperationId}RequestHeader;` : "" %>
|
|
35
|
+
<%= paramTsType ? `param: I${pascalCaseOperationId}RequestParam;` : "" %>
|
|
36
|
+
<%= queryTsType ? `query: I${pascalCaseOperationId}RequestQuery;` : "" %>
|
|
37
|
+
<%= bodyTsType ? `body: I${pascalCaseOperationId}RequestBody;` : "" %>
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
<% if (!hasSuccessResponses) { %>
|
|
41
|
+
export type Successful<%= pascalCaseOperationId %>Response = never;
|
|
42
|
+
<% } else if (!hasErrorResponses) { %>
|
|
43
|
+
export type Successful<%= pascalCaseOperationId %>Response = <%= pascalCaseOperationId %>Response;
|
|
44
|
+
<% } else { %>
|
|
45
|
+
export type Successful<%= pascalCaseOperationId %>Response = Exclude<<%= pascalCaseOperationId %>Response, <%= [...ownErrorResponses, ...sharedErrorResponses].map(({ name }) => `${name}Response`).join(" | ") %>
|
|
46
|
+
>;
|
|
47
|
+
<% } %>
|
|
48
|
+
|
|
49
|
+
export class <%= pascalCaseOperationId %>RequestCommand extends RequestCommand implements I<%= pascalCaseOperationId %>Request {
|
|
50
|
+
public override readonly method = definition.method as HttpMethod.<%= method %>;
|
|
51
|
+
public override readonly path = definition.path;
|
|
52
|
+
|
|
53
|
+
public <%= headerTsType ? `override` : `declare` %> readonly header: <%= headerTsType ? `I${pascalCaseOperationId}RequestHeader` : `undefined` %>;
|
|
54
|
+
public <%= paramTsType ? `override` : `declare` %> readonly param: <%= paramTsType ? `I${pascalCaseOperationId}RequestParam` : `undefined` %>;
|
|
55
|
+
public <%= queryTsType ? `override` : `declare` %> readonly query: <%= queryTsType ? `I${pascalCaseOperationId}RequestQuery` : `undefined` %>;
|
|
56
|
+
public <%= bodyTsType ? `override` : `declare` %> readonly body: <%= bodyTsType ? `I${pascalCaseOperationId}RequestBody` : `undefined` %>;
|
|
57
|
+
|
|
58
|
+
private readonly responseValidator: <%= pascalCaseOperationId %>ResponseValidator;
|
|
59
|
+
|
|
60
|
+
public constructor(input: Omit<I<%= pascalCaseOperationId %>Request, "method" | "path">) {
|
|
61
|
+
super();
|
|
62
|
+
|
|
63
|
+
<% if (headerTsType) { %>
|
|
64
|
+
this.header = input.header;
|
|
65
|
+
<% } %>
|
|
66
|
+
|
|
67
|
+
<% if (paramTsType) { %>
|
|
68
|
+
this.param = input.param;
|
|
69
|
+
<% } %>
|
|
70
|
+
|
|
71
|
+
<% if (queryTsType) { %>
|
|
72
|
+
this.query = input.query;
|
|
73
|
+
<% } %>
|
|
74
|
+
|
|
75
|
+
<% if (bodyTsType) { %>
|
|
76
|
+
this.body = input.body;
|
|
77
|
+
<% } %>
|
|
78
|
+
|
|
79
|
+
this.responseValidator = new <%= pascalCaseOperationId %>ResponseValidator();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public processResponse(response: IHttpResponse): Successful<%= pascalCaseOperationId %>Response {
|
|
83
|
+
const result = this.responseValidator.validate(response);
|
|
84
|
+
|
|
85
|
+
<% for (const successResponse of [...ownSuccessResponses, ...sharedSuccessResponses]) { %>
|
|
86
|
+
if (result instanceof <%= successResponse.name %>Response) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
<% } %>
|
|
90
|
+
|
|
91
|
+
throw result;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import definition from "<%= sourcePath %>";
|
|
2
|
+
import {
|
|
3
|
+
type IHttpRequest,
|
|
4
|
+
type SafeRequestValidationResult,
|
|
5
|
+
RequestValidationError
|
|
6
|
+
} from "@rexeus/typeweaver-core";
|
|
7
|
+
import { RequestValidator } from "../lib/types";
|
|
8
|
+
import type { I<%= pascalCaseOperationId %>Request } from "<%= requestFile %>";
|
|
9
|
+
|
|
10
|
+
export class <%= pascalCaseOperationId %>RequestValidator extends RequestValidator {
|
|
11
|
+
public safeValidate(request: IHttpRequest): SafeRequestValidationResult<I<%= pascalCaseOperationId %>Request> {
|
|
12
|
+
const error = new RequestValidationError();
|
|
13
|
+
const validatedRequest: IHttpRequest = {
|
|
14
|
+
method: request.method,
|
|
15
|
+
path: request.path,
|
|
16
|
+
query: undefined,
|
|
17
|
+
header: undefined,
|
|
18
|
+
body: undefined,
|
|
19
|
+
param: undefined,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
<% if(body) { %>
|
|
23
|
+
if (definition.request.body) {
|
|
24
|
+
const result = definition.request.body.safeParse(request.body);
|
|
25
|
+
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
error.addBodyIssues(result.error.issues);
|
|
28
|
+
} else {
|
|
29
|
+
validatedRequest.body = result.data;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
<% } %>
|
|
33
|
+
|
|
34
|
+
<% if(header) { %>
|
|
35
|
+
if (definition.request.header) {
|
|
36
|
+
const result = definition.request.header.safeParse(request.header);
|
|
37
|
+
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
error.addHeaderIssues(result.error.issues);
|
|
40
|
+
} else {
|
|
41
|
+
validatedRequest.header = result.data;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
<% } %>
|
|
45
|
+
|
|
46
|
+
<% if(param) { %>
|
|
47
|
+
if (definition.request.param) {
|
|
48
|
+
const result = definition.request.param.safeParse(request.param);
|
|
49
|
+
|
|
50
|
+
if (!result.success) {
|
|
51
|
+
error.addPathParamIssues(result.error.issues);
|
|
52
|
+
} else {
|
|
53
|
+
validatedRequest.param = result.data;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
<% } %>
|
|
57
|
+
|
|
58
|
+
<% if(query) { %>
|
|
59
|
+
if (definition.request.query) {
|
|
60
|
+
const result = definition.request.query.safeParse(request.query);
|
|
61
|
+
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
error.addQueryIssues(result.error.issues);
|
|
64
|
+
} else {
|
|
65
|
+
validatedRequest.query = result.data;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
<% } %>
|
|
69
|
+
|
|
70
|
+
if (error.hasIssues()) {
|
|
71
|
+
return {
|
|
72
|
+
isValid: false,
|
|
73
|
+
error,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
isValid: true,
|
|
79
|
+
data: validatedRequest as I<%= pascalCaseOperationId %>Request,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public validate(request: IHttpRequest): I<%= pascalCaseOperationId %>Request {
|
|
84
|
+
const result = this.safeValidate(request);
|
|
85
|
+
|
|
86
|
+
if (!result.isValid) {
|
|
87
|
+
throw result.error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result.data;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { HttpResponse, HttpStatusCode } from "<%= coreDir %>";
|
|
2
|
+
<% for(const sharedResponse of sharedResponses) { %>
|
|
3
|
+
import type { I<%= sharedResponse.name %>Response, <%= sharedResponse.name %>Response } from "<%= sharedResponse.path %>";
|
|
4
|
+
<% } %>
|
|
5
|
+
|
|
6
|
+
<% for (const ownResponse of ownResponses) { %>
|
|
7
|
+
<% if (ownResponse.header) { %>
|
|
8
|
+
export type I<%= ownResponse.name %>ResponseHeader = <%- ownResponse.header %>;
|
|
9
|
+
<% } %>
|
|
10
|
+
|
|
11
|
+
<% if (ownResponse.body) { %>
|
|
12
|
+
export type I<%= ownResponse.name %>ResponseBody = <%- ownResponse.body %>;
|
|
13
|
+
<% } %>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export type I<%= ownResponse.name %>Response = {
|
|
17
|
+
statusCode: HttpStatusCode.<%= ownResponse.statusCodeKey %>
|
|
18
|
+
<%= ownResponse.header ? `header: I${ownResponse.name}ResponseHeader;` : "" %>
|
|
19
|
+
<%= ownResponse.body ? `body: I${ownResponse.name}ResponseBody;` : "" %>
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class <%= ownResponse.name %>Response extends HttpResponse<
|
|
23
|
+
<%= ownResponse.header ? `I${ownResponse.name}ResponseHeader` : 'undefined' %>,
|
|
24
|
+
<%= ownResponse.body ? `I${ownResponse.name}ResponseBody` : 'undefined' %>,
|
|
25
|
+
> implements I<%= ownResponse.name %>Response {
|
|
26
|
+
public override readonly statusCode: HttpStatusCode.<%= ownResponse.statusCodeKey %>;
|
|
27
|
+
|
|
28
|
+
public constructor(response: I<%= ownResponse.name %>Response) {
|
|
29
|
+
super(
|
|
30
|
+
response.statusCode,
|
|
31
|
+
<%= ownResponse.header ? `response.header` : 'undefined' %>,
|
|
32
|
+
<%= ownResponse.body ? `response.body` : 'undefined' %>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if(response.statusCode !== HttpStatusCode.<%= ownResponse.statusCodeKey %>) {
|
|
36
|
+
throw new Error(`Invalid status code: '${response.statusCode}' for <%= ownResponse.name %>Response`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.statusCode = response.statusCode;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
<% } %>
|
|
43
|
+
|
|
44
|
+
<%
|
|
45
|
+
// Identify success responses (status codes 200-299)
|
|
46
|
+
const successResponses = ownResponses.filter(response => response.statusCode >= 200 && response.statusCode < 300);
|
|
47
|
+
const errorResponses = ownResponses.filter(response => response.statusCode < 200 || response.statusCode >= 300);
|
|
48
|
+
%>
|
|
49
|
+
|
|
50
|
+
<% if (successResponses.length > 0) { %>
|
|
51
|
+
export type I<%= pascalCaseOperationId %>SuccessResponses =<% if (successResponses.length === 1) { %> I<%= successResponses[0].name %>Response;<% } else { %>
|
|
52
|
+
<% for (const [index, successResponse] of successResponses.entries()) { %>
|
|
53
|
+
<% if (index === 0) { %> I<%= successResponse.name %>Response<% } else { %>
|
|
54
|
+
| I<%= successResponse.name %>Response<% } %>
|
|
55
|
+
<% } %>;<% } %>
|
|
56
|
+
|
|
57
|
+
export type <%= pascalCaseOperationId %>SuccessResponses =<% if (successResponses.length === 1) { %> <%= successResponses[0].name %>Response;<% } else { %>
|
|
58
|
+
<% for (const [index, successResponse] of successResponses.entries()) { %>
|
|
59
|
+
<% if (index === 0) { %> <%= successResponse.name %>Response<% } else { %>
|
|
60
|
+
| <%= successResponse.name %>Response<% } %>
|
|
61
|
+
<% } %>;<% } %>
|
|
62
|
+
<% } %>
|
|
63
|
+
|
|
64
|
+
export type I<%= pascalCaseOperationId %>Response =
|
|
65
|
+
<% for (const ownResponse of ownResponses) { %>
|
|
66
|
+
| I<%= ownResponse.name %>Response
|
|
67
|
+
<% } %>
|
|
68
|
+
<% for (const sharedResponse of sharedResponses) { %>
|
|
69
|
+
| I<%= sharedResponse.name %>Response
|
|
70
|
+
<% } %>;
|
|
71
|
+
|
|
72
|
+
export type <%= pascalCaseOperationId %>Response =
|
|
73
|
+
<% for (const ownResponse of ownResponses) { %>
|
|
74
|
+
| <%= ownResponse.name %>Response
|
|
75
|
+
<% } %>
|
|
76
|
+
<% for (const sharedResponse of sharedResponses) { %>
|
|
77
|
+
| <%= sharedResponse.name %>Response
|
|
78
|
+
<% } %>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import definition from "<%= sourcePath %>";
|
|
2
|
+
import {
|
|
3
|
+
type IHttpResponse,
|
|
4
|
+
type SafeResponseValidationResult,
|
|
5
|
+
ResponseValidationError
|
|
6
|
+
} from "@rexeus/typeweaver-core";
|
|
7
|
+
import {
|
|
8
|
+
ResponseValidator,
|
|
9
|
+
InvalidResponseStatusCodeError,
|
|
10
|
+
assert,
|
|
11
|
+
} from "../lib/types";
|
|
12
|
+
import {
|
|
13
|
+
type <%= pascalCaseOperationId %>Response,
|
|
14
|
+
<% for (const ownResponse of ownResponses) { %>
|
|
15
|
+
type I<%= ownResponse.name %>Response,
|
|
16
|
+
<%= ownResponse.name %>Response,
|
|
17
|
+
<% } %>
|
|
18
|
+
} from "<%= responseFile %>";
|
|
19
|
+
<% for (const sharedResponse of sharedResponses) { %>
|
|
20
|
+
import {
|
|
21
|
+
type I<%= sharedResponse.name %>Response,
|
|
22
|
+
<%= sharedResponse.name %>Response,
|
|
23
|
+
} from "<%= sharedResponse.importPath %>";
|
|
24
|
+
<% } %>
|
|
25
|
+
|
|
26
|
+
export class <%= pascalCaseOperationId %>ResponseValidator extends ResponseValidator {
|
|
27
|
+
public safeValidate(response: IHttpResponse): SafeResponseValidationResult<<%= pascalCaseOperationId %>Response> {
|
|
28
|
+
const error = new ResponseValidationError(response.statusCode);
|
|
29
|
+
const validationResult = this.validateAgainstDefinedResponses(response, error);
|
|
30
|
+
|
|
31
|
+
if (error.hasIssues() && !validationResult.validResponseName) {
|
|
32
|
+
return {
|
|
33
|
+
isValid: false,
|
|
34
|
+
error,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let data: <%= pascalCaseOperationId %>Response;
|
|
39
|
+
switch (response.statusCode) {
|
|
40
|
+
<% for (const statusCode of allStatusCodes) { %>
|
|
41
|
+
case <%= statusCode.statusCode %>: {
|
|
42
|
+
<%
|
|
43
|
+
for (const response of [...ownResponses, ...sharedResponses]) {
|
|
44
|
+
if (response.statusCode === statusCode.statusCode) {
|
|
45
|
+
%>
|
|
46
|
+
if (validationResult.validResponseName === "<%= response.name %>") {
|
|
47
|
+
data = new <%= response.name %>Response(validationResult.validatedResponse as unknown as I<%= response.name %>Response);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
<% } } %>
|
|
51
|
+
throw new Error("Could not find a response for status code '<%= statusCode.statusCode %>'");
|
|
52
|
+
}
|
|
53
|
+
<% } %>
|
|
54
|
+
|
|
55
|
+
default: {
|
|
56
|
+
throw new InvalidResponseStatusCodeError(response);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
isValid: true,
|
|
62
|
+
data,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public validate(response: IHttpResponse): <%= pascalCaseOperationId %>Response {
|
|
67
|
+
const result = this.safeValidate(response);
|
|
68
|
+
|
|
69
|
+
if (!result.isValid) {
|
|
70
|
+
throw result.error;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result.data;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private validateAgainstDefinedResponses(response: IHttpResponse, error: ResponseValidationError): { validResponseName?: string; validatedResponse?: IHttpResponse } {
|
|
77
|
+
const validatedResponse: IHttpResponse = {
|
|
78
|
+
statusCode: response.statusCode,
|
|
79
|
+
header: undefined,
|
|
80
|
+
body: undefined,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
<% for (const statusCode of allStatusCodes) { %>
|
|
84
|
+
if (response.statusCode === <%= statusCode.statusCode %>) {
|
|
85
|
+
<%
|
|
86
|
+
for (const response of [...ownResponses, ...sharedResponses]) {
|
|
87
|
+
if (response.statusCode === statusCode.statusCode) {
|
|
88
|
+
%>
|
|
89
|
+
const is<%= response.name %>Response = this.validate<%= response.name %>Response(response, validatedResponse, error);
|
|
90
|
+
if (is<%= response.name %>Response) {
|
|
91
|
+
return { validResponseName: "<%= response.name %>", validatedResponse };
|
|
92
|
+
}
|
|
93
|
+
<% } } %>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
<% } %>
|
|
97
|
+
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
<% for (const response of [...ownResponses, ...sharedResponses]) { %>
|
|
102
|
+
private validate<%= response.name %>Response(response: IHttpResponse, validatedResponse: IHttpResponse, error: ResponseValidationError): boolean {
|
|
103
|
+
let isValid = true;
|
|
104
|
+
|
|
105
|
+
<% if(response.hasBody) { %>
|
|
106
|
+
assert(
|
|
107
|
+
definition.responses[<%= response.index %>]
|
|
108
|
+
&& "body" in definition.responses[<%= response.index %>]
|
|
109
|
+
&& definition.responses[<%= response.index %>].body,
|
|
110
|
+
"'<%= response.name %>ResponseBody' has to be defined in the definition"
|
|
111
|
+
);
|
|
112
|
+
const validateBodyResult = definition.responses[<%= response.index %>].body.safeParse(response.body);
|
|
113
|
+
|
|
114
|
+
if (!validateBodyResult.success) {
|
|
115
|
+
error.addBodyIssues(validateBodyResult.error.issues);
|
|
116
|
+
isValid = false;
|
|
117
|
+
} else {
|
|
118
|
+
validatedResponse.body = validateBodyResult.data;
|
|
119
|
+
}
|
|
120
|
+
<% } %>
|
|
121
|
+
|
|
122
|
+
<% if(response.hasHeader) { %>
|
|
123
|
+
assert(
|
|
124
|
+
definition.responses[<%= response.index %>]
|
|
125
|
+
&& "header" in definition.responses[<%= response.index %>]
|
|
126
|
+
&& definition.responses[<%= response.index %>].header,
|
|
127
|
+
"'<%= response.name %>ResponseHeader' has to be defined in the definition"
|
|
128
|
+
);
|
|
129
|
+
const validateHeaderResult = definition.responses[<%= response.index %>].header.safeParse(response.header);
|
|
130
|
+
|
|
131
|
+
if (!validateHeaderResult.success) {
|
|
132
|
+
error.addHeaderIssues(validateHeaderResult.error.issues);
|
|
133
|
+
isValid = false;
|
|
134
|
+
} else {
|
|
135
|
+
validatedResponse.header = validateHeaderResult.data;
|
|
136
|
+
}
|
|
137
|
+
<% } %>
|
|
138
|
+
|
|
139
|
+
return isValid;
|
|
140
|
+
}
|
|
141
|
+
<% } %>
|
|
142
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { HttpResponse, HttpStatusCode } from "<%= coreDir %>";
|
|
2
|
+
|
|
3
|
+
<% if (headerTsType) { %>
|
|
4
|
+
export type I<%= pascalCaseName %>ResponseHeader = <%- headerTsType %>;
|
|
5
|
+
<% } %>
|
|
6
|
+
|
|
7
|
+
<% if (bodyTsType) { %>
|
|
8
|
+
export type I<%= pascalCaseName %>ResponseBody = <%- bodyTsType %>;
|
|
9
|
+
<% } %>
|
|
10
|
+
|
|
11
|
+
export type I<%= pascalCaseName %>Response = {
|
|
12
|
+
statusCode: HttpStatusCode.<%= httpStatusCode[sharedResponse.statusCode] %>
|
|
13
|
+
<%= sharedResponse.header ? `header: I${pascalCaseName}ResponseHeader;` : "" %>
|
|
14
|
+
<%= sharedResponse.body ? `body: I${pascalCaseName}ResponseBody;` : "" %>
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class <%= pascalCaseName %>Response extends HttpResponse<
|
|
18
|
+
<%= sharedResponse.header ? `I${pascalCaseName}ResponseHeader` : 'undefined' %>,
|
|
19
|
+
<%= sharedResponse.body ? `I${pascalCaseName}ResponseBody` : 'undefined' %>
|
|
20
|
+
> implements I<%= pascalCaseName %>Response {
|
|
21
|
+
public override readonly statusCode: HttpStatusCode.<%= httpStatusCode[sharedResponse.statusCode] %>;
|
|
22
|
+
|
|
23
|
+
public constructor(response: I<%= pascalCaseName %>Response) {
|
|
24
|
+
super(
|
|
25
|
+
response.statusCode,
|
|
26
|
+
<%= sharedResponse.header ? `response.header` : 'undefined' %>,
|
|
27
|
+
<%= sharedResponse.body ? `response.body` : 'undefined' %>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (response.statusCode !== HttpStatusCode.<%= httpStatusCode[sharedResponse.statusCode] %>) {
|
|
31
|
+
throw new Error(`Invalid status code: '${response.statusCode}' for <%= pascalCaseName %>Response`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.statusCode = response.statusCode;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rexeus/typeweaver-types",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "TypeScript type and Zod validator generators for TypeWeaver APIs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"package.json",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"api",
|
|
15
|
+
"spec",
|
|
16
|
+
"definition",
|
|
17
|
+
"typescript",
|
|
18
|
+
"zod",
|
|
19
|
+
"generator",
|
|
20
|
+
"typeweaver"
|
|
21
|
+
],
|
|
22
|
+
"author": "Dennis Wentzien <dw@rexeus.com>",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/rexeus/typeweaver.git",
|
|
27
|
+
"directory": "packages/types"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/rexeus/typeweaver/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/rexeus/typeweaver#readme",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@rexeus/typeweaver-core": "*",
|
|
35
|
+
"@rexeus/typeweaver-gen": "*"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@rexeus/typeweaver-core": "0.0.1",
|
|
39
|
+
"@rexeus/typeweaver-gen": "0.0.1"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"typescript": "^5.8.3"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"format": "prettier --write .",
|
|
47
|
+
"build": "pkgroll --clean-dist && cp -r ./src/templates ./dist/templates && cp -r ./src/lib ./dist/lib",
|
|
48
|
+
"preversion": "npm run build"
|
|
49
|
+
}
|
|
50
|
+
}
|