mcp-from-openapi 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/LICENSE +201 -0
- package/README.md +666 -0
- package/package.json +51 -0
- package/src/errors.d.ts +38 -0
- package/src/errors.js +62 -0
- package/src/errors.js.map +1 -0
- package/src/generator.d.ts +65 -0
- package/src/generator.js +348 -0
- package/src/generator.js.map +1 -0
- package/src/index.d.ts +8 -0
- package/src/index.js +27 -0
- package/src/index.js.map +1 -0
- package/src/parameter-resolver.d.ts +32 -0
- package/src/parameter-resolver.js +211 -0
- package/src/parameter-resolver.js.map +1 -0
- package/src/response-builder.d.ts +30 -0
- package/src/response-builder.js +147 -0
- package/src/response-builder.js.map +1 -0
- package/src/schema-builder.d.ts +123 -0
- package/src/schema-builder.js +296 -0
- package/src/schema-builder.js.map +1 -0
- package/src/types.d.ts +371 -0
- package/src/types.js +38 -0
- package/src/types.js.map +1 -0
- package/src/validator.d.ts +26 -0
- package/src/validator.js +211 -0
- package/src/validator.js.map +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ParameterResolver = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
/**
|
|
6
|
+
* Resolves parameters and handles naming conflicts
|
|
7
|
+
*/
|
|
8
|
+
class ParameterResolver {
|
|
9
|
+
constructor(namingStrategy) {
|
|
10
|
+
this.namingStrategy = namingStrategy ?? {
|
|
11
|
+
conflictResolver: this.defaultConflictResolver,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Default conflict resolver: prefix with location
|
|
16
|
+
*/
|
|
17
|
+
defaultConflictResolver(paramName, location, index) {
|
|
18
|
+
const locationPrefix = {
|
|
19
|
+
path: 'path',
|
|
20
|
+
query: 'query',
|
|
21
|
+
header: 'header',
|
|
22
|
+
cookie: 'cookie',
|
|
23
|
+
body: 'body',
|
|
24
|
+
}[location];
|
|
25
|
+
return `${locationPrefix}${paramName.charAt(0).toUpperCase()}${paramName.slice(1)}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve all parameters for an operation
|
|
29
|
+
*/
|
|
30
|
+
resolve(operation, pathParameters) {
|
|
31
|
+
const allParameters = [
|
|
32
|
+
...(pathParameters ?? []),
|
|
33
|
+
...(operation.parameters ?? []),
|
|
34
|
+
];
|
|
35
|
+
const requestBody = operation.requestBody;
|
|
36
|
+
// Collect all parameter names and detect conflicts
|
|
37
|
+
const parametersByName = new Map();
|
|
38
|
+
// Process standard parameters
|
|
39
|
+
allParameters.forEach((param) => {
|
|
40
|
+
const info = {
|
|
41
|
+
name: param.name,
|
|
42
|
+
location: param.in,
|
|
43
|
+
required: param.required ?? (param.in === 'path'),
|
|
44
|
+
schema: param.schema ?? { type: 'string' },
|
|
45
|
+
description: param.description,
|
|
46
|
+
style: param.style,
|
|
47
|
+
explode: param.explode,
|
|
48
|
+
allowReserved: param.allowReserved,
|
|
49
|
+
deprecated: param.deprecated,
|
|
50
|
+
};
|
|
51
|
+
if (!parametersByName.has(param.name)) {
|
|
52
|
+
parametersByName.set(param.name, []);
|
|
53
|
+
}
|
|
54
|
+
parametersByName.get(param.name).push(info);
|
|
55
|
+
});
|
|
56
|
+
// Process request body
|
|
57
|
+
if (requestBody?.content) {
|
|
58
|
+
const contentType = this.selectContentType(requestBody.content);
|
|
59
|
+
const mediaType = requestBody.content[contentType];
|
|
60
|
+
if (mediaType?.schema) {
|
|
61
|
+
this.extractBodyParameters(mediaType.schema, parametersByName, requestBody.required ?? false, contentType);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Resolve conflicts and build schema + mapper
|
|
65
|
+
const properties = {};
|
|
66
|
+
const required = [];
|
|
67
|
+
const mapper = [];
|
|
68
|
+
for (const [originalName, params] of parametersByName.entries()) {
|
|
69
|
+
if (params.length === 1) {
|
|
70
|
+
// No conflict
|
|
71
|
+
const param = params[0];
|
|
72
|
+
const inputKey = originalName;
|
|
73
|
+
properties[inputKey] = this.buildParameterSchema(param);
|
|
74
|
+
if (param.required) {
|
|
75
|
+
required.push(inputKey);
|
|
76
|
+
}
|
|
77
|
+
mapper.push({
|
|
78
|
+
inputKey,
|
|
79
|
+
type: param.location,
|
|
80
|
+
key: originalName,
|
|
81
|
+
required: param.required,
|
|
82
|
+
style: param.style,
|
|
83
|
+
explode: param.explode,
|
|
84
|
+
serialization: param.serialization,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Conflict - need to resolve
|
|
89
|
+
params.forEach((param, index) => {
|
|
90
|
+
const inputKey = this.namingStrategy.conflictResolver(originalName, param.location, index);
|
|
91
|
+
properties[inputKey] = this.buildParameterSchema(param);
|
|
92
|
+
if (param.required) {
|
|
93
|
+
required.push(inputKey);
|
|
94
|
+
}
|
|
95
|
+
mapper.push({
|
|
96
|
+
inputKey,
|
|
97
|
+
type: param.location,
|
|
98
|
+
key: originalName,
|
|
99
|
+
required: param.required,
|
|
100
|
+
style: param.style,
|
|
101
|
+
explode: param.explode,
|
|
102
|
+
serialization: param.serialization,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const inputSchema = {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties,
|
|
110
|
+
...(required.length > 0 && { required }),
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
};
|
|
113
|
+
return { inputSchema, mapper };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extract parameters from request body schema
|
|
117
|
+
*/
|
|
118
|
+
extractBodyParameters(schema, parametersByName, required, contentType, prefix = '') {
|
|
119
|
+
if (!schema || typeof schema !== 'object')
|
|
120
|
+
return;
|
|
121
|
+
// Convert to JSONSchema7 for processing
|
|
122
|
+
const jsonSchema = (0, types_1.toJSONSchema7)(schema);
|
|
123
|
+
// Handle object schemas
|
|
124
|
+
if (jsonSchema.type === 'object' && jsonSchema.properties) {
|
|
125
|
+
const requiredFields = new Set(jsonSchema.required ?? []);
|
|
126
|
+
for (const [propName, propSchema] of Object.entries(jsonSchema.properties)) {
|
|
127
|
+
const fullName = prefix ? `${prefix}.${propName}` : propName;
|
|
128
|
+
const isRequired = required && requiredFields.has(propName);
|
|
129
|
+
if (typeof propSchema === 'object' && !('$ref' in propSchema)) {
|
|
130
|
+
const info = {
|
|
131
|
+
name: fullName,
|
|
132
|
+
location: 'body',
|
|
133
|
+
required: isRequired,
|
|
134
|
+
schema: propSchema,
|
|
135
|
+
description: propSchema.description,
|
|
136
|
+
serialization: {
|
|
137
|
+
contentType,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
if (!parametersByName.has(fullName)) {
|
|
141
|
+
parametersByName.set(fullName, []);
|
|
142
|
+
}
|
|
143
|
+
parametersByName.get(fullName).push(info);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// For non-object bodies (arrays, primitives), treat as single parameter
|
|
149
|
+
const bodyParamName = prefix || 'body';
|
|
150
|
+
const info = {
|
|
151
|
+
name: bodyParamName,
|
|
152
|
+
location: 'body',
|
|
153
|
+
required,
|
|
154
|
+
schema,
|
|
155
|
+
serialization: {
|
|
156
|
+
contentType,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
if (!parametersByName.has(bodyParamName)) {
|
|
160
|
+
parametersByName.set(bodyParamName, []);
|
|
161
|
+
}
|
|
162
|
+
parametersByName.get(bodyParamName).push(info);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build JSON Schema for a parameter
|
|
167
|
+
*/
|
|
168
|
+
buildParameterSchema(param) {
|
|
169
|
+
const schema = (0, types_1.toJSONSchema7)(param.schema);
|
|
170
|
+
if (param.description) {
|
|
171
|
+
schema.description = param.description;
|
|
172
|
+
}
|
|
173
|
+
if (param.deprecated) {
|
|
174
|
+
schema['deprecated'] = true;
|
|
175
|
+
}
|
|
176
|
+
// Add parameter metadata
|
|
177
|
+
schema['x-parameter-location'] = param.location;
|
|
178
|
+
if (param.style) {
|
|
179
|
+
schema['x-parameter-style'] = param.style;
|
|
180
|
+
}
|
|
181
|
+
if (param.explode !== undefined) {
|
|
182
|
+
schema['x-parameter-explode'] = param.explode;
|
|
183
|
+
}
|
|
184
|
+
return schema;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Select the most appropriate content type
|
|
188
|
+
*/
|
|
189
|
+
selectContentType(content) {
|
|
190
|
+
// Preference order
|
|
191
|
+
const preferences = [
|
|
192
|
+
'application/json',
|
|
193
|
+
'application/x-www-form-urlencoded',
|
|
194
|
+
'multipart/form-data',
|
|
195
|
+
'application/xml',
|
|
196
|
+
'text/plain',
|
|
197
|
+
];
|
|
198
|
+
for (const pref of preferences) {
|
|
199
|
+
if (content[pref])
|
|
200
|
+
return pref;
|
|
201
|
+
}
|
|
202
|
+
// Fallback to first available
|
|
203
|
+
const firstKey = Object.keys(content)[0];
|
|
204
|
+
if (!firstKey) {
|
|
205
|
+
throw new Error('No content type available in request body');
|
|
206
|
+
}
|
|
207
|
+
return firstKey;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.ParameterResolver = ParameterResolver;
|
|
211
|
+
//# sourceMappingURL=parameter-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parameter-resolver.js","sourceRoot":"","sources":["../../src/parameter-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JSONSchema7 } from 'json-schema';\nimport type {\n ParameterMapper,\n ParameterObject,\n RequestBodyObject,\n NamingStrategy,\n ParameterLocation,\n SchemaObject,\n ReferenceObject,\n} from './types';\nimport { toJSONSchema7 } from './types';\n\n/**\n * Resolves parameters and handles naming conflicts\n */\nexport class ParameterResolver {\n private namingStrategy: NamingStrategy;\n\n constructor(namingStrategy?: NamingStrategy) {\n this.namingStrategy = namingStrategy ?? {\n conflictResolver: this.defaultConflictResolver,\n };\n }\n\n /**\n * Default conflict resolver: prefix with location\n */\n private defaultConflictResolver(\n paramName: string,\n location: ParameterLocation,\n index: number\n ): string {\n const locationPrefix = {\n path: 'path',\n query: 'query',\n header: 'header',\n cookie: 'cookie',\n body: 'body',\n }[location];\n\n return `${locationPrefix}${paramName.charAt(0).toUpperCase()}${paramName.slice(1)}`;\n }\n\n /**\n * Resolve all parameters for an operation\n */\n resolve(\n operation: any,\n pathParameters?: ParameterObject[]\n ): {\n inputSchema: JSONSchema7;\n mapper: ParameterMapper[];\n } {\n const allParameters: ParameterObject[] = [\n ...(pathParameters ?? []),\n ...(operation.parameters ?? []),\n ];\n\n const requestBody = operation.requestBody as RequestBodyObject | undefined;\n\n // Collect all parameter names and detect conflicts\n const parametersByName = new Map<string, ParameterInfo[]>();\n\n // Process standard parameters\n allParameters.forEach((param) => {\n const info: ParameterInfo = {\n name: param.name,\n location: param.in as ParameterLocation,\n required: param.required ?? (param.in === 'path'),\n schema: param.schema ?? { type: 'string' },\n description: param.description,\n style: param.style,\n explode: param.explode,\n allowReserved: param.allowReserved,\n deprecated: param.deprecated,\n };\n\n if (!parametersByName.has(param.name)) {\n parametersByName.set(param.name, []);\n }\n parametersByName.get(param.name)!.push(info);\n });\n\n // Process request body\n if (requestBody?.content) {\n const contentType = this.selectContentType(requestBody.content);\n const mediaType = requestBody.content[contentType];\n\n if (mediaType?.schema) {\n this.extractBodyParameters(\n mediaType.schema,\n parametersByName,\n requestBody.required ?? false,\n contentType\n );\n }\n }\n\n // Resolve conflicts and build schema + mapper\n const properties: Record<string, JSONSchema7> = {};\n const required: string[] = [];\n const mapper: ParameterMapper[] = [];\n\n for (const [originalName, params] of parametersByName.entries()) {\n if (params.length === 1) {\n // No conflict\n const param = params[0];\n const inputKey = originalName;\n\n properties[inputKey] = this.buildParameterSchema(param);\n if (param.required) {\n required.push(inputKey);\n }\n\n mapper.push({\n inputKey,\n type: param.location,\n key: originalName,\n required: param.required,\n style: param.style,\n explode: param.explode,\n serialization: param.serialization,\n });\n } else {\n // Conflict - need to resolve\n params.forEach((param, index) => {\n const inputKey = this.namingStrategy.conflictResolver(\n originalName,\n param.location,\n index\n );\n\n properties[inputKey] = this.buildParameterSchema(param);\n if (param.required) {\n required.push(inputKey);\n }\n\n mapper.push({\n inputKey,\n type: param.location,\n key: originalName,\n required: param.required,\n style: param.style,\n explode: param.explode,\n serialization: param.serialization,\n });\n });\n }\n }\n\n const inputSchema: JSONSchema7 = {\n type: 'object',\n properties,\n ...(required.length > 0 && { required }),\n additionalProperties: false,\n };\n\n return { inputSchema, mapper };\n }\n\n /**\n * Extract parameters from request body schema\n */\n private extractBodyParameters(\n schema: SchemaObject | ReferenceObject,\n parametersByName: Map<string, ParameterInfo[]>,\n required: boolean,\n contentType: string,\n prefix = ''\n ): void {\n if (!schema || typeof schema !== 'object') return;\n\n // Convert to JSONSchema7 for processing\n const jsonSchema = toJSONSchema7(schema);\n\n // Handle object schemas\n if (jsonSchema.type === 'object' && jsonSchema.properties) {\n const requiredFields = new Set(jsonSchema.required ?? []);\n\n for (const [propName, propSchema] of Object.entries(jsonSchema.properties)) {\n const fullName = prefix ? `${prefix}.${propName}` : propName;\n const isRequired = required && requiredFields.has(propName);\n\n if (typeof propSchema === 'object' && !('$ref' in propSchema)) {\n const info: ParameterInfo = {\n name: fullName,\n location: 'body',\n required: isRequired,\n schema: propSchema as JSONSchema7,\n description: (propSchema as any).description,\n serialization: {\n contentType,\n },\n };\n\n if (!parametersByName.has(fullName)) {\n parametersByName.set(fullName, []);\n }\n parametersByName.get(fullName)!.push(info);\n }\n }\n } else {\n // For non-object bodies (arrays, primitives), treat as single parameter\n const bodyParamName = prefix || 'body';\n const info: ParameterInfo = {\n name: bodyParamName,\n location: 'body',\n required,\n schema,\n serialization: {\n contentType,\n },\n };\n\n if (!parametersByName.has(bodyParamName)) {\n parametersByName.set(bodyParamName, []);\n }\n parametersByName.get(bodyParamName)!.push(info);\n }\n }\n\n /**\n * Build JSON Schema for a parameter\n */\n private buildParameterSchema(param: ParameterInfo): JSONSchema7 {\n const schema: JSONSchema7 = toJSONSchema7(param.schema as any);\n\n if (param.description) {\n schema.description = param.description;\n }\n\n if (param.deprecated) {\n schema['deprecated'] = true;\n }\n\n // Add parameter metadata\n (schema as any)['x-parameter-location'] = param.location;\n if (param.style) {\n (schema as any)['x-parameter-style'] = param.style;\n }\n if (param.explode !== undefined) {\n (schema as any)['x-parameter-explode'] = param.explode;\n }\n\n return schema;\n }\n\n /**\n * Select the most appropriate content type\n */\n private selectContentType(content: Record<string, any>): string {\n // Preference order\n const preferences = [\n 'application/json',\n 'application/x-www-form-urlencoded',\n 'multipart/form-data',\n 'application/xml',\n 'text/plain',\n ];\n\n for (const pref of preferences) {\n if (content[pref]) return pref;\n }\n\n // Fallback to first available\n const firstKey = Object.keys(content)[0];\n if (!firstKey) {\n throw new Error('No content type available in request body');\n }\n return firstKey;\n }\n}\n\n/**\n * Internal parameter info structure\n */\ninterface ParameterInfo {\n name: string;\n location: ParameterLocation;\n required: boolean;\n schema: SchemaObject | ReferenceObject | JSONSchema7;\n description?: string;\n style?: string;\n explode?: boolean;\n allowReserved?: boolean;\n deprecated?: boolean;\n serialization?: {\n contentType?: string;\n encoding?: Record<string, any>;\n };\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
import type { GenerateOptions, ResponsesObject } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Builds output schemas from OpenAPI response definitions
|
|
5
|
+
*/
|
|
6
|
+
export declare class ResponseBuilder {
|
|
7
|
+
private preferredStatusCodes;
|
|
8
|
+
private includeAllResponses;
|
|
9
|
+
constructor(options?: GenerateOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Build output schema from responses
|
|
12
|
+
*/
|
|
13
|
+
build(responses?: ResponsesObject): JSONSchema7 | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Extract schemas from all responses
|
|
16
|
+
*/
|
|
17
|
+
private extractResponseSchemas;
|
|
18
|
+
/**
|
|
19
|
+
* Extract schema from a single response
|
|
20
|
+
*/
|
|
21
|
+
private extractResponseSchema;
|
|
22
|
+
/**
|
|
23
|
+
* Select the most appropriate content type
|
|
24
|
+
*/
|
|
25
|
+
private selectContentType;
|
|
26
|
+
/**
|
|
27
|
+
* Select the preferred schema based on status code preferences
|
|
28
|
+
*/
|
|
29
|
+
private selectPreferredSchema;
|
|
30
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResponseBuilder = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
/**
|
|
6
|
+
* Builds output schemas from OpenAPI response definitions
|
|
7
|
+
*/
|
|
8
|
+
class ResponseBuilder {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.preferredStatusCodes = options.preferredStatusCodes ?? [200, 201, 204, 202, 203, 206];
|
|
11
|
+
this.includeAllResponses = options.includeAllResponses ?? true;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build output schema from responses
|
|
15
|
+
*/
|
|
16
|
+
build(responses) {
|
|
17
|
+
if (!responses || Object.keys(responses).length === 0) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const schemas = this.extractResponseSchemas(responses);
|
|
21
|
+
if (schemas.length === 0) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
if (schemas.length === 1) {
|
|
25
|
+
return schemas[0].schema;
|
|
26
|
+
}
|
|
27
|
+
if (this.includeAllResponses) {
|
|
28
|
+
// Return union of all response schemas
|
|
29
|
+
return {
|
|
30
|
+
oneOf: schemas.map((s) => s.schema),
|
|
31
|
+
description: 'Response can be one of multiple status codes',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Return only the preferred status code
|
|
36
|
+
const preferred = this.selectPreferredSchema(schemas);
|
|
37
|
+
return preferred.schema;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extract schemas from all responses
|
|
42
|
+
*/
|
|
43
|
+
extractResponseSchemas(responses) {
|
|
44
|
+
const schemas = [];
|
|
45
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
46
|
+
// Skip if it's a reference object
|
|
47
|
+
if ((0, types_1.isReferenceObject)(response))
|
|
48
|
+
continue;
|
|
49
|
+
// Skip 'default' for now, we'll handle it separately
|
|
50
|
+
if (statusCode === 'default')
|
|
51
|
+
continue;
|
|
52
|
+
const code = parseInt(statusCode, 10);
|
|
53
|
+
if (isNaN(code))
|
|
54
|
+
continue;
|
|
55
|
+
const schema = this.extractResponseSchema(response, code);
|
|
56
|
+
if (schema) {
|
|
57
|
+
schemas.push(schema);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Handle default response if no other schemas found
|
|
61
|
+
if (schemas.length === 0 && responses['default']) {
|
|
62
|
+
const defaultResponse = responses['default'];
|
|
63
|
+
if (!(0, types_1.isReferenceObject)(defaultResponse)) {
|
|
64
|
+
const schema = this.extractResponseSchema(defaultResponse, 0);
|
|
65
|
+
if (schema) {
|
|
66
|
+
schemas.push(schema);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return schemas;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract schema from a single response
|
|
74
|
+
*/
|
|
75
|
+
extractResponseSchema(response, statusCode) {
|
|
76
|
+
if (!response.content) {
|
|
77
|
+
// Response with no content (e.g., 204 No Content)
|
|
78
|
+
return {
|
|
79
|
+
statusCode,
|
|
80
|
+
schema: {
|
|
81
|
+
type: 'null',
|
|
82
|
+
description: response.description,
|
|
83
|
+
'x-status-code': statusCode,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const contentType = this.selectContentType(response.content);
|
|
88
|
+
const mediaType = response.content[contentType];
|
|
89
|
+
if (!mediaType?.schema) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const schema = {
|
|
93
|
+
...(0, types_1.toJSONSchema7)(mediaType.schema),
|
|
94
|
+
'x-status-code': statusCode,
|
|
95
|
+
};
|
|
96
|
+
// Add description if not already present
|
|
97
|
+
if (!schema.description && response.description) {
|
|
98
|
+
schema.description = response.description;
|
|
99
|
+
}
|
|
100
|
+
// Add content type metadata
|
|
101
|
+
schema['x-content-type'] = contentType;
|
|
102
|
+
return { statusCode, schema };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Select the most appropriate content type
|
|
106
|
+
*/
|
|
107
|
+
selectContentType(content) {
|
|
108
|
+
// Preference order for responses
|
|
109
|
+
const preferences = [
|
|
110
|
+
'application/json',
|
|
111
|
+
'application/hal+json',
|
|
112
|
+
'application/problem+json',
|
|
113
|
+
'application/xml',
|
|
114
|
+
'text/plain',
|
|
115
|
+
'text/html',
|
|
116
|
+
];
|
|
117
|
+
for (const pref of preferences) {
|
|
118
|
+
if (content[pref])
|
|
119
|
+
return pref;
|
|
120
|
+
}
|
|
121
|
+
// Fallback to first available
|
|
122
|
+
return Object.keys(content)[0];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Select the preferred schema based on status code preferences
|
|
126
|
+
*/
|
|
127
|
+
selectPreferredSchema(schemas) {
|
|
128
|
+
// First, try to find exact match in preferred list
|
|
129
|
+
for (const preferredCode of this.preferredStatusCodes) {
|
|
130
|
+
const found = schemas.find((s) => s.statusCode === preferredCode);
|
|
131
|
+
if (found)
|
|
132
|
+
return found;
|
|
133
|
+
}
|
|
134
|
+
// Next, try to find any 2xx response
|
|
135
|
+
const success = schemas.find((s) => s.statusCode >= 200 && s.statusCode < 300);
|
|
136
|
+
if (success)
|
|
137
|
+
return success;
|
|
138
|
+
// Next, try to find any 3xx response
|
|
139
|
+
const redirect = schemas.find((s) => s.statusCode >= 300 && s.statusCode < 400);
|
|
140
|
+
if (redirect)
|
|
141
|
+
return redirect;
|
|
142
|
+
// Fallback to first available
|
|
143
|
+
return schemas[0];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.ResponseBuilder = ResponseBuilder;
|
|
147
|
+
//# sourceMappingURL=response-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-builder.js","sourceRoot":"","sources":["../../src/response-builder.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JSONSchema7 } from 'json-schema';\nimport type { ResponseObject, GenerateOptions, ResponsesObject } from './types';\nimport { isReferenceObject, toJSONSchema7 } from './types';\n\n/**\n * Builds output schemas from OpenAPI response definitions\n */\nexport class ResponseBuilder {\n private preferredStatusCodes: number[];\n private includeAllResponses: boolean;\n\n constructor(options: GenerateOptions = {}) {\n this.preferredStatusCodes = options.preferredStatusCodes ?? [200, 201, 204, 202, 203, 206];\n this.includeAllResponses = options.includeAllResponses ?? true;\n }\n\n /**\n * Build output schema from responses\n */\n build(responses?: ResponsesObject): JSONSchema7 | undefined {\n if (!responses || Object.keys(responses).length === 0) {\n return undefined;\n }\n\n const schemas = this.extractResponseSchemas(responses);\n\n if (schemas.length === 0) {\n return undefined;\n }\n\n if (schemas.length === 1) {\n return schemas[0].schema;\n }\n\n if (this.includeAllResponses) {\n // Return union of all response schemas\n return {\n oneOf: schemas.map((s) => s.schema),\n description: 'Response can be one of multiple status codes',\n };\n } else {\n // Return only the preferred status code\n const preferred = this.selectPreferredSchema(schemas);\n return preferred.schema;\n }\n }\n\n /**\n * Extract schemas from all responses\n */\n private extractResponseSchemas(responses: ResponsesObject): ResponseSchema[] {\n const schemas: ResponseSchema[] = [];\n\n for (const [statusCode, response] of Object.entries(responses)) {\n // Skip if it's a reference object\n if (isReferenceObject(response)) continue;\n\n // Skip 'default' for now, we'll handle it separately\n if (statusCode === 'default') continue;\n\n const code = parseInt(statusCode, 10);\n if (isNaN(code)) continue;\n\n const schema = this.extractResponseSchema(response, code);\n if (schema) {\n schemas.push(schema);\n }\n }\n\n // Handle default response if no other schemas found\n if (schemas.length === 0 && responses['default']) {\n const defaultResponse = responses['default'];\n if (!isReferenceObject(defaultResponse)) {\n const schema = this.extractResponseSchema(defaultResponse, 0);\n if (schema) {\n schemas.push(schema);\n }\n }\n }\n\n return schemas;\n }\n\n /**\n * Extract schema from a single response\n */\n private extractResponseSchema(response: ResponseObject, statusCode: number): ResponseSchema | null {\n if (!response.content) {\n // Response with no content (e.g., 204 No Content)\n return {\n statusCode,\n schema: {\n type: 'null',\n description: response.description,\n 'x-status-code': statusCode,\n } as JSONSchema7,\n };\n }\n\n const contentType = this.selectContentType(response.content);\n const mediaType = response.content[contentType];\n\n if (!mediaType?.schema) {\n return null;\n }\n\n const schema: JSONSchema7 & { 'x-status-code'?: number } = {\n ...toJSONSchema7(mediaType.schema),\n 'x-status-code': statusCode,\n };\n\n // Add description if not already present\n if (!schema.description && response.description) {\n schema.description = response.description;\n }\n\n // Add content type metadata\n (schema as any)['x-content-type'] = contentType;\n\n return { statusCode, schema };\n }\n\n /**\n * Select the most appropriate content type\n */\n private selectContentType(content: Record<string, any>): string {\n // Preference order for responses\n const preferences = [\n 'application/json',\n 'application/hal+json',\n 'application/problem+json',\n 'application/xml',\n 'text/plain',\n 'text/html',\n ];\n\n for (const pref of preferences) {\n if (content[pref]) return pref;\n }\n\n // Fallback to first available\n return Object.keys(content)[0];\n }\n\n /**\n * Select the preferred schema based on status code preferences\n */\n private selectPreferredSchema(schemas: ResponseSchema[]): ResponseSchema {\n // First, try to find exact match in preferred list\n for (const preferredCode of this.preferredStatusCodes) {\n const found = schemas.find((s) => s.statusCode === preferredCode);\n if (found) return found;\n }\n\n // Next, try to find any 2xx response\n const success = schemas.find((s) => s.statusCode >= 200 && s.statusCode < 300);\n if (success) return success;\n\n // Next, try to find any 3xx response\n const redirect = schemas.find((s) => s.statusCode >= 300 && s.statusCode < 400);\n if (redirect) return redirect;\n\n // Fallback to first available\n return schemas[0];\n }\n}\n\n/**\n * Internal response schema structure\n */\ninterface ResponseSchema {\n statusCode: number;\n schema: JSONSchema7;\n}\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
/**
|
|
3
|
+
* Helper class for building and manipulating JSON schemas
|
|
4
|
+
*/
|
|
5
|
+
export declare class SchemaBuilder {
|
|
6
|
+
/**
|
|
7
|
+
* Merge multiple schemas into one
|
|
8
|
+
*/
|
|
9
|
+
static merge(schemas: JSONSchema7[]): JSONSchema7;
|
|
10
|
+
/**
|
|
11
|
+
* Create a union schema (oneOf)
|
|
12
|
+
*/
|
|
13
|
+
static union(schemas: JSONSchema7[]): JSONSchema7;
|
|
14
|
+
/**
|
|
15
|
+
* Deep clone a schema
|
|
16
|
+
*/
|
|
17
|
+
static clone(schema: JSONSchema7): JSONSchema7;
|
|
18
|
+
/**
|
|
19
|
+
* Remove $ref from schema (assumes already dereferenced)
|
|
20
|
+
*/
|
|
21
|
+
static removeRefs(schema: JSONSchema7): JSONSchema7;
|
|
22
|
+
private static removeRefsRecursive;
|
|
23
|
+
/**
|
|
24
|
+
* Add description to schema
|
|
25
|
+
*/
|
|
26
|
+
static withDescription(schema: JSONSchema7, description: string): JSONSchema7;
|
|
27
|
+
/**
|
|
28
|
+
* Mark schema as required
|
|
29
|
+
*/
|
|
30
|
+
static required(schema: JSONSchema7): JSONSchema7;
|
|
31
|
+
/**
|
|
32
|
+
* Mark schema as optional
|
|
33
|
+
*/
|
|
34
|
+
static optional(schema: JSONSchema7): JSONSchema7;
|
|
35
|
+
/**
|
|
36
|
+
* Add example to schema
|
|
37
|
+
*/
|
|
38
|
+
static withExample(schema: JSONSchema7, example: any): JSONSchema7;
|
|
39
|
+
/**
|
|
40
|
+
* Add default value to schema
|
|
41
|
+
*/
|
|
42
|
+
static withDefault(schema: JSONSchema7, defaultValue: any): JSONSchema7;
|
|
43
|
+
/**
|
|
44
|
+
* Add format to schema
|
|
45
|
+
*/
|
|
46
|
+
static withFormat(schema: JSONSchema7, format: string): JSONSchema7;
|
|
47
|
+
/**
|
|
48
|
+
* Add pattern to schema
|
|
49
|
+
*/
|
|
50
|
+
static withPattern(schema: JSONSchema7, pattern: string): JSONSchema7;
|
|
51
|
+
/**
|
|
52
|
+
* Add enum to schema
|
|
53
|
+
*/
|
|
54
|
+
static withEnum(schema: JSONSchema7, values: any[]): JSONSchema7;
|
|
55
|
+
/**
|
|
56
|
+
* Add minimum/maximum constraints
|
|
57
|
+
*/
|
|
58
|
+
static withRange(schema: JSONSchema7, min?: number, max?: number, options?: {
|
|
59
|
+
exclusive?: boolean;
|
|
60
|
+
}): JSONSchema7;
|
|
61
|
+
/**
|
|
62
|
+
* Add minLength/maxLength constraints
|
|
63
|
+
*/
|
|
64
|
+
static withLength(schema: JSONSchema7, minLength?: number, maxLength?: number): JSONSchema7;
|
|
65
|
+
/**
|
|
66
|
+
* Create object schema
|
|
67
|
+
*/
|
|
68
|
+
static object(properties: Record<string, JSONSchema7>, required?: string[]): JSONSchema7;
|
|
69
|
+
/**
|
|
70
|
+
* Create array schema
|
|
71
|
+
*/
|
|
72
|
+
static array(items: JSONSchema7, constraints?: {
|
|
73
|
+
minItems?: number;
|
|
74
|
+
maxItems?: number;
|
|
75
|
+
uniqueItems?: boolean;
|
|
76
|
+
}): JSONSchema7;
|
|
77
|
+
/**
|
|
78
|
+
* Create string schema
|
|
79
|
+
*/
|
|
80
|
+
static string(constraints?: {
|
|
81
|
+
minLength?: number;
|
|
82
|
+
maxLength?: number;
|
|
83
|
+
pattern?: string;
|
|
84
|
+
format?: string;
|
|
85
|
+
enum?: string[];
|
|
86
|
+
}): JSONSchema7;
|
|
87
|
+
/**
|
|
88
|
+
* Create number schema
|
|
89
|
+
*/
|
|
90
|
+
static number(constraints?: {
|
|
91
|
+
minimum?: number;
|
|
92
|
+
maximum?: number;
|
|
93
|
+
exclusiveMinimum?: number;
|
|
94
|
+
exclusiveMaximum?: number;
|
|
95
|
+
multipleOf?: number;
|
|
96
|
+
}): JSONSchema7;
|
|
97
|
+
/**
|
|
98
|
+
* Create integer schema
|
|
99
|
+
*/
|
|
100
|
+
static integer(constraints?: {
|
|
101
|
+
minimum?: number;
|
|
102
|
+
maximum?: number;
|
|
103
|
+
exclusiveMinimum?: number;
|
|
104
|
+
exclusiveMaximum?: number;
|
|
105
|
+
multipleOf?: number;
|
|
106
|
+
}): JSONSchema7;
|
|
107
|
+
/**
|
|
108
|
+
* Create boolean schema
|
|
109
|
+
*/
|
|
110
|
+
static boolean(): JSONSchema7;
|
|
111
|
+
/**
|
|
112
|
+
* Create null schema
|
|
113
|
+
*/
|
|
114
|
+
static null(): JSONSchema7;
|
|
115
|
+
/**
|
|
116
|
+
* Flatten nested oneOf/anyOf/allOf schemas
|
|
117
|
+
*/
|
|
118
|
+
static flatten(schema: JSONSchema7, maxDepth?: number): JSONSchema7;
|
|
119
|
+
/**
|
|
120
|
+
* Simplify schema by removing unnecessary fields
|
|
121
|
+
*/
|
|
122
|
+
static simplify(schema: JSONSchema7): JSONSchema7;
|
|
123
|
+
}
|