Skip to content

bug: GraphQL verification bugs (generated output fails graphql-js) #248

Description

@apoorv7g

Generated GraphQL SDL from Concerto CTO models is written by GraphQLVisitor (lib/codegen/fromcto/graphql/graphqlvisitor.js). Today, npm test only checks snapshots, it does not run a GraphQL parser on the output.
When you compile with graphql-js parse() standard repo fixtures produce invalid SDL.

Related: bug: JSON Schema verification bugs (generated output fails Ajv) #240


Bug 1: Empty enum in hr_base.cto

Cause

hr_base.cto defines an empty enum:

enum Level {}

For types with no properties, GraphQLVisitor emits a placeholder field _: Boolean (see graphql/graphql-spec#568). That works for object types, but for an empty enum the visitor emits:

enum Level {
   _: Boolean
}

_: Boolean is object field syntax, not a valid enum value. graphql-js parse() fails on this SDL.

Reproduce

From the repo root (after npm ci):

node -e "
const fs = require('fs');
const path = require('path');
const { parse } = require('graphql');
const { dir } = require('tmp-promise');
const { ModelManager } = require('@accordproject/concerto-core');
const { FileWriter } = require('@accordproject/concerto-util');
const GraphQLVisitor = require('./lib/codegen/fromcto/graphql/graphqlvisitor.js');

process.env.ENABLE_MAP_TYPE = 'true';
process.env.IMPORT_ALIASING = 'true';

(async () => {
  const mm = new ModelManager();
  mm.addCTOModel(
    fs.readFileSync('./test/codegen/fromcto/data/model/hr_base.cto', 'utf8'),
    'hr_base.cto'
  );

  const { path: out, cleanup } = await dir({ unsafeCleanup: true });
  mm.accept(new GraphQLVisitor(), { fileWriter: new FileWriter(out) });

  const sdl = fs.readFileSync(path.join(out, 'model.gql'), 'utf8');
  parse(sdl);
  console.log('OK');
  await cleanup();
})().catch((err) => {
  console.error(err.message);
  process.exit(1);
});
"

Expected error

Syntax Error: Expected Name, found ":".

Generated fragment

enum Level {
   _: Boolean
}

Bug 2: Custom scalars not emitted in hr_base.cto + hr.cto

Cause

hr_base.cto defines Concerto scalars such as SSN and Time. hr.cto also references types like KinName, KinTelephone, and Concept. The visitor uses these names in field and map type definitions but does not emit corresponding scalar (or type) declarations in the SDL.

With both HR fixtures loaded (same pair as test/codegen/codegen.js), the generated SDL references undefined types. graphql-js parse() accepts the syntax, but buildSchema() fails when building a schema from the output.

Note: Bug 1 must be worked around first (empty Level enum) to reach this failure. Remove or fix the enum Level { ... } block in the generated SDL before calling buildSchema().

Reproduce

node -e "
const fs = require('fs');
const path = require('path');
const { buildSchema } = require('graphql');
const { dir } = require('tmp-promise');
const { ModelManager } = require('@accordproject/concerto-core');
const { FileWriter } = require('@accordproject/concerto-util');
const GraphQLVisitor = require('./lib/codegen/fromcto/graphql/graphqlvisitor.js');

process.env.ENABLE_MAP_TYPE = 'true';
process.env.IMPORT_ALIASING = 'true';

const MODEL_DIR = './test/codegen/fromcto/data/model';

(async () => {
  const mm = new ModelManager();
  mm.addCTOModel(fs.readFileSync(path.join(MODEL_DIR, 'hr_base.cto'), 'utf8'), 'hr_base.cto');
  mm.addCTOModel(fs.readFileSync(path.join(MODEL_DIR, 'hr.cto'), 'utf8'), 'hr.cto');

  const { path: out, cleanup } = await dir({ unsafeCleanup: true });
  mm.accept(new GraphQLVisitor(), {
    fileWriter: new FileWriter(out),
    showCompositionRelationships: true,
  });

  let sdl = fs.readFileSync(path.join(out, 'model.gql'), 'utf8');
  // Work around Bug 1 so we can validate type references
  sdl = sdl.replace(/enum Level \{[^}]+\}\n?/, 'enum Level { PLACEHOLDER }\n');

  buildSchema(sdl);
  console.log('OK');
  await cleanup();
})().catch((err) => {
  console.error(err.message);
  process.exit(1);
});
"

Expected error

Unknown type "SSN".

Unknown type "Time".

(Additional errors for KinName, KinTelephone, Concept, and invalid directive locations on enums may also appear.)

Example generated references without definitions

type EmployeeTShirtSizes {
   key: SSN
   value: TShirtSizeType
}

type EmployeeDirectory {
   key: SSN
   value: Employee
}

map fields also reference Time, KinName, KinTelephone, Concept

Only scalar DateTime is declared in the generated output; SSN, Time, and other custom types are not.


Related code

File Role
lib/codegen/fromcto/graphql/graphqlvisitor.js Emits GraphQL SDL (visitMapDeclaration, empty-type _: Boolean placeholder)
test/codegen/codegen.js Snapshot tests (no GraphQL parser on output)
test/codegen/fromcto/graphql/graphqlvisitor.js Unit tests with mocked FileWriter, not full HR fixtures

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions