graphql#isListType TypeScript Examples

The following examples show how to use graphql#isListType. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: resolveDataByUnionInputType.ts    From graphql-mesh with MIT License 6 votes vote down vote up
export function resolveDataByUnionInputType(data: any, type: GraphQLInputType, schemaComposer: SchemaComposer): any {
  if (data) {
    if (isListType(type)) {
      return asArray(data).map(elem => resolveDataByUnionInputType(elem, type.ofType, schemaComposer));
    }
    if (isNonNullType(type)) {
      return resolveDataByUnionInputType(data, type.ofType, schemaComposer);
    }
    if (isInputObjectType(type)) {
      const fieldMap = type.getFields();
      const isOneOf = schemaComposer.getAnyTC(type).getDirectiveByName('oneOf');
      data = asArray(data)[0];
      for (const propertyName in data) {
        const fieldName = sanitizeNameForGraphQL(propertyName);
        const field = fieldMap[fieldName];
        if (field) {
          if (isOneOf) {
            const resolvedData = resolveDataByUnionInputType(data[fieldName], field.type, schemaComposer);
            return resolvedData;
          }
          const realFieldName = (field.extensions?.propertyName as string) || fieldName;
          data[realFieldName] = resolveDataByUnionInputType(data[fieldName], field.type, schemaComposer);
        }
      }
    }
  }
  return data;
}
Example #2
Source File: types.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export function typeNameFromGraphQLType(
  context: LegacyCompilerContext,
  type: GraphQLType,
  bareTypeName?: string | null,
  nullable = true
): string {
  if (isNonNullType(type)) {
    return typeNameFromGraphQLType(context, type.ofType, bareTypeName, false);
  }

  let typeName;
  if (isListType(type)) {
    typeName = `Array< ${typeNameFromGraphQLType(context, type.ofType, bareTypeName, true)} >`;
  } else if (type instanceof GraphQLScalarType) {
    typeName =
      builtInScalarMap[type.name] ||
      appSyncScalars[type.name] ||
      (context.options.passthroughCustomScalars ? context.options.customScalarsPrefix + type.name : builtInScalarMap[GraphQLString.name]);
  } else {
    typeName = bareTypeName || type.name;
  }

  return nullable ? typeName + ' | null' : typeName;
}
Example #3
Source File: isRequired.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export default function isRequired(typeObj: GraphQLType): boolean {
  if (isNonNullType(typeObj) && isListType(typeObj.ofType)) {
    // See if it's a Non-null List of Non-null Types
    return isRequired(typeObj.ofType.ofType);
  }
  if (isListType(typeObj)) {
    // See if it's a Nullable List of Non-null Types
    return isNonNullType(typeObj.ofType);
  }
  return isNonNullType(typeObj);
}
Example #4
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
function getObjectTypeName(type: GraphQLType): string {
  if (isListType(type)) {
    return getObjectTypeName(type.ofType);
  }
  if (isNonNullType(type)) {
    return getObjectTypeName(type.ofType);
  }
  if (isObjectType(type)) {
    return `"${type.name}"`;
  }
  if (isUnionType(type)) {
    return type
      .getTypes()
      .map(type => getObjectTypeName(type))
      .join(' | ');
  }
  return `"${type.name}"`;
}
Example #5
Source File: index.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
function getReturnTypeName(generator: CodeGenerator, op: LegacyOperation): String {
  const { operationName, operationType } = op;
  const { type } = op.fields[0];
  //List of scalar or enum type
  if (isListType(type)) {
    const { ofType } = type;
    if (isScalarType(ofType) || isEnumType(ofType)) {
      return `Array<${typeNameFromGraphQLType(generator.context, ofType)}>`;
    }
  }
  //Scalar and enum type
  if (isScalarType(type) || isEnumType(type)) {
    return typeNameFromGraphQLType(generator.context, type);
  }
  //Non scalar type
  else {
    let returnType = interfaceNameFromOperation({ operationName, operationType });
    if (op.operationType === 'subscription' && op.fields.length) {
      returnType = `Pick<__SubscriptionContainer, "${op.fields[0].responseName}">`;
    }
    if (isList(type)) {
      returnType = `Array<${returnType}>`;
    }
    return returnType;
  }
}
Example #6
Source File: utils.ts    From graphql-eslint with MIT License 6 votes vote down vote up
export function getBaseType(type: GraphQLOutputType): GraphQLNamedType {
  if (isNonNullType(type) || isListType(type)) {
    return getBaseType(type.ofType);
  }
  return type;
}
Example #7
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
mapExpressionForType(type: GraphQLType, expression: (identifier: string) => string, identifier = ''): string {
    let isOptional;
    if (isNonNullType(type)) {
      isOptional = false;
      type = type.ofType;
    } else {
      isOptional = true;
    }

    if (isListType(type)) {
      if (isOptional) {
        return `${identifier}.flatMap { $0.map { ${this.mapExpressionForType(type.ofType, expression, '$0')} } }`;
      } else {
        return `${identifier}.map { ${this.mapExpressionForType(type.ofType, expression, '$0')} }`;
      }
    } else if (isOptional) {
      return `${identifier}.flatMap { ${expression('$0')} }`;
    } else {
      return expression(identifier);
    }
  }
Example #8
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
// Types

  typeNameFromGraphQLType(type: GraphQLType, unmodifiedTypeName?: string, isOptional?: boolean): string {
    if (isNonNullType(type)) {
      return this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName, false);
    } else if (isOptional === undefined) {
      isOptional = true;
    }

    let typeName;
    if (isListType(type)) {
      typeName = '[' + this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) + ']';
    } else if (type instanceof GraphQLScalarType) {
      typeName = this.typeNameForScalarType(type);
    } else {
      typeName = unmodifiedTypeName || type.name;
    }

    return isOptional ? typeName + '?' : typeName;
  }
Example #9
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
fieldTypeEnum(type: GraphQLType, structName: string): string {
    if (isNonNullType(type)) {
      return `.nonNull(${this.fieldTypeEnum(type.ofType, structName)})`;
    } else if (isListType(type)) {
      return `.list(${this.fieldTypeEnum(type.ofType, structName)})`;
    } else if (type instanceof GraphQLScalarType) {
      return `.scalar(${this.typeNameForScalarType(type)}.self)`;
    } else if (type instanceof GraphQLEnumType) {
      return `.scalar(${type.name}.self)`;
    } else if (isCompositeType(type)) {
      return `.object(${structName}.selections)`;
    } else {
      throw new Error(`Unknown field type: ${type}`);
    }
  }
