Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/quicktype-graphql-input/src/GraphQLSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export enum TypeKind {
NON_NULL = "NON_NULL", // Indicates this type is a non-null. `ofType` is a valid field.
}

export enum VariableKind {
NAMED = "NamedType",
LIST = "ListType",
NON_NULL = "NonNullType",
}

export type GraphQLSchema = {
__schema: {
__typename: "__Schema";
Expand Down
145 changes: 138 additions & 7 deletions packages/quicktype-graphql-input/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
removeNullFromUnion,
} from "quicktype-core";

import { type GraphQLSchema, TypeKind } from "./GraphQLSchema";
import { type GraphQLSchema, TypeKind, VariableKind } from "./GraphQLSchema";

interface GQLType {
description?: string;
Expand Down Expand Up @@ -278,8 +278,12 @@ class GQLQuery {
);
break;
case TypeKind.INPUT_OBJECT:
// FIXME: Support input objects
return panic("Input objects not supported");
return this.makeIRTypeFromInputObject(
builder,
fieldType,
fieldNode.name.value,
containingTypeName,
);
case TypeKind.LIST:
if (!fieldType.ofType) {
return panic("No type for list");
Expand Down Expand Up @@ -334,6 +338,125 @@ class GQLQuery {
return fragment;
};

private _inputObjectDepth = 0;
private readonly makeIRTypeFromInputObject = (
builder: TypeBuilder,
gqlType: GQLType,
containingFieldName: string | null,
containingTypeName: string | null,
overrideName?: string,
): TypeRef => {
if (this._inputObjectDepth > 3) {
// TODO: Support objects with depth > 3 and recursive references
return builder.getPrimitiveType("null");
}
this._inputObjectDepth++;
if (!gqlType.name) {
return panic("Input object type doesn't have a name.");
}
if (!gqlType.inputFields) {
return panic("Input object type doesn't have fields.");
}
const nameOrOverride = overrideName ?? gqlType.name;
const properties = new Map<string, ClassProperty>();
for (const field of gqlType.inputFields) {
const fieldType = this.makeIRTypeFromFieldNode(
builder,
{
kind: "Field",
name: {
value: field.name,
kind: "Name",
},
} as FieldNode,
field.type,
nameOrOverride,
);
properties.set(
field.name,
builder.makeClassProperty(
fieldType,
!!field.defaultValue ||
field.type.kind !== TypeKind.NON_NULL,
),
);
}
this._inputObjectDepth--;
return builder.getClassType(
makeNames(nameOrOverride, containingFieldName, containingTypeName),
properties,
);
};

public readonly makeVariablesType = (
builder: TypeBuilder,
query: OperationDefinitionNode,
queryName: string,
): TypeRef | undefined => {
const name = `${queryName}Variables`;
const defs = query.variableDefinitions;
if (defs === undefined || defs === null || defs.length === 0)
return undefined;

const properties = new Map<string, ClassProperty>();
for (const definition of defs) {
let variableType = definition.type;
let optional = true;
if (variableType.kind === VariableKind.NON_NULL) {
optional = false;
variableType = variableType.type;
}

// Build the type from the unwrapped variable type
let irType: TypeRef;
if (variableType.kind === VariableKind.LIST) {
const listItemType = variableType.type;
if (listItemType.kind !== VariableKind.NAMED) {
return panic(
`Named type not found for list variable "${definition.variable.name.value}"`,
);
}
const gqlType = this._schema.types[listItemType.name.value];
const itemType = this.makeIRTypeFromFieldNode(
builder,
{
kind: "Field",
name: listItemType.name,
} as FieldNode,
gqlType,
name,
);
irType = builder.getArrayType(emptyTypeAttributes, itemType);
} else if (variableType.kind === VariableKind.NAMED) {
const gqlType = this._schema.types[variableType.name.value];
irType = this.makeIRTypeFromFieldNode(
builder,
{
kind: "Field",
name: variableType.name,
} as FieldNode,
gqlType,
name,
);
} else {
return panic(
`Invalid variable type for "${definition.variable.name.value}"`,
);
}

// Remove null from the type if the variable is required
if (!optional) {
irType = removeNull(builder, irType);
}

properties.set(
definition.variable.name.value,
builder.makeClassProperty(irType, optional),
);
}
return builder.getClassType(makeNames(name, null, null), properties);
};

private readonly makeIRTypeFromSelectionSet = (
builder: TypeBuilder,
selectionSet: SelectionSetNode,
Expand Down Expand Up @@ -600,6 +723,7 @@ function makeGraphQLQueryTypes(
}

const dataType = query.makeType(builder, odn, queryName);
const variablesType = query.makeVariablesType(builder, odn, queryName);
const dataOrNullType = builder.getUnionType(
emptyTypeAttributes,
new Set([dataType, builder.getPrimitiveType("null")]),
Expand Down Expand Up @@ -632,12 +756,19 @@ function makeGraphQLQueryTypes(
),
errorType,
);
const topLevelProperties: { [name: string]: ClassProperty } = {
data: builder.makeClassProperty(dataOrNullType, false),
errors: builder.makeClassProperty(errorArray, true),
};
if (variablesType !== undefined) {
topLevelProperties.variables = builder.makeClassProperty(
variablesType,
true,
);
}
const t = builder.getClassType(
makeNamesTypeAttributes(queryName, false),
mapFromObject({
data: builder.makeClassProperty(dataOrNullType, false),
errors: builder.makeClassProperty(errorArray, true),
}),
mapFromObject(topLevelProperties),
);
types.set(queryName, t);
}
Expand Down
30 changes: 30 additions & 0 deletions test/inputs/graphql/github-variables.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"data": {
"viewer": {
"repositories": {
"nodes": [
{
"name": "quicktype"
}
]
}
},
"search": {
"repositoryCount": 42
},
"node": {
"id": "MDQ6VXNlcjE="
}
},
"variables": {
"order": {
"field": "CREATED_AT",
"direction": "ASC"
},
"query": "language:typescript",
"first": 10,
"type": "REPOSITORY",
"id": "MDQ6VXNlcjE=",
"includeExtra": false
}
}
22 changes: 22 additions & 0 deletions test/inputs/graphql/github-variables.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
query ComprehensiveVariablesTest(
$order: RepositoryOrder!,
$query: String!,
$first: Int,
$type: SearchType!,
$id: ID,
$includeExtra: Boolean
) {
viewer {
repositories(first: 10, orderBy: $order) {
nodes {
name
}
}
}
search(query: $query, first: $first, type: $type) {
repositoryCount
}
node(id: $id) {
id
}
}
19 changes: 19 additions & 0 deletions test/inputs/graphql/github10.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"data": {
"viewer": {
"repositories": {
"nodes": [
{
"name": "quicktype"
}
]
}
}
},
"variables": {
"order": {
"field": "CREATED_AT",
"direction": "ASC"
}
}
}
9 changes: 9 additions & 0 deletions test/inputs/graphql/github10.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
query MyQuery($order: RepositoryOrder!) {
viewer {
repositories(first: 10, orderBy: $order) {
nodes {
name
}
}
}
}