diff --git a/.changeset/rich-cats-study.md b/.changeset/rich-cats-study.md new file mode 100644 index 000000000000..b8b0ed49262a --- /dev/null +++ b/.changeset/rich-cats-study.md @@ -0,0 +1,5 @@ +--- +'@omnigraph/json-schema': patch +--- + +Handle non object type resolution correctly in union types diff --git a/packages/loaders/json-schema/src/directives.ts b/packages/loaders/json-schema/src/directives.ts index 1dd1e3d37ca1..f3fc63b918c4 100644 --- a/packages/loaders/json-schema/src/directives.ts +++ b/packages/loaders/json-schema/src/directives.ts @@ -108,8 +108,12 @@ export const ResolveRootDirective = new GraphQLDirective({ locations: [DirectiveLocation.FIELD_DEFINITION], }); +function rootResolver(root: any) { + return root; +} + export function processResolveRootAnnotations(field: GraphQLField) { - field.resolve = root => root; + field.resolve = rootResolver; } export const ResolveRootFieldDirective = new GraphQLDirective({ @@ -703,7 +707,11 @@ export const EnumDirective = new GraphQLDirective({ export const OneOfDirective = new GraphQLDirective({ name: 'oneOf', - locations: [DirectiveLocation.OBJECT, DirectiveLocation.INTERFACE], + locations: [ + DirectiveLocation.OBJECT, + DirectiveLocation.INTERFACE, + DirectiveLocation.INPUT_OBJECT, + ], }); export const ExampleDirective = new GraphQLDirective({ diff --git a/packages/loaders/json-schema/src/getTypeResolverFromOutputTCs.ts b/packages/loaders/json-schema/src/getTypeResolverFromOutputTCs.ts index 6d304d2b594a..410003b15225 100644 --- a/packages/loaders/json-schema/src/getTypeResolverFromOutputTCs.ts +++ b/packages/loaders/json-schema/src/getTypeResolverFromOutputTCs.ts @@ -1,5 +1,5 @@ -import { GraphQLObjectType, GraphQLTypeResolver } from 'graphql'; -import { createGraphQLError } from '@graphql-tools/utils'; +import { GraphQLObjectType, GraphQLResolveInfo, GraphQLTypeResolver } from 'graphql'; +import { createGraphQLError, getDirective } from '@graphql-tools/utils'; export function getTypeResolverFromOutputTCs({ possibleTypes, @@ -12,7 +12,7 @@ export function getTypeResolverFromOutputTCs({ discriminatorMapping?: Record; statusCodeTypeNameMap?: Record; }): GraphQLTypeResolver { - return function resolveType(data: any) { + return function resolveType(data: any, _ctx: any, info: GraphQLResolveInfo) { if (data.__typename) { return data.__typename; } else if (discriminatorField != null && data[discriminatorField]) { @@ -27,13 +27,34 @@ export function getTypeResolverFromOutputTCs({ } } + const dataTypeOf = typeof data; + + if (dataTypeOf !== 'object') { + for (const possibleType of possibleTypes) { + const fieldMap = possibleType.getFields(); + const fields = Object.values(fieldMap); + if (fields.length === 1) { + const field = fields[0]; + const directiveObjs = getDirective(info.schema, field, 'resolveRoot'); + if (directiveObjs?.length) { + const fieldType = field.type; + if ('parseValue' in fieldType) { + try { + fieldType.parseValue(data); + return possibleType.name; + } catch (e) {} + } + } + } + } + } + // const validationErrors: Record = {}; - const dataKeys = - typeof data === 'object' - ? Object.keys(data) - // Remove metadata fields used to pass data - .filter(property => !property.toString().startsWith('$')) - : null; + const dataKeys = dataTypeOf + ? Object.keys(data) + // Remove metadata fields used to pass data + .filter(property => !property.toString().startsWith('$')) + : null; for (const possibleType of possibleTypes) { const typeName = possibleType.name; if (dataKeys != null) { diff --git a/packages/loaders/json-schema/src/getUnionTypeComposers.ts b/packages/loaders/json-schema/src/getUnionTypeComposers.ts index df941872fe16..9d1714b11eeb 100644 --- a/packages/loaders/json-schema/src/getUnionTypeComposers.ts +++ b/packages/loaders/json-schema/src/getUnionTypeComposers.ts @@ -10,7 +10,7 @@ import { } from 'graphql-compose'; import { Logger } from '@graphql-mesh/types'; import { JSONSchemaObject } from '@json-schema-tools/meta-schema'; -import { StatusCodeTypeNameDirective } from './directives.js'; +import { ResolveRootDirective, StatusCodeTypeNameDirective } from './directives.js'; import { TypeComposers } from './getComposerFromJSONSchema.js'; export interface GetUnionTypeComposersOpts { @@ -25,11 +25,16 @@ export interface GetUnionTypeComposersOpts { export function getContainerTC(schemaComposer: SchemaComposer, output: ComposeInputType) { const containerTypeName = `${output.getTypeName()}_container`; + schemaComposer.addDirective(ResolveRootDirective); return schemaComposer.getOrCreateOTC(containerTypeName, otc => otc.addFields({ [output.getTypeName()]: { type: output as any, - resolve: root => root, + directives: [ + { + name: 'resolveRoot', + }, + ], }, }), ); diff --git a/packages/loaders/openapi/tests/__snapshots__/basket.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/basket.test.ts.snap index 488a74e2469f..32772995c927 100644 --- a/packages/loaders/openapi/tests/__snapshots__/basket.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/basket.test.ts.snap @@ -6,7 +6,7 @@ exports[`Basket should generate the correct schema 1`] = ` mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION diff --git a/packages/loaders/openapi/tests/__snapshots__/calendly.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/calendly.test.ts.snap index cbc3ba4ef8ff..6496dfceab9e 100644 --- a/packages/loaders/openapi/tests/__snapshots__/calendly.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/calendly.test.ts.snap @@ -6,7 +6,7 @@ exports[`Calendly should generate the correct schema 1`] = ` mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @enum(value: String) on ENUM_VALUE @@ -20,6 +20,8 @@ directive @regexp(pattern: String) on SCALAR directive @length(min: Int, max: Int) on SCALAR +directive @resolveRoot on FIELD_DEFINITION + directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT directive @httpOperation(path: String, operationSpecificHeaders: ObjMap, httpMethod: HTTPMethod, isBinary: Boolean, requestBaseBody: ObjMap, queryParamArgMap: ObjMap, queryStringOptionsByParam: ObjMap) on FIELD_DEFINITION @@ -1469,7 +1471,7 @@ input post_organizations_uuid_invitations_request_Input @example(value: "{\\"val union revoke_users_organization_invitation_response @statusCodeTypeName(statusCode: 204, typeName: "Void_container") @statusCodeTypeName(statusCode: 400, typeName: "Error_Response") @statusCodeTypeName(statusCode: 401, typeName: "UNAUTHENTICATED_response") @statusCodeTypeName(statusCode: 403, typeName: "PERMISSION_DENIED_response") @statusCodeTypeName(statusCode: 404, typeName: "NOT_FOUND_response") @statusCodeTypeName(statusCode: 500, typeName: "UNKNOWN_response") = Void_container | Error_Response | UNAUTHENTICATED_response | PERMISSION_DENIED_response | NOT_FOUND_response | UNKNOWN_response type Void_container { - Void: Void + Void: Void @resolveRoot } "Represents empty values" @@ -1557,7 +1559,7 @@ enum _1_const @typescript(type: "1") @example(value: "1") { union post_data_compliance_deletion_invitees_response @statusCodeTypeName(statusCode: 202, typeName: "JSON_container") @statusCodeTypeName(statusCode: 400, typeName: "INVALID_ARGUMENT_response") @statusCodeTypeName(statusCode: 401, typeName: "UNAUTHENTICATED_response") @statusCodeTypeName(statusCode: 403, typeName: "Error_Response") @statusCodeTypeName(statusCode: 404, typeName: "NOT_FOUND_response") @statusCodeTypeName(statusCode: 500, typeName: "UNKNOWN_response") = JSON_container | INVALID_ARGUMENT_response | UNAUTHENTICATED_response | Error_Response | NOT_FOUND_response | UNKNOWN_response type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } input post_data_compliance_deletion_invitees_request_Input @example(value: "{\\"value\\":{\\"emails\\":[\\"test@example.com\\"]}}") { diff --git a/packages/loaders/openapi/tests/__snapshots__/cloudflare.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/cloudflare.test.ts.snap index c91ae71d67f8..5dc8ecf79280 100644 --- a/packages/loaders/openapi/tests/__snapshots__/cloudflare.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/cloudflare.test.ts.snap @@ -6,7 +6,7 @@ exports[`Cloudflare should generate the correct schema: cloudflare 1`] = ` mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @resolveRoot on FIELD_DEFINITION @@ -3699,7 +3699,7 @@ type cloudflare_images_image_details_4xx_response { union cloudflare_images_base_image_response @statusCodeTypeName(statusCode: 200, typeName: "String_container") @statusCodeTypeName(statusCode: "4xx", typeName: "cloudflare_images_base_image_4xx_response") = String_container | cloudflare_images_base_image_4xx_response type String_container { - String: String + String: String @resolveRoot } type cloudflare_images_base_image_4xx_response { @@ -16581,7 +16581,7 @@ type query_filters_get_a_filter_oneOf_0_allOf_1_result_oneOf_0 { } type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } type filters_get_a_filter_4xx_response { @@ -27371,7 +27371,7 @@ type magic_network_monitoring_rules_update_rule_4xx_response { union magic_network_monitoring_rules_update_advertisement_for_rule_response @statusCodeTypeName(statusCode: 200, typeName: "Boolean_container") @statusCodeTypeName(statusCode: "4xx", typeName: "magic_network_monitoring_rules_update_advertisement_for_rule_4xx_response") = Boolean_container | magic_network_monitoring_rules_update_advertisement_for_rule_4xx_response type Boolean_container { - Boolean: Boolean + Boolean: Boolean @resolveRoot } type magic_network_monitoring_rules_update_advertisement_for_rule_4xx_response { diff --git a/packages/loaders/openapi/tests/__snapshots__/discriminator.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/discriminator.test.ts.snap index 949626d0c82b..20ce995f6e09 100644 --- a/packages/loaders/openapi/tests/__snapshots__/discriminator.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/discriminator.test.ts.snap @@ -5,7 +5,7 @@ exports[`Discriminator Mapping should generate correct schema: discriminator-map query: Query } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @discriminator(field: String, mapping: ObjMap) on INTERFACE | UNION diff --git a/packages/loaders/openapi/tests/__snapshots__/example_api.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/example_api.test.ts.snap index 619bfaa28176..f3ec7bb58148 100644 --- a/packages/loaders/openapi/tests/__snapshots__/example_api.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/example_api.test.ts.snap @@ -8,7 +8,7 @@ exports[`example_api should generate the schema correctly 1`] = ` directive @enum(value: String) on ENUM_VALUE -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @resolveRootField(field: String) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION diff --git a/packages/loaders/openapi/tests/__snapshots__/example_api4.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/example_api4.test.ts.snap index 7ec3553ee4e9..29158b9865d1 100644 --- a/packages/loaders/openapi/tests/__snapshots__/example_api4.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/example_api4.test.ts.snap @@ -5,7 +5,7 @@ exports[`OpenAPI loader: Handle anyOf and oneOf should generate the schema corre query: Query } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @resolveRoot on FIELD_DEFINITION @@ -65,13 +65,13 @@ type differentAttributeObject { union oneOf2_200_response = commonAttributeObject | Int_container type Int_container { - Int: Int + Int: Int @resolveRoot } union oneOf3_200_response = String_container | Int_container type String_container { - String: String + String: String @resolveRoot } union oneOf5_200_response = commonAttributeObject | differentAttributeObject diff --git a/packages/loaders/openapi/tests/__snapshots__/example_api6.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/example_api6.test.ts.snap index 531d38c9cc92..482f4085a592 100644 --- a/packages/loaders/openapi/tests/__snapshots__/example_api6.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/example_api6.test.ts.snap @@ -6,7 +6,7 @@ exports[`example_api6 should generate the schema correctly 1`] = ` mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT diff --git a/packages/loaders/openapi/tests/__snapshots__/government_social_work.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/government_social_work.test.ts.snap index 5ec059991a0d..624fe9483fb9 100644 --- a/packages/loaders/openapi/tests/__snapshots__/government_social_work.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/government_social_work.test.ts.snap @@ -6,7 +6,7 @@ exports[`openAPI loader: government_social_work should generate the schema corre mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @enum(value: String) on ENUM_VALUE diff --git a/packages/loaders/openapi/tests/__snapshots__/nested_objects.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/nested_objects.test.ts.snap index 46eaa3385422..63dbcf954d73 100644 --- a/packages/loaders/openapi/tests/__snapshots__/nested_objects.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/nested_objects.test.ts.snap @@ -5,7 +5,7 @@ exports[`OpanAPI: nested objects should generate the schema correctly 1`] = ` query: Query } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION diff --git a/packages/loaders/openapi/tests/__snapshots__/oneof-without-descriminator.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/oneof-without-descriminator.test.ts.snap new file mode 100644 index 000000000000..26bae1712ab5 --- /dev/null +++ b/packages/loaders/openapi/tests/__snapshots__/oneof-without-descriminator.test.ts.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`oneOf without discriminator should generate correct schema: discriminator-mapping 1`] = ` +"schema { + query: Query + mutation: Mutation +} + +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT + +directive @enum(value: String) on ENUM_VALUE + +directive @typescript(type: String) on SCALAR | ENUM + +directive @example(value: ObjMap) repeatable on FIELD_DEFINITION | OBJECT | INPUT_OBJECT | ENUM | SCALAR + +directive @resolveRoot on FIELD_DEFINITION + +directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT + +directive @httpOperation(path: String, operationSpecificHeaders: ObjMap, httpMethod: HTTPMethod, isBinary: Boolean, requestBaseBody: ObjMap, queryParamArgMap: ObjMap, queryStringOptionsByParam: ObjMap) on FIELD_DEFINITION + +type Query @globalOptions(sourceName: "test") { + dummy: String +} + +type Mutation { + test_endpoint(input: TestType_Input): TestType @httpOperation(path: "/test", operationSpecificHeaders: "{\\"Content-Type\\":\\"application/json\\",\\"accept\\":\\"application/json\\"}", httpMethod: POST) +} + +union TestType = A_const_container | mutation_test_endpoint_oneOf_1 + +type A_const_container { + A_const: A_const @resolveRoot +} + +enum A_const @typescript(type: "\\"A\\"") @example(value: "\\"A\\"") { + A @enum(value: "\\"A\\"") +} + +type mutation_test_endpoint_oneOf_1 { + B: String +} + +input TestType_Input @oneOf { + A_const: A_const + mutation_test_endpoint_oneOf_1_Input: mutation_test_endpoint_oneOf_1_Input +} + +input mutation_test_endpoint_oneOf_1_Input { + B: String +} + +scalar ObjMap + +enum HTTPMethod { + GET + HEAD + POST + PUT + DELETE + CONNECT + OPTIONS + TRACE + PATCH +}" +`; diff --git a/packages/loaders/openapi/tests/__snapshots__/schemas.test.ts.snap b/packages/loaders/openapi/tests/__snapshots__/schemas.test.ts.snap index 28a40a85b14c..fa1aab5ebce4 100644 --- a/packages/loaders/openapi/tests/__snapshots__/schemas.test.ts.snap +++ b/packages/loaders/openapi/tests/__snapshots__/schemas.test.ts.snap @@ -6,16 +6,16 @@ exports[`Schemas BlockFrost should generate the correct schema: BlockFrost 1`] = mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION directive @length(min: Int, max: Int) on SCALAR -directive @resolveRootField(field: String) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION - directive @resolveRoot on FIELD_DEFINITION +directive @resolveRootField(field: String) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION + directive @enum(value: String) on ENUM_VALUE directive @typescript(type: String) on SCALAR | ENUM @@ -799,7 +799,7 @@ type _404_response { union blocks_latest_txs_response @statusCodeTypeName(statusCode: 200, typeName: "String_container") @statusCodeTypeName(statusCode: 400, typeName: "_400_response") @statusCodeTypeName(statusCode: 403, typeName: "_403_response") @statusCodeTypeName(statusCode: 404, typeName: "_404_response") @statusCodeTypeName(statusCode: 418, typeName: "_418_response") @statusCodeTypeName(statusCode: 429, typeName: "_429_response") @statusCodeTypeName(statusCode: 500, typeName: "_500_response") = String_container | _400_response | _403_response | _404_response | _418_response | _429_response | _500_response type String_container { - String: String + String: String @resolveRoot } "Integers that will have a value greater than 0." @@ -1269,7 +1269,7 @@ type query_txs_by_hash_metadata_oneOf_0_items { union query_txs_by_hash_metadata_oneOf_0_items_json_metadata = String_container | JSON_container type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } union txs_by_hash_metadata_cbor_response @statusCodeTypeName(statusCode: 200, typeName: "query_txs_by_hash_metadata_cbor_oneOf_0_items") @statusCodeTypeName(statusCode: 400, typeName: "_400_response") @statusCodeTypeName(statusCode: 403, typeName: "_403_response") @statusCodeTypeName(statusCode: 404, typeName: "_404_response") @statusCodeTypeName(statusCode: 418, typeName: "_418_response") @statusCodeTypeName(statusCode: 429, typeName: "_429_response") @statusCodeTypeName(statusCode: 500, typeName: "_500_response") = query_txs_by_hash_metadata_cbor_oneOf_0_items | _400_response | _403_response | _404_response | _418_response | _429_response | _500_response @@ -2523,7 +2523,7 @@ type Mutation { union post_tx_submit_response @statusCodeTypeName(statusCode: 200, typeName: "post_tx_submit_200_response_container") @statusCodeTypeName(statusCode: 400, typeName: "_400_response") @statusCodeTypeName(statusCode: 403, typeName: "_403_response") @statusCodeTypeName(statusCode: 404, typeName: "_404_response") @statusCodeTypeName(statusCode: 418, typeName: "_418_response") @statusCodeTypeName(statusCode: 425, typeName: "_425_response") @statusCodeTypeName(statusCode: 429, typeName: "_429_response") @statusCodeTypeName(statusCode: 500, typeName: "_500_response") = post_tx_submit_200_response_container | _400_response | _403_response | _404_response | _418_response | _425_response | _429_response | _500_response type post_tx_submit_200_response_container { - post_tx_submit_200_response: post_tx_submit_200_response + post_tx_submit_200_response: post_tx_submit_200_response @resolveRoot } scalar post_tx_submit_200_response @length(min: 64, max: 64) @@ -2589,7 +2589,7 @@ exports[`Schemas CloudFlare should generate the correct schema: CloudFlare 1`] = mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @resolveRoot on FIELD_DEFINITION @@ -6282,7 +6282,7 @@ type cloudflare_images_image_details_4xx_response { union cloudflare_images_base_image_response @statusCodeTypeName(statusCode: 200, typeName: "String_container") @statusCodeTypeName(statusCode: "4xx", typeName: "cloudflare_images_base_image_4xx_response") = String_container | cloudflare_images_base_image_4xx_response type String_container { - String: String + String: String @resolveRoot } type cloudflare_images_base_image_4xx_response { @@ -19164,7 +19164,7 @@ type query_filters_get_a_filter_oneOf_0_allOf_1_result_oneOf_0 { } type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } type filters_get_a_filter_4xx_response { @@ -29954,7 +29954,7 @@ type magic_network_monitoring_rules_update_rule_4xx_response { union magic_network_monitoring_rules_update_advertisement_for_rule_response @statusCodeTypeName(statusCode: 200, typeName: "Boolean_container") @statusCodeTypeName(statusCode: "4xx", typeName: "magic_network_monitoring_rules_update_advertisement_for_rule_4xx_response") = Boolean_container | magic_network_monitoring_rules_update_advertisement_for_rule_4xx_response type Boolean_container { - Boolean: Boolean + Boolean: Boolean @resolveRoot } type magic_network_monitoring_rules_update_advertisement_for_rule_4xx_response { @@ -41421,7 +41421,7 @@ directive @enum(value: String) on ENUM_VALUE directive @resolveRoot on FIELD_DEFINITION -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION @@ -41523,7 +41523,7 @@ union getGlossary_response @statusCodeTypeName(statusCode: 200, typeName: "Gloss union getGlossaryEntries_response @statusCodeTypeName(statusCode: 200, typeName: "String_container") @statusCodeTypeName(statusCode: 400, typeName: "BadRequestGlossaries_response") = String_container | BadRequestGlossaries_response type String_container { - String: String + String: String @resolveRoot } type getUsage_200_response { @@ -41765,7 +41765,7 @@ input DocumentKey_x_www_form_urlencoded_request_Input { union downloadDocument_response @statusCodeTypeName(statusCode: 200, typeName: "File_container") @statusCodeTypeName(statusCode: 404, typeName: "DocumentTranslationError") @statusCodeTypeName(statusCode: 503, typeName: "DocumentTranslationError") = File_container | DocumentTranslationError type File_container { - File: File + File: File @resolveRoot } type DocumentTranslationError { @@ -41792,7 +41792,7 @@ enum tsv_const @typescript(type: "\\"tsv\\"") @example(value: "\\"tsv\\"") { union deleteGlossary_response @statusCodeTypeName(statusCode: 204, typeName: "Void_container") @statusCodeTypeName(statusCode: 400, typeName: "BadRequestGlossaries_response") = Void_container | BadRequestGlossaries_response type Void_container { - Void: Void + Void: Void @resolveRoot } "Represents empty values" @@ -41945,7 +41945,9 @@ directive @typescript(type: String) on SCALAR | ENUM directive @example(value: ObjMap) repeatable on FIELD_DEFINITION | OBJECT | INPUT_OBJECT | ENUM | SCALAR -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT + +directive @resolveRoot on FIELD_DEFINITION directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION @@ -49246,7 +49248,7 @@ type mutation_post_repos_by_owner_by_repo_merges_oneOf_0_parents_items { } type Void_container { - Void: Void + Void: Void @resolveRoot } type mergesConflict { @@ -49425,7 +49427,7 @@ exports[`Schemas IBMLanguageTranslator should generate the correct schema: IBMLa mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @enum(value: String) on ENUM_VALUE @@ -49647,7 +49649,7 @@ directive @typescript(type: String) on SCALAR | ENUM directive @example(value: ObjMap) repeatable on FIELD_DEFINITION | OBJECT | INPUT_OBJECT | ENUM | SCALAR -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @discriminator(field: String, mapping: ObjMap) on INTERFACE | UNION @@ -50326,7 +50328,7 @@ exports[`Schemas Jira should generate the correct schema: Jira 1`] = ` mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @enum(value: String) on ENUM_VALUE @@ -56349,7 +56351,7 @@ enum queryInput_getAvatars_type { union getAvatarImageByType_response @statusCodeTypeName(statusCode: 200, typeName: "JSON_container") @statusCodeTypeName(statusCode: 401, typeName: "ErrorCollection") @statusCodeTypeName(statusCode: 403, typeName: "ErrorCollection") @statusCodeTypeName(statusCode: 404, typeName: "ErrorCollection") = JSON_container | ErrorCollection type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } "The icon type of the avatar." @@ -59242,7 +59244,7 @@ union updateDashboard_response @statusCodeTypeName(statusCode: 200, typeName: "D union deleteDashboard_response @statusCodeTypeName(statusCode: 204, typeName: "Void_container") @statusCodeTypeName(statusCode: 400, typeName: "ErrorCollection") @statusCodeTypeName(statusCode: 401, typeName: "ErrorCollection") = Void_container | ErrorCollection type Void_container { - Void: Void + Void: Void @resolveRoot } union copyDashboard_response @statusCodeTypeName(statusCode: 200, typeName: "Dashboard") @statusCodeTypeName(statusCode: 400, typeName: "ErrorCollection") @statusCodeTypeName(statusCode: 401, typeName: "ErrorCollection") @statusCodeTypeName(statusCode: 404, typeName: "ErrorCollection") = Dashboard | ErrorCollection @@ -62314,7 +62316,7 @@ type deleteStatusesById_400_response @example(value: "{\\"errorMessages\\":[\\"T } type String_container { - String: String + String: String @resolveRoot } "Identifiers for a UI modification." @@ -62988,7 +62990,7 @@ directive @resolveRootField(field: String) on FIELD_DEFINITION | ARGUMENT_DEFINI directive @dictionary on FIELD_DEFINITION -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT @@ -89159,7 +89161,9 @@ exports[`Schemas MeiliSearch should generate the correct schema: MeiliSearch 1`] mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT + +directive @resolveRoot on FIELD_DEFINITION directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION @@ -89167,8 +89171,6 @@ directive @example(value: ObjMap) repeatable on FIELD_DEFINITION | OBJECT | INPU directive @resolveRootField(field: String) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION -directive @resolveRoot on FIELD_DEFINITION - directive @enum(value: String) on ENUM_VALUE directive @typescript(type: String) on SCALAR | ENUM @@ -89635,7 +89637,7 @@ union indexes_settings_synonyms_get_response @statusCodeTypeName(statusCode: 200 union indexes_settings_sortable_attributes_get_response @statusCodeTypeName(statusCode: 200, typeName: "String_container") @statusCodeTypeName(statusCode: 401, typeName: "error") = String_container | error type String_container { - String: String + String: String @resolveRoot } union indexes_settings_stopWords_get_response @statusCodeTypeName(statusCode: 200, typeName: "String_container") @statusCodeTypeName(statusCode: 401, typeName: "error") = String_container | error @@ -90230,7 +90232,7 @@ type Mutation { union dumps_create_response @statusCodeTypeName(statusCode: 202, typeName: "JSON_container") @statusCodeTypeName(statusCode: 401, typeName: "error") = JSON_container | error type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } union indexes_create_response @statusCodeTypeName(statusCode: 202, typeName: "JSON_container") @statusCodeTypeName(statusCode: 400, typeName: "error") @statusCodeTypeName(statusCode: 401, typeName: "error") = JSON_container | error @@ -90579,7 +90581,7 @@ exports[`Schemas Stripe should generate the correct schema: Stripe 1`] = ` mutation: Mutation } -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @length(min: Int, max: Int) on SCALAR @@ -109998,7 +110000,7 @@ scalar mutationInput_PostAccountLoginLinks_input_expand_items @length(min: null, union PutAccountLogout_response @statusCodeTypeName(statusCode: 200, typeName: "JSON_container") @statusCodeTypeName(statusCode: "default", typeName: "error") = JSON_container | error type JSON_container { - JSON: JSON + JSON: JSON @resolveRoot } input PutAccountLogout_request_Input { @@ -122743,7 +122745,7 @@ directive @length(min: Int, max: Int) on SCALAR directive @enum(value: String) on ENUM_VALUE -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION @@ -123646,7 +123648,7 @@ exports[`Schemas Toto should generate the correct schema: Toto 1`] = ` directive @enum(value: String) on ENUM_VALUE -directive @oneOf on OBJECT | INTERFACE +directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @regexp(pattern: String) on SCALAR @@ -123654,6 +123656,8 @@ directive @typescript(type: String) on SCALAR | ENUM directive @example(value: ObjMap) repeatable on FIELD_DEFINITION | OBJECT | INPUT_OBJECT | ENUM | SCALAR +directive @resolveRoot on FIELD_DEFINITION + directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT @@ -124021,7 +124025,7 @@ input update_resource_at_the_root_folder_v1_request_Input @oneOf { union delete_resource_at_the_root_folder_v1_response @statusCodeTypeName(statusCode: 204, typeName: "Void_container") @statusCodeTypeName(statusCode: "default", typeName: "Error") = Void_container | Error type Void_container { - Void: Void + Void: Void @resolveRoot } "Represents empty values" diff --git a/packages/loaders/openapi/tests/fixtures/one-of-no-discriminator.yml b/packages/loaders/openapi/tests/fixtures/one-of-no-discriminator.yml new file mode 100644 index 000000000000..1b8f671f31ed --- /dev/null +++ b/packages/loaders/openapi/tests/fixtures/one-of-no-discriminator.yml @@ -0,0 +1,42 @@ +openapi: 3.0.3 +info: + title: one-of-no-discriminator + description: '' + contact: + name: '' + license: + name: '' + version: 0.1.0 +paths: + /test: + post: + description: '' + operationId: test_endpoint + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TestType' + required: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/TestType' + '500': + description: '' + deprecated: false +components: + schemas: + TestType: + oneOf: + - type: string + enum: + - A + - type: object + properties: + B: + type: string +tags: [] diff --git a/packages/loaders/openapi/tests/multiple-responses-swagger.test.ts b/packages/loaders/openapi/tests/multiple-responses-swagger.test.ts index d5535c2cbde5..eb1df2d89b49 100644 --- a/packages/loaders/openapi/tests/multiple-responses-swagger.test.ts +++ b/packages/loaders/openapi/tests/multiple-responses-swagger.test.ts @@ -13,10 +13,12 @@ describe('Multiple Responses Swagger', () => { mutation: Mutation } - directive @oneOf on OBJECT | INTERFACE + directive @oneOf on OBJECT | INTERFACE | INPUT_OBJECT directive @statusCodeTypeName(typeName: String, statusCode: ID) repeatable on UNION + directive @resolveRoot on FIELD_DEFINITION + directive @globalOptions(sourceName: String, endpoint: String, operationHeaders: ObjMap, queryStringOptions: ObjMap, queryParams: ObjMap) on OBJECT directive @httpOperation(path: String, operationSpecificHeaders: ObjMap, httpMethod: HTTPMethod, isBinary: Boolean, requestBaseBody: ObjMap, queryParamArgMap: ObjMap, queryStringOptionsByParam: ObjMap) on FIELD_DEFINITION @@ -45,7 +47,7 @@ describe('Multiple Responses Swagger', () => { union post_response @statusCodeTypeName(statusCode: 204, typeName: "Void_container") @statusCodeTypeName(statusCode: 500, typeName: "Error") = Void_container | Error type Void_container { - Void: Void + Void: Void @resolveRoot } "Represents empty values" diff --git a/packages/loaders/openapi/tests/oneof-without-descriminator.test.ts b/packages/loaders/openapi/tests/oneof-without-descriminator.test.ts new file mode 100644 index 000000000000..5df190f2707d --- /dev/null +++ b/packages/loaders/openapi/tests/oneof-without-descriminator.test.ts @@ -0,0 +1,83 @@ +import { execute, GraphQLSchema, parse } from 'graphql'; +import { printSchemaWithDirectives } from '@graphql-tools/utils'; +import { Response } from '@whatwg-node/fetch'; +import { loadGraphQLSchemaFromOpenAPI } from '../src/loadGraphQLSchemaFromOpenAPI.js'; + +describe('oneOf without discriminator', () => { + let createdSchema: GraphQLSchema; + beforeAll(async () => { + createdSchema = await loadGraphQLSchemaFromOpenAPI('test', { + source: './fixtures/one-of-no-discriminator.yml', + cwd: __dirname, + ignoreErrorResponses: true, + async fetch(url, options, context) { + switch (options.body) { + case '{"B":"string"}': + return Response.json({ + B: 'Value', + }); + case 'A': + return Response.json('A'); + default: + return new Response(null, { + status: 404, + }); + } + }, + }); + }); + it('should generate correct schema', () => { + expect(printSchemaWithDirectives(createdSchema)).toMatchSnapshot('discriminator-mapping'); + }); + it('should handle object response', async () => { + const query = /* GraphQL */ ` + mutation { + test_endpoint(input: { mutation_test_endpoint_oneOf_1_Input: { B: "string" } }) { + ... on mutation_test_endpoint_oneOf_1 { + B + } + ... on A_const_container { + A_const + } + } + } + `; + const result = await execute({ + schema: createdSchema, + document: parse(query), + }); + expect(result).toEqual({ + data: { + test_endpoint: { + B: 'Value', + }, + }, + }); + }); + it('should handle enum response', async () => { + const query = /* GraphQL */ ` + mutation { + test_endpoint(input: { A_const: A }) { + ... on mutation_test_endpoint_oneOf_1 { + B + } + ... on A_const_container { + A_const + } + } + } + `; + const result = await execute({ + schema: createdSchema, + document: parse(query), + contextValue: { requestId: 1 }, + }); + expect(result).toEqual({ + data: { + test_endpoint: { + A_const: 'A', + }, + }, + }); + }); +});