Example #10
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export function createTypeAnnotationFromGraphQLTypeFunction(compilerOptions: CompilerOptions): Function {
  return function typeAnnotationFromGraphQLType(
    type: GraphQLType,
    { nullable }: { nullable: boolean } = {
      nullable: true,
    }
  ): t.FlowTypeAnnotation {
    if (isNonNullType(type)) {
      return typeAnnotationFromGraphQLType(type.ofType, { nullable: false });
    }

    if (isListType(type)) {
      const typeAnnotation = t.arrayTypeAnnotation(typeAnnotationFromGraphQLType(type.ofType));

      if (nullable) {
        return t.nullableTypeAnnotation(typeAnnotation);
      } else {
        return typeAnnotation;
      }
    }

    let typeAnnotation;
    if (type instanceof GraphQLScalarType) {
      const builtIn = builtInScalarMap[type.name];
      if (builtIn) {
        typeAnnotation = builtIn;
      } else {
        if (compilerOptions.passthroughCustomScalars) {
          typeAnnotation = t.anyTypeAnnotation();
        } else {
          typeAnnotation = t.genericTypeAnnotation(t.identifier(type.name));
        }
      }
    } else {
      typeAnnotation = t.genericTypeAnnotation(t.identifier(type.name));
    }

    if (nullable) {
      return t.nullableTypeAnnotation(typeAnnotation);
    } else {
      return typeAnnotation;
    }
  };
}
Example #11
Source File: renderTyping.ts    From genql with MIT License 5 votes vote down vote up
render = (
  type: GraphQLOutputType | GraphQLInputType,
  nonNull: boolean,
  root: boolean,
  undefinableValues: boolean,
  undefinableFields: boolean,
  wrap: (x: string) => string = x => x
): string => {
    
  if (root) {
    if (undefinableFields) {
      if (isNonNullType(type)) {
        return `: ${render(type.ofType, true, false, undefinableValues, undefinableFields, wrap)}`
      } else {
        const rendered = render(type, true, false, undefinableValues, undefinableFields, wrap)
        return undefinableValues ? `?: ${rendered}` : `?: (${rendered} | null)`
      }
    } else {
      return `: ${render(type, false, false, undefinableValues, undefinableFields, wrap)}`
    }
  }

  if (isNamedType(type)) {
    let typeName = type.name

    // if is a scalar use the scalar interface to not expose reserved words
    if (isScalarType(type)) {
      typeName = `Scalars['${typeName}']`
    }

    const typing = wrap(typeName)

    if (undefinableValues) {
      return nonNull ? typing : `(${typing} | undefined)`
    } else {
      return nonNull ? typing : `(${typing} | null)`
    }
  }

  if (isListType(type)) {
    const typing = `${render(type.ofType, false, false, undefinableValues, undefinableFields, wrap)}[]`

    if (undefinableValues) {
      return nonNull ? typing : `(${typing} | undefined)`
    } else {
      return nonNull ? typing : `(${typing} | null)`
    }
  }

  return render((<GraphQLNonNull<any>>type).ofType, true, false, undefinableValues, undefinableFields, wrap)
}
Example #12
Source File: objectType.ts    From genql with MIT License 5 votes vote down vote up
objectType = (
    type: GraphQLObjectType | GraphQLInterfaceType,
    ctx: RenderContext,
    wrapper: 'Promise' | 'Observable',
) => {
    // console.log(Object.keys(type.getFields()))
    const fieldsMap: GraphQLFieldMap<any, any> = ctx.config?.sortProperties
        ? sortKeys(type.getFields())
        : type.getFields()

    const fieldStrings = Object.keys(fieldsMap).map((fieldName) => {
        const field = fieldsMap[fieldName]
        const resolvedType = getNamedType(field.type)
        // leaf type, obly has.get() method
        const stopChain =
            isListType(field.type) ||
            (isNonNullType(field.type) && isListType(field.type.ofType)) ||
            isUnionType(resolvedType)
        // non leaf type, has .get method
        const resolvable = !(
            isEnumType(resolvedType) || isScalarType(resolvedType)
        )
        const argsPresent = field.args.length > 0
        const argsOptional = !field.args.find((a) => isNonNullType(a.type))
        const argsString = toArgsString(field)

        const executeReturnType = renderTyping(field.type, true, false, false)
        const executeReturnTypeWithTypeMap = renderTyping(
            field.type,
            true,
            false,
            false,
            (x: string) => `FieldsSelection<${x}, R>`,
        )

        //     get: <R extends RepositoryRequest>(
        //         request: R,
        //         defaultValue?: Repository,
        //     ) => Promise<FieldsSelection<Repository, R>>
        // }

        const getFnType = `{get: <R extends ${requestTypeName(
            resolvedType,
        )}>(request: R, defaultValue?: ${executeReturnTypeWithTypeMap}) => ${wrapper}<${executeReturnTypeWithTypeMap}>}`
        const fieldType = resolvable
            ? stopChain
                ? getFnType
                : `${chainTypeName(resolvedType, wrapper)} & ${getFnType}`
            : `{get: (request?: boolean|number, defaultValue?: ${executeReturnType}) => ${wrapper}<${executeReturnType}>}`

        const result: string[] = []

        if (argsPresent) {
            result.push(
                `((args${
                    argsOptional ? '?' : ''
                }: ${argsString}) => ${fieldType})`,
            )
        }

        if (!argsPresent || argsOptional) {
            result.push(`(${fieldType})`)
        }

        return `${fieldComment(field)}${field.name}: ${result.join('&')}`
    })

    ctx.addImport(RUNTIME_LIB_NAME, false, 'FieldsSelection', true, true)

    if (wrapper === 'Observable') {
        ctx.addImport(RUNTIME_LIB_NAME, false, 'Observable', true, true)
    }

    ctx.addCodeBlock(
        `${typeComment(type)}export interface ${chainTypeName(
            type,
            wrapper,
        )}{\n    ${fieldStrings.join(',\n    ')}\n}`,
    )
}
Example #13
Source File: graphql.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export function isList(type: GraphQLType): boolean {
  return isListType(type) || (isNonNullType(type) && isListType(type.ofType));
}
Example #14
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export function propertyFromField(
  context: LegacyCompilerContext,
  field: {
    name?: string;
    type: GraphQLType;
    fields?: any[];
    responseName?: string;
    description?: Maybe<string>;
    fragmentSpreads?: any;
    inlineFragments?: LegacyInlineFragment[];
    fieldName?: string;
  },
): Property {
  let { name: fieldName, type: fieldType, description, fragmentSpreads, inlineFragments } = field;
  fieldName = fieldName || field.responseName;

  const propertyName = fieldName;

  let property = { fieldName, fieldType, propertyName, description };

  const namedType = getNamedType(fieldType);

  let isNullable = true;
  if (isNonNullType(fieldType)) {
    isNullable = false;
  }

  if (isCompositeType(namedType)) {
    let typeName = namedType.toString();
    let isArray = false;
    let isArrayElementNullable = null;
    if (isListType(fieldType)) {
      isArray = true;
      isArrayElementNullable = !isNonNullType(fieldType.ofType);
    } else if (isNonNullType(fieldType) && isListType(fieldType.ofType)) {
      isArray = true;
      isArrayElementNullable = !isNonNullType(fieldType.ofType.ofType);
    } else if (!isNonNullType(fieldType)) {
      typeName = typeNameFromGraphQLType(context, fieldType, null, isNullable);
    }

    return {
      ...property,
      typeName,
      fields: field.fields,
      isComposite: true,
      fragmentSpreads,
      inlineFragments,
      fieldType,
      isArray,
      isNullable,
      isArrayElementNullable,
    };
  } else {
    if (field.fieldName === '__typename') {
      const typeName = typeNameFromGraphQLType(context, fieldType, null, false);
      return { ...property, typeName, isComposite: false, fieldType, isNullable: false };
    } else {
      const typeName = typeNameFromGraphQLType(context, fieldType, null, isNullable);
      return { ...property, typeName, isComposite: false, fieldType, isNullable };
    }
  }
}
Example #15
Source File: isRequiredList.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export default function isRequired(typeObj: GraphQLType): boolean {
  return isNonNullType(typeObj) && isListType(typeObj.ofType);
}
Example #16
Source File: isList.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export default function isList(typeObj: GraphQLType): boolean {
  if (isNonNullType(typeObj)) {
    return isList(typeObj.ofType);
  }
  return isListType(typeObj);
}
Example #17
Source File: addExecutionLogicToComposer.ts    From graphql-mesh with MIT License 5 votes vote down vote up
isListTypeOrNonNullListType = memoize1(function isListTypeOrNonNullListType(type: GraphQLOutputType) {
  if (isNonNullType(type)) {
    return isListType(type.ofType);
  }
  return isListType(type);
})
Example #18
Source File: codeGeneration.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
classDeclarationForOperation(operation: Operation) {
    const { operationName, operationType, variables, source, selectionSet } = operation;

    let className;
    let protocol;

    switch (operationType) {
      case 'query':
        className = `${this.helpers.operationClassName(operationName)}Query`;
        protocol = 'GraphQLQuery';
        break;
      case 'mutation':
        className = `${this.helpers.operationClassName(operationName)}Mutation`;
        protocol = 'GraphQLMutation';
        break;
      case 'subscription':
        className = `${this.helpers.operationClassName(operationName)}Subscription`;
        protocol = 'GraphQLSubscription';
        break;
      default:
        throw new GraphQLError(`Unsupported operation type "${operationType}"`);
    }

    this.classDeclaration(
      {
        className,
        modifiers: ['public', 'final'],
        adoptedProtocols: [protocol],
      },
      () => {
        if (source) {
          this.printOnNewline('public static let operationString =');
          this.withIndent(() => {
            this.multilineString(source);
          });
        }

        const fragmentsReferenced = collectFragmentsReferenced(operation.selectionSet, this.context.fragments);

        if (this.context.options.generateOperationIds) {
          const { operationId } = generateOperationId(operation, this.context.fragments, fragmentsReferenced);
          operation.operationId = operationId;
          this.printNewlineIfNeeded();
          this.printOnNewline(`public static let operationIdentifier: String? = "${operationId}"`);
        }

        if (fragmentsReferenced.size > 0) {
          this.printNewlineIfNeeded();
          this.printOnNewline('public static var requestString: String { return operationString');
          fragmentsReferenced.forEach(fragmentName => {
            this.print(`.appending(${this.helpers.structNameForFragmentName(fragmentName)}.fragmentString)`);
          });
          this.print(' }');
        }

        this.printNewlineIfNeeded();

        if (variables && variables.length > 0) {
          const properties = variables.map(({ name, type }) => {
            const typeName = this.helpers.typeNameFromGraphQLType(type);
            const isOptional = !(isNonNullType(type) || (isListType(type) && isNonNullType(type.ofType)));
            return { name, propertyName: name, type, typeName, isOptional };
          });

          this.propertyDeclarations(properties);

          this.printNewlineIfNeeded();
          this.initializerDeclarationForProperties(properties);

          this.printNewlineIfNeeded();
          this.printOnNewline(`public var variables: GraphQLMap?`);
          this.withinBlock(() => {
            this.printOnNewline(
              wrap(
                `return [`,
                join(properties.map(({ name, propertyName }) => `"${name}": ${escapeIdentifierIfNeeded(propertyName)}`), ', ') || ':',
                `]`
              )
            );
          });
        } else {
          this.initializerDeclarationForProperties([]);
        }

        this.structDeclarationForSelectionSet({
          structName: 'Data',
          selectionSet,
        });
      }
    );
  }
