Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Union one of issue #5415

Merged
merged 3 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-cats-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@omnigraph/json-schema': patch
---

Handle non object type resolution correctly in union types
12 changes: 10 additions & 2 deletions packages/loaders/json-schema/src/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,12 @@ export const ResolveRootDirective = new GraphQLDirective({
locations: [DirectiveLocation.FIELD_DEFINITION],
});

function rootResolver(root: any) {
return root;
}

export function processResolveRootAnnotations(field: GraphQLField<any, any>) {
field.resolve = root => root;
field.resolve = rootResolver;
}

export const ResolveRootFieldDirective = new GraphQLDirective({
Expand Down Expand Up @@ -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({
Expand Down
39 changes: 30 additions & 9 deletions packages/loaders/json-schema/src/getTypeResolverFromOutputTCs.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -12,7 +12,7 @@ export function getTypeResolverFromOutputTCs({
discriminatorMapping?: Record<string, string>;
statusCodeTypeNameMap?: Record<string, string>;
}): GraphQLTypeResolver<any, any> {
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]) {
Expand All @@ -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<string, ErrorObject[]> = {};
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) {
Expand Down
9 changes: 7 additions & 2 deletions packages/loaders/json-schema/src/getUnionTypeComposers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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',
},
],
},
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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\\"]}}") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}"
`;