Skip to content

Commit 5742f45

Browse files
authored
feat(openapi): an object type with discriminator reflected as an interface (#4378)
1 parent 3473a26 commit 5742f45

File tree

5 files changed

+167
-14
lines changed

5 files changed

+167
-14
lines changed

.changeset/beige-timers-explain.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@omnigraph/json-schema": minor
3+
"@omnigraph/openapi": minor
4+
---
5+
6+
If an object type has a discriminator, it becomes an interface type and any other allOf references with that implements that interface

packages/loaders/json-schema/src/getComposerFromJSONSchema.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ComposeOutputType,
1515
ComposeInputType,
1616
EnumTypeComposer,
17+
InterfaceTypeComposer,
1718
} from 'graphql-compose';
1819
import {
1920
getNamedType,
@@ -498,6 +499,20 @@ export function getComposerFromJSONSchema(schema: JSONSchema, logger: Logger): P
498499
default: subSchema.default,
499500
};
500501
}
502+
const config = {
503+
name: getValidTypeName({
504+
schemaComposer,
505+
isInput: false,
506+
subSchema,
507+
}),
508+
description: subSchema.description,
509+
fields: {},
510+
extensions: {
511+
validateWithJSONSchema,
512+
examples: subSchema.examples,
513+
default: subSchema.default,
514+
},
515+
};
501516
return {
502517
input: schemaComposer.createInputTC({
503518
name: getValidTypeName({
@@ -512,20 +527,14 @@ export function getComposerFromJSONSchema(schema: JSONSchema, logger: Logger): P
512527
default: subSchema.default,
513528
},
514529
}),
515-
output: schemaComposer.createObjectTC({
516-
name: getValidTypeName({
517-
schemaComposer,
518-
isInput: false,
519-
subSchema,
520-
}),
521-
description: subSchema.description,
522-
fields: {},
523-
extensions: {
524-
validateWithJSONSchema,
525-
examples: subSchema.examples,
526-
default: subSchema.default,
527-
},
528-
}),
530+
output: subSchema.discriminator
531+
? schemaComposer.createInterfaceTC({
532+
...config,
533+
resolveType(root: any) {
534+
return root[subSchema.discriminator];
535+
},
536+
})
537+
: schemaComposer.createObjectTC(config),
529538
...subSchema,
530539
...(subSchema.properties ? { properties: { ...subSchema.properties } } : {}),
531540
...(subSchema.allOf ? { allOf: [...subSchema.allOf] } : {}),
@@ -615,6 +624,9 @@ export function getComposerFromJSONSchema(schema: JSONSchema, logger: Logger): P
615624
}
616625
}
617626
} else {
627+
if (outputTypeComposer instanceof InterfaceTypeComposer) {
628+
(subSchemaAndTypeComposers.output as ObjectTypeComposer).addInterface(outputTypeComposer);
629+
}
618630
const typeElemFieldMap = outputTypeComposer.getFields();
619631
for (const fieldName in typeElemFieldMap) {
620632
const field = typeElemFieldMap[fieldName];
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Pet should generate the correct schema 1`] = `
4+
"schema {
5+
query: Query
6+
}
7+
8+
type Query {
9+
pets_by_id(id: String!): Pet
10+
dogs_by_id(id: String!): Dog
11+
cats_by_id(id: String!): Cat
12+
}
13+
14+
interface Pet {
15+
name: String!
16+
petType: String
17+
}
18+
19+
type Dog implements Pet {
20+
name: String!
21+
petType: String
22+
dog_exclusive: String
23+
}
24+
25+
type Cat implements Pet {
26+
name: String!
27+
petType: String
28+
cat_exclusive: String
29+
}"
30+
`;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 1.0.0
4+
title: Swagger Petstore
5+
license:
6+
name: MIT
7+
paths:
8+
/pets/{id}:
9+
get:
10+
parameters:
11+
- name: id
12+
required: true
13+
in: path
14+
schema:
15+
type: string
16+
responses:
17+
200:
18+
content:
19+
application/json:
20+
schema:
21+
$ref: '#/components/schemas/Pet'
22+
/dogs/{id}:
23+
get:
24+
parameters:
25+
- name: id
26+
required: true
27+
in: path
28+
schema:
29+
type: string
30+
responses:
31+
200:
32+
content:
33+
application/json:
34+
schema:
35+
$ref: '#/components/schemas/Dog'
36+
/cats/{id}:
37+
get:
38+
parameters:
39+
- name: id
40+
required: true
41+
in: path
42+
schema:
43+
type: string
44+
responses:
45+
200:
46+
content:
47+
application/json:
48+
schema:
49+
$ref: '#/components/schemas/Cat'
50+
51+
components:
52+
schemas:
53+
Pet:
54+
required:
55+
- name
56+
type: object
57+
discriminator:
58+
propertyName: petType
59+
mapping:
60+
Dog: '#/components/schemas/Dog'
61+
Cat: '#/components/schemas/Cat'
62+
properties:
63+
name:
64+
type: string
65+
petType:
66+
type: string
67+
Cat:
68+
allOf:
69+
- $ref: '#/components/schemas/Pet'
70+
- type: object
71+
properties:
72+
cat_exclusive:
73+
type: string
74+
Dog:
75+
allOf:
76+
- $ref: '#/components/schemas/Pet'
77+
- type: object
78+
properties:
79+
dog_exclusive:
80+
type: string
81+
requestBodies:
82+
Pet:
83+
required: true
84+
content:
85+
application/vnd.api+json:
86+
schema:
87+
type: object
88+
properties:
89+
data:
90+
type: object
91+
properties:
92+
attributes:
93+
$ref: '#/components/schemas/Pet'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { printSchemaWithDirectives } from '@graphql-tools/utils';
2+
import loadGraphQLSchemaFromOpenAPI from '../src';
3+
4+
describe('Pet', () => {
5+
it('should generate the correct schema', async () => {
6+
const schema = await loadGraphQLSchemaFromOpenAPI('toto', {
7+
source: './fixtures/pet.yml',
8+
cwd: __dirname,
9+
});
10+
expect(printSchemaWithDirectives(schema)).toMatchSnapshot();
11+
});
12+
});

0 commit comments

Comments
 (0)