Example #19
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
transformSchema(schema: GraphQLSchema, rawSource: SubschemaConfig) {
    const federationConfig: FederationConfig<any> = {};

    rawSource.merge = {};
    if (this.config?.types) {
      const queryType = schema.getQueryType();
      const queryTypeFields = queryType.getFields();
      for (const type of this.config.types) {
        rawSource.merge[type.name] = {};
        const fields: FederationFieldsConfig = {};
        if (type.config?.fields) {
          for (const field of type.config.fields) {
            fields[field.name] = field.config;
            rawSource.merge[type.name].fields = rawSource.merge[type.name].fields || {};
            rawSource.merge[type.name].fields[field.name] = rawSource.merge[type.name].fields[field.name] || {};
            if (field.config.requires) {
              rawSource.merge[type.name].fields[field.name].computed = true;
              rawSource.merge[type.name].fields[field.name].selectionSet = `{ ${field.config.requires} }`;
            }
          }
        }
        // If a field is a key field, it should be GraphQLID

        if (type.config?.keyFields) {
          rawSource.merge[type.name].selectionSet = `{ ${type.config.keyFields.join(' ')} }`;
          for (const fieldName of type.config.keyFields) {
            const objectType = schema.getType(type.name) as GraphQLObjectType;
            if (objectType) {
              const existingType = objectType.getFields()[fieldName].type;
              objectType.getFields()[fieldName].type = isNonNullType(existingType)
                ? new GraphQLNonNull(GraphQLID)
                : GraphQLID;
            }
          }
        }

        let resolveReference: MergedTypeResolver<any>;
        if (type.config?.resolveReference) {
          const resolveReferenceConfig = type.config.resolveReference;
          if (typeof resolveReferenceConfig === 'string') {
            const fn$ = loadFromModuleExportExpression<any>(resolveReferenceConfig, {
              cwd: this.baseDir,
              defaultExportName: 'default',
              importFn: this.importFn,
            });
            resolveReference = (...args: any[]) => fn$.then(fn => fn(...args));
          } else if (typeof resolveReferenceConfig === 'function') {
            resolveReference = resolveReferenceConfig;
          } else {
            const queryField = queryTypeFields[resolveReferenceConfig.queryFieldName];
            const keyArg = resolveReferenceConfig.keyArg || queryField.args[0].name;
            const keyField = type.config.keyFields[0];
            const isBatch = isListType(queryField.args.find(arg => arg.name === keyArg));
            resolveReference = async (root, context, info) => {
              const result = await context[this.apiName].Query[queryField.name]({
                root,
                ...(isBatch
                  ? {
                      key: root[keyField],
                      argsFromKeys: (keys: string[]) => ({
                        [keyArg]: keys,
                      }),
                    }
                  : {
                      args: {
                        [keyArg]: root[keyField],
                      },
                    }),
                context,
                info,
              });
              return {
                ...root,
                ...result,
              };
            };
          }
          rawSource.merge[type.name].resolve = resolveReference;
        }
        federationConfig[type.name] = {
          ...type.config,
          resolveReference,
          fields,
        };
      }
    }

    const entityTypes = Object.fromEntries(
      Object.entries(federationConfig)
        .filter(([, { keyFields }]) => keyFields?.length)
        .map(([objectName]) => {
          const type = schema.getType(objectName);
          if (!isObjectType(type)) {
            throw new Error(`Type "${objectName}" is not an object type and can't have a key directive`);
          }
          return [objectName, type];
        })
    );

    const hasEntities = !!Object.keys(entityTypes).length;

    const sdlWithFederationDirectives = addFederationAnnotations(printSchemaWithDirectives(schema), federationConfig);

    const schemaWithFederationQueryType = mapSchema(schema, {
      [MapperKind.QUERY]: type => {
        const config = type.toConfig();
        return new GraphQLObjectType({
          ...config,
          fields: {
            ...config.fields,
            ...(hasEntities && {
              _entities: entitiesField,
              _service: {
                ...serviceField,
                resolve: () => ({ sdl: sdlWithFederationDirectives }),
              },
            }),
          },
        });
      },
    });

    const schemaWithUnionType = mapSchema(schemaWithFederationQueryType, {
      [MapperKind.UNION_TYPE]: type => {
        if (type.name === EntityType.name) {
          return new GraphQLUnionType({
            ...EntityType.toConfig(),
            types: Object.values(entityTypes),
          });
        }
        return type;
      },
    });

    // Not using transformSchema since it will remove resolveReference
    Object.entries(federationConfig).forEach(([objectName, currentFederationConfig]) => {
      if (currentFederationConfig.resolveReference) {
        const type = schemaWithUnionType.getType(objectName);
        if (!isObjectType(type)) {
          throw new Error(`Type "${objectName}" is not an object type and can't have a resolveReference function`);
        }
        type.resolveObject = currentFederationConfig.resolveReference;
      }
    });

    return schemaWithUnionType;
  }
