poe-code 3.0.288 → 3.0.290

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "3.0.288",
3
+ "version": "3.0.290",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1230,7 +1230,12 @@ function resolveEquivalentCompositionSchema(document, schema, operationId, conte
1230
1230
  const { anyOf: _anyOf, oneOf: _oneOf, ...wrapper } = schema;
1231
1231
  void _anyOf;
1232
1232
  void _oneOf;
1233
- return { ...wrapper, ...createSchemaFromEquivalentShape(firstShape) };
1233
+ const enumValues = collectEquivalentCompositionEnumValues(resolved);
1234
+ return {
1235
+ ...wrapper,
1236
+ ...createSchemaFromEquivalentShape(firstShape),
1237
+ ...(enumValues === undefined ? {} : { enum: enumValues })
1238
+ };
1234
1239
  }
1235
1240
  function getEquivalentSchemaShape(schema) {
1236
1241
  if (getCompositionKeyword(schema) !== undefined || Array.isArray(schema.type)) {
@@ -1250,6 +1255,20 @@ function createSchemaFromEquivalentShape(shape) {
1250
1255
  }
1251
1256
  return { type: "array", items: createSchemaFromEquivalentShape(shape.slice("array:".length)) };
1252
1257
  }
1258
+ function collectEquivalentCompositionEnumValues(schemas) {
1259
+ if (schemas.some((schema) => schema.enum === undefined)) {
1260
+ return undefined;
1261
+ }
1262
+ const values = [];
1263
+ for (const schema of schemas) {
1264
+ for (const value of schema.enum ?? []) {
1265
+ if (!values.some((existing) => Object.is(existing, value))) {
1266
+ values.push(value);
1267
+ }
1268
+ }
1269
+ }
1270
+ return values.length === 0 ? undefined : values;
1271
+ }
1253
1272
  function normalizeNullableTypeArray(schema) {
1254
1273
  if (!Array.isArray(schema.type)) {
1255
1274
  return schema;
@@ -34,6 +34,10 @@ export async function mockFetch(options) {
34
34
  body: parsedBody,
35
35
  at: new Date()
36
36
  });
37
+ const parameterErrors = validateRequestParameters(operation, requestUrl, headers, document);
38
+ if (parameterErrors.length > 0) {
39
+ return jsonResponse(422, { errors: parameterErrors });
40
+ }
37
41
  if (operation.requestBodySchema !== undefined && parsedBody !== undefined) {
38
42
  const errors = validateAgainstSchema(parsedBody, operation.requestBodySchema, document, "$");
39
43
  if (errors.length > 0) {
@@ -46,7 +50,7 @@ export async function mockFetch(options) {
46
50
  const fixture = await fixtureLoader(operation.operationId);
47
51
  if (fixture !== undefined) {
48
52
  const status = fixture.status ?? operation.defaultStatus;
49
- const responseSchema = operation.responseSchemas.get(status);
53
+ const responseSchema = findResponseSchema(operation.responseSchemas, status);
50
54
  if (responseSchema !== undefined && fixture.body !== undefined) {
51
55
  const errors = validateAgainstSchema(fixture.body, responseSchema, document, "$response");
52
56
  if (errors.length > 0) {
@@ -110,13 +114,16 @@ function compileOperations(document) {
110
114
  const operationId = resolvedOperation.operationId ?? `${method.toUpperCase()} ${pathTemplate}`;
111
115
  const { defaultStatus, defaultExample, responseSchemas } = pickResponseMetadata(resolvedOperation, document);
112
116
  const { schema: requestBodySchema, required: requestBodyRequired } = pickRequestBody(resolvedOperation, document);
117
+ const pathParameterNames = collectPathParameterNames(pathTemplate);
113
118
  compiled.push({
114
119
  method: method.toUpperCase(),
115
120
  pathTemplate,
116
121
  pathRegex: pathTemplateToRegex(pathTemplate),
122
+ pathParameterNames,
117
123
  pathSpecificity: countPathPlaceholders(pathTemplate),
118
124
  operationId,
119
125
  operation: resolvedOperation,
126
+ parameters: collectCompiledParameters(pathItem.parameters, resolvedOperation.parameters, document),
120
127
  defaultStatus,
121
128
  defaultExample,
122
129
  requestBodySchema,
@@ -131,14 +138,31 @@ function compileOperations(document) {
131
138
  return compiled;
132
139
  }
133
140
  function countPathPlaceholders(template) {
134
- return (template.match(/\{[^}]+\}/g) ?? []).length;
141
+ return collectPathParameterNames(template).length;
135
142
  }
136
143
  function pathTemplateToRegex(template) {
137
144
  // Escape regex metacharacters except for "{...}" placeholders, which become non-slash captures.
138
145
  const pattern = template.replace(/[.*+?^${}()|[\]\\]/g, (match) => match === "{" || match === "}" ? match : `\\${match}`);
139
- const withParams = pattern.replace(/\{[^}]+\}/g, "[^/]+");
146
+ const withParams = pattern.replace(/\{[^}]+\}/g, "([^/]+)");
140
147
  return new RegExp(`^${withParams}$`);
141
148
  }
149
+ function collectPathParameterNames(template) {
150
+ const names = [];
151
+ let index = 0;
152
+ while (index < template.length) {
153
+ const start = template.indexOf("{", index);
154
+ if (start === -1) {
155
+ break;
156
+ }
157
+ const end = template.indexOf("}", start + 1);
158
+ if (end === -1) {
159
+ break;
160
+ }
161
+ names.push(template.slice(start + 1, end));
162
+ index = end + 1;
163
+ }
164
+ return names;
165
+ }
142
166
  function resolveOperation(operation, document) {
143
167
  if (isReference(operation)) {
144
168
  return resolveReference(operation, document);
@@ -147,10 +171,11 @@ function resolveOperation(operation, document) {
147
171
  }
148
172
  function pickResponseMetadata(operation, document) {
149
173
  const responses = operation.responses ?? {};
150
- const responseSchemas = new Map();
174
+ const responseSchemas = [];
175
+ const successResponses = [];
151
176
  for (const [code, response] of Object.entries(responses)) {
152
- const status = parseInt(code, 10);
153
- if (!Number.isFinite(status) || response === undefined) {
177
+ const status = parseResponseStatus(code);
178
+ if (status === undefined || response === undefined) {
154
179
  continue;
155
180
  }
156
181
  const resolved = isReference(response)
@@ -158,27 +183,46 @@ function pickResponseMetadata(operation, document) {
158
183
  : response;
159
184
  const schema = extractResponseSchema(resolved, document);
160
185
  if (schema !== undefined) {
161
- responseSchemas.set(status, schema);
186
+ responseSchemas.push({ ...status, schema });
187
+ }
188
+ if (isSuccessStatus(status.status)) {
189
+ successResponses.push({ status: status.status, response });
162
190
  }
163
191
  }
164
- const successCodes = Object.keys(responses)
165
- .map((code) => parseInt(code, 10))
166
- .filter((code) => Number.isFinite(code) && code >= 200 && code < 300)
167
- .sort((a, b) => a - b);
168
- if (successCodes.length === 0) {
192
+ successResponses.sort((a, b) => a.status - b.status);
193
+ const firstSuccess = successResponses[0];
194
+ if (firstSuccess === undefined) {
169
195
  return { defaultStatus: 200, defaultExample: undefined, responseSchemas };
170
196
  }
171
- const status = successCodes[0];
172
- const response = responses[String(status)];
173
- const resolvedResponse = response !== undefined && isReference(response)
174
- ? resolveReference(response, document)
175
- : response;
197
+ const resolvedResponse = isReference(firstSuccess.response)
198
+ ? resolveReference(firstSuccess.response, document)
199
+ : firstSuccess.response;
176
200
  return {
177
- defaultStatus: status,
201
+ defaultStatus: firstSuccess.status,
178
202
  defaultExample: extractExample(resolvedResponse, document),
179
203
  responseSchemas
180
204
  };
181
205
  }
206
+ function parseResponseStatus(code) {
207
+ if (code.length === 3 && code[1]?.toUpperCase() === "X" && code[2]?.toUpperCase() === "X") {
208
+ const family = Number(code[0]);
209
+ return Number.isInteger(family) && family >= 1 && family <= 5
210
+ ? { status: family * 100, range: true }
211
+ : undefined;
212
+ }
213
+ if (code.length !== 3 || [...code].some((character) => character < "0" || character > "9")) {
214
+ return undefined;
215
+ }
216
+ return { status: Number(code), range: false };
217
+ }
218
+ function isSuccessStatus(status) {
219
+ return status >= 200 && status < 300;
220
+ }
221
+ function findResponseSchema(schemas, status) {
222
+ return (schemas.find((candidate) => !candidate.range && candidate.status === status)?.schema ??
223
+ schemas.find((candidate) => candidate.range && Math.floor(status / 100) === candidate.status / 100)
224
+ ?.schema);
225
+ }
182
226
  function extractResponseSchema(response, document) {
183
227
  if (response === undefined) {
184
228
  return undefined;
@@ -211,6 +255,95 @@ function pickRequestBody(operation, document) {
211
255
  : media.schema;
212
256
  return { schema, required: resolved.required === true };
213
257
  }
258
+ function collectCompiledParameters(pathParameters, operationParameters, document) {
259
+ const parameters = new Map();
260
+ for (const raw of [...(pathParameters ?? []), ...(operationParameters ?? [])]) {
261
+ const parameter = isReference(raw)
262
+ ? resolveReference(raw, document)
263
+ : raw;
264
+ if (parameter.in !== "path" && parameter.in !== "query" && parameter.in !== "header") {
265
+ continue;
266
+ }
267
+ parameters.set(`${parameter.in}:${parameter.name}`, {
268
+ name: parameter.name,
269
+ location: parameter.in,
270
+ required: parameter.in === "path" || parameter.required === true,
271
+ schema: parameter.schema
272
+ });
273
+ }
274
+ return [...parameters.values()];
275
+ }
276
+ function validateRequestParameters(operation, requestUrl, headers, document) {
277
+ const pathValues = collectPathParameterValues(operation, requestUrl.pathname);
278
+ const errors = [];
279
+ for (const parameter of operation.parameters) {
280
+ const pointer = `$${parameter.location}/${parameter.name}`;
281
+ const value = readParameterValue(parameter, requestUrl, headers, pathValues);
282
+ if (value === undefined) {
283
+ if (parameter.required) {
284
+ errors.push(`${pointer}: required`);
285
+ }
286
+ continue;
287
+ }
288
+ if (parameter.schema !== undefined) {
289
+ errors.push(...validateAgainstSchema(value, parameter.schema, document, pointer));
290
+ }
291
+ }
292
+ return errors;
293
+ }
294
+ function collectPathParameterValues(operation, pathname) {
295
+ const match = operation.pathRegex.exec(pathname);
296
+ const values = new Map();
297
+ if (match === null) {
298
+ return values;
299
+ }
300
+ for (let index = 0; index < operation.pathParameterNames.length; index++) {
301
+ const value = match[index + 1];
302
+ if (value !== undefined) {
303
+ values.set(operation.pathParameterNames[index], decodeUrlComponent(value));
304
+ }
305
+ }
306
+ return values;
307
+ }
308
+ function readParameterValue(parameter, requestUrl, headers, pathValues) {
309
+ switch (parameter.location) {
310
+ case "path":
311
+ return coerceParameterValue(parameter.schema, pathValues.get(parameter.name));
312
+ case "query": {
313
+ const values = requestUrl.searchParams.getAll(parameter.name);
314
+ if (values.length === 0) {
315
+ return undefined;
316
+ }
317
+ return schemaAcceptsArray(parameter.schema) ? coerceParameterValues(values) : values[0];
318
+ }
319
+ case "header":
320
+ return coerceParameterValue(parameter.schema, headers[parameter.name.toLowerCase()]);
321
+ }
322
+ }
323
+ function coerceParameterValue(schema, value) {
324
+ if (value === undefined || !schemaAcceptsArray(schema)) {
325
+ return value;
326
+ }
327
+ return coerceParameterValues([value]);
328
+ }
329
+ function coerceParameterValues(values) {
330
+ return values.flatMap((value) => value.split(","));
331
+ }
332
+ function schemaAcceptsArray(schema) {
333
+ if (schema === undefined || isReference(schema)) {
334
+ return false;
335
+ }
336
+ const type = schema.type;
337
+ return Array.isArray(type) ? type.includes("array") : type === "array";
338
+ }
339
+ function decodeUrlComponent(value) {
340
+ try {
341
+ return decodeURIComponent(value);
342
+ }
343
+ catch {
344
+ return value;
345
+ }
346
+ }
214
347
  function extractExample(response, document) {
215
348
  if (response === undefined) {
216
349
  return undefined;
@@ -265,7 +398,7 @@ function resolveReference(reference, document) {
265
398
  const segments = ref
266
399
  .slice(2)
267
400
  .split("/")
268
- .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
401
+ .map(decodeReferenceSegment);
269
402
  let current = document;
270
403
  for (const segment of segments) {
271
404
  if (current === null || typeof current !== "object") {
@@ -281,6 +414,16 @@ function resolveReference(reference, document) {
281
414
  }
282
415
  return current;
283
416
  }
417
+ function decodeReferenceSegment(segment) {
418
+ let decoded = segment;
419
+ try {
420
+ decoded = decodeURIComponent(segment);
421
+ }
422
+ catch {
423
+ decoded = segment;
424
+ }
425
+ return decoded.replace(/~1/g, "/").replace(/~0/g, "~");
426
+ }
284
427
  function validateAgainstSchema(value, schema, document, pointer) {
285
428
  const resolved = isReference(schema)
286
429
  ? resolveReference(schema, document)
@@ -334,11 +477,36 @@ function validateAgainstSchema(value, schema, document, pointer) {
334
477
  errors.push(...validateAgainstSchema(value[i], resolved.items, document, `${pointer}/${i}`));
335
478
  }
336
479
  }
480
+ if (typeof value === "number") {
481
+ validateNumberConstraints(value, resolved, pointer, errors);
482
+ }
483
+ if (typeof value === "string") {
484
+ validateStringConstraints(value, resolved, pointer, errors);
485
+ }
337
486
  if (resolved.enum !== undefined && !resolved.enum.includes(value)) {
338
487
  errors.push(`${pointer}: not in enum`);
339
488
  }
340
489
  return errors;
341
490
  }
491
+ function validateNumberConstraints(value, schema, pointer, errors) {
492
+ if (schema.minimum !== undefined && value < schema.minimum) {
493
+ errors.push(`${pointer}: expected value to be >= ${schema.minimum}`);
494
+ }
495
+ if (schema.maximum !== undefined && value > schema.maximum) {
496
+ errors.push(`${pointer}: expected value to be <= ${schema.maximum}`);
497
+ }
498
+ }
499
+ function validateStringConstraints(value, schema, pointer, errors) {
500
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
501
+ errors.push(`${pointer}: expected length to be >= ${schema.minLength}`);
502
+ }
503
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
504
+ errors.push(`${pointer}: expected length to be <= ${schema.maxLength}`);
505
+ }
506
+ if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
507
+ errors.push(`${pointer}: expected value to match pattern ${schema.pattern}`);
508
+ }
509
+ }
342
510
  function normalizeTypes(schema) {
343
511
  const type = schema.type;
344
512
  if (type === undefined) {