genoc 0.4.2 → 0.5.0
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 +2 -1
- package/dist/analyzer/analyze.js +139 -28
- package/dist/analyzer/path-analyzer.d.ts +21 -1
- package/dist/analyzer/path-analyzer.js +3 -1
- package/dist/analyzer/types.d.ts +25 -0
- package/dist/cli/impl.js +3 -1
- package/dist/generator/client-generator.js +65 -5
- package/dist/generator/contracts-generator.js +7 -0
- package/dist/generator/method-generator.d.ts +9 -1
- package/dist/generator/method-generator.js +38 -1
- package/dist/parser/spec-reader.js +6 -25
- package/dist/parser/version/spec-links.d.ts +55 -0
- package/dist/parser/version/spec-links.js +65 -0
- package/dist/parser/version/v3.0/validator.d.ts +2 -0
- package/dist/parser/version/v3.0/validator.js +23 -13
- package/dist/parser/version/v3.1/validator.d.ts +2 -0
- package/dist/parser/version/v3.1/validator.js +25 -13
- package/dist/parser/version/validate-security.d.ts +17 -0
- package/dist/parser/version/validate-security.js +218 -0
- package/dist/parser/version/version-detector.js +27 -8
- package/dist/runtime/defined-errors.d.ts +3 -2
- package/dist/runtime/defined-errors.js +6 -3
- package/dist/runtime/errors.d.ts +1 -3
- package/dist/runtime/errors.js +2 -6
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/requester.d.ts +67 -0
- package/dist/types/openapi.d.ts +5 -4
- package/dist/utils/http-methods.d.ts +8 -0
- package/dist/utils/http-methods.js +17 -0
- package/dist/utils/operation-naming.js +6 -0
- package/package.json +1 -1
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Version detector for multi-version OpenAPI support
|
|
3
3
|
*/
|
|
4
|
+
import { specLinkUrl, SpecViolationError } from './spec-links.js';
|
|
4
5
|
export function detectSpecVersion(rawSpec) {
|
|
5
6
|
if (typeof rawSpec !== 'object' || rawSpec === null || Array.isArray(rawSpec)) {
|
|
6
|
-
throw new
|
|
7
|
+
throw new SpecViolationError('Invalid spec: must be a non-null object', 'document-root');
|
|
7
8
|
}
|
|
8
9
|
const spec = rawSpec;
|
|
9
10
|
// Check for Swagger 2.0
|
|
10
11
|
if ('swagger' in spec && typeof spec.swagger === 'string') {
|
|
11
12
|
if (spec.swagger === '2.0') {
|
|
13
|
+
// Unsupported feature, not a spec violation — the document is valid.
|
|
12
14
|
throw new Error('Swagger 2.0 is not supported. Convert to OpenAPI using swagger2openapi first.');
|
|
13
15
|
}
|
|
14
16
|
throw new Error(`Unsupported Swagger version: ${spec.swagger}`);
|
|
15
17
|
}
|
|
16
18
|
// Check for OpenAPI 3.0+
|
|
17
19
|
if (!('openapi' in spec) || typeof spec.openapi !== 'string') {
|
|
18
|
-
throw new
|
|
20
|
+
throw new SpecViolationError("Invalid spec: missing or invalid 'openapi' field", 'openapi-field');
|
|
19
21
|
}
|
|
20
22
|
const openapiVersion = spec.openapi;
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
throw new Error(`Invalid OpenAPI version format: ${openapiVersion}`);
|
|
23
|
+
// The version must be major.minor, optionally with a patch segment.
|
|
24
|
+
if (!openapiVersion.match(/^\d+\.\d+(\.\d+)?$/)) {
|
|
25
|
+
throw new SpecViolationError(`Invalid OpenAPI version format: ${openapiVersion}`, 'openapi-field');
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
// The format check above guarantees two leading numeric segments.
|
|
28
|
+
const majorMinorVersion = openapiVersion.split('.').slice(0, 2).join('.');
|
|
27
29
|
// Validate supported versions
|
|
28
30
|
const supportedVersions = ['3.0', '3.1', '3.2'];
|
|
29
31
|
if (!supportedVersions.includes(majorMinorVersion)) {
|
|
32
|
+
// Unsupported feature, not a spec violation — the document is valid.
|
|
30
33
|
throw new Error(`Unsupported OpenAPI version: ${openapiVersion}. ` +
|
|
31
34
|
`Supported versions: ${supportedVersions.join(', ')}`);
|
|
32
35
|
}
|
|
@@ -40,8 +43,24 @@ export function detectSpecVersion(rawSpec) {
|
|
|
40
43
|
* dialect; `preserveRefSiblings` follows the effective dialect.
|
|
41
44
|
*/
|
|
42
45
|
export function resolveVersion(doc, override) {
|
|
43
|
-
|
|
46
|
+
let detected;
|
|
47
|
+
try {
|
|
48
|
+
detected = detectSpecVersion(doc);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof SpecViolationError) {
|
|
52
|
+
// The dialect is unknown (or the document is not a document at all), so
|
|
53
|
+
// the link targets the dialect being applied; the override decides when
|
|
54
|
+
// given, otherwise the latest dialect. Two-space indent: this error is
|
|
55
|
+
// thrown standalone, not as a ` - ` bullet of validateSpec's message.
|
|
56
|
+
throw new Error(`${error.message}\n ${specLinkUrl(override ?? '3.1', error.linkKey)}`, {
|
|
57
|
+
cause: error,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
44
62
|
if (detected !== '3.0' && detected !== '3.1') {
|
|
63
|
+
// Unsupported feature, not a spec violation — the document is valid.
|
|
45
64
|
throw new Error('OpenAPI 3.2 is not yet supported. Supported versions: 3.0, 3.1');
|
|
46
65
|
}
|
|
47
66
|
const effective = override ?? detected;
|
|
@@ -9,8 +9,9 @@ export declare function decorateWithErrors<T, E>(item: T, runtimeErrors: unknown
|
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* Type guard that narrows a caught error to a method's defined error union.
|
|
12
|
-
* Returns false for `UnspecifiedApiError` (status not declared in the spec)
|
|
13
|
-
*
|
|
12
|
+
* Returns false for `UnspecifiedApiError` (status not declared in the spec),
|
|
13
|
+
* for `DefaultApiError` (declared `default` response - not part of the
|
|
14
|
+
* per-status error union), and for anything that is not an `ApiError`.
|
|
14
15
|
*/
|
|
15
16
|
export declare function isDefinedError<E extends ApiError<number, unknown>>(err: unknown, _fn: {
|
|
16
17
|
__definedErrors: E;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiError, UnspecifiedApiError } from './errors.js';
|
|
1
|
+
import { ApiError, DefaultApiError, UnspecifiedApiError } from './errors.js';
|
|
2
2
|
/**
|
|
3
3
|
* Attaches the `__definedErrors` metadata property to a client method.
|
|
4
4
|
* The property carries the method's error codes and is used by
|
|
@@ -15,12 +15,15 @@ export function decorateWithErrors(item, runtimeErrors) {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Type guard that narrows a caught error to a method's defined error union.
|
|
18
|
-
* Returns false for `UnspecifiedApiError` (status not declared in the spec)
|
|
19
|
-
*
|
|
18
|
+
* Returns false for `UnspecifiedApiError` (status not declared in the spec),
|
|
19
|
+
* for `DefaultApiError` (declared `default` response - not part of the
|
|
20
|
+
* per-status error union), and for anything that is not an `ApiError`.
|
|
20
21
|
*/
|
|
21
22
|
export function isDefinedError(err, _fn) {
|
|
22
23
|
if (err instanceof UnspecifiedApiError)
|
|
23
24
|
return false;
|
|
25
|
+
if (err instanceof DefaultApiError)
|
|
26
|
+
return false;
|
|
24
27
|
if (!(err instanceof ApiError))
|
|
25
28
|
return false;
|
|
26
29
|
return true;
|
package/dist/runtime/errors.d.ts
CHANGED
|
@@ -16,9 +16,7 @@ export declare class UnspecifiedApiError extends ApiError<number, unknown> {
|
|
|
16
16
|
constructor(status: number, data: unknown, message: string);
|
|
17
17
|
}
|
|
18
18
|
/** Error thrown for operations that declare a `default` response. */
|
|
19
|
-
export declare class DefaultApiError<TData> extends
|
|
20
|
-
readonly status: number;
|
|
21
|
-
readonly data: TData;
|
|
19
|
+
export declare class DefaultApiError<TData> extends ApiError<number, TData> {
|
|
22
20
|
constructor(status: number, data: TData, message: string);
|
|
23
21
|
}
|
|
24
22
|
/** Wraps unexpected failures inside a `Requester` implementation. */
|
package/dist/runtime/errors.js
CHANGED
|
@@ -24,13 +24,9 @@ export class UnspecifiedApiError extends ApiError {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
/** Error thrown for operations that declare a `default` response. */
|
|
27
|
-
export class DefaultApiError extends
|
|
28
|
-
status;
|
|
29
|
-
data;
|
|
27
|
+
export class DefaultApiError extends ApiError {
|
|
30
28
|
constructor(status, data, message) {
|
|
31
|
-
super(message);
|
|
32
|
-
this.status = status;
|
|
33
|
-
this.data = data;
|
|
29
|
+
super(status, data, message);
|
|
34
30
|
this.name = 'DefaultApiError';
|
|
35
31
|
}
|
|
36
32
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
export { ApiError, UnspecifiedApiError, DefaultApiError, RequesterFailError } from './errors.js';
|
|
9
9
|
export { StreamResponse, ErrorResponse } from './responses.js';
|
|
10
10
|
export { decorateWithErrors, isDefinedError } from './defined-errors.js';
|
|
11
|
-
export type { Requester } from './requester.js';
|
|
11
|
+
export type { Requester, SecurityScheme, SecurityCall, SecurityRequirement, OAuthFlow, OAuthFlows, } from './requester.js';
|
|
@@ -1,14 +1,81 @@
|
|
|
1
1
|
import type { ErrorResponse, StreamResponse } from './responses.js';
|
|
2
|
+
/**
|
|
3
|
+
* OAuth 2.0 flow configuration, spec-agnostic structural twin of the OpenAPI
|
|
4
|
+
* OAuth Flow Object. Which URLs are meaningful depends on the flow kind.
|
|
5
|
+
*/
|
|
6
|
+
export type OAuthFlow = {
|
|
7
|
+
authorizationUrl?: string;
|
|
8
|
+
tokenUrl?: string;
|
|
9
|
+
refreshUrl?: string;
|
|
10
|
+
scopes: Record<string, string>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* OAuth 2.0 flows, spec-agnostic structural twin of the OpenAPI OAuth Flows
|
|
14
|
+
* Object.
|
|
15
|
+
*/
|
|
16
|
+
export type OAuthFlows = {
|
|
17
|
+
implicit?: OAuthFlow;
|
|
18
|
+
password?: OAuthFlow;
|
|
19
|
+
clientCredentials?: OAuthFlow;
|
|
20
|
+
authorizationCode?: OAuthFlow;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Spec-agnostic structural twin of an OpenAPI security scheme object,
|
|
24
|
+
* discriminated on `type` so implementations can narrow.
|
|
25
|
+
*/
|
|
26
|
+
export type SecurityScheme = {
|
|
27
|
+
type: 'apiKey';
|
|
28
|
+
name: string;
|
|
29
|
+
in: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'http';
|
|
33
|
+
scheme: string;
|
|
34
|
+
bearerFormat?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'oauth2';
|
|
38
|
+
flows: OAuthFlows;
|
|
39
|
+
description?: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'openIdConnect';
|
|
42
|
+
openIdConnectUrl: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'mutualTLS';
|
|
46
|
+
description?: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* One scheme invocation inside a security requirement: the scheme name as
|
|
50
|
+
* declared in the spec, the scopes requested for it, and the resolved scheme
|
|
51
|
+
* object.
|
|
52
|
+
*/
|
|
53
|
+
export type SecurityCall = {
|
|
54
|
+
schemeName: string;
|
|
55
|
+
scopes: readonly string[];
|
|
56
|
+
scheme: SecurityScheme;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* One security alternative. Several `SecurityCall`s inside one requirement are
|
|
60
|
+
* conjunctive — all must be satisfied.
|
|
61
|
+
*/
|
|
62
|
+
export type SecurityRequirement = readonly SecurityCall[];
|
|
2
63
|
/**
|
|
3
64
|
* Performs an HTTP request and returns the response.
|
|
4
65
|
*
|
|
5
66
|
* When `expectStream` is true, the implementation should return
|
|
6
67
|
* a `StreamResponse` containing the stream data, filename (from
|
|
7
68
|
* Content-Disposition header), and response headers.
|
|
69
|
+
*
|
|
70
|
+
* When `security` is present, it carries the operation's security requirements
|
|
71
|
+
* as an OR-list of AND-alternatives (spec `security` semantics): satisfying any
|
|
72
|
+
* one requirement authorizes the request. Each call carries the fully resolved
|
|
73
|
+
* scheme object. Absent `security` means the operation is public.
|
|
8
74
|
*/
|
|
9
75
|
export type Requester = <TResponse>(method: string, path: string, options: {
|
|
10
76
|
query?: Record<string, unknown>;
|
|
11
77
|
body?: unknown;
|
|
12
78
|
headers?: Record<string, string>;
|
|
13
79
|
expectStream?: true;
|
|
80
|
+
security?: readonly SecurityRequirement[];
|
|
14
81
|
}) => Promise<TResponse | StreamResponse | ErrorResponse>;
|
package/dist/types/openapi.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export type OperationObject = {
|
|
|
72
72
|
requestBody?: RequestBodyObject | ReferenceObject;
|
|
73
73
|
responses: ResponsesObject;
|
|
74
74
|
deprecated?: boolean;
|
|
75
|
-
security?:
|
|
75
|
+
security?: SecurityRequirementObject[];
|
|
76
76
|
servers?: unknown[];
|
|
77
77
|
};
|
|
78
78
|
export type PathItemObject = {
|
|
@@ -140,8 +140,9 @@ export type OAuth2FlowsObject = {
|
|
|
140
140
|
clientCredentials?: OAuth2FlowClientCredentials;
|
|
141
141
|
authorizationCode?: OAuth2FlowAuthorizationCode;
|
|
142
142
|
};
|
|
143
|
+
export type SecurityRequirementObject = Record<string, string[]>;
|
|
143
144
|
export type SecuritySchemeObject = {
|
|
144
|
-
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
|
|
145
|
+
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect' | 'mutualTLS';
|
|
145
146
|
description?: string;
|
|
146
147
|
name?: string;
|
|
147
148
|
in?: 'query' | 'header' | 'cookie';
|
|
@@ -156,7 +157,7 @@ export type ComponentsObject = {
|
|
|
156
157
|
parameters?: Record<string, ParameterObject | ReferenceObject>;
|
|
157
158
|
requestBodies?: Record<string, RequestBodyObject | ReferenceObject>;
|
|
158
159
|
headers?: Record<string, unknown>;
|
|
159
|
-
securitySchemes?: Record<string, SecuritySchemeObject>;
|
|
160
|
+
securitySchemes?: Record<string, SecuritySchemeObject | ReferenceObject>;
|
|
160
161
|
links?: Record<string, unknown>;
|
|
161
162
|
callbacks?: Record<string, unknown>;
|
|
162
163
|
examples?: Record<string, unknown>;
|
|
@@ -167,7 +168,7 @@ export type OpenAPIDocument = {
|
|
|
167
168
|
servers?: ServerObject[];
|
|
168
169
|
paths?: PathsObject;
|
|
169
170
|
components?: ComponentsObject;
|
|
170
|
-
security?:
|
|
171
|
+
security?: SecurityRequirementObject[];
|
|
171
172
|
tags?: unknown[];
|
|
172
173
|
externalDocs?: {
|
|
173
174
|
description?: string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eight OpenAPI Operation Object HTTP methods, in canonical generation
|
|
3
|
+
* order (the order methods appear in generated clients).
|
|
4
|
+
*
|
|
5
|
+
* Single source shared by the analyzer (path-item iteration) and the
|
|
6
|
+
* validators (per-operation checks) — do not inline local copies.
|
|
7
|
+
*/
|
|
8
|
+
export declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete", "options", "head", "trace"];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eight OpenAPI Operation Object HTTP methods, in canonical generation
|
|
3
|
+
* order (the order methods appear in generated clients).
|
|
4
|
+
*
|
|
5
|
+
* Single source shared by the analyzer (path-item iteration) and the
|
|
6
|
+
* validators (per-operation checks) — do not inline local copies.
|
|
7
|
+
*/
|
|
8
|
+
export const HTTP_METHODS = [
|
|
9
|
+
'get',
|
|
10
|
+
'post',
|
|
11
|
+
'put',
|
|
12
|
+
'patch',
|
|
13
|
+
'delete',
|
|
14
|
+
'options',
|
|
15
|
+
'head',
|
|
16
|
+
'trace',
|
|
17
|
+
];
|
|
@@ -44,6 +44,7 @@ export const RESERVED_TYPE_NAMES = new Set([
|
|
|
44
44
|
...RUNTIME_CLASS_NAMES,
|
|
45
45
|
...CLIENT_SURFACE_NAMES,
|
|
46
46
|
'FileInput',
|
|
47
|
+
'SecuritySchemeName',
|
|
47
48
|
]);
|
|
48
49
|
/**
|
|
49
50
|
* Build a PascalCase type-name prefix from an operation's method + path.
|
|
@@ -151,6 +152,11 @@ export function clientImportedNames(op) {
|
|
|
151
152
|
names.push(emissions.headers);
|
|
152
153
|
if (emissions.body !== undefined)
|
|
153
154
|
names.push(emissions.body);
|
|
155
|
+
// `$ref` path params reference named contract types directly in the method
|
|
156
|
+
// signature, so the client file must import them (fact collected by
|
|
157
|
+
// `analyze()` from the mapper's import list).
|
|
158
|
+
if (op.pathParamTypeNames !== undefined)
|
|
159
|
+
names.push(...op.pathParamTypeNames);
|
|
154
160
|
if (emissions.response !== undefined) {
|
|
155
161
|
const successType = getSuccessType(op);
|
|
156
162
|
if (successType !== 'void' && successType !== 'unknown' && /^[A-Z]/.test(successType)) {
|