Example #20
Source File: getComposerFromSchema.test.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('getComposerFromJSONSchema', () => {
  const logger = new DefaultLogger('getComposerFromJSONSchema - test');
  it('should return JSON scalar if given schema is boolean true', async () => {
    const result = await getComposerFromJSONSchema(true, logger);
    expect(result.input.getType()).toBe(GraphQLJSON);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLJSON);
  });
  it('should generate a new scalar type that validates the value against the given pattern in string schema', async () => {
    const pattern = '^\\d{10}$';
    const title = 'ExampleRegEx';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      pattern,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Scalar types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ScalarTypeComposer;
    expect(isScalarType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    const serializeFn = outputComposer.getSerialize();
    expect(() => serializeFn('not-valid-phone-number')).toThrow();
    expect(serializeFn('1231231234')).toBe('1231231234');
  });
  it('should generate a new scalar type that validates the value against the given const in string schema', async () => {
    const constStr = 'FOO';
    const title = 'ExampleConst';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      const: constStr,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Scalar types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ScalarTypeComposer;
    expect(isScalarType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    const serializeFn = outputComposer.getSerialize();
    expect(() => serializeFn('bar')).toThrow();
    expect(serializeFn(constStr)).toBe(constStr);
  });
  it('should generate a new enum type from enum schema', async () => {
    const enumValues = ['foo', 'bar', 'qux'];
    const title = 'ExampleEnum';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      enum: enumValues,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Enum types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as EnumTypeComposer;
    expect(outputComposer.toSDL()).toBe(
      /* GraphQL */ `
enum ExampleEnum {
  foo
  bar
  qux
}`.trim()
    );
  });
  it('should generate a new enum type from enum schema by sanitizing enum keys', async () => {
    const enumValues = ['0-foo', '1+bar', '2)qux'];
    const title = 'ExampleEnum';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      enum: enumValues,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Enum types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as EnumTypeComposer;
    expect(outputComposer.toSDL()).toMatchInlineSnapshot(`
      "enum ExampleEnum {
        _0_foo
        _1_PLUS_bar
        _2_RIGHT_PARENTHESIS_qux
      }"
    `);
  });
  it('should generate union types from oneOf object types', async () => {
    const inputSchema: JSONSchema = {
      title: 'User',
      type: 'object',
      oneOf: [
        {
          title: 'Writer',
          type: 'object',
          properties: {
            id: {
              type: 'string',
            },
            name: {
              type: 'string',
            },
            posts: {
              type: 'array',
              items: {
                title: 'Post',
                type: 'object',
                properties: {
                  id: {
                    type: 'string',
                  },
                  title: {
                    type: 'string',
                  },
                  content: {
                    type: 'string',
                  },
                },
              },
            },
          },
        },
        {
          title: 'Admin',
          type: 'object',
          properties: {
            id: {
              type: 'string',
            },
            name: {
              type: 'string',
            },
            permissions: {
              type: 'array',
              items: {
                title: 'AdminPermission',
                type: 'string',
                enum: ['edit', 'delete'],
              },
            },
          },
        },
      ],
    };
    const outputSchema = /* GraphQL */ `
union User = Writer | Admin

type Writer {
  id: String
  name: String
  posts: [Post]
}

${printType(GraphQLString)}

type Post {
  id: String
  title: String
  content: String
}

type Admin {
  id: String
  name: String
  permissions: [AdminPermission]
}

enum AdminPermission {
  edit
  delete
}
    `.trim();

    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const unionComposer = result.output as UnionTypeComposer;
    expect(
      unionComposer.toSDL({
        deep: true,
      })
    ).toBe(outputSchema);
  });
  it('should generate an input union type for oneOf definitions that contain scalar types', async () => {
    const title = 'ExampleOneOf';
    const inputSchema: JSONSchema = {
      title,
      oneOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(
      (result.input as InputTypeComposer).toSDL({
        deep: true,
      })
    ).toBe(
      /* GraphQL */ `
input ExampleOneOf_Input @oneOf {
  String: String
  ExampleObject_Input: ExampleObject_Input
}

${printType(GraphQLString)}

input ExampleObject_Input {
  id: String
}
    `.trim()
    );
  });
  it('should generate merged object types from allOf definitions', async () => {
    const inputSchema: JSONSchema = {
      title: 'ExampleAllOf',
      allOf: [
        {
          type: 'object',
          title: 'Foo',
          properties: {
            id: {
              type: 'string',
            },
          },
          required: ['id'],
        },
        {
          type: 'object',
          title: 'Bar',
          properties: {
            name: {
              type: 'string',
            },
          },
          required: ['name'],
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleAllOf_Input {
  id: String!
  name: String!
}
    `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleAllOf {
  id: String!
  name: String!
}
    `.trim()
    );
  });
  it('should generate container types and fields for allOf definitions that contain scalar types', async () => {
    const title = 'ExampleAllOf';
    const inputSchema: JSONSchema = {
      title,
      allOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const outputComposer = result.output as ObjectTypeComposer;
    expect(isObjectType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    expect(outputComposer.getFieldNames().includes('String')).toBeTruthy();
    expect(outputComposer.getFieldNames().includes('id')).toBeTruthy();
  });
  it('should generate correct types for anyOf definitions', async () => {
    const inputSchema: JSONSchema = {
      title: 'ExampleAnyOf',
      anyOf: [
        {
          type: 'object',
          title: 'Foo',
          properties: {
            id: {
              type: 'string',
            },
          },
          required: ['id'],
        },
        {
          type: 'object',
          title: 'Bar',
          properties: {
            name: {
              type: 'string',
            },
          },
          required: ['name'],
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleAnyOf_Input {
  id: String!
  name: String!
}
    `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleAnyOf {
  id: String!
  name: String!
}
    `.trim()
    );
  });
  it('should generate container types and fields for anyOf definitions that contain scalar types', async () => {
    const title = 'ExampleAnyOf';
    const inputSchema: JSONSchema = {
      title,
      allOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const outputComposer = result.output as ObjectTypeComposer;
    expect(isObjectType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    expect(outputComposer.getFieldNames().includes('String')).toBeTruthy();
    expect(outputComposer.getFieldNames().includes('id')).toBeTruthy();
  });
  it('should return Boolean for boolean definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'boolean',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLBoolean);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLBoolean);
  });
  it('should return Void for null definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'null',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType().name).toBe('Void');
    expect((result.output as ScalarTypeComposer).getType().name).toBe('Void');
  });
  it('should return BigInt for int64 definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
      format: 'int64',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLBigInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLBigInt);
  });
  it('should return Int for int32 definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
      format: 'int32',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLInt);
  });
  it('should return Int for integer definitions without format', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLInt);
  });
  it('should return Float for number definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'number',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLFloat);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLFloat);
  });
  it('should generate scalar types for minLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      minLength: 1,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('')).toThrow();
    expect(serializeFn('aa')).toBe('aa');
  });
  it('should generate scalar types for maxLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      maxLength: 2,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('aaa')).toThrow();
    expect(serializeFn('a')).toBe('a');
  });
  it('should generate scalar types for both minLength and maxLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      minLength: 1,
      maxLength: 2,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('aaa')).toThrow();
    expect(() => serializeFn('')).toThrow();
    expect(serializeFn('a')).toBe('a');
  });
  it('should return DateTime scalar for date-time format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'date-time',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLDateTime);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLDateTime);
  });
  it('should return Time scalar for time format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'time',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLTime);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLTime);
  });
  it('should return EmailAddress scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'email',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLEmailAddress);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLEmailAddress);
  });
  it('should return IPv4 scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'ipv4',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLIPv4);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLIPv4);
  });
  it('should return IPv6 scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'ipv6',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLIPv6);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLIPv6);
  });
  it('should return URL scalar for uri format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'uri',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLURL);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLURL);
  });
  it('should return String for string definitions without format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLString);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLString);
  });
  it('should return list type for array definitions with items as object', async () => {
    const inputSchema: JSONSchema = {
      type: 'array',
      items: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(isListType(result.input.getType())).toBeTruthy();
    expect((result.input as ListComposer).ofType.getType()).toBe(GraphQLString);
    expect(isListType((result.output as ListComposer).getType())).toBeTruthy();
    expect((result.output as ListComposer).ofType.getType()).toBe(GraphQLString);
  });
  it('should return generic JSON type for array definitions with contains', async () => {
    const title = 'ExampleArray';
    const inputSchema: JSONSchema = {
      title,
      type: 'array',
      contains: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ListComposer;
    expect(isListType(outputComposer.getType())).toBeTruthy();
    expect(isScalarType(outputComposer.ofType.getType())).toBeTruthy();
    expect(outputComposer.ofType.getTypeName()).toBe(title);
  });
  it('should return union type inside a list type if array definition has items as an array', async () => {
    const title = 'FooOrBar';
    const inputSchema: JSONSchema = {
      title: 'ExampleObject',
      type: 'object',
      properties: {
        fooOrBar: {
          title,
          type: 'array',
          items: [
            {
              title: 'Foo',
              type: 'object',
              properties: {
                id: {
                  type: 'string',
                },
              },
            },
            {
              title: 'Bar',
              type: 'object',
              properties: {
                name: {
                  type: 'string',
                },
              },
            },
          ],
        },
      },
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(
      (result.output as ObjectTypeComposer).toSDL({
        deep: true,
      })
    ).toBe(
      /* GraphQL */ `
type ExampleObject {
  fooOrBar: [FooOrBar]
}

union FooOrBar = Foo | Bar

type Foo {
  id: String
}

${printType(GraphQLString)}

type Bar {
  name: String
}
`.trim()
    );
  });
  it('should create correct object types from object definition', async () => {
    const title = 'ExampleObject';
    const inputSchema: JSONSchema = {
      title,
      type: 'object',
      properties: {
        id: {
          type: 'string',
        },
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleObject_Input {
  id: String
}
     `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleObject {
  id: String
}
     `.trim()
    );
  });
  it('should create correct object types from object definition with additionalProperties', async () => {
    const title = 'ExampleObject';
    const inputSchema: JSONSchema = {
      title,
      type: 'object',
      properties: {
        id: {
          type: 'string',
        },
      },
      additionalProperties: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toContain(
      /* GraphQL */ `
scalar ExampleObject_Input
     `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleObject {
  id: String
  additionalProperties: JSON
}
     `.trim()
    );
  });
  it('should return GraphQLSchema if object definition given with _schema title', async () => {
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            foo: {
              type: 'string',
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Query {
  foo: String
}
     `.trim()
    );
  });
  it('should return Query type if object definition given with Query title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Query',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Query {
  foo: String
}
     `.trim()
    );
  });
  it('should return Mutation type if object definition given with Query title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Mutation',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Mutation {
  foo: String
}
     `.trim()
    );
  });
  it('should return Subscription type if object definition given with Subscription title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Subscription',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Subscription {
  foo: String
}
     `.trim()
    );
  });
  it('should add arguments to Query fields with the object definition QueryTitle', async () => {
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            foo: {
              type: 'string',
            },
          },
        },
        queryInput: {
          title: 'QueryInput',
          type: 'object',
          properties: {
            foo: {
              title: 'Foo',
              type: 'object',
              properties: {
                bar: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toBe(
      /* GraphQL */ `
type Query {
  foo(input: Foo_Input): String
}

${printType(GraphQLString)}

input Foo_Input {
  bar: String
}
     `.trim()
    );
  });
  it('should choose correct type in union type generated from oneOf', async () => {
    const FooOrBar = {
      title: 'FooOrBar',
      oneOf: [
        {
          title: 'Foo',
          type: 'object',
          properties: {
            fooId: {
              type: 'string',
            },
          },
          required: ['fooId'],
        },
        {
          title: 'Bar',
          type: 'object',
          properties: {
            barId: {
              type: 'string',
            },
          },
        },
      ],
    };
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            fooOrBarButFoo: FooOrBar,
            fooOrBarButBar: FooOrBar,
          },
        },
      },
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    const schemaComposer = result.output as SchemaComposer;
    const fooId = 'FOO_ID';
    const barId = 'BAR_ID';
    schemaComposer.addResolveMethods({
      Query: {
        fooOrBarButFoo: () => ({
          fooId: 'FOO_ID',
        }),
        fooOrBarButBar: () => ({
          barId: 'BAR_ID',
        }),
      },
    });
    const schema = schemaComposer.buildSchema();
    const executionResponse: any = await execute({
      schema,
      document: parse(/* GraphQL */ `
        fragment FooOrBarFragment on FooOrBar {
          __typename
          ... on Foo {
            fooId
          }
          ... on Bar {
            barId
          }
        }
        query TestQuery {
          fooOrBarButFoo {
            ...FooOrBarFragment
          }
          fooOrBarButBar {
            ...FooOrBarFragment
          }
        }
      `),
    });
    expect(executionResponse?.data?.fooOrBarButFoo?.__typename).toBe('Foo');
    expect(executionResponse?.data?.fooOrBarButFoo?.fooId).toBe(fooId);
    expect(executionResponse?.data?.fooOrBarButBar?.__typename).toBe('Bar');
    expect(executionResponse?.data?.fooOrBarButBar?.barId).toBe(barId);
  });
  it('should handle non-string enum values', async () => {
    const FooEnum = {
      title: 'FooEnum',
      type: 'string' as const,
      enum: [-1, 1],
    };
    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    expect(enumValuesMap).toMatchInlineSnapshot(`
      Object {
        "NEGATIVE_1": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": -1,
        },
        "_1": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": 1,
        },
      }
    `);
  });
  it('should handle strings with non-latin characters', async () => {
    const FooEnum = {
      title: 'FooEnum',
      type: 'string' as const,
      enum: ['לא', 'כן'],
    };

    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    expect(enumValuesMap).toMatchInlineSnapshot(`
      Object {
        "_1499__1503_": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": "כן",
        },
        "_1500__1488_": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": "לא",
        },
      }
    `);
  });
  it('should handle invalid property names', async () => {
    const jsonSchema: JSONSchemaObject = {
      type: 'object',
      title: '_schema',
      properties: {
        query: {
          type: 'object',
          title: 'Query',
          properties: {
            foo: {
              type: 'object',
              title: 'Foo',
              properties: {
                '0Bar': {
                  type: 'object',
                  title: 'Bar',
                  properties: {
                    barId: {
                      type: 'string',
                    },
                  },
                },
                '1Baz': {
                  type: 'object',
                  title: 'Baz',
                  properties: {
                    bazId: {
                      type: 'string',
                    },
                  },
                },
              },
            },
          },
        },
        queryInput: {
          type: 'object',
          title: 'QueryInput',
          properties: {
            foo: {
              type: 'object',
              title: 'Foo_Input',
              properties: {
                '0BarId': {
                  type: 'string',
                },
                '1BazId': {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(jsonSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    const schema = (output as SchemaComposer).buildSchema();
    expect(printSchemaWithDirectives(schema)).toMatchInlineSnapshot(`
      "schema {
        query: Query
      }

      type Query {
        foo(input: Foo_Input_Input): Foo
      }

      type Foo {
        _0Bar: Bar
        _1Baz: Baz
      }

      type Bar {
        barId: String
      }

      type Baz {
        bazId: String
      }

      input Foo_Input_Input {
        _0BarId: String
        _1BazId: String
      }"
    `);
  });
  it('should workaround GraphQLjs falsy enum values bug', async () => {
    const values = [0, false, ''];
    const FooEnum = {
      title: 'FooEnum',
      type: ['number', 'boolean', 'string'] as any,
      enum: values,
    };
    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    Object.values(enumValuesMap).forEach((valueConfig, i) => {
      expect(valueConfig.value).toBe(values[i]?.toString());
    });
    expect.assertions(4);
  });
});
Example #21
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
async getMeshSource(): Promise<MeshSource> {
    let fetch: ReturnType<typeof getCachedFetch>;
    if (this.config.customFetch) {
      fetch =
        typeof this.config.customFetch === 'string'
          ? await loadFromModuleExportExpression<ReturnType<typeof getCachedFetch>>(this.config.customFetch, {
              cwd: this.baseDir,
              importFn: this.importFn,
              defaultExportName: 'default',
            })
          : this.config.customFetch;
    } else {
      fetch = getCachedFetch(this.cache);
    }

    const { baseUrl: nonInterpolatedBaseUrl, operationHeaders } = this.config;
    const baseUrl = stringInterpolator.parse(nonInterpolatedBaseUrl, {
      env: process.env,
    });

    const schemaComposer = new SchemaComposer();
    schemaComposer.add(GraphQLBigInt);
    schemaComposer.add(GraphQLGUID);
    schemaComposer.add(GraphQLDateTime);
    schemaComposer.add(GraphQLJSON);
    schemaComposer.add(GraphQLByte);
    schemaComposer.add(GraphQLDate);
    schemaComposer.add(GraphQLISO8601Duration);

    const aliasNamespaceMap = new Map<string, string>();

    const metadataJson = await this.getCachedMetadataJson(fetch);
    const schemas = metadataJson.Edmx[0].DataServices[0].Schema;
    const multipleSchemas = schemas.length > 1;
    const namespaces = new Set<string>();

    const contextDataloaderName = Symbol(`${this.name}DataLoader`);

    function getNamespaceFromTypeRef(typeRef: string) {
      let namespace = '';
      namespaces?.forEach(el => {
        if (
          typeRef.startsWith(el) &&
          el.length > namespace.length && // It can be deeper namespace
          !typeRef.replace(el + '.', '').includes('.') // Typename cannot have `.`
        ) {
          namespace = el;
        }
      });
      return namespace;
    }

    function getTypeNameFromRef({
      typeRef,
      isInput,
      isRequired,
    }: {
      typeRef: string;
      isInput: boolean;
      isRequired: boolean;
    }) {
      const typeRefArr = typeRef.split('Collection(');
      const arrayDepth = typeRefArr.length;
      let actualTypeRef = typeRefArr.join('').split(')').join('');
      const typeNamespace = getNamespaceFromTypeRef(actualTypeRef);
      if (aliasNamespaceMap.has(typeNamespace)) {
        const alias = aliasNamespaceMap.get(typeNamespace);
        actualTypeRef = actualTypeRef.replace(typeNamespace, alias);
      }
      const actualTypeRefArr = actualTypeRef.split('.');
      const typeName = multipleSchemas
        ? pascalCase(actualTypeRefArr.join('_'))
        : actualTypeRefArr[actualTypeRefArr.length - 1];
      let realTypeName = typeName;
      if (SCALARS.has(actualTypeRef)) {
        realTypeName = SCALARS.get(actualTypeRef);
      } else if (schemaComposer.isEnumType(typeName)) {
        realTypeName = typeName;
      } else if (isInput) {
        realTypeName += 'Input';
      }
      const fakeEmptyArr = new Array(arrayDepth);
      realTypeName = fakeEmptyArr.join('[') + realTypeName + fakeEmptyArr.join(']');
      if (isRequired) {
        realTypeName += '!';
      }
      return realTypeName;
    }

    function getUrlString(url: URL) {
      return decodeURIComponent(url.toString()).split('+').join(' ');
    }

    function handleResponseText(responseText: string, urlString: string, info: GraphQLResolveInfo) {
      let responseJson: any;
      try {
        responseJson = JSON.parse(responseText);
      } catch (error) {
        const actualError = new Error(responseText);
        Object.assign(actualError, {
          extensions: {
            url: urlString,
          },
        });
        throw actualError;
      }
      if (responseJson.error) {
        const actualError = new Error(responseJson.error.message || responseJson.error) as any;
        actualError.extensions = responseJson.error;
        throw actualError;
      }
      const urlStringWithoutSearchParams = urlString.split('?')[0];
      if (isListType(info.returnType)) {
        const actualReturnType = getNamedType(info.returnType) as GraphQLObjectType;
        const entityTypeExtensions = actualReturnType.extensions as unknown as EntityTypeExtensions;
        if ('Message' in responseJson && !('value' in responseJson)) {
          const error = new Error(responseJson.Message);
          Object.assign(error, { extensions: responseJson });
          throw error;
        }
        const returnList: any[] = responseJson.value;
        return returnList.map(element => {
          if (!entityTypeExtensions?.entityInfo) {
            return element;
          }
          const urlOfElement = new URL(urlStringWithoutSearchParams);
          addIdentifierToUrl(
            urlOfElement,
            entityTypeExtensions.entityInfo.identifierFieldName,
            entityTypeExtensions.entityInfo.identifierFieldTypeRef,
            element
          );
          const identifierUrl = element['@odata.id'] || getUrlString(urlOfElement);
          const fieldMap = actualReturnType.getFields();
          for (const fieldName in element) {
            if (entityTypeExtensions.entityInfo.navigationFields.includes(fieldName)) {
              const field = element[fieldName];
              let fieldType = fieldMap[fieldName].type;
              if ('ofType' in fieldType) {
                fieldType = fieldType.ofType;
              }
              const { entityInfo: fieldEntityInfo } = (fieldType as any).extensions as EntityTypeExtensions;
              if (field instanceof Array) {
                for (const fieldElement of field) {
                  const urlOfField = new URL(urljoin(identifierUrl, fieldName));
                  addIdentifierToUrl(
                    urlOfField,
                    fieldEntityInfo.identifierFieldName,
                    fieldEntityInfo.identifierFieldTypeRef,
                    fieldElement
                  );
                  fieldElement['@odata.id'] = fieldElement['@odata.id'] || getUrlString(urlOfField);
                }
              } else {
                const urlOfField = new URL(urljoin(identifierUrl, fieldName));
                addIdentifierToUrl(
                  urlOfField,
                  fieldEntityInfo.identifierFieldName,
                  fieldEntityInfo.identifierFieldTypeRef,
                  field
                );
                field['@odata.id'] = field['@odata.id'] || getUrlString(urlOfField);
              }
            }
          }
          return {
            '@odata.id': identifierUrl,
            ...element,
          };
        });
      } else {
        const actualReturnType = info.returnType as GraphQLObjectType;
        const entityTypeExtensions = actualReturnType.extensions as unknown as EntityTypeExtensions;
        if (!entityTypeExtensions?.entityInfo) {
          return responseJson;
        }
        const identifierUrl = responseJson['@odata.id'] || urlStringWithoutSearchParams;
        const fieldMap = actualReturnType.getFields();
        for (const fieldName in responseJson) {
          if (entityTypeExtensions?.entityInfo.navigationFields.includes(fieldName)) {
            const field = responseJson[fieldName];
            let fieldType = fieldMap[fieldName].type;
            if ('ofType' in fieldType) {
              fieldType = fieldType.ofType;
            }
            const { entityInfo: fieldEntityInfo } = (fieldType as any).extensions as EntityTypeExtensions;
            if (field instanceof Array) {
              for (const fieldElement of field) {
                const urlOfField = new URL(urljoin(identifierUrl, fieldName));
                addIdentifierToUrl(
                  urlOfField,
                  fieldEntityInfo.identifierFieldName,
                  fieldEntityInfo.identifierFieldTypeRef,
                  fieldElement
                );
                fieldElement['@odata.id'] = fieldElement['@odata.id'] || getUrlString(urlOfField);
              }
            } else {
              const urlOfField = new URL(urljoin(identifierUrl, fieldName));
              addIdentifierToUrl(
                urlOfField,
                fieldEntityInfo.identifierFieldName,
                fieldEntityInfo.identifierFieldTypeRef,
                field
              );
              field['@odata.id'] = field['@odata.id'] || getUrlString(urlOfField);
            }
          }
        }
        return {
          '@odata.id': responseJson['@odata.id'] || urlStringWithoutSearchParams,
          ...responseJson,
        };
      }
    }

    schemaComposer.createEnumTC({
      name: 'InlineCount',
      values: {
        allpages: {
          value: 'allpages',
          description:
            'The OData MUST include a count of the number of entities in the collection identified by the URI (after applying any $filter System Query Options present on the URI)',
        },
        none: {
          value: 'none',
          description:
            'The OData service MUST NOT include a count in the response. This is equivalence to a URI that does not include a $inlinecount query string parameter.',
        },
      },
    });

    schemaComposer.createInputTC({
      name: 'QueryOptions',
      fields: queryOptionsFields,
    });

    const origHeadersFactory = getInterpolatedHeadersFactory(operationHeaders);
    const headersFactory = (resolverData: ResolverData, method: string) => {
      const headers = origHeadersFactory(resolverData);
      if (headers.accept == null) {
        headers.accept = 'application/json';
      }
      if (headers['content-type'] == null && method !== 'GET') {
        headers['content-type'] = 'application/json';
      }
      return headers;
    };
    const { args: commonArgs, contextVariables } = parseInterpolationStrings([
      ...Object.values(operationHeaders || {}),
      baseUrl,
    ]);

    function getTCByTypeNames(...typeNames: string[]) {
      for (const typeName of typeNames) {
        try {
          return schemaComposer.getAnyTC(typeName);
        } catch {}
      }
      return null;
    }

    function addIdentifierToUrl(url: URL, identifierFieldName: string, identifierFieldTypeRef: string, args: any) {
      url.href += `/${args[identifierFieldName]}/`;
    }

    function rebuildOpenInputObjects(input: any) {
      if (typeof input === 'object') {
        if ('rest' in input) {
          Object.assign(input, input.rest);
          delete input.rest;
        }
        for (const fieldName in input) {
          rebuildOpenInputObjects(input[fieldName]);
        }
      }
    }

    function handleBatchJsonResults(batchResponseJson: any, requests: Request[]) {
      if ('error' in batchResponseJson) {
        const error = new Error(batchResponseJson.error.message);
        Object.assign(error, {
          extensions: batchResponseJson.error,
        });
        throw error;
      }
      if (!('responses' in batchResponseJson)) {
        const error = new Error(
          batchResponseJson.ExceptionMessage ||
            batchResponseJson.Message ||
            `Batch Request didn't return a valid response.`
        );
        Object.assign(error, {
          extensions: batchResponseJson,
        });
        throw error;
      }
      return requests.map((_req, index) => {
        const responseObj = batchResponseJson.responses.find((res: any) => res.id === index.toString());
        return new Response(JSON.stringify(responseObj.body), {
          status: responseObj.status,
          headers: responseObj.headers,
        });
      });
    }

    const DATALOADER_FACTORIES = {
      multipart: (context: any) =>
        new DataLoader(async (requests: Request[]): Promise<Response[]> => {
          let requestBody = '';
          const requestBoundary = 'batch_' + Date.now();
          for (const requestIndex in requests) {
            requestBody += `--${requestBoundary}\n`;
            const request = requests[requestIndex];
            requestBody += `Content-Type: application/http\n`;
            requestBody += `Content-Transfer-Encoding:binary\n`;
            requestBody += `Content-ID: ${requestIndex}\n\n`;
            requestBody += `${request.method} ${request.url} HTTP/1.1\n`;
            request.headers?.forEach((value, key) => {
              requestBody += `${key}: ${value}\n`;
            });
            if (request.body) {
              const bodyAsStr = await request.text();
              requestBody += `Content-Length: ${bodyAsStr.length}`;
              requestBody += `\n`;
              requestBody += bodyAsStr;
            }
            requestBody += `\n`;
          }
          requestBody += `--${requestBoundary}--\n`;
          const batchHeaders = headersFactory(
            {
              context,
              env: process.env,
            },
            'POST'
          );
          batchHeaders['content-type'] = `multipart/mixed;boundary=${requestBoundary}`;
          const batchResponse = await fetch(urljoin(baseUrl, '$batch'), {
            method: 'POST',
            body: requestBody,
            headers: batchHeaders,
          });
          if (batchResponse.headers.get('content-type').includes('json')) {
            const batchResponseJson = await batchResponse.json();
            return handleBatchJsonResults(batchResponseJson, requests);
          }
          const batchResponseText = await batchResponse.text();
          const responseLines = batchResponseText.split('\n');
          const responseBoundary = responseLines[0];
          const actualResponse = responseLines.slice(1, responseLines.length - 2).join('\n');
          const responseTextArr = actualResponse.split(responseBoundary);
          return responseTextArr.map(responseTextWithContentHeader => {
            const responseText = responseTextWithContentHeader.split('\n').slice(4).join('\n');
            const { body, headers, statusCode, statusMessage } = parseResponse(responseText);
            return new Response(body, {
              headers,
              status: parseInt(statusCode),
              statusText: statusMessage,
            });
          });
        }),
      json: (context: any) =>
        new DataLoader(async (requests: Request[]): Promise<Response[]> => {
          const batchHeaders = headersFactory(
            {
              context,
              env: process.env,
            },
            'POST'
          );
          batchHeaders['content-type'] = 'application/json';
          const batchResponse = await fetch(urljoin(baseUrl, '$batch'), {
            method: 'POST',
            body: JSON.stringify({
              requests: await Promise.all(
                requests.map(async (request, index) => {
                  const id = index.toString();
                  const url = request.url.replace(baseUrl, '');
                  const method = request.method;
                  const headers: HeadersInit = {};
                  request.headers?.forEach((value, key) => {
                    headers[key] = value;
                  });
                  return {
                    id,
                    url,
                    method,
                    body: request.body && (await request.json()),
                    headers,
                  };
                })
              ),
            }),
            headers: batchHeaders,
          });
          const batchResponseJson = await batchResponse.json();
          return handleBatchJsonResults(batchResponseJson, requests);
        }),
      none: () =>
        new DataLoader(
          (requests: Request[]): Promise<Response[]> => Promise.all(requests.map(request => fetch(request)))
        ),
    };

    const dataLoaderFactory = memoize1(DATALOADER_FACTORIES[this.config.batch || 'none']);

    function buildName({ schemaNamespace, name }: { schemaNamespace: string; name: string }) {
      const alias = aliasNamespaceMap.get(schemaNamespace) || schemaNamespace;
      const ref = alias + '.' + name;
      return multipleSchemas ? pascalCase(ref.split('.').join('_')) : name;
    }

    schemas?.forEach((schemaObj: any) => {
      const schemaNamespace = schemaObj.attributes.Namespace;
      namespaces.add(schemaNamespace);
      const schemaAlias = schemaObj.attributes.Alias;
      if (schemaAlias) {
        aliasNamespaceMap.set(schemaNamespace, schemaAlias);
      }
    });

    schemas?.forEach((schemaObj: any) => {
      const schemaNamespace = schemaObj.attributes.Namespace;

      schemaObj.EnumType?.forEach((enumObj: any) => {
        const values: Record<string, EnumTypeComposerValueConfigDefinition> = {};
        enumObj.Member?.forEach((memberObj: any) => {
          const key = memberObj.attributes.Name;
          // This doesn't work.
          // const value = memberElement.getAttribute('Value')!;
          values[key] = {
            value: key,
            extensions: { memberObj },
          };
        });
        const enumTypeName = buildName({ schemaNamespace, name: enumObj.attributes.Name });
        schemaComposer.createEnumTC({
          name: enumTypeName,
          values,
          extensions: { enumObj },
        });
      });

      const allTypes = (schemaObj.EntityType || []).concat(schemaObj.ComplexType || []);
      const typesWithBaseType = allTypes.filter((typeObj: any) => typeObj.attributes.BaseType);

      allTypes?.forEach((typeObj: any) => {
        const entityTypeName = buildName({ schemaNamespace, name: typeObj.attributes.Name });
        const isOpenType = typeObj.attributes.OpenType === 'true';
        const isAbstract = typeObj.attributes.Abstract === 'true';
        const eventEmitter = new EventEmitter();
        eventEmitter.setMaxListeners(Infinity);
        this.eventEmitterSet.add(eventEmitter);
        const extensions: EntityTypeExtensions = {
          entityInfo: {
            actualFields: [],
            navigationFields: [],
            isOpenType,
          },
          typeObj,
          eventEmitter,
        };
        const inputType = schemaComposer.createInputTC({
          name: entityTypeName + 'Input',
          fields: {},
          extensions: () => extensions,
        });
        let abstractType: InterfaceTypeComposer;
        if (
          typesWithBaseType.some((typeObj: any) => typeObj.attributes.BaseType.includes(`.${entityTypeName}`)) ||
          isAbstract
        ) {
          abstractType = schemaComposer.createInterfaceTC({
            name: isAbstract ? entityTypeName : `I${entityTypeName}`,
            extensions,
            resolveType: (root: any) => {
              const typeRef = root['@odata.type']?.replace('#', '');
              if (typeRef) {
                const typeName = getTypeNameFromRef({
                  typeRef: root['@odata.type'].replace('#', ''),
                  isInput: false,
                  isRequired: false,
                });
                return typeName;
              }
              return isAbstract ? `T${entityTypeName}` : entityTypeName;
            },
          });
        }
        const outputType = schemaComposer.createObjectTC({
          name: isAbstract ? `T${entityTypeName}` : entityTypeName,
          extensions,
          interfaces: abstractType ? [abstractType] : [],
        });

        abstractType?.setInputTypeComposer(inputType);
        outputType.setInputTypeComposer(inputType);

        const propertyRefObj = typeObj.Key && typeObj.Key[0].PropertyRef[0];
        if (propertyRefObj) {
          extensions.entityInfo.identifierFieldName = propertyRefObj.attributes.Name;
        }

        typeObj.Property?.forEach((propertyObj: any) => {
          const propertyName = propertyObj.attributes.Name;
          extensions.entityInfo.actualFields.push(propertyName);
          const propertyTypeRef = propertyObj.attributes.Type;
          if (propertyName === extensions.entityInfo.identifierFieldName) {
            extensions.entityInfo.identifierFieldTypeRef = propertyTypeRef;
          }
          const isRequired = propertyObj.attributes.Nullable === 'false';
          inputType.addFields({
            [propertyName]: {
              type: getTypeNameFromRef({
                typeRef: propertyTypeRef,
                isInput: true,
                isRequired,
              }),
              extensions: { propertyObj },
            },
          });
          const field: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
            type: getTypeNameFromRef({
              typeRef: propertyTypeRef,
              isInput: false,
              isRequired,
            }),
            extensions: { propertyObj },
          };
          abstractType?.addFields({
            [propertyName]: field,
          });
          outputType.addFields({
            [propertyName]: field,
          });
        });
        typeObj.NavigationProperty?.forEach((navigationPropertyObj: any) => {
          const navigationPropertyName = navigationPropertyObj.attributes.Name;
          extensions.entityInfo.navigationFields.push(navigationPropertyName);
          const navigationPropertyTypeRef = navigationPropertyObj.attributes.Type;
          const isRequired = navigationPropertyObj.attributes.Nullable === 'false';
          const isList = navigationPropertyTypeRef.startsWith('Collection(');
          if (isList) {
            const singularField: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
              type: getTypeNameFromRef({
                typeRef: navigationPropertyTypeRef,
                isInput: false,
                isRequired,
              })
                .replace('[', '')
                .replace(']', ''),
              args: {
                ...commonArgs,
                id: {
                  type: 'ID',
                },
              },
              extensions: { navigationPropertyObj },
              resolve: async (root, args, context, info) => {
                if (navigationPropertyName in root) {
                  return root[navigationPropertyName];
                }
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + navigationPropertyName);
                const returnType = info.returnType as GraphQLObjectType;
                const { entityInfo } = returnType.extensions as unknown as EntityTypeExtensions;
                addIdentifierToUrl(url, entityInfo.identifierFieldName, entityInfo.identifierFieldTypeRef, args);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
            const pluralField: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
              type: getTypeNameFromRef({
                typeRef: navigationPropertyTypeRef,
                isInput: false,
                isRequired,
              }),
              args: {
                ...commonArgs,
                queryOptions: { type: 'QueryOptions' },
              },
              extensions: { navigationPropertyObj },
              resolve: async (root, args, context, info) => {
                if (navigationPropertyName in root) {
                  return root[navigationPropertyName];
                }
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + navigationPropertyName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
            abstractType?.addFields({
              [navigationPropertyName]: pluralField,
              [`${navigationPropertyName}ById`]: singularField,
            });
            outputType.addFields({
              [navigationPropertyName]: pluralField,
              [`${navigationPropertyName}ById`]: singularField,
            });
          } else {
            const field: ObjectTypeComposerFieldConfigDefinition<any, unknown> = {
              type: getTypeNameFromRef({
                typeRef: navigationPropertyTypeRef,
                isInput: false,
                isRequired,
              }),
              args: {
                ...commonArgs,
              },
              extensions: { navigationPropertyObj },
              resolve: async (root, args, context, info) => {
                if (navigationPropertyName in root) {
                  return root[navigationPropertyName];
                }
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + navigationPropertyName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
            abstractType?.addFields({
              [navigationPropertyName]: field,
            });
            outputType.addFields({
              [navigationPropertyName]: field,
            });
          }
        });
        if (isOpenType || outputType.getFieldNames().length === 0) {
          extensions.entityInfo.isOpenType = true;
          inputType.addFields({
            rest: {
              type: 'JSON',
            },
          });
          abstractType?.addFields({
            rest: {
              type: 'JSON',
              resolve: (root: any) => root,
            },
          });
          outputType.addFields({
            rest: {
              type: 'JSON',
              resolve: (root: any) => root,
            },
          });
        }
        const updateInputType = inputType.clone(`${entityTypeName}UpdateInput`);
        updateInputType.getFieldNames()?.forEach(fieldName => updateInputType.makeOptional(fieldName));
        // Types might be considered as unused implementations of interfaces so we must prevent that
        schemaComposer.addSchemaMustHaveType(outputType);
      });

      const handleUnboundFunctionObj = (unboundFunctionObj: any) => {
        const functionName = unboundFunctionObj.attributes.Name;
        const returnTypeRef = unboundFunctionObj.ReturnType[0].attributes.Type;
        const returnType = getTypeNameFromRef({
          typeRef: returnTypeRef,
          isInput: false,
          isRequired: false,
        });
        schemaComposer.Query.addFields({
          [functionName]: {
            type: returnType,
            args: {
              ...commonArgs,
            },
            resolve: async (root, args, context, info) => {
              const url = new URL(baseUrl);
              url.href = urljoin(url.href, '/' + functionName);
              url.href += `(${Object.entries(args)
                .filter(argEntry => argEntry[0] !== 'queryOptions')
                .map(argEntry => argEntry.join(' = '))
                .join(', ')})`;
              const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
              const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
              searchParams?.forEach((value, key) => {
                url.searchParams.set(key, value);
              });
              const urlString = getUrlString(url);
              const method = 'GET';
              const request = new Request(urlString, {
                method,
                headers: headersFactory(
                  {
                    root,
                    args,
                    context,
                    info,
                    env: process.env,
                  },
                  method
                ),
              });
              const response = await context[contextDataloaderName].load(request);
              const responseText = await response.text();
              return handleResponseText(responseText, urlString, info);
            },
          },
        });
        unboundFunctionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterType = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          schemaComposer.Query.addFieldArgs(functionName, {
            [parameterName]: {
              type: parameterType,
            },
          });
        });
      };

      const handleBoundFunctionObj = (boundFunctionObj: any) => {
        const functionName = boundFunctionObj.attributes.Name;
        const functionRef = schemaNamespace + '.' + functionName;
        const returnTypeRef = boundFunctionObj.ReturnType[0].attributes.Type;
        const returnType = getTypeNameFromRef({
          typeRef: returnTypeRef,
          isInput: false,
          isRequired: false,
        });
        const args: ObjectTypeComposerArgumentConfigMapDefinition<any> = {
          ...commonArgs,
        };
        // eslint-disable-next-line prefer-const
        let entitySetPath = boundFunctionObj.attributes.EntitySetPath?.split('/')[0];
        let field: ObjectTypeComposerFieldConfigDefinition<any, any, any>;
        let boundEntityTypeName: string;
        boundFunctionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterTypeName = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          // If entitySetPath is not available, take first parameter as entity
          // The first segment of the entity set path must match the binding parameter name
          // (see: http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530388)
          entitySetPath = (entitySetPath && entitySetPath.split('/')[0]) || parameterName;
          if (entitySetPath === parameterName) {
            boundEntityTypeName = getTypeNameFromRef({
              typeRef: parameterTypeRef,
              isInput: false,
              isRequired: false,
            })
              .replace('[', '')
              .replace(']', '');
            field = {
              type: returnType,
              args,
              resolve: async (root, args, context, info) => {
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + functionRef);
                const argsEntries = Object.entries(args);
                if (argsEntries.length) {
                  url.href += `(${argsEntries
                    .filter(argEntry => argEntry[0] !== 'queryOptions')
                    .map(([argName, value]) => [argName, typeof value === 'string' ? `'${value}'` : value])
                    .map(argEntry => argEntry.join('='))
                    .join(',')})`;
                }
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
          }
          args[parameterName] = {
            type: parameterTypeName,
          };
        });
        const boundEntityType = schemaComposer.getAnyTC(boundEntityTypeName) as InterfaceTypeComposer;
        const boundEntityOtherType = getTCByTypeNames(
          'I' + boundEntityTypeName,
          'T' + boundEntityTypeName
        ) as InterfaceTypeComposer;
        boundEntityType.addFields({
          [functionName]: field,
        });
        boundEntityOtherType?.addFields({
          [functionName]: field,
        });
      };

      schemaObj.Function?.forEach((functionObj: any) => {
        if (functionObj.attributes?.IsBound === 'true') {
          handleBoundFunctionObj(functionObj);
        } else {
          handleUnboundFunctionObj(functionObj);
        }
      });

      const handleUnboundActionObj = (unboundActionObj: any) => {
        const actionName = unboundActionObj.attributes.Name;
        schemaComposer.Mutation.addFields({
          [actionName]: {
            type: 'JSON',
            args: {
              ...commonArgs,
            },
            resolve: async (root, args, context, info) => {
              const url = new URL(baseUrl);
              url.href = urljoin(url.href, '/' + actionName);
              const urlString = getUrlString(url);
              const method = 'POST';
              const request = new Request(urlString, {
                method,
                headers: headersFactory(
                  {
                    root,
                    args,
                    context,
                    info,
                    env: process.env,
                  },
                  method
                ),
                body: JSON.stringify(args),
              });
              const response = await context[contextDataloaderName].load(request);
              const responseText = await response.text();
              return handleResponseText(responseText, urlString, info);
            },
          },
        });

        unboundActionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterType = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          schemaComposer.Mutation.addFieldArgs(actionName, {
            [parameterName]: {
              type: parameterType,
            },
          });
        });
      };

      const handleBoundActionObj = (boundActionObj: any) => {
        const actionName = boundActionObj.attributes.Name;
        const actionRef = schemaNamespace + '.' + actionName;
        const args: ObjectTypeComposerArgumentConfigMapDefinition<any> = {
          ...commonArgs,
        };
        let entitySetPath = boundActionObj.attributes.EntitySetPath;
        let boundField: ObjectTypeComposerFieldConfigDefinition<any, any, any>;
        let boundEntityTypeName: string;
        boundActionObj.Parameter?.forEach((parameterObj: any) => {
          const parameterName = parameterObj.attributes.Name;
          const parameterTypeRef = parameterObj.attributes.Type;
          const isRequired = parameterObj.attributes.Nullable === 'false';
          const parameterTypeName = getTypeNameFromRef({
            typeRef: parameterTypeRef,
            isInput: true,
            isRequired,
          });
          // If entitySetPath is not available, take first parameter as entity
          entitySetPath = entitySetPath || parameterName;
          if (entitySetPath === parameterName) {
            boundEntityTypeName = getTypeNameFromRef({
              typeRef: parameterTypeRef,
              isInput: false,
              isRequired: false,
            })
              .replace('[', '')
              .replace(']', ''); // Todo temp workaround
            boundField = {
              type: 'JSON',
              args,
              resolve: async (root, args, context, info) => {
                const url = new URL(root['@odata.id']);
                url.href = urljoin(url.href, '/' + actionRef);
                const urlString = getUrlString(url);
                const method = 'POST';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                  body: JSON.stringify(args),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            };
          }
          args[parameterName] = {
            type: parameterTypeName,
          };
        });
        const boundEntityType = schemaComposer.getAnyTC(boundEntityTypeName) as InterfaceTypeComposer;
        boundEntityType.addFields({
          [actionName]: boundField,
        });
        const otherType = getTCByTypeNames(
          `I${boundEntityTypeName}`,
          `T${boundEntityTypeName}`
        ) as InterfaceTypeComposer;
        otherType?.addFields({
          [actionName]: boundField,
        });
      };

      schemaObj.Action?.forEach((actionObj: any) => {
        if (actionObj.attributes?.IsBound === 'true') {
          handleBoundActionObj(actionObj);
        } else {
          handleUnboundActionObj(actionObj);
        }
      });

      // Rearrange fields for base types and implementations
      typesWithBaseType?.forEach((typeObj: any) => {
        const typeName = buildName({
          schemaNamespace,
          name: typeObj.attributes.Name,
        });
        const inputType = schemaComposer.getITC(typeName + 'Input') as InputTypeComposer;
        const abstractType = getTCByTypeNames('I' + typeName, typeName) as InterfaceTypeComposer;
        const outputType = getTCByTypeNames('T' + typeName, typeName) as ObjectTypeComposer;
        const baseTypeRef = typeObj.attributes.BaseType;
        const { entityInfo, eventEmitter } = outputType.getExtensions() as EntityTypeExtensions;
        const baseTypeName = getTypeNameFromRef({
          typeRef: baseTypeRef,
          isInput: false,
          isRequired: false,
        });
        const baseInputType = schemaComposer.getAnyTC(baseTypeName + 'Input') as InputTypeComposer;
        const baseAbstractType = getTCByTypeNames('I' + baseTypeName, baseTypeName) as InterfaceTypeComposer;
        const baseOutputType = getTCByTypeNames('T' + baseTypeName, baseTypeName) as ObjectTypeComposer;
        const { entityInfo: baseEntityInfo, eventEmitter: baseEventEmitter } =
          baseOutputType.getExtensions() as EntityTypeExtensions;
        const baseEventEmitterListener = () => {
          inputType.addFields(baseInputType.getFields());
          entityInfo.identifierFieldName = baseEntityInfo.identifierFieldName || entityInfo.identifierFieldName;
          entityInfo.identifierFieldTypeRef =
            baseEntityInfo.identifierFieldTypeRef || entityInfo.identifierFieldTypeRef;
          entityInfo.actualFields.unshift(...baseEntityInfo.actualFields);
          abstractType?.addFields(baseAbstractType?.getFields());
          outputType.addFields(baseOutputType.getFields());
          if (baseAbstractType instanceof InterfaceTypeComposer) {
            // abstractType.addInterface(baseAbstractType.getTypeName());
            outputType.addInterface(baseAbstractType.getTypeName());
          }
          eventEmitter.emit('onFieldChange');
        };
        baseEventEmitter.on('onFieldChange', baseEventEmitterListener);
        baseEventEmitterListener();
      });
    });

    schemas?.forEach((schemaObj: any) => {
      schemaObj.EntityContainer?.forEach((entityContainerObj: any) => {
        entityContainerObj.Singleton?.forEach((singletonObj: any) => {
          const singletonName = singletonObj.attributes.Name;
          const singletonTypeRef = singletonObj.attributes.Type;
          const singletonTypeName = getTypeNameFromRef({
            typeRef: singletonTypeRef,
            isInput: false,
            isRequired: false,
          });
          schemaComposer.Query.addFields({
            [singletonName]: {
              type: singletonTypeName,
              args: {
                ...commonArgs,
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + singletonName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
          });
        });

        entityContainerObj?.EntitySet?.forEach((entitySetObj: any) => {
          const entitySetName = entitySetObj.attributes.Name;
          const entitySetTypeRef = entitySetObj.attributes.EntityType;
          const entityTypeName = getTypeNameFromRef({
            typeRef: entitySetTypeRef,
            isInput: false,
            isRequired: false,
          });
          const entityOutputTC = getTCByTypeNames('I' + entityTypeName, entityTypeName) as
            | InterfaceTypeComposer
            | ObjectTypeComposer;
          const { entityInfo } = entityOutputTC.getExtensions() as EntityTypeExtensions;
          const identifierFieldName = entityInfo.identifierFieldName;
          const identifierFieldTypeRef = entityInfo.identifierFieldTypeRef;
          const identifierFieldTypeName = entityOutputTC.getFieldTypeName(identifierFieldName);
          const typeName = entityOutputTC.getTypeName();
          const commonFields: Record<string, ObjectTypeComposerFieldConfigDefinition<any, any>> = {
            [entitySetName]: {
              type: `[${typeName}]`,
              args: {
                ...commonArgs,
                queryOptions: { type: 'QueryOptions' },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
            [`${entitySetName}By${identifierFieldName}`]: {
              type: typeName,
              args: {
                ...commonArgs,
                [identifierFieldName]: {
                  type: identifierFieldTypeName,
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                addIdentifierToUrl(url, identifierFieldName, identifierFieldTypeRef, args);
                const parsedInfoFragment = parseResolveInfo(info) as ResolveTree;
                const searchParams = this.prepareSearchParams(parsedInfoFragment, info.schema);
                searchParams?.forEach((value, key) => {
                  url.searchParams.set(key, value);
                });
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
          };
          schemaComposer.Query.addFields({
            ...commonFields,
            [`${entitySetName}Count`]: {
              type: 'Int',
              args: {
                ...commonArgs,
                queryOptions: { type: 'QueryOptions' },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, `/${entitySetName}/$count`);
                const urlString = getUrlString(url);
                const method = 'GET';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return responseText;
              },
            },
          });
          schemaComposer.Mutation.addFields({
            ...commonFields,
            [`create${entitySetName}`]: {
              type: typeName,
              args: {
                ...commonArgs,
                input: {
                  type: entityTypeName + 'Input',
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                const urlString = getUrlString(url);
                rebuildOpenInputObjects(args.input);
                const method = 'POST';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                  body: JSON.stringify(args.input),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
            [`delete${entitySetName}By${identifierFieldName}`]: {
              type: 'JSON',
              args: {
                ...commonArgs,
                [identifierFieldName]: {
                  type: identifierFieldTypeName,
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                addIdentifierToUrl(url, identifierFieldName, identifierFieldTypeRef, args);
                const urlString = getUrlString(url);
                const method = 'DELETE';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
            [`update${entitySetName}By${identifierFieldName}`]: {
              type: typeName,
              args: {
                ...commonArgs,
                [identifierFieldName]: {
                  type: identifierFieldTypeName,
                },
                input: {
                  type: entityTypeName + 'UpdateInput',
                },
              },
              resolve: async (root, args, context, info) => {
                const url = new URL(baseUrl);
                url.href = urljoin(url.href, '/' + entitySetName);
                addIdentifierToUrl(url, identifierFieldName, identifierFieldTypeRef, args);
                const urlString = getUrlString(url);
                rebuildOpenInputObjects(args.input);
                const method = 'PATCH';
                const request = new Request(urlString, {
                  method,
                  headers: headersFactory(
                    {
                      root,
                      args,
                      context,
                      info,
                      env: process.env,
                    },
                    method
                  ),
                  body: JSON.stringify(args.input),
                });
                const response = await context[contextDataloaderName].load(request);
                const responseText = await response.text();
                return handleResponseText(responseText, urlString, info);
              },
            },
          });
        });
      });
    });

    // graphql-compose doesn't add @defer and @stream to the schema
    specifiedDirectives.forEach(directive => schemaComposer.addDirective(directive));

    const schema = schemaComposer.buildSchema();
    this.eventEmitterSet.forEach(ee => ee.removeAllListeners());
    this.eventEmitterSet.clear();

    const executor = createDefaultExecutor(schema);

    return {
      schema,
      executor: <TResult>(executionRequest: ExecutionRequest) => {
        const odataContext = {
          [contextDataloaderName]: dataLoaderFactory(executionRequest.context),
        };
        return executor({
          ...executionRequest,
          context: {
            ...executionRequest.context,
            ...odataContext,
          },
        }) as ExecutionResult<TResult>;
      },
      contextVariables,
      batch: true,
    };
  }