kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(api): add normalizeEmptyAndEnumSchemas function for OpenAPI spec processing

Andrej 13c544d1 3b0e2a67

+65 -3
+5 -2
apps/api/src/index.ts
··· 59 59 markOptionalSchemaFieldsNullable, 60 60 mergeOpenApiSpecs, 61 61 normalizeApiServerUrl, 62 + normalizeEmptyAndEnumSchemas, 62 63 normalizeEmptyRequiredArrays, 63 64 normalizeNullableSchemasForOpenApi30, 64 65 normalizeOrganizationAuthOperations, ··· 309 310 dedupeOperationIds( 310 311 markOptionalSchemaFieldsNullable( 311 312 normalizeNullableSchemasForOpenApi30( 312 - normalizeEmptyRequiredArrays( 313 - mergeOpenApiSpecs(honoSpec, normalizedAuthSpec), 313 + normalizeEmptyAndEnumSchemas( 314 + normalizeEmptyRequiredArrays( 315 + mergeOpenApiSpecs(honoSpec, normalizedAuthSpec), 316 + ), 314 317 ), 315 318 ), 316 319 ),
+59
apps/api/src/utils/openapi-spec.ts
··· 437 437 return spec; 438 438 }; 439 439 440 + export const normalizeEmptyAndEnumSchemas = (spec: Record<string, unknown>) => { 441 + const visit = (node: unknown, parentIsProperties?: boolean): void => { 442 + if (Array.isArray(node)) { 443 + for (const item of node) { 444 + visit(item, false); 445 + } 446 + return; 447 + } 448 + 449 + if (!isPlainObject(node)) { 450 + return; 451 + } 452 + 453 + // propertyNames is not valid in OpenAPI 3.0.x — remove it 454 + if ("propertyNames" in node) { 455 + delete node.propertyNames; 456 + } 457 + 458 + // Schema with enum but no type → add type: "string" 459 + if (Array.isArray(node.enum) && !node.type && !node.$ref) { 460 + node.type = "string"; 461 + } 462 + 463 + // $ref with siblings is invalid in 3.0.x → wrap in allOf 464 + if (typeof node.$ref === "string" && Object.keys(node).length > 1) { 465 + const ref = node.$ref as string; 466 + delete node.$ref; 467 + const rest = { ...node }; 468 + for (const k of Object.keys(node)) { 469 + delete node[k]; 470 + } 471 + Object.assign(node, { allOf: [{ $ref: ref }], ...rest }); 472 + } 473 + 474 + // For "properties" maps, check children for empty schemas (v.date() → {}) 475 + if (isPlainObject(node.properties)) { 476 + const props = node.properties as Record<string, unknown>; 477 + for (const [name, schema] of Object.entries(props)) { 478 + if (isPlainObject(schema) && Object.keys(schema).length === 0) { 479 + props[name] = { type: "string", format: "date-time" }; 480 + } 481 + } 482 + } 483 + 484 + for (const [k, value] of Object.entries(node)) { 485 + // Replace remaining empty schemas {} (e.g. v.any() or v.unknown()). 486 + if (isPlainObject(value) && Object.keys(value).length === 0) { 487 + // additionalProperties: {} → true (means "any additional properties") 488 + node[k] = k === "additionalProperties" ? true : { type: "object" }; 489 + continue; 490 + } 491 + visit(value, k === "properties"); 492 + } 493 + }; 494 + 495 + visit(spec, false); 496 + return spec; 497 + }; 498 + 440 499 export const ensureOperationSummaries = (spec: Record<string, unknown>) => { 441 500 const paths = ((spec as { paths?: unknown }).paths || {}) as Record< 442 501 string,
+1 -1
apps/docs/docs.json
··· 115 115 }, 116 116 { 117 117 "tab": "API Reference", 118 - "openapi": "https://cloud.kaneo.app/api/openapi" 118 + "openapi": "http://localhost:1337/api/openapi" 119 119 } 120 120 ], 121 121 "global": {