-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmod.ts
110 lines (101 loc) · 3.54 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {
type GraphQLField,
type GraphQLObjectType,
type GraphQLType,
isEnumType,
isListType,
isNonNullType,
isObjectType,
isScalarType,
isUnionType
} from 'graphql';
import type { JSONSchema7 } from 'json-schema';
export function defsFromGraphQLTypeMap(typeMap: Record<string, GraphQLType>, isCollectionModel: (type: GraphQLType) => boolean = isCollectionModelGraphQLType): JSONSchema7 & { $defs: Record<string, JSONSchema7> } {
return {
$defs: Object.fromEntries(Object.entries(typeMap).flatMap(([name, type]) => {
if (!isObjectType(type)) return [];
return [[name, visitObjectType(new Set([type]), type, isCollectionModel)]];
})),
};
}
export function jsonSchemaFromGraphQlType(type: GraphQLObjectType, isCollectionModel: (type: GraphQLType) => boolean = isCollectionModelGraphQLType): JSONSchema7 {
const refs = new Set<GraphQLObjectType>([type]);
const defs = new Map<GraphQLObjectType, JSONSchema7>();
while (true) {
const type = [...refs.values()].find(type => ![...defs.keys()].includes(type));
if (!type) break;
const schema = visitObjectType(refs, type, isCollectionModel);
defs.set(type, schema);
}
return {
...defs.get(type),
$defs: Object.fromEntries([...defs.entries()]
.filter(([_type]) => _type !== type)
.map(([type, def]) => [type.name, def])
),
};
}
function visitObjectType(refs: Set<GraphQLObjectType>, type: GraphQLObjectType, isCollectionModel: (type: GraphQLType) => boolean): JSONSchema7 {
const properties = Object.fromEntries(Object.entries(type.getFields()).map(([key, field]: [string, GraphQLField<any, any>]) => {
const type = isNonNullType(field.type) ? field.type.ofType : field.type;
return [key, { ...visitType(refs, type, isCollectionModel), title: key }];
}));
properties.__typename = { const: type.name, title: '__typename' };
const required = Object.entries(type.getFields()).filter(([, field]: [string, GraphQLField<any, any>]) => {
return isNonNullType(field.type);
}).map(([key]) => key);
return { type: 'object', properties, required };
}
function visitType(refs: Set<GraphQLObjectType>, type: GraphQLType, isCollectionModel: (type: GraphQLType) => boolean): JSONSchema7 {
if (isObjectType(type)) {
if (isCollectionModel(type)) {
refs.add(type);
return { type: 'string', title: `${type.name}_id` };
} else { // component
refs.add(type);
return { $ref: `#/$defs/${type.name}` };
}
}
if (isUnionType(type)) return {
oneOf: type.getTypes().map(type => visitType(refs, type, isCollectionModel)),
};
if (isScalarType(type)) {
if (type.name in SCALAR_TO_JSON) return {
type: SCALAR_TO_JSON[type.name as keyof typeof SCALAR_TO_JSON],
};
return {}; // means any
}
if (isEnumType(type)) return {
type: 'string',
enum: type.getValues().map(value => value.value),
title: type.name,
};
if (isListType(type)) {
if (isNonNullType(type.ofType)) {
return {
type: 'array',
items: visitType(refs, type.ofType.ofType, isCollectionModel),
};
}
return {
type: 'array',
items: {
oneOf: [
{ type: 'null', title: 'Null' },
visitType(refs, type.ofType, isCollectionModel),
],
},
};
}
throw new Error(`Unexpected: ${type}`);
}
function isCollectionModelGraphQLType(type: GraphQLType): boolean {
return isObjectType(type) && 'id' in type.getFields();
}
const SCALAR_TO_JSON = Object.freeze({
Boolean: 'boolean',
Float: 'number',
ID: 'string',
Int: 'number',
String: 'string',
});