@openapi-flow/core 0.1.0 → 0.2.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 +27 -5
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/arguments/list-api-request-media-types.d.ts +2 -0
- package/dist/arguments/list-api-request-media-types.js +8 -0
- package/dist/arguments/request-contract.d.ts +4 -0
- package/dist/arguments/request-contract.js +38 -0
- package/dist/arguments/request-media-type-priority.d.ts +2 -0
- package/dist/arguments/request-media-type-priority.js +8 -0
- package/dist/arguments/select-default-api-request-media-type.d.ts +3 -0
- package/dist/arguments/select-default-api-request-media-type.js +17 -0
- package/dist/arguments/validate-api-arguments.d.ts +2 -0
- package/dist/arguments/validate-api-arguments.js +140 -0
- package/dist/bindings/combine-binding-schemas.d.ts +4 -0
- package/dist/bindings/combine-binding-schemas.js +17 -0
- package/dist/bindings/create-native-array-item-schema.d.ts +4 -0
- package/dist/bindings/create-native-array-item-schema.js +16 -0
- package/dist/bindings/json-pointer.d.ts +3 -0
- package/dist/bindings/json-pointer.js +24 -0
- package/dist/bindings/materialize-api-arguments.d.ts +4 -0
- package/dist/bindings/materialize-api-arguments.js +54 -0
- package/dist/bindings/native-binding-schema.d.ts +3 -0
- package/dist/bindings/native-binding-schema.js +6 -0
- package/dist/bindings/project-array-item-schema.d.ts +5 -0
- package/dist/bindings/project-array-item-schema.js +67 -0
- package/dist/bindings/request-binding-schema.d.ts +2 -0
- package/dist/bindings/request-binding-schema.js +23 -0
- package/dist/bindings/request-container-kind.d.ts +3 -0
- package/dist/bindings/request-container-kind.js +17 -0
- package/dist/bindings/resources/clone-schema-resource.d.ts +3 -0
- package/dist/bindings/resources/clone-schema-resource.js +38 -0
- package/dist/bindings/resources/create-schema-resource-context.d.ts +4 -0
- package/dist/bindings/resources/create-schema-resource-context.js +130 -0
- package/dist/bindings/response-binding-schema.d.ts +3 -0
- package/dist/bindings/response-binding-schema.js +21 -0
- package/dist/bindings/schema-allows-container.d.ts +3 -0
- package/dist/bindings/schema-allows-container.js +20 -0
- package/dist/bindings/schema-at-pointer.d.ts +4 -0
- package/dist/bindings/schema-at-pointer.js +89 -0
- package/dist/bindings/validate-api-binding-assignments.d.ts +2 -0
- package/dist/bindings/validate-api-binding-assignments.js +81 -0
- package/dist/bindings/validate-api-binding-gaps.d.ts +2 -0
- package/dist/bindings/validate-api-binding-gaps.js +10 -0
- package/dist/bindings/validate-api-binding-plan.d.ts +3 -0
- package/dist/bindings/validate-api-binding-plan.js +11 -0
- package/dist/bindings/validate-native-output-contracts.d.ts +3 -0
- package/dist/bindings/validate-native-output-contracts.js +16 -0
- package/dist/internal-api.d.ts +30 -0
- package/dist/internal-api.js +25 -0
- package/dist/openapi/catalog/create-api-catalog.d.ts +2 -0
- package/dist/openapi/catalog/create-api-catalog.js +34 -0
- package/dist/openapi/catalog/list-api-operations.d.ts +2 -0
- package/dist/openapi/catalog/list-api-operations.js +3 -0
- package/dist/openapi/common/check-oas-value.d.ts +3 -0
- package/dist/openapi/common/check-oas-value.js +6 -0
- package/dist/openapi/common/check-schema-value.d.ts +2 -0
- package/dist/openapi/common/check-schema-value.js +12 -0
- package/dist/openapi/common/create-oas-value-validator.d.ts +3 -0
- package/dist/openapi/common/create-oas-value-validator.js +39 -0
- package/dist/openapi/common/normalize-oas-value-schema.d.ts +4 -0
- package/dist/openapi/common/normalize-oas-value-schema.js +50 -0
- package/dist/openapi/common/operation-metadata.d.ts +2 -0
- package/dist/openapi/common/operation-metadata.js +25 -0
- package/dist/openapi/common/operation-reference.d.ts +3 -0
- package/dist/openapi/{operation-reference.js → common/operation-reference.js} +1 -4
- package/dist/openapi/common/parse-spec-utils.d.ts +3 -0
- package/dist/openapi/common/parse-spec-utils.js +12 -0
- package/dist/openapi/common/resolve-openapi-security.d.ts +3 -0
- package/dist/openapi/common/resolve-openapi-security.js +28 -0
- package/dist/openapi/common/unsupported-operation-error.d.ts +4 -0
- package/dist/openapi/common/unsupported-operation-error.js +8 -0
- package/dist/openapi/common/validate-spec.d.ts +3 -0
- package/dist/openapi/{validate-spec.js → common/validate-spec.js} +2 -1
- package/dist/openapi/inbound/callback/parse-callback-operations.d.ts +3 -0
- package/dist/openapi/inbound/callback/parse-callback-operations.js +31 -0
- package/dist/openapi/inbound/inbound-path-item-operations.d.ts +4 -0
- package/dist/openapi/inbound/inbound-path-item-operations.js +31 -0
- package/dist/openapi/inbound/list-inbound-operations.d.ts +4 -0
- package/dist/openapi/inbound/list-inbound-operations.js +13 -0
- package/dist/openapi/inbound/webhook/parse-webhook-operations.d.ts +3 -0
- package/dist/openapi/inbound/webhook/parse-webhook-operations.js +13 -0
- package/dist/openapi/request/create-operation-catalog.d.ts +2 -0
- package/dist/openapi/request/create-operation-catalog.js +24 -0
- package/dist/openapi/request/encode-form-field.d.ts +2 -0
- package/dist/openapi/request/encode-form-field.js +6 -0
- package/dist/openapi/request/map-http-operation-contract.d.ts +4 -0
- package/dist/openapi/request/map-http-operation-contract.js +21 -0
- package/dist/openapi/request/operation-mapping.d.ts +3 -0
- package/dist/openapi/request/operation-mapping.js +10 -0
- package/dist/openapi/request/parse-operation-responses.d.ts +3 -0
- package/dist/openapi/request/parse-operation-responses.js +30 -0
- package/dist/openapi/request/parse-request-body.d.ts +3 -0
- package/dist/openapi/request/parse-request-body.js +29 -0
- package/dist/openapi/request/parse-request-operations.d.ts +7 -0
- package/dist/openapi/request/parse-request-operations.js +50 -0
- package/dist/openapi/request/parse-request-parameters.d.ts +3 -0
- package/dist/openapi/request/parse-request-parameters.js +93 -0
- package/dist/openapi/request/parse-security.d.ts +3 -0
- package/dist/openapi/request/parse-security.js +31 -0
- package/dist/openapi/request/request-operation-reference.d.ts +1 -0
- package/dist/openapi/request/request-operation-reference.js +4 -0
- package/dist/openapi/request/response-contract.d.ts +6 -0
- package/dist/openapi/request/response-contract.js +32 -0
- package/dist/openapi/request/scalar-response-schema.d.ts +2 -0
- package/dist/openapi/request/scalar-response-schema.js +20 -0
- package/dist/openapi/request/serialize-form-body.d.ts +3 -0
- package/dist/openapi/request/serialize-form-body.js +89 -0
- package/dist/openapi/request/serialize-parameter.d.ts +5 -0
- package/dist/openapi/request/serialize-parameter.js +145 -0
- package/dist/openapi/resolve-api-operations.d.ts +3 -0
- package/dist/openapi/resolve-api-operations.js +84 -0
- package/dist/openapi/response/api-response-validation-value.d.ts +3 -0
- package/dist/openapi/response/api-response-validation-value.js +21 -0
- package/dist/openapi/response/create-api-response-schema.d.ts +4 -0
- package/dist/openapi/response/create-api-response-schema.js +114 -0
- package/dist/openapi/response/create-response-array-item-schema.d.ts +4 -0
- package/dist/openapi/response/create-response-array-item-schema.js +12 -0
- package/dist/openapi/response/create-response-value-schema.d.ts +4 -0
- package/dist/openapi/response/create-response-value-schema.js +8 -0
- package/dist/openapi/response/list-api-response-pointers.d.ts +4 -0
- package/dist/openapi/response/list-api-response-pointers.js +30 -0
- package/dist/openapi/response/list-declared-schema-pointers.d.ts +4 -0
- package/dist/openapi/response/list-declared-schema-pointers.js +91 -0
- package/dist/public-api.d.ts +49 -7
- package/dist/public-api.js +35 -5
- package/dist/runtime-api.d.ts +6 -0
- package/dist/runtime-api.js +7 -0
- package/dist/schemas/create-api-argument-generation-contract.d.ts +4 -0
- package/dist/schemas/create-api-argument-generation-contract.js +72 -0
- package/dist/schemas/create-api-arguments-schema.d.ts +3 -0
- package/dist/schemas/create-api-arguments-schema.js +4 -0
- package/dist/schemas/create-api-request-schema.d.ts +4 -0
- package/dist/schemas/create-api-request-schema.js +50 -0
- package/dist/schemas/create-oas-value-schema.d.ts +6 -0
- package/dist/schemas/create-oas-value-schema.js +25 -0
- package/dist/schemas/operation-plan-schema.d.ts +8 -0
- package/dist/schemas/operation-plan-schema.js +99 -0
- package/dist/schemas/project-literal-array-schema.d.ts +4 -0
- package/dist/schemas/project-literal-array-schema.js +55 -0
- package/dist/schemas/project-literal-input-schema.d.ts +3 -0
- package/dist/schemas/project-literal-input-schema.js +129 -0
- package/dist/types/api-argument-generation.d.ts +13 -0
- package/dist/types/api-arguments.d.ts +42 -0
- package/dist/types/api-arguments.js +1 -0
- package/dist/types/api-bindings.d.ts +34 -0
- package/dist/types/api-bindings.js +1 -0
- package/dist/types/api-catalog.d.ts +34 -0
- package/dist/types/api-catalog.js +1 -0
- package/dist/types/api-operation.d.ts +26 -0
- package/dist/types/api-operation.js +1 -0
- package/dist/types/api-response-pointers.d.ts +20 -0
- package/dist/types/api-response-pointers.js +1 -0
- package/dist/types/api-response.d.ts +14 -0
- package/dist/types/api-response.js +1 -0
- package/dist/types/array-projection.d.ts +5 -0
- package/dist/types/array-projection.js +1 -0
- package/dist/types/catalog.d.ts +17 -0
- package/dist/types/catalog.js +1 -0
- package/dist/types/inbound.d.ts +18 -0
- package/dist/types/inbound.js +1 -0
- package/dist/types/legacy-plan.d.ts +47 -0
- package/dist/types/legacy-plan.js +1 -0
- package/dist/types/node-output.d.ts +11 -0
- package/dist/types/node-output.js +1 -0
- package/dist/types/oas-value-validation.d.ts +8 -0
- package/dist/types/oas-value-validation.js +1 -0
- package/dist/types/openapi.d.ts +7 -23
- package/dist/types/request.d.ts +41 -0
- package/dist/types/request.js +1 -0
- package/dist/types/reviewable-workflow.d.ts +53 -0
- package/dist/types/reviewable-workflow.js +1 -0
- package/dist/types/schema-dialect.d.ts +1 -0
- package/dist/types/schema-dialect.js +1 -0
- package/dist/types/schema-json.d.ts +4 -0
- package/dist/types/schema-json.js +1 -0
- package/dist/types/schema-resource.d.ts +12 -0
- package/dist/types/schema-resource.js +1 -0
- package/dist/types/security.d.ts +17 -0
- package/dist/types/security.js +1 -0
- package/dist/types/workflow-capability-planning.d.ts +10 -0
- package/dist/types/workflow-capability-planning.js +1 -0
- package/dist/types/workflow-plan.d.ts +104 -0
- package/dist/types/workflow-plan.js +1 -0
- package/dist/utils/is-object.d.ts +2 -0
- package/dist/utils/is-object.js +3 -0
- package/dist/utils/json-values-equal.d.ts +2 -0
- package/dist/utils/json-values-equal.js +16 -0
- package/dist/workflow/assert-api-call-identities.d.ts +1 -0
- package/dist/workflow/assert-api-call-identities.js +6 -0
- package/dist/workflow/create-native-output-contracts.d.ts +3 -0
- package/dist/workflow/create-native-output-contracts.js +23 -0
- package/dist/workflow/create-reviewable-node-contracts.d.ts +3 -0
- package/dist/workflow/create-reviewable-node-contracts.js +36 -0
- package/dist/workflow/create-reviewable-workflow-graph-plan.d.ts +2 -0
- package/dist/workflow/create-reviewable-workflow-graph-plan.js +29 -0
- package/dist/workflow/create-workflow-graph-plan.d.ts +2 -0
- package/dist/workflow/create-workflow-graph-plan.js +17 -0
- package/dist/workflow/create-workflow-node-contracts.d.ts +2 -0
- package/dist/workflow/create-workflow-node-contracts.js +50 -0
- package/dist/workflow/create-workflow-planning-capabilities.d.ts +3 -0
- package/dist/workflow/create-workflow-planning-capabilities.js +20 -0
- package/dist/workflow/parse-native-node-parameters.d.ts +3 -0
- package/dist/workflow/parse-native-node-parameters.js +7 -0
- package/dist/workflow/validate-exclusive-output-rejoin.d.ts +3 -0
- package/dist/workflow/validate-exclusive-output-rejoin.js +26 -0
- package/dist/workflow/validate-native-api-response-references.d.ts +2 -0
- package/dist/workflow/validate-native-api-response-references.js +26 -0
- package/dist/workflow/validate-reviewable-workflow-graph-plan.d.ts +2 -0
- package/dist/workflow/validate-reviewable-workflow-graph-plan.js +98 -0
- package/dist/workflow/validate-workflow-edges.d.ts +2 -0
- package/dist/workflow/validate-workflow-edges.js +19 -0
- package/dist/workflow/validate-workflow-graph-plan.d.ts +3 -0
- package/dist/workflow/validate-workflow-graph-plan.js +41 -0
- package/dist/workflow/validate-workflow-topology.d.ts +2 -0
- package/dist/workflow/validate-workflow-topology.js +105 -0
- package/package.json +19 -25
- package/dist/openapi/operation-reference.d.ts +0 -3
- package/dist/openapi/parse-spec-utils.d.ts +0 -5
- package/dist/openapi/parse-spec-utils.js +0 -52
- package/dist/openapi/parse-spec.d.ts +0 -5
- package/dist/openapi/parse-spec.js +0 -182
- package/dist/openapi/validate-spec.d.ts +0 -3
- package/dist/planning/generate-workflow.d.ts +0 -2
- package/dist/planning/generate-workflow.js +0 -126
- package/dist/schemas/planning-schemas.d.ts +0 -62
- package/dist/schemas/planning-schemas.js +0 -48
- package/dist/types/workflow.d.ts +0 -75
- package/dist/utils/validation.d.ts +0 -7
- package/dist/utils/validation.js +0 -44
- package/dist/utils/workflow-id.d.ts +0 -1
- package/dist/utils/workflow-id.js +0 -8
- package/dist/workflow/compile-sequence.d.ts +0 -2
- package/dist/workflow/compile-sequence.js +0 -167
- package/dist/workflow/compile-workflow.d.ts +0 -4
- package/dist/workflow/compile-workflow.js +0 -128
- package/dist/workflow/sequence-url.d.ts +0 -3
- package/dist/workflow/sequence-url.js +0 -81
- package/dist/workflow/workflow-helpers.d.ts +0 -6
- package/dist/workflow/workflow-helpers.js +0 -81
- /package/dist/types/{workflow.js → api-argument-generation.js} +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { dereferencedObject, object } from '../common/parse-spec-utils.js';
|
|
2
|
+
import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
|
|
3
|
+
export function parseOperationSecurity(spec, operation, operationRef) {
|
|
4
|
+
const security = operation.security === undefined ? spec.security : operation.security;
|
|
5
|
+
if (security === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (!Array.isArray(security)) {
|
|
8
|
+
throw new Error(`operationRef ${operationRef}.security must be an array`);
|
|
9
|
+
}
|
|
10
|
+
if (security.length === 0)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (security.length !== 1) {
|
|
13
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.security has unsupported alternatives`);
|
|
14
|
+
}
|
|
15
|
+
const requirement = object(security[0], `operationRef ${operationRef}.security[0]`);
|
|
16
|
+
const names = Object.keys(requirement);
|
|
17
|
+
if (names.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (names.length !== 1) {
|
|
20
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.security has unsupported combined schemes`);
|
|
21
|
+
}
|
|
22
|
+
const schemeName = names[0];
|
|
23
|
+
const schemes = object(object(spec.components, `operationRef ${operationRef}.components`)
|
|
24
|
+
.securitySchemes, `operationRef ${operationRef}.components.securitySchemes`);
|
|
25
|
+
const scheme = dereferencedObject(schemes[schemeName], `operationRef ${operationRef}.components.securitySchemes.${schemeName}`);
|
|
26
|
+
if (scheme.type !== 'http' ||
|
|
27
|
+
String(scheme.scheme).toLowerCase() !== 'bearer') {
|
|
28
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.security.${schemeName} cannot be mapped to a supported n8n credential`);
|
|
29
|
+
}
|
|
30
|
+
return { schemeName, credentialType: 'httpBearerAuth' };
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function requestOperationReference(path: string, key: string): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JsonObject } from '../../types/openapi.js';
|
|
2
|
+
import type { Operation } from '../../types/request.js';
|
|
3
|
+
export declare function responseFieldNames(operation: Operation): string[];
|
|
4
|
+
export declare function responseFieldSchemas(operation: Operation, field: string): (JsonObject | boolean)[];
|
|
5
|
+
export declare function validateResponseField(operation: Operation, field: string, value: unknown, source: string): void;
|
|
6
|
+
export declare function responseFieldType(operation: Operation, field: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { checkSchemaValue } from '../common/check-schema-value.js';
|
|
2
|
+
import { scalarResponseSchema } from './scalar-response-schema.js';
|
|
3
|
+
export function responseFieldNames(operation) {
|
|
4
|
+
return [...new Set(Object.values(operation.responses).flatMap(Object.keys))];
|
|
5
|
+
}
|
|
6
|
+
export function responseFieldSchemas(operation, field) {
|
|
7
|
+
return Object.values(operation.responses).flatMap((properties) => Object.hasOwn(properties, field) ? [properties[field]] : []);
|
|
8
|
+
}
|
|
9
|
+
export function validateResponseField(operation, field, value, source) {
|
|
10
|
+
const schemas = responseFieldSchemas(operation, field);
|
|
11
|
+
if (schemas.length === 0)
|
|
12
|
+
throw new Error(source + ' is not in the OAS response schema');
|
|
13
|
+
for (const schema of schemas) {
|
|
14
|
+
try {
|
|
15
|
+
checkSchemaValue(value, schema, source);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Another declared response may define this field differently.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
throw new Error(source + ' does not match the OAS schema');
|
|
23
|
+
}
|
|
24
|
+
export function responseFieldType(operation, field) {
|
|
25
|
+
const schemas = responseFieldSchemas(operation, field);
|
|
26
|
+
if (schemas.length === 0)
|
|
27
|
+
throw new Error('prior response field ' + field + ' is not in the OAS');
|
|
28
|
+
const types = new Set(schemas.map((schema) => scalarResponseSchema(schema, 'operationRef ' + operation.operationRef + ' response field ' + field, operation.operationRef).type));
|
|
29
|
+
if (types.size !== 1)
|
|
30
|
+
throw new Error('prior response field ' + field + ' has ambiguous OAS types');
|
|
31
|
+
return [...types][0];
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { dereferencedObject } from '../common/parse-spec-utils.js';
|
|
2
|
+
import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
|
|
3
|
+
export function scalarResponseSchema(value, source, operationRef) {
|
|
4
|
+
const schema = dereferencedObject(value, source);
|
|
5
|
+
if (!['string', 'integer', 'number', 'boolean'].includes(String(schema.type))) {
|
|
6
|
+
throw new UnsupportedOperationError(operationRef, `${source} must be a primitive schema`);
|
|
7
|
+
}
|
|
8
|
+
if (schema.enum !== undefined &&
|
|
9
|
+
(!Array.isArray(schema.enum) ||
|
|
10
|
+
schema.enum.length === 0 ||
|
|
11
|
+
schema.enum.some((entry) => typeof entry !== 'string' &&
|
|
12
|
+
typeof entry !== 'number' &&
|
|
13
|
+
typeof entry !== 'boolean'))) {
|
|
14
|
+
throw new UnsupportedOperationError(operationRef, `${source}.enum must contain primitive values`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
type: schema.type,
|
|
18
|
+
enum: schema.enum,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { serializeQueryParameter } from './serialize-parameter.js';
|
|
2
|
+
import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
|
|
3
|
+
import { dereferencedObject } from '../common/parse-spec-utils.js';
|
|
4
|
+
import { isObject } from '../../utils/is-object.js';
|
|
5
|
+
import { encodeFormField } from './encode-form-field.js';
|
|
6
|
+
function defaultFieldMediaType(schema) {
|
|
7
|
+
const type = schema.type === 'array' && isObject(schema.items)
|
|
8
|
+
? schema.items.type
|
|
9
|
+
: schema.type;
|
|
10
|
+
if (type === 'object' || type === 'array')
|
|
11
|
+
return 'application/json';
|
|
12
|
+
if (['string', 'number', 'integer', 'boolean'].includes(String(type)))
|
|
13
|
+
return 'text/plain';
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
function encodedField(name, field, schema, contentType, operationRef) {
|
|
17
|
+
if (contentType !== 'application/json' && contentType !== 'text/plain')
|
|
18
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} contentType ${contentType} has no form mapping`);
|
|
19
|
+
const values = Array.isArray(field) ? field : [field];
|
|
20
|
+
if (values.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const form = [];
|
|
23
|
+
for (const value of values) {
|
|
24
|
+
if (contentType === 'text/plain' && typeof value === 'object')
|
|
25
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} cannot use text/plain for a complex value`);
|
|
26
|
+
if (contentType === 'application/json') {
|
|
27
|
+
form.push(encodeFormField(name) + '=' + encodeFormField(JSON.stringify(value)));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
form.push(encodeFormField(name) + '=' + encodeFormField(String(value)));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (schema.contentEncoding !== undefined)
|
|
34
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} contentEncoding requires explicit binary serialization`);
|
|
35
|
+
return form;
|
|
36
|
+
}
|
|
37
|
+
function formFieldSchema(operationRef, media, name) {
|
|
38
|
+
const source = `operationRef ${operationRef}.requestBody.schema.properties.${name}`;
|
|
39
|
+
const declared = media.properties[name];
|
|
40
|
+
if (declared !== undefined) {
|
|
41
|
+
if (typeof declared === 'boolean')
|
|
42
|
+
throw new UnsupportedOperationError(operationRef, `${source} does not define a form encoding type`);
|
|
43
|
+
return dereferencedObject(declared, source);
|
|
44
|
+
}
|
|
45
|
+
if (isObject(media.schema) && media.schema.additionalProperties !== undefined)
|
|
46
|
+
if (typeof media.schema.additionalProperties !== 'boolean')
|
|
47
|
+
return dereferencedObject(media.schema.additionalProperties, `operationRef ${operationRef}.requestBody.schema.additionalProperties`);
|
|
48
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} has no schema to determine form encoding`);
|
|
49
|
+
}
|
|
50
|
+
export function serializeFormBody(operationRef, media, value) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
for (const [name, field] of Object.entries(value)) {
|
|
53
|
+
const schema = formFieldSchema(operationRef, media, name);
|
|
54
|
+
const rawEncoding = media.encoding?.[name];
|
|
55
|
+
const encoding = rawEncoding === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: dereferencedObject(rawEncoding, `operationRef ${operationRef}.requestBody.encoding.${name}`);
|
|
58
|
+
if (encoding?.encoding !== undefined ||
|
|
59
|
+
encoding?.prefixEncoding !== undefined ||
|
|
60
|
+
encoding?.itemEncoding !== undefined)
|
|
61
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} needs nested encoding`);
|
|
62
|
+
const usesStyle = encoding?.style !== undefined ||
|
|
63
|
+
encoding?.explode !== undefined ||
|
|
64
|
+
encoding?.allowReserved !== undefined;
|
|
65
|
+
if (usesStyle) {
|
|
66
|
+
const style = typeof encoding?.style === 'string' ? encoding.style : 'form';
|
|
67
|
+
const parameter = {
|
|
68
|
+
name,
|
|
69
|
+
in: 'query',
|
|
70
|
+
required: false,
|
|
71
|
+
schema,
|
|
72
|
+
style,
|
|
73
|
+
explode: typeof encoding?.explode === 'boolean'
|
|
74
|
+
? encoding.explode
|
|
75
|
+
: style === 'form',
|
|
76
|
+
allowReserved: encoding?.allowReserved === true,
|
|
77
|
+
};
|
|
78
|
+
parts.push(...serializeQueryParameter(parameter, operationRef, field));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const contentType = typeof encoding?.contentType === 'string'
|
|
82
|
+
? encoding.contentType
|
|
83
|
+
: defaultFieldMediaType(schema);
|
|
84
|
+
if (contentType === undefined)
|
|
85
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef}.requestBody field ${name} has no form content mapping`);
|
|
86
|
+
parts.push(...encodedField(name, field, schema, contentType, operationRef));
|
|
87
|
+
}
|
|
88
|
+
return parts.join('&');
|
|
89
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OperationParameter } from '../../types/request.js';
|
|
2
|
+
export declare function serializePathParameter(parameter: OperationParameter, operationRef: string, value: unknown): string;
|
|
3
|
+
export declare function serializeQueryParameter(parameter: OperationParameter, operationRef: string, value: unknown): string[];
|
|
4
|
+
export declare function serializeHeaderParameter(parameter: OperationParameter, operationRef: string, value: unknown): string;
|
|
5
|
+
export declare function serializeCookieParameter(parameter: OperationParameter, operationRef: string, value: unknown): string[];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isObject } from '../../utils/is-object.js';
|
|
2
|
+
import { UnsupportedOperationError } from '../common/unsupported-operation-error.js';
|
|
3
|
+
function unsupported(parameter, operationRef, reason) {
|
|
4
|
+
throw new UnsupportedOperationError(operationRef, `operationRef ${operationRef} parameter ${parameter.in}.${parameter.name} ${reason}`);
|
|
5
|
+
}
|
|
6
|
+
function flatValue(parameter, operationRef, value) {
|
|
7
|
+
if (typeof value === 'string' ||
|
|
8
|
+
typeof value === 'boolean' ||
|
|
9
|
+
(typeof value === 'number' && Number.isFinite(value)))
|
|
10
|
+
return value;
|
|
11
|
+
return unsupported(parameter, operationRef, 'requires a scalar value');
|
|
12
|
+
}
|
|
13
|
+
function encoded(value, allowReserved) {
|
|
14
|
+
const escaped = encodeURIComponent(String(value)).replace(/[!'()*]/g, (character) => '%' + character.charCodeAt(0).toString(16).toUpperCase());
|
|
15
|
+
if (!allowReserved)
|
|
16
|
+
return escaped;
|
|
17
|
+
return escaped.replace(/%(3A|2F|3F|23|5B|5D|40|21|24|26|27|28|29|2A|2B|2C|3B|3D)/gi, (match) => String.fromCharCode(Number.parseInt(match.slice(1), 16)));
|
|
18
|
+
}
|
|
19
|
+
function entries(parameter, operationRef, value, encode) {
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return value.map((item) => encode(flatValue(parameter, operationRef, item)));
|
|
22
|
+
if (isObject(value))
|
|
23
|
+
return Object.entries(value).flatMap(([key, item]) => [
|
|
24
|
+
encode(key),
|
|
25
|
+
encode(flatValue(parameter, operationRef, item)),
|
|
26
|
+
]);
|
|
27
|
+
return [encode(flatValue(parameter, operationRef, value))];
|
|
28
|
+
}
|
|
29
|
+
function contentValue(parameter, operationRef, value) {
|
|
30
|
+
if (parameter.contentMediaType !== 'application/json')
|
|
31
|
+
return unsupported(parameter, operationRef, 'has no content mapping');
|
|
32
|
+
const result = JSON.stringify(value);
|
|
33
|
+
if (result === undefined)
|
|
34
|
+
return unsupported(parameter, operationRef, 'cannot be JSON serialized');
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export function serializePathParameter(parameter, operationRef, value) {
|
|
38
|
+
if (parameter.contentMediaType) {
|
|
39
|
+
return encoded(contentValue(parameter, operationRef, value), false);
|
|
40
|
+
}
|
|
41
|
+
const encode = (scalar) => encoded(scalar, parameter.allowReserved);
|
|
42
|
+
const parts = entries(parameter, operationRef, value, encode);
|
|
43
|
+
const object = isObject(value);
|
|
44
|
+
if (parameter.style === 'simple') {
|
|
45
|
+
if (!object || !parameter.explode)
|
|
46
|
+
return parts.join(',');
|
|
47
|
+
return parts
|
|
48
|
+
.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
|
|
49
|
+
.join(',');
|
|
50
|
+
}
|
|
51
|
+
if (parameter.style === 'label') {
|
|
52
|
+
if (!object || !parameter.explode)
|
|
53
|
+
return '.' + parts.join(parameter.explode ? '.' : ',');
|
|
54
|
+
return ('.' +
|
|
55
|
+
parts
|
|
56
|
+
.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
|
|
57
|
+
.join('.'));
|
|
58
|
+
}
|
|
59
|
+
if (parameter.style === 'matrix') {
|
|
60
|
+
const name = encoded(parameter.name, false);
|
|
61
|
+
if (!parameter.explode)
|
|
62
|
+
return ';' + name + '=' + parts.join(',');
|
|
63
|
+
if (object)
|
|
64
|
+
return parts
|
|
65
|
+
.flatMap((item, index) => index % 2 === 0 ? [';' + item + '=' + parts[index + 1]] : [])
|
|
66
|
+
.join('');
|
|
67
|
+
return parts.map((item) => ';' + name + '=' + item).join('');
|
|
68
|
+
}
|
|
69
|
+
return unsupported(parameter, operationRef, `style ${parameter.style} is not a path style`);
|
|
70
|
+
}
|
|
71
|
+
export function serializeQueryParameter(parameter, operationRef, value) {
|
|
72
|
+
const name = encoded(parameter.name, false);
|
|
73
|
+
if (parameter.contentMediaType)
|
|
74
|
+
return [
|
|
75
|
+
name + '=' + encoded(contentValue(parameter, operationRef, value), false),
|
|
76
|
+
];
|
|
77
|
+
const encode = (scalar) => encoded(scalar, parameter.allowReserved);
|
|
78
|
+
const parts = entries(parameter, operationRef, value, encode);
|
|
79
|
+
const object = isObject(value);
|
|
80
|
+
if (parameter.style === 'deepObject') {
|
|
81
|
+
if (!object)
|
|
82
|
+
return unsupported(parameter, operationRef, 'deepObject requires an object');
|
|
83
|
+
return parts.flatMap((item, index) => index % 2 === 0 ? [name + '%5B' + item + '%5D=' + parts[index + 1]] : []);
|
|
84
|
+
}
|
|
85
|
+
if (parameter.style === 'form') {
|
|
86
|
+
if (!parameter.explode)
|
|
87
|
+
return [name + '=' + parts.join(',')];
|
|
88
|
+
if (object)
|
|
89
|
+
return parts.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : []);
|
|
90
|
+
return parts.map((item) => name + '=' + item);
|
|
91
|
+
}
|
|
92
|
+
if ((parameter.style === 'spaceDelimited' ||
|
|
93
|
+
parameter.style === 'pipeDelimited') &&
|
|
94
|
+
!parameter.explode &&
|
|
95
|
+
(object || Array.isArray(value))) {
|
|
96
|
+
return [
|
|
97
|
+
name +
|
|
98
|
+
'=' +
|
|
99
|
+
parts.join(parameter.style === 'spaceDelimited' ? '%20' : '%7C'),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
return unsupported(parameter, operationRef, `style ${parameter.style} is not mapped for query`);
|
|
103
|
+
}
|
|
104
|
+
export function serializeHeaderParameter(parameter, operationRef, value) {
|
|
105
|
+
if (parameter.contentMediaType)
|
|
106
|
+
return contentValue(parameter, operationRef, value);
|
|
107
|
+
if (parameter.style !== 'simple')
|
|
108
|
+
return unsupported(parameter, operationRef, `style ${parameter.style} is not a header style`);
|
|
109
|
+
const parts = entries(parameter, operationRef, value, String);
|
|
110
|
+
const result = isObject(value) && parameter.explode
|
|
111
|
+
? parts
|
|
112
|
+
.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
|
|
113
|
+
.join(',')
|
|
114
|
+
: parts.join(',');
|
|
115
|
+
if (/[\r\n]/.test(result))
|
|
116
|
+
throw new Error(`plan.inputs.header.${parameter.name} must not contain a newline`);
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
export function serializeCookieParameter(parameter, operationRef, value) {
|
|
120
|
+
if (parameter.contentMediaType)
|
|
121
|
+
return unsupported(parameter, operationRef, 'cookie content needs a declared escaping strategy');
|
|
122
|
+
if (parameter.style !== 'form' && parameter.style !== 'cookie')
|
|
123
|
+
return unsupported(parameter, operationRef, `style ${parameter.style} is not a cookie style`);
|
|
124
|
+
const encode = parameter.style === 'cookie'
|
|
125
|
+
? (scalar) => String(scalar)
|
|
126
|
+
: (scalar) => encoded(scalar, parameter.allowReserved);
|
|
127
|
+
const parts = entries(parameter, operationRef, value, encode);
|
|
128
|
+
const name = encode(parameter.name);
|
|
129
|
+
if (!parameter.explode && (isObject(value) || Array.isArray(value)))
|
|
130
|
+
return unsupported(parameter, operationRef, 'explode:false produces invalid Cookie syntax');
|
|
131
|
+
const result = isObject(value)
|
|
132
|
+
? parts.flatMap((item, index) => index % 2 === 0 ? [item + '=' + parts[index + 1]] : [])
|
|
133
|
+
: parts.map((item) => name + '=' + item);
|
|
134
|
+
if (result.some((part) => [...part].some((character) => {
|
|
135
|
+
const code = character.charCodeAt(0);
|
|
136
|
+
return (code <= 32 ||
|
|
137
|
+
code === 34 ||
|
|
138
|
+
code === 44 ||
|
|
139
|
+
code === 59 ||
|
|
140
|
+
code === 92 ||
|
|
141
|
+
code === 127);
|
|
142
|
+
})))
|
|
143
|
+
return unsupported(parameter, operationRef, 'value is not valid Cookie syntax');
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { object } from './common/parse-spec-utils.js';
|
|
3
|
+
import { validateAndResolveOpenApiDocument } from './common/validate-spec.js';
|
|
4
|
+
import { selectedOperationSource } from './request/parse-request-operations.js';
|
|
5
|
+
export async function resolveApiOperations(catalog, keys) {
|
|
6
|
+
if (!Array.isArray(keys))
|
|
7
|
+
throw new Error('operation keys must be an array');
|
|
8
|
+
const documents = new Map();
|
|
9
|
+
const contracts = [];
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (!key ||
|
|
12
|
+
typeof key.documentId !== 'string' ||
|
|
13
|
+
!key.documentId.trim() ||
|
|
14
|
+
typeof key.snapshotId !== 'string' ||
|
|
15
|
+
!key.snapshotId ||
|
|
16
|
+
typeof key.operationRef !== 'string' ||
|
|
17
|
+
!key.operationRef)
|
|
18
|
+
throw new Error('operation keys require documentId, snapshotId and operationRef');
|
|
19
|
+
const source = catalog.documents.find((item) => item.id === key.documentId);
|
|
20
|
+
if (!source)
|
|
21
|
+
throw new Error(`operation.documentId ${key.documentId} is not in catalog`);
|
|
22
|
+
if (source.snapshotId !== key.snapshotId)
|
|
23
|
+
throw new Error(`operation.snapshotId does not match sources[${source.id}]`);
|
|
24
|
+
if (createHash('sha256').update(JSON.stringify(source.spec)).digest('hex') !==
|
|
25
|
+
source.snapshotId)
|
|
26
|
+
throw new Error(`sources[${source.id}].spec changed after catalog creation`);
|
|
27
|
+
if (!catalog.operations.some((item) => item.key.documentId === key.documentId &&
|
|
28
|
+
item.operationRef === key.operationRef))
|
|
29
|
+
throw new Error(`sources[${source.id}] has no operationRef ${key.operationRef}`);
|
|
30
|
+
let document = documents.get(source.id);
|
|
31
|
+
if (!document) {
|
|
32
|
+
document = await validateAndResolveOpenApiDocument(source.spec);
|
|
33
|
+
documents.set(source.id, document);
|
|
34
|
+
}
|
|
35
|
+
const { candidate, pathItem, entry } = selectedOperationSource(document, key.operationRef);
|
|
36
|
+
const operation = object(entry.value, `sources[${source.id}].${key.operationRef}`);
|
|
37
|
+
const parameters = new Map();
|
|
38
|
+
for (const level of [pathItem, operation]) {
|
|
39
|
+
if (level.parameters === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
if (!Array.isArray(level.parameters))
|
|
42
|
+
throw new Error(`sources[${source.id}].${key.operationRef}.parameters must be an array`);
|
|
43
|
+
for (const raw of level.parameters) {
|
|
44
|
+
const parameter = object(raw, `${key.operationRef}.parameters`);
|
|
45
|
+
parameters.set(JSON.stringify([parameter.in, parameter.name]), parameter);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const security = operation.security === undefined
|
|
49
|
+
? document.spec.security
|
|
50
|
+
: operation.security;
|
|
51
|
+
const servers = operation.servers === undefined
|
|
52
|
+
? pathItem.servers === undefined
|
|
53
|
+
? document.spec.servers
|
|
54
|
+
: pathItem.servers
|
|
55
|
+
: operation.servers;
|
|
56
|
+
const components = document.spec.components === undefined
|
|
57
|
+
? undefined
|
|
58
|
+
: object(document.spec.components, 'spec.components');
|
|
59
|
+
if (typeof document.spec.openapi !== 'string')
|
|
60
|
+
throw new Error(`sources[${source.id}].spec.openapi is missing`);
|
|
61
|
+
contracts.push({
|
|
62
|
+
key: { ...key },
|
|
63
|
+
openapiVersion: document.spec.openapi,
|
|
64
|
+
source: 'paths',
|
|
65
|
+
method: candidate.method,
|
|
66
|
+
path: candidate.path,
|
|
67
|
+
operation: structuredClone(operation),
|
|
68
|
+
pathItem: structuredClone(pathItem),
|
|
69
|
+
effective: {
|
|
70
|
+
parameters: [...parameters.values()],
|
|
71
|
+
security: security === undefined
|
|
72
|
+
? []
|
|
73
|
+
: structuredClone(security),
|
|
74
|
+
securitySchemes: components?.securitySchemes === undefined
|
|
75
|
+
? {}
|
|
76
|
+
: object(components.securitySchemes, 'spec.components.securitySchemes'),
|
|
77
|
+
...(servers === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { servers: structuredClone(servers) }),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return contracts;
|
|
84
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ApiResponseEnvelope, ApiResponseValidationValue } from '../../types/api-response.js';
|
|
2
|
+
/** Project transport metadata for validation without rewriting response data. */
|
|
3
|
+
export declare function apiResponseValidationValue(response: ApiResponseEnvelope): ApiResponseValidationValue;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Project transport metadata for validation without rewriting response data. */
|
|
2
|
+
export function apiResponseValidationValue(response) {
|
|
3
|
+
if (!response || !Number.isInteger(response.statusCode))
|
|
4
|
+
throw new Error('API response.statusCode is required');
|
|
5
|
+
if (!response.headers ||
|
|
6
|
+
typeof response.headers !== 'object' ||
|
|
7
|
+
Array.isArray(response.headers))
|
|
8
|
+
throw new Error('API response.headers is required');
|
|
9
|
+
const headers = Object.entries(response.headers).filter(([name]) => name.toLowerCase() === 'content-type');
|
|
10
|
+
if (headers.length > 1)
|
|
11
|
+
throw new Error('API response.headers has ambiguous Content-Type');
|
|
12
|
+
const value = { statusCode: response.statusCode };
|
|
13
|
+
if (headers.length) {
|
|
14
|
+
if (typeof headers[0][1] !== 'string')
|
|
15
|
+
throw new Error('API response Content-Type must be a string');
|
|
16
|
+
value.mediaType = headers[0][1].split(';')[0].trim().toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
if (Object.hasOwn(response, 'body'))
|
|
19
|
+
value.body = response.body;
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiResponseValidationInput } from '../../types/api-response.js';
|
|
2
|
+
import type { JsonObject } from '../../types/openapi.js';
|
|
3
|
+
/** Validate the observed response, not an LLM-planned expected status. */
|
|
4
|
+
export declare function createApiResponseSchema({ operation, }: ApiResponseValidationInput): JsonObject;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isObject } from '../../utils/is-object.js';
|
|
2
|
+
import { createOasValueValidator } from '../common/create-oas-value-validator.js';
|
|
3
|
+
function statusSchema(key, keys) {
|
|
4
|
+
if (/^[1-5][0-9]{2}$/.test(key))
|
|
5
|
+
return { const: Number(key) };
|
|
6
|
+
if (/^[1-5]XX$/.test(key))
|
|
7
|
+
return {
|
|
8
|
+
minimum: Number(key[0]) * 100,
|
|
9
|
+
maximum: Number(key[0]) * 100 + 99,
|
|
10
|
+
...(keys.some((item) => /^[1-5][0-9]{2}$/.test(item))
|
|
11
|
+
? {
|
|
12
|
+
not: {
|
|
13
|
+
enum: keys
|
|
14
|
+
.filter((item) => /^[1-5][0-9]{2}$/.test(item))
|
|
15
|
+
.map(Number),
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
: {}),
|
|
19
|
+
};
|
|
20
|
+
if (key === 'default')
|
|
21
|
+
return keys.some((item) => item !== 'default')
|
|
22
|
+
? {
|
|
23
|
+
not: {
|
|
24
|
+
anyOf: keys
|
|
25
|
+
.filter((item) => item !== 'default')
|
|
26
|
+
.map((item) => statusSchema(item, keys)),
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
: {};
|
|
30
|
+
throw new Error(`responses.${key} is not an OAS response status`);
|
|
31
|
+
}
|
|
32
|
+
function mediaPattern(media) {
|
|
33
|
+
return ('^' +
|
|
34
|
+
media
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.split('*')
|
|
37
|
+
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
38
|
+
.join('[^/;]+') +
|
|
39
|
+
'$');
|
|
40
|
+
}
|
|
41
|
+
/** Validate the observed response, not an LLM-planned expected status. */
|
|
42
|
+
export function createApiResponseSchema({ operation, }) {
|
|
43
|
+
const responses = operation.operation.responses;
|
|
44
|
+
if (!isObject(responses))
|
|
45
|
+
throw new Error(`${operation.key.operationRef}.responses is required`);
|
|
46
|
+
const keys = Object.keys(responses).filter((key) => !key.startsWith('x-'));
|
|
47
|
+
const dialect = operation.openapiVersion.startsWith('3.0.')
|
|
48
|
+
? 'openapi-3.0'
|
|
49
|
+
: 'draft-2020-12';
|
|
50
|
+
const branches = keys.flatMap((key) => {
|
|
51
|
+
const response = responses[key];
|
|
52
|
+
if (!isObject(response))
|
|
53
|
+
throw new Error(`${operation.key.operationRef}.responses.${key} must be resolved`);
|
|
54
|
+
if ('$ref' in response)
|
|
55
|
+
throw new Error(`${operation.key.operationRef}.responses.${key} must be resolved`);
|
|
56
|
+
const status = statusSchema(key, keys);
|
|
57
|
+
const content = response.content;
|
|
58
|
+
if (!isObject(content) || !Object.keys(content).length)
|
|
59
|
+
return [{ properties: { statusCode: status } }];
|
|
60
|
+
const mediaTypes = Object.keys(content);
|
|
61
|
+
return mediaTypes.map((mediaType) => {
|
|
62
|
+
const media = content[mediaType];
|
|
63
|
+
if (!isObject(media))
|
|
64
|
+
throw new Error(`${operation.key.operationRef}.responses.${key}.content.${mediaType} must be resolved`);
|
|
65
|
+
if ('$ref' in media)
|
|
66
|
+
throw new Error(`${operation.key.operationRef}.responses.${key}.content.${mediaType} must be resolved`);
|
|
67
|
+
const schema = media.schema;
|
|
68
|
+
if (schema !== undefined &&
|
|
69
|
+
typeof schema !== 'boolean' &&
|
|
70
|
+
!isObject(schema))
|
|
71
|
+
throw new Error(`${operation.key.operationRef}.responses.${key}.content.${mediaType}.schema must be an OAS schema`);
|
|
72
|
+
const body = schema === undefined
|
|
73
|
+
? true
|
|
74
|
+
: createOasValueValidator(schema, dialect, `${operation.key.documentId}${operation.key.operationRef}/responses/${key}/${mediaType}`).schema;
|
|
75
|
+
const moreSpecific = mediaTypes.filter((other) => other !== mediaType &&
|
|
76
|
+
(mediaType === '*/*'
|
|
77
|
+
? other !== '*/*'
|
|
78
|
+
: mediaType.endsWith('/*') &&
|
|
79
|
+
!other.includes('*') &&
|
|
80
|
+
other.split('/')[0].toLowerCase() ===
|
|
81
|
+
mediaType.split('/')[0].toLowerCase()));
|
|
82
|
+
return {
|
|
83
|
+
properties: {
|
|
84
|
+
statusCode: status,
|
|
85
|
+
mediaType: {
|
|
86
|
+
pattern: mediaPattern(mediaType),
|
|
87
|
+
...(moreSpecific.length
|
|
88
|
+
? {
|
|
89
|
+
not: {
|
|
90
|
+
anyOf: moreSpecific.map((other) => ({
|
|
91
|
+
pattern: mediaPattern(other),
|
|
92
|
+
})),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
: {}),
|
|
96
|
+
},
|
|
97
|
+
body,
|
|
98
|
+
},
|
|
99
|
+
required: ['mediaType'],
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
statusCode: { type: 'integer', minimum: 100, maximum: 599 },
|
|
108
|
+
mediaType: { type: 'string' },
|
|
109
|
+
body: true,
|
|
110
|
+
},
|
|
111
|
+
required: ['statusCode'],
|
|
112
|
+
...(branches.length ? { anyOf: branches } : { not: {} }),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiOperationContract } from '../../types/api-operation.js';
|
|
2
|
+
import type { BindingSchema } from '../../types/api-bindings.js';
|
|
3
|
+
/** Item candidates come from OAS; the original full response is validated at runtime. */
|
|
4
|
+
export declare function createResponseArrayItemSchema(operation: ApiOperationContract, pointer: string): BindingSchema;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { responseBindingSchemas } from '../../bindings/response-binding-schema.js';
|
|
2
|
+
import { projectArrayItemSchema } from '../../bindings/project-array-item-schema.js';
|
|
3
|
+
/** Item candidates come from OAS; the original full response is validated at runtime. */
|
|
4
|
+
export function createResponseArrayItemSchema(operation, pointer) {
|
|
5
|
+
const items = responseBindingSchemas(operation, pointer).flatMap((schema) => {
|
|
6
|
+
const projection = projectArrayItemSchema(schema);
|
|
7
|
+
return projection.allowsArray ? [projection.schema] : [];
|
|
8
|
+
});
|
|
9
|
+
if (!items.length)
|
|
10
|
+
throw new Error(`${operation.key.operationRef}${pointer} has no declared response array`);
|
|
11
|
+
return { anyOf: items };
|
|
12
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiOperationContract } from '../../types/api-operation.js';
|
|
2
|
+
import type { BindingSchema } from '../../types/api-bindings.js';
|
|
3
|
+
/** Candidate schemas for a pointer; validate the selected full response at runtime. */
|
|
4
|
+
export declare function createResponseValueSchema(operation: ApiOperationContract, pointer: string): BindingSchema;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { responseBindingSchemas } from '../../bindings/response-binding-schema.js';
|
|
2
|
+
/** Candidate schemas for a pointer; validate the selected full response at runtime. */
|
|
3
|
+
export function createResponseValueSchema(operation, pointer) {
|
|
4
|
+
const schemas = responseBindingSchemas(operation, pointer);
|
|
5
|
+
if (!schemas.length)
|
|
6
|
+
throw new Error(`${operation.key.operationRef}${pointer} has no declared response value`);
|
|
7
|
+
return { anyOf: schemas };
|
|
8
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiOperationContract } from '../../types/api-operation.js';
|
|
2
|
+
import type { ApiResponsePointerCatalog } from '../../types/api-response-pointers.js';
|
|
3
|
+
/** Describe finite schema declarations, not guaranteed runtime fields or a whitelist. */
|
|
4
|
+
export declare function listApiResponsePointers(operation: ApiOperationContract): ApiResponsePointerCatalog[];
|