fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

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

Fix discriminator values in writable type variants

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>

+152 -40
+1 -1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-one-of-read-write/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - export type { AnimalPayload, CatPayload, CatPayloadWritable, ClientOptions, CreatePetRequest, CreatePetRequestWritable, CreatePetResponse, CreatePetResponseWritable, DogPayload, DogPayloadWritable, PostPetsData, PostPetsResponse, PostPetsResponses } from './types.gen'; 3 + export type { AnimalPayload, AnimalPayloadWritable, CatPayload, CatPayloadWritable, ClientOptions, CreatePetRequest, CreatePetRequestWritable, CreatePetResponse, CreatePetResponseWritable, DogPayload, DogPayloadWritable, PostPetsData, PostPetsResponse, PostPetsResponses } from './types.gen';
+8 -4
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-one-of-read-write/types.gen.ts
··· 41 41 } & CatPayload); 42 42 }; 43 43 44 - export type DogPayloadWritable = Omit<AnimalPayload, 'typeDiscriminator'> & { 44 + export type DogPayloadWritable = Omit<AnimalPayloadWritable, 'typeDiscriminator'> & { 45 45 breed: string; 46 46 canFetch: boolean; 47 - typeDiscriminator: 'DogPayloadWritable'; 47 + typeDiscriminator: 'dog'; 48 48 }; 49 49 50 - export type CatPayloadWritable = Omit<AnimalPayload, 'typeDiscriminator'> & { 50 + export type CatPayloadWritable = Omit<AnimalPayloadWritable, 'typeDiscriminator'> & { 51 51 breed: string; 52 52 livesRemaining: number; 53 - typeDiscriminator: 'CatPayloadWritable'; 53 + typeDiscriminator: 'cat'; 54 54 }; 55 55 56 56 export type CreatePetRequestWritable = { ··· 70 70 } & DogPayloadWritable) | ({ 71 71 typeDiscriminator: 'cat'; 72 72 } & CatPayloadWritable); 73 + }; 74 + 75 + export type AnimalPayloadWritable = { 76 + typeDiscriminator: string; 73 77 }; 74 78 75 79 export type PostPetsData = {
+143 -35
packages/shared/src/openApi/shared/transforms/readWrite.ts
··· 437 437 split.reverseMapping[writePointer] = pointer; 438 438 } 439 439 440 - // Helper function to update discriminator mappings in a schema 441 - const updateDiscriminatorMappingsInSchema = ( 442 - schema: unknown, 443 - contextVariant: 'read' | 'write', 444 - ) => { 445 - if (schema && typeof schema === 'object') { 446 - // If this schema has a discriminator with a mapping 440 + //Create writable variants of parent schemas that have discriminators and are referenced by split schemas 441 + const parentSchemasToSplit = new Map<string, Set<'read' | 'write'>>(); 442 + 443 + // First pass: identify parent schemas that need writable variants 444 + for (const [name, schema] of Object.entries(split.schemas)) { 445 + const pointer = `${schemasPointerNamespace}${name}`; 446 + const originalPointer = split.reverseMapping[pointer]; 447 + 448 + if (originalPointer) { 449 + const mapping = split.mapping[originalPointer]; 450 + if (mapping) { 451 + const contextVariant = 452 + mapping.read === pointer ? 'read' : mapping.write === pointer ? 'write' : null; 453 + 454 + if (contextVariant && schema && typeof schema === 'object') { 455 + // Check allOf for $refs to schemas with discriminators 456 + if ('allOf' in schema && Array.isArray((schema as Record<string, unknown>).allOf)) { 457 + const allOf = (schema as Record<string, unknown>).allOf as Array<unknown>; 458 + 459 + for (const comp of allOf) { 460 + if ( 461 + comp && 462 + typeof comp === 'object' && 463 + '$ref' in comp && 464 + typeof comp.$ref === 'string' 465 + ) { 466 + const ref = comp.$ref as string; 467 + const schemasObj = getSchemasObject(spec); 468 + 469 + if (schemasObj) { 470 + const refPath = jsonPointerToPath(ref); 471 + const schemaName = refPath[refPath.length - 1]; 472 + 473 + if (typeof schemaName === 'string' && schemaName in schemasObj) { 474 + const resolvedSchema = schemasObj[schemaName]; 475 + 476 + // Check if this schema has a discriminator with mapping 477 + if ( 478 + resolvedSchema && 479 + typeof resolvedSchema === 'object' && 480 + 'discriminator' in resolvedSchema && 481 + resolvedSchema.discriminator && 482 + typeof resolvedSchema.discriminator === 'object' && 483 + 'mapping' in resolvedSchema.discriminator && 484 + resolvedSchema.discriminator.mapping && 485 + typeof resolvedSchema.discriminator.mapping === 'object' 486 + ) { 487 + // This parent schema needs a variant for this context 488 + if (!parentSchemasToSplit.has(ref)) { 489 + parentSchemasToSplit.set(ref, new Set()); 490 + } 491 + parentSchemasToSplit.get(ref)!.add(contextVariant); 492 + } 493 + } 494 + } 495 + } 496 + } 497 + } 498 + } 499 + } 500 + } 501 + } 502 + 503 + // Second pass: create writable variants of parent schemas and update their discriminator mappings 504 + const parentSchemaVariants = new Map<string, { read?: string; write?: string }>(); 505 + 506 + for (const [parentRef, contexts] of parentSchemasToSplit) { 507 + const schemasObj = getSchemasObject(spec); 508 + if (!schemasObj) continue; 509 + 510 + const refPath = jsonPointerToPath(parentRef); 511 + const parentName = refPath[refPath.length - 1]; 512 + 513 + if (typeof parentName !== 'string' || !(parentName in schemasObj)) continue; 514 + 515 + const parentSchema = schemasObj[parentName]; 516 + if (!parentSchema || typeof parentSchema !== 'object') continue; 517 + 518 + const variants: { read?: string; write?: string } = {}; 519 + 520 + // Create variants for each context 521 + for (const context of contexts) { 522 + const variantSchema = deepClone(parentSchema); 523 + 524 + // Update discriminator mapping in the variant 447 525 if ( 448 - 'discriminator' in schema && 449 - schema.discriminator && 450 - typeof schema.discriminator === 'object' && 451 - 'mapping' in schema.discriminator && 452 - schema.discriminator.mapping && 453 - typeof schema.discriminator.mapping === 'object' 526 + 'discriminator' in variantSchema && 527 + variantSchema.discriminator && 528 + typeof variantSchema.discriminator === 'object' && 529 + 'mapping' in variantSchema.discriminator && 530 + variantSchema.discriminator.mapping && 531 + typeof variantSchema.discriminator.mapping === 'object' 454 532 ) { 455 - const mapping = schema.discriminator.mapping as Record<string, string>; 533 + const mapping = (variantSchema.discriminator as any).mapping as Record<string, string>; 456 534 const updatedMapping: Record<string, string> = {}; 457 535 458 536 for (const [discriminatorValue, originalRef] of Object.entries(mapping)) { 459 537 const map = split.mapping[originalRef]; 460 538 if (map) { 461 - // Update to the appropriate variant 462 - if (contextVariant === 'read' && map.read) { 539 + if (context === 'read' && map.read) { 463 540 updatedMapping[discriminatorValue] = map.read; 464 - } else if (contextVariant === 'write' && map.write) { 541 + } else if (context === 'write' && map.write) { 465 542 updatedMapping[discriminatorValue] = map.write; 466 543 } else { 467 544 updatedMapping[discriminatorValue] = originalRef; ··· 471 548 } 472 549 } 473 550 474 - (schema.discriminator as Record<string, unknown>).mapping = updatedMapping; 551 + (variantSchema as any).discriminator.mapping = updatedMapping; 475 552 } 476 553 477 - // Recursively update discriminators in allOf, oneOf, anyOf 478 - for (const key of ['allOf', 'oneOf', 'anyOf'] as const) { 479 - if (key in schema && Array.isArray((schema as Record<string, unknown>)[key])) { 480 - const compositions = (schema as Record<string, unknown>)[key] as Array<unknown>; 481 - for (const comp of compositions) { 482 - updateDiscriminatorMappingsInSchema(comp, contextVariant); 483 - } 484 - } 554 + // Add the variant to split.schemas with an appropriate name 555 + if (context === 'write') { 556 + const variantBase = applyNaming(parentName, config.requests); 557 + const variantName = 558 + variantBase === parentName 559 + ? `${parentName}Writable` 560 + : getUniqueComponentName({ 561 + base: variantBase, 562 + components: existingNames, 563 + }); 564 + existingNames.add(variantName); 565 + split.schemas[variantName] = variantSchema; 566 + variants.write = `${schemasPointerNamespace}${variantName}`; 485 567 } 568 + // We could create read variants too, but typically they're not needed 569 + // since the original schema serves as the read variant 486 570 } 487 - }; 571 + 572 + parentSchemaVariants.set(parentRef, variants); 573 + } 488 574 489 - // Update discriminator mappings in all split schemas 575 + // Third pass: update $refs in split schemas to point to the parent variants 490 576 for (const [name, schema] of Object.entries(split.schemas)) { 491 577 const pointer = `${schemasPointerNamespace}${name}`; 492 578 const originalPointer = split.reverseMapping[pointer]; ··· 494 580 if (originalPointer) { 495 581 const mapping = split.mapping[originalPointer]; 496 582 if (mapping) { 497 - // Determine if this is a read or write variant 498 - const isRead = mapping.read === pointer; 499 - const isWrite = mapping.write === pointer; 583 + const contextVariant = 584 + mapping.read === pointer ? 'read' : mapping.write === pointer ? 'write' : null; 500 585 501 - if (isRead) { 502 - updateDiscriminatorMappingsInSchema(schema, 'read'); 503 - } else if (isWrite) { 504 - updateDiscriminatorMappingsInSchema(schema, 'write'); 586 + if (contextVariant && schema && typeof schema === 'object') { 587 + // Update $refs in allOf 588 + if ('allOf' in schema && Array.isArray((schema as Record<string, unknown>).allOf)) { 589 + const allOf = (schema as Record<string, unknown>).allOf as Array<unknown>; 590 + 591 + for (let i = 0; i < allOf.length; i++) { 592 + const comp = allOf[i]; 593 + 594 + if ( 595 + comp && 596 + typeof comp === 'object' && 597 + '$ref' in comp && 598 + typeof comp.$ref === 'string' 599 + ) { 600 + const ref = comp.$ref as string; 601 + const variants = parentSchemaVariants.get(ref); 602 + 603 + if (variants) { 604 + if (contextVariant === 'write' && variants.write) { 605 + (comp as any).$ref = variants.write; 606 + } else if (contextVariant === 'read' && variants.read) { 607 + (comp as any).$ref = variants.read; 608 + } 609 + } 610 + } 611 + } 612 + } 505 613 } 506 614 } 507 615 }