schema: allow annotating primitive field types (#926)
Summary: Prior to this change, primitive fields were un\[i\]typed. This commit allows adding type annotations. Such annotations do not change the semantics at all, but we will be able to use them to generate Flow types corresponding to a schema. This commit also strengthens the validation on schemata to catch some errors that would previously have gone unnoticed at schema construction time: e.g., a node reference to a type that does not exist. Test Plan: Unit tests updated, retaining full coverage. The demo script continues to work when loading `example-github` or `sourcecred`. wchargin-branch: schema-annotated-primitives
This commit is contained in:
parent
4558d775a5
commit
e787db53c4
|
@ -808,6 +808,15 @@ export class Mirror {
|
||||||
}
|
}
|
||||||
const b = Queries.build;
|
const b = Queries.build;
|
||||||
switch (type.type) {
|
switch (type.type) {
|
||||||
|
case "SCALAR":
|
||||||
|
throw new Error(
|
||||||
|
"Cannot create selections for scalar type: " +
|
||||||
|
JSON.stringify(typename)
|
||||||
|
);
|
||||||
|
case "ENUM":
|
||||||
|
throw new Error(
|
||||||
|
"Cannot create selections for enum type: " + JSON.stringify(typename)
|
||||||
|
);
|
||||||
case "OBJECT":
|
case "OBJECT":
|
||||||
return [b.field("__typename"), b.field("id")];
|
return [b.field("__typename"), b.field("id")];
|
||||||
case "UNION":
|
case "UNION":
|
||||||
|
@ -1831,6 +1840,12 @@ export function _buildSchemaInfo(schema: Schema.Schema): SchemaInfo {
|
||||||
for (const typename of Object.keys(schema)) {
|
for (const typename of Object.keys(schema)) {
|
||||||
const type = schema[typename];
|
const type = schema[typename];
|
||||||
switch (type.type) {
|
switch (type.type) {
|
||||||
|
case "SCALAR":
|
||||||
|
// Nothing to do.
|
||||||
|
break;
|
||||||
|
case "ENUM":
|
||||||
|
// Nothing to do.
|
||||||
|
break;
|
||||||
case "OBJECT": {
|
case "OBJECT": {
|
||||||
const entry: {|
|
const entry: {|
|
||||||
+fields: {|+[Schema.Fieldname]: Schema.FieldType|},
|
+fields: {|+[Schema.Fieldname]: Schema.FieldType|},
|
||||||
|
|
|
@ -41,6 +41,16 @@ describe("graphql/mirror", () => {
|
||||||
function buildGithubSchema(): Schema.Schema {
|
function buildGithubSchema(): Schema.Schema {
|
||||||
const s = Schema;
|
const s = Schema;
|
||||||
const types: {[Schema.Typename]: Schema.NodeType} = {
|
const types: {[Schema.Typename]: Schema.NodeType} = {
|
||||||
|
URI: s.scalar("string"),
|
||||||
|
Date: s.scalar("string"),
|
||||||
|
ReactionContent: s.enum([
|
||||||
|
"THUMBS_UP",
|
||||||
|
"THUMBS_DOWN",
|
||||||
|
"LAUGH",
|
||||||
|
"HOORAY",
|
||||||
|
"CONFUSED",
|
||||||
|
"HEART",
|
||||||
|
]),
|
||||||
Repository: s.object({
|
Repository: s.object({
|
||||||
id: s.id(),
|
id: s.id(),
|
||||||
url: s.primitive(),
|
url: s.primitive(),
|
||||||
|
@ -48,7 +58,7 @@ describe("graphql/mirror", () => {
|
||||||
}),
|
}),
|
||||||
Issue: s.object({
|
Issue: s.object({
|
||||||
id: s.id(),
|
id: s.id(),
|
||||||
url: s.primitive(),
|
url: s.primitive(s.nonNull("URI")),
|
||||||
author: s.node("Actor"),
|
author: s.node("Actor"),
|
||||||
repository: s.node("Repository"),
|
repository: s.node("Repository"),
|
||||||
title: s.primitive(),
|
title: s.primitive(),
|
||||||
|
@ -64,7 +74,7 @@ describe("graphql/mirror", () => {
|
||||||
id: s.id(),
|
id: s.id(),
|
||||||
oid: s.primitive(),
|
oid: s.primitive(),
|
||||||
author: /* GitActor */ s.nested({
|
author: /* GitActor */ s.nested({
|
||||||
date: s.primitive(),
|
date: s.primitive(s.nonNull("Date")),
|
||||||
user: s.node("User"),
|
user: s.node("User"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
@ -1142,6 +1152,20 @@ describe("graphql/mirror", () => {
|
||||||
mirror._queryShallow("Wat");
|
mirror._queryShallow("Wat");
|
||||||
}).toThrow('No such type: "Wat"');
|
}).toThrow('No such type: "Wat"');
|
||||||
});
|
});
|
||||||
|
it("fails when given a scalar type", () => {
|
||||||
|
const db = new Database(":memory:");
|
||||||
|
const mirror = new Mirror(db, buildGithubSchema());
|
||||||
|
expect(() => {
|
||||||
|
mirror._queryShallow("Date");
|
||||||
|
}).toThrow('Cannot create selections for scalar type: "Date"');
|
||||||
|
});
|
||||||
|
it("fails when given an enum type", () => {
|
||||||
|
const db = new Database(":memory:");
|
||||||
|
const mirror = new Mirror(db, buildGithubSchema());
|
||||||
|
expect(() => {
|
||||||
|
mirror._queryShallow("ReactionContent");
|
||||||
|
}).toThrow('Cannot create selections for enum type: "ReactionContent"');
|
||||||
|
});
|
||||||
it("handles an object type", () => {
|
it("handles an object type", () => {
|
||||||
const db = new Database(":memory:");
|
const db = new Database(":memory:");
|
||||||
const mirror = new Mirror(db, buildGithubSchema());
|
const mirror = new Mirror(db, buildGithubSchema());
|
||||||
|
@ -3143,7 +3167,7 @@ describe("graphql/mirror", () => {
|
||||||
expect(result.objectTypes["Commit"].nestedFieldNames).toEqual(["author"]);
|
expect(result.objectTypes["Commit"].nestedFieldNames).toEqual(["author"]);
|
||||||
expect(result.objectTypes["Commit"].nestedFields).toEqual({
|
expect(result.objectTypes["Commit"].nestedFields).toEqual({
|
||||||
author: {
|
author: {
|
||||||
primitives: {date: Schema.primitive()},
|
primitives: {date: Schema.primitive(Schema.nonNull("Date"))},
|
||||||
nodes: {user: Schema.node("User")},
|
nodes: {user: Schema.node("User")},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,9 @@ export type ObjectId = string;
|
||||||
// - Connections are supported as object fields, but arbitrary lists
|
// - Connections are supported as object fields, but arbitrary lists
|
||||||
// are not.
|
// are not.
|
||||||
//
|
//
|
||||||
|
// Primitive and enum fields on an object type may optionally be
|
||||||
|
// annotated with their representations.
|
||||||
|
//
|
||||||
// To accommodate schemata where some object types do not have IDs,
|
// To accommodate schemata where some object types do not have IDs,
|
||||||
// objects may have "nested" fields of primitive or node-reference type.
|
// objects may have "nested" fields of primitive or node-reference type.
|
||||||
// These may be nested to depth exactly 1. Suppose that `Foo` is an
|
// These may be nested to depth exactly 1. Suppose that `Foo` is an
|
||||||
|
@ -46,6 +49,8 @@ export type ObjectId = string;
|
||||||
// contains the eggs.)
|
// contains the eggs.)
|
||||||
export type Schema = {+[Typename]: NodeType};
|
export type Schema = {+[Typename]: NodeType};
|
||||||
export type NodeType =
|
export type NodeType =
|
||||||
|
| {|+type: "SCALAR", +representation: "string" | "number" | "boolean"|}
|
||||||
|
| {|+type: "ENUM", +values: {|+[string]: true|}|}
|
||||||
| {|+type: "OBJECT", +fields: {|+[Fieldname]: FieldType|}|}
|
| {|+type: "OBJECT", +fields: {|+[Fieldname]: FieldType|}|}
|
||||||
| {|+type: "UNION", +clauses: {|+[Typename]: true|}|};
|
| {|+type: "UNION", +clauses: {|+[Typename]: true|}|};
|
||||||
|
|
||||||
|
@ -56,7 +61,14 @@ export type FieldType =
|
||||||
| ConnectionFieldType
|
| ConnectionFieldType
|
||||||
| NestedFieldType;
|
| NestedFieldType;
|
||||||
export type IdFieldType = {|+type: "ID"|};
|
export type IdFieldType = {|+type: "ID"|};
|
||||||
export type PrimitiveFieldType = {|+type: "PRIMITIVE"|};
|
export type PrimitiveFieldType = {|
|
||||||
|
+type: "PRIMITIVE",
|
||||||
|
+annotation: null | PrimitiveTypeAnnotation,
|
||||||
|
|};
|
||||||
|
export type PrimitiveTypeAnnotation = {|
|
||||||
|
+nonNull: boolean,
|
||||||
|
+elementType: Typename,
|
||||||
|
|};
|
||||||
export type NodeFieldType = {|+type: "NODE", +elementType: Typename|};
|
export type NodeFieldType = {|+type: "NODE", +elementType: Typename|};
|
||||||
export type ConnectionFieldType = {|
|
export type ConnectionFieldType = {|
|
||||||
+type: "CONNECTION",
|
+type: "CONNECTION",
|
||||||
|
@ -76,7 +88,89 @@ export function schema(types: {[Typename]: NodeType}): Schema {
|
||||||
for (const typename of Object.keys(types)) {
|
for (const typename of Object.keys(types)) {
|
||||||
const type = types[typename];
|
const type = types[typename];
|
||||||
switch (type.type) {
|
switch (type.type) {
|
||||||
|
case "SCALAR":
|
||||||
|
result[typename] = {
|
||||||
|
type: "SCALAR",
|
||||||
|
representation: type.representation,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "ENUM":
|
||||||
|
result[typename] = {type: "ENUM", values: {...type.values}};
|
||||||
|
break;
|
||||||
case "OBJECT":
|
case "OBJECT":
|
||||||
|
for (const fieldname of Object.keys(type.fields)) {
|
||||||
|
const field = type.fields[fieldname];
|
||||||
|
function assertKind(path, elementTypename, validKinds) {
|
||||||
|
const self = `field ${path
|
||||||
|
.map((x) => JSON.stringify(x))
|
||||||
|
.join("/")}`;
|
||||||
|
const elementType = types[elementTypename];
|
||||||
|
if (elementType == null) {
|
||||||
|
throw new Error(`${self} has unknown type: "${elementTypename}"`);
|
||||||
|
}
|
||||||
|
if (!validKinds.includes(elementType.type)) {
|
||||||
|
throw new Error(
|
||||||
|
`${self} has invalid type "${elementTypename}" ` +
|
||||||
|
`of kind "${elementType.type}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (field.type) {
|
||||||
|
case "ID":
|
||||||
|
// Nothing to check.
|
||||||
|
break;
|
||||||
|
case "PRIMITIVE":
|
||||||
|
if (field.annotation != null) {
|
||||||
|
assertKind(
|
||||||
|
[typename, fieldname],
|
||||||
|
field.annotation.elementType,
|
||||||
|
["SCALAR", "ENUM"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "NODE":
|
||||||
|
assertKind([typename, fieldname], field.elementType, [
|
||||||
|
"OBJECT",
|
||||||
|
"UNION",
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case "CONNECTION":
|
||||||
|
assertKind([typename, fieldname], field.elementType, [
|
||||||
|
"OBJECT",
|
||||||
|
"UNION",
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case "NESTED":
|
||||||
|
for (const eggName of Object.keys(field.eggs)) {
|
||||||
|
const egg = field.eggs[eggName];
|
||||||
|
switch (egg.type) {
|
||||||
|
case "PRIMITIVE":
|
||||||
|
if (egg.annotation != null) {
|
||||||
|
assertKind(
|
||||||
|
[typename, fieldname, eggName],
|
||||||
|
egg.annotation.elementType,
|
||||||
|
["SCALAR", "ENUM"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "NODE":
|
||||||
|
assertKind(
|
||||||
|
[typename, fieldname, eggName],
|
||||||
|
egg.elementType,
|
||||||
|
["OBJECT", "UNION"]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// istanbul ignore next: unreachable per Flow
|
||||||
|
default:
|
||||||
|
throw new Error((egg.type: empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// istanbul ignore next: unreachable per Flow
|
||||||
|
default:
|
||||||
|
throw new Error((field.type: empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
result[typename] = {type: "OBJECT", fields: {...type.fields}};
|
result[typename] = {type: "OBJECT", fields: {...type.fields}};
|
||||||
break;
|
break;
|
||||||
case "UNION":
|
case "UNION":
|
||||||
|
@ -107,6 +201,21 @@ export function schema(types: {[Typename]: NodeType}): Schema {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function scalar(
|
||||||
|
representation: "string" | "number" | "boolean"
|
||||||
|
): NodeType {
|
||||||
|
return {type: "SCALAR", representation};
|
||||||
|
}
|
||||||
|
|
||||||
|
function enum_(values: $ReadOnlyArray<string>): NodeType {
|
||||||
|
const valuesObject: {|[string]: true|} = ({}: any);
|
||||||
|
for (const v of values) {
|
||||||
|
valuesObject[v] = true;
|
||||||
|
}
|
||||||
|
return {type: "ENUM", values: valuesObject};
|
||||||
|
}
|
||||||
|
export {enum_ as enum};
|
||||||
|
|
||||||
export function object(fields: {[Fieldname]: FieldType}): NodeType {
|
export function object(fields: {[Fieldname]: FieldType}): NodeType {
|
||||||
for (const fieldname of Object.keys(fields)) {
|
for (const fieldname of Object.keys(fields)) {
|
||||||
const field = fields[fieldname];
|
const field = fields[fieldname];
|
||||||
|
@ -141,8 +250,10 @@ export function id(): IdFieldType {
|
||||||
return {type: "ID"};
|
return {type: "ID"};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function primitive(): PrimitiveFieldType {
|
export function primitive(
|
||||||
return {type: "PRIMITIVE"};
|
annotation?: PrimitiveTypeAnnotation
|
||||||
|
): PrimitiveFieldType {
|
||||||
|
return {type: "PRIMITIVE", annotation: annotation || null};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function node(elementType: Typename): NodeFieldType {
|
export function node(elementType: Typename): NodeFieldType {
|
||||||
|
@ -153,6 +264,14 @@ export function connection(elementType: Typename): ConnectionFieldType {
|
||||||
return {type: "CONNECTION", elementType};
|
return {type: "CONNECTION", elementType};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nonNull(elementType: Typename): PrimitiveTypeAnnotation {
|
||||||
|
return {nonNull: true, elementType};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nullable(elementType: Typename): PrimitiveTypeAnnotation {
|
||||||
|
return {nonNull: false, elementType};
|
||||||
|
}
|
||||||
|
|
||||||
export function nested(eggs: {
|
export function nested(eggs: {
|
||||||
+[Fieldname]: PrimitiveFieldType | NodeFieldType,
|
+[Fieldname]: PrimitiveFieldType | NodeFieldType,
|
||||||
}): NestedFieldType {
|
}): NestedFieldType {
|
||||||
|
|
|
@ -6,6 +6,8 @@ describe("graphql/schema", () => {
|
||||||
function buildGithubTypes(): {[Schema.Typename]: Schema.NodeType} {
|
function buildGithubTypes(): {[Schema.Typename]: Schema.NodeType} {
|
||||||
const s = Schema;
|
const s = Schema;
|
||||||
return {
|
return {
|
||||||
|
DateTime: s.scalar("string"),
|
||||||
|
Color: s.enum(["RED", "GREEN", "BLUE"]),
|
||||||
Repository: s.object({
|
Repository: s.object({
|
||||||
id: s.id(),
|
id: s.id(),
|
||||||
url: s.primitive(),
|
url: s.primitive(),
|
||||||
|
@ -23,7 +25,7 @@ describe("graphql/schema", () => {
|
||||||
id: s.id(),
|
id: s.id(),
|
||||||
oid: s.primitive(),
|
oid: s.primitive(),
|
||||||
author: /* GitActor */ s.nested({
|
author: /* GitActor */ s.nested({
|
||||||
date: s.primitive(),
|
date: s.primitive(s.nonNull("DateTime")),
|
||||||
user: s.node("User"),
|
user: s.node("User"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
@ -48,6 +50,15 @@ describe("graphql/schema", () => {
|
||||||
url: s.primitive(),
|
url: s.primitive(),
|
||||||
login: s.primitive(),
|
login: s.primitive(),
|
||||||
}),
|
}),
|
||||||
|
ColorPalette: s.object({
|
||||||
|
id: s.id(),
|
||||||
|
currentColor: s.primitive(s.nonNull("Color")),
|
||||||
|
favoriteColor: s.primitive(s.nullable("Color")),
|
||||||
|
nested: s.nested({
|
||||||
|
exists: s.primitive(),
|
||||||
|
forall: s.primitive(s.nonNull("Color")),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function buildGithubSchema(): Schema.Schema {
|
function buildGithubSchema(): Schema.Schema {
|
||||||
|
@ -84,6 +95,160 @@ describe("graphql/schema", () => {
|
||||||
const schema = buildGithubSchema();
|
const schema = buildGithubSchema();
|
||||||
expect(JSON.parse(JSON.stringify(schema))).toEqual(schema);
|
expect(JSON.parse(JSON.stringify(schema))).toEqual(schema);
|
||||||
});
|
});
|
||||||
|
it("disallows objects with primitives of unknown type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({id: s.id(), foo: s.primitive(s.nonNull("Wat"))}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has unknown type: "Wat"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with primitives of object type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({id: s.id(), foo: s.primitive(s.nonNull("O"))}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has invalid type "O" of kind "OBJECT"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with primitives of union type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
U: s.union(["O"]),
|
||||||
|
O: s.object({id: s.id(), foo: s.primitive(s.nonNull("U"))}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has invalid type "U" of kind "UNION"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with node fields of unknown type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({id: s.id(), foo: s.node("Wat")}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has unknown type: "Wat"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with node fields of scalar type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
DateTime: s.scalar("string"),
|
||||||
|
O: s.object({id: s.id(), foo: s.node("DateTime")}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has invalid type "DateTime" of kind "SCALAR"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with node fields of enum type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
Color: s.enum(["RED", "GREEN", "BLUE"]),
|
||||||
|
O: s.object({id: s.id(), foo: s.node("Color")}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has invalid type "Color" of kind "ENUM"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with connection fields of unknown type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({id: s.id(), foo: s.connection("Wat")}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has unknown type: "Wat"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with connection fields of scalar type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
DateTime: s.scalar("string"),
|
||||||
|
O: s.object({id: s.id(), foo: s.connection("DateTime")}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has invalid type "DateTime" of kind "SCALAR"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with connection fields of enum type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
Color: s.enum(["RED", "GREEN", "BLUE"]),
|
||||||
|
O: s.object({id: s.id(), foo: s.connection("Color")}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo" has invalid type "Color" of kind "ENUM"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows objects with egg primitives of unknown type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({
|
||||||
|
id: s.id(),
|
||||||
|
foo: s.nested({bar: s.primitive(s.nonNull("Wat"))}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo"/"bar" has unknown type: "Wat"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with egg primitives of object type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({
|
||||||
|
id: s.id(),
|
||||||
|
foo: s.nested({bar: s.primitive(s.nonNull("O"))}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo"/"bar" has invalid type "O" of kind "OBJECT"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with egg primitives of union type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
U: s.union(["O"]),
|
||||||
|
O: s.object({
|
||||||
|
id: s.id(),
|
||||||
|
foo: s.nested({bar: s.primitive(s.nonNull("U"))}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo"/"bar" has invalid type "U" of kind "UNION"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with egg node fields of unknown type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
O: s.object({id: s.id(), foo: s.nested({bar: s.node("Wat")})}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo"/"bar" has unknown type: "Wat"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with egg node fields of scalar type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
DateTime: s.scalar("string"),
|
||||||
|
O: s.object({id: s.id(), foo: s.nested({bar: s.node("DateTime")})}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo"/"bar" has invalid type "DateTime" of kind "SCALAR"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("disallows objects with egg node fields of enum type", () => {
|
||||||
|
const s = Schema;
|
||||||
|
const types = {
|
||||||
|
Color: s.enum(["RED", "GREEN", "BLUE"]),
|
||||||
|
O: s.object({id: s.id(), foo: s.nested({bar: s.node("Color")})}),
|
||||||
|
};
|
||||||
|
expect(() => Schema.schema(types)).toThrowError(
|
||||||
|
'field "O"/"foo"/"bar" has invalid type "Color" of kind "ENUM"'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("disallows unions with unknown clauses", () => {
|
it("disallows unions with unknown clauses", () => {
|
||||||
const s = Schema;
|
const s = Schema;
|
||||||
const types = {
|
const types = {
|
||||||
|
|
Loading…
Reference in New Issue