-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
[TypeScript] Implement oneOf type resolution without discriminator #22201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
0e6998f
451f488
74351c7
fe10060
234643d
e03add7
49e768a
97a775d
eee422e
81acbbc
8e7a128
de4e09e
7da0c2a
ca525a9
0bd35fc
a117fcc
c590359
ded868c
c806d60
1a4b46d
8fd54d2
6be8f0c
69954aa
23a2dd7
3bfe9cc
14d1edc
d4984c0
b04a5ed
f8c65fb
34c403a
3cd8558
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /** | ||
| * Validates if data contains all required attributes from the attributeTypeMap. | ||
| * | ||
| * @param data - The data object to validate | ||
| * @param attributeTypeMap - Array of attribute metadata including required flag | ||
| * @returns true if all required attributes are present in data, false otherwise | ||
| */ | ||
| export function instanceOfType(data: any, attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string, required: boolean}>): boolean { | ||
| for (const attribute of attributeTypeMap) { | ||
| if (attribute.required) { | ||
| // Check both that the property exists AND that it's not undefined. | ||
| // This is important because `data[attribute.baseName] === undefined` alone | ||
| // would be true for both missing properties and properties explicitly set to undefined, | ||
| // while `!(attribute.baseName in data)` distinguishes between these cases. | ||
| // For proper OpenAPI validation, required fields must actually be present in the data. | ||
| if (!(attribute.baseName in data) || data[attribute.baseName] === undefined) { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Attempts to find a matching type from an array of possible types by validating | ||
| * the data against each type's attribute requirements. | ||
| * | ||
| * @param data - The data object to match | ||
| * @param types - Array of possible type constructors | ||
| * @returns The name of the matching type, or undefined if no match found | ||
| */ | ||
| export function findMatchingType(data: any, types: Array<any>): string | undefined { | ||
| for (const type of types) { | ||
| if (instanceOfType(data, type.getAttributeTypeMap())) { | ||
| return type.name; | ||
| } | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,14 @@ | |
| {{#tsImports}} | ||
| import { {{classname}} } from '{{filename}}{{importFileExtension}}'; | ||
| {{/tsImports}} | ||
| {{#oneOf}} | ||
| {{#-first}} | ||
| import { findMatchingType } from '../models/TypeMatcher{{importFileExtension}}'; | ||
| {{/-first}} | ||
| {{/oneOf}} | ||
| {{^oneOf}} | ||
| import { HttpFile } from '../http/http{{importFileExtension}}'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we sure this is not used in the oneOf case? |
||
| {{/oneOf}} | ||
|
|
||
| {{#description}} | ||
| /** | ||
|
|
@@ -46,13 +53,14 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ | |
| {{/hasDiscriminatorWithNonEmptyMapping}} | ||
|
|
||
| {{^isArray}} | ||
| static {{#parent}}override {{/parent}}readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [ | ||
| static {{#parent}}override {{/parent}}readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string, required: boolean}> = [ | ||
| {{#vars}} | ||
| { | ||
| "name": "{{name}}", | ||
| "baseName": "{{baseName}}", | ||
| "type": "{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}", | ||
| "format": "{{dataFormat}}" | ||
| "format": "{{dataFormat}}", | ||
| "required": {{required}} | ||
| }{{^-last}}, | ||
| {{/-last}} | ||
| {{/vars}} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,3 @@ | ||
| {{#hasImports}} | ||
| import { | ||
| {{#imports}} | ||
| {{{.}}}{{importFileExtension}}, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this not needed anymore? |
||
| {{/imports}} | ||
| } from './'; | ||
|
|
||
| {{/hasImports}} | ||
| /** | ||
| * @type {{classname}} | ||
| * Type | ||
|
|
@@ -37,4 +29,16 @@ export class {{classname}}Class { | |
|
|
||
| static readonly mapping: {[index: string]: string} | undefined = undefined; | ||
| {{/hasDiscriminatorWithNonEmptyMapping}} | ||
|
|
||
| private static readonly arrayOfTypes: Array<{{#oneOf}}typeof {{{.}}}{{^-last}} | {{/-last}}{{/oneOf}}> = [{{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}]; | ||
|
|
||
| /** | ||
| * Determines which oneOf schema matches the provided data. | ||
| * | ||
| * @param data - The data object to match against oneOf schemas | ||
| * @returns The name of the matching type, or undefined if no unique match is found | ||
| */ | ||
| public static findMatchingType(data: any): string | undefined { | ||
| return findMatchingType(data, this.arrayOfTypes); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| openapi: 3.0.0 | ||
| info: | ||
| version: 1.0.0 | ||
| title: testing oneOf | ||
| servers: | ||
| - url: http://localhost:3000 | ||
| paths: | ||
| /test: | ||
| get: | ||
| operationId: testWithoutDiscriminator | ||
| responses: | ||
| 200: | ||
| description: OK | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/PetResponse' | ||
| /test-discriminator: | ||
| get: | ||
| operationId: testDiscriminator | ||
| responses: | ||
| 200: | ||
| description: OK | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/PetDiscriminatorResponse' | ||
| components: | ||
| schemas: | ||
| PetDiscriminatorResponse: | ||
| discriminator: | ||
| propertyName: petType | ||
| mapping: | ||
| cat: "#/components/schemas/Cat" | ||
| dog: "#/components/schemas/Dog" | ||
| oneOf: | ||
| - $ref: "#/components/schemas/Cat" | ||
| - $ref: "#/components/schemas/Dog" | ||
| PetResponse: | ||
| oneOf: | ||
| - $ref: "#/components/schemas/Cat" | ||
| - $ref: "#/components/schemas/Dog" | ||
| Cat: | ||
| type: object | ||
| properties: | ||
| name: | ||
| type: string | ||
| petType: | ||
| type: string | ||
| required: | ||
| - name | ||
| - petType | ||
| Dog: | ||
| type: object | ||
| properties: | ||
| bark: | ||
| type: string | ||
| petType: | ||
| type: string | ||
| required: | ||
| - bark | ||
| - petType |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.