Skip to content

Commit

Permalink
MET-128: either should be supported in the introspection (#243)
Browse files Browse the repository at this point in the history
* init tests

* current wip

* union => input object

* make recursive call

* add either/union in visitor

* fix tree-view

* add proper test

* remove comment

* update snapshots

* merge fields

* fix empty object

* regenerate snapshots

* add comment

* update snapshot
  • Loading branch information
michael-0acf4 authored Apr 5, 2023
1 parent 97ddb03 commit 2b86c1e
Show file tree
Hide file tree
Showing 17 changed files with 1,555 additions and 968 deletions.
1,529 changes: 843 additions & 686 deletions examples/typegraphs/mybusiness/mybusiness.py

Large diffs are not rendered by default.

75 changes: 41 additions & 34 deletions typegate/src/runtimes/typegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
} from "../typegraph/visitor.ts";
import { distinctBy } from "std/collections/distinct_by.ts";
import { isInjected } from "../typegraph/utils.ts";
import { PolicyIndices } from "../types/typegraph.ts";
import { EitherNode, PolicyIndices, UnionNode } from "../types/typegraph.ts";

type DeprecatedArg = { includeDeprecated?: boolean };

Expand Down Expand Up @@ -312,40 +312,47 @@ export class TypeGraphRuntime extends Runtime {
}
}

if (isUnion(type)) {
return {
...common,
kind: () => TypeKind.UNION,
name: () => type.title,
description: () => `${type.title} type`,
possibleTypes: () => {
const variantNodes = type.anyOf.map((typeIndex) =>
this.tg.types[typeIndex]
);

return variantNodes.map((variant) =>
this.formatType(variant, false, false)
);
},
};
}

if (isEither(type)) {
return {
...common,
kind: () => TypeKind.UNION,
name: () => type.title,
description: () => `${type.title} type`,
possibleTypes: () => {
const variantNodes = type.oneOf.map((typeIndex) =>
this.tg.types[typeIndex]
);
if (isEither(type) || isUnion(type)) {
const getVariants = (type: UnionNode | EitherNode) =>
isUnion(type) ? type.anyOf : type.oneOf;

const variants = getVariants(type);
const variantsAsObject = {
title: type.title,
type: "object",
properties: {},
} as ObjectNode;
let count = 0;
const objects = new Set<[string, number]>();
const remaining = new Set<[string, number]>();
for (let i = 0; i < variants.length; i++) {
const idx = variants[i];
const node = this.tg.types[idx];
if (isObject(node)) {
for (const [field, idx] of Object.entries(node.properties)) {
objects.add([field, idx]);
}
} else {
// name for scalars and nested union/either
const field = `${type.type}_${count++}`;
remaining.add([field, idx]);
}
}

return variantNodes.map((variant) =>
this.formatType(variant, false, false)
);
},
};
for (const [field, idx] of objects) {
variantsAsObject.properties[field] = idx;
}
for (const [field, idx] of remaining) {
variantsAsObject.properties[field] = idx;
}
// quick fix
// return {
// ...common,
// kind: () => TypeKind.SCALAR,
// name: () => type.title,
// description: () => `${type.type} type`,
// };
return this.formatType(variantsAsObject, required, asInput);
}

throw Error(`unexpected type format ${(type as any).type}`);
Expand Down
13 changes: 0 additions & 13 deletions typegate/src/typegraph/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,6 @@ export function treeView(tg: TypeGraphDS, rootIdx = 0, depth = 4) {
const edge = cyan(`${path.edges[path.edges.length - 1] ?? "[root]"}`);
const idxStr = green(`${idx}`);
console.log(`${indent}${edge}${idxStr} ${type.type}:${type.title}`);
if (type.type == "union") {
console.log(`${indent}anyOf → ids [${type.anyOf.join(", ")}]`);
for (const id of type.anyOf) {
const type = tg.types[id];
console.log(`${indent} * ${type.type}:${type.title}`);
}
} else if (type.type == "either") {
console.log(`${indent}oneOf → ids [${type.oneOf.join(", ")}]`);
for (const id of type.oneOf) {
const type = tg.types[id];
console.log(`${indent} * ${type.type}:${type.title}`);
}
}
return path.edges.length < depth;
}, { allowCircular: true });
}
Expand Down
9 changes: 9 additions & 0 deletions typegate/src/typegraph/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ export function getEdges(type: TypeNode): Record<string, number> {
return { [Edge.OPTIONAL_ITEM]: type.item };
case Type.ARRAY:
return { [Edge.ARRAY_ITEMS]: type.items };
case Type.UNION:
case Type.EITHER: {
const variants = type.type == Type.UNION ? type.anyOf : type.oneOf;
const rec = {} as Record<string, number>;
for (let i = 0; i < variants.length; i++) {
rec[`[u${i}]`] = variants[i];
}
return rec;
}
case Type.OBJECT:
return { ...type.properties };
case Type.FUNCTION:
Expand Down
132 changes: 132 additions & 0 deletions typegate/tests/introspection/__snapshots__/union_either_test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
export const snapshot = {};

snapshot[`Basic introspection 1`] = `
{
__schema: {
types: [
{
fields: null,
kind: "SCALAR",
name: "String",
},
{
fields: null,
kind: "SCALAR",
name: "Int",
},
{
fields: [
{
name: "test",
},
],
kind: "OBJECT",
name: "Query",
},
{
fields: [
{
name: "favorite",
},
{
name: "name",
},
],
kind: "OBJECT",
name: "Output",
},
{
fields: [
{
name: "ref",
},
{
name: "model",
},
{
name: "color",
},
{
name: "size",
},
{
name: "name",
},
],
kind: "OBJECT",
name: "FavoriteToy",
},
{
fields: [
{
name: "size",
},
{
name: "name",
},
],
kind: "OBJECT",
name: "Rubix",
},
{
fields: [
{
name: "color",
},
],
kind: "OBJECT",
name: "Toygun",
},
{
fields: [
{
name: "ref",
},
{
name: "model",
},
],
kind: "OBJECT",
name: "Gunpla",
},
{
fields: [
{
name: "union_1",
},
{
name: "union_0",
},
],
kind: "OBJECT",
name: "union_9",
},
{
fields: null,
kind: "INPUT_OBJECT",
name: "FavoriteToyInp",
},
{
fields: null,
kind: "INPUT_OBJECT",
name: "RubixInp",
},
{
fields: null,
kind: "INPUT_OBJECT",
name: "ToygunInp",
},
{
fields: null,
kind: "INPUT_OBJECT",
name: "GunplaInp",
},
{
fields: null,
kind: "INPUT_OBJECT",
name: "union_9Inp",
},
],
},
}
`;
24 changes: 24 additions & 0 deletions typegate/tests/introspection/union_either.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typegraph import TypeGraph, policies, t
from typegraph.runtimes.deno import PredefinedFunMat

with TypeGraph("introspect-union-either") as g:
rubix_cube = t.struct({"name": t.string(), "size": t.integer()}).named("Rubix")
toygun = t.struct({"color": t.string()}).named("Toygun")
gunpla = t.struct(
{"model": t.string(), "ref": t.union([t.string(), t.integer()])}
).named("Gunpla")
toy = t.either([rubix_cube, toygun, gunpla])

user = t.struct(
{"name": t.string().named("Username"), "favorite": toy.named("FavoriteToy")}
).named("User")

g.expose(
test=t.func(
user.named("Input"),
user.named("Output"),
PredefinedFunMat("identity"),
)
.named("f")
.add_policy(policies.public()),
)
27 changes: 27 additions & 0 deletions typegate/tests/introspection/union_either_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright Metatype OÜ under the Elastic License 2.0 (ELv2). See LICENSE.md for usage.

import { gql, test } from "../utils.ts";

// https://github.com/graphql/graphql-js/blob/main/src/__tests__/starWarsIntrospection-test.ts

test("Basic introspection", async (t) => {
const e = await t.pythonFile("introspection/union_either.py");

await t.should("allow querying the schema for query type", async () => {
await gql`
query IntrospectionQuery {
__schema {
types {
name
kind
fields {
name
}
}
}
}
`
.matchSnapshot(t)
.on(e);
});
}, { introspection: true });
Loading

0 comments on commit 2b86c1e

Please sign in to comment.