graphql#isScalarType TypeScript Examples
The following examples show how to use
graphql#isScalarType.
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: isS3Object.ts From amplify-codegen with Apache License 2.0 | 6 votes |
export default function isS3Object(typeObj: GraphQLType): boolean {
if (isObjectType(typeObj)) {
const fields = typeObj.getFields();
const fieldName = typeObj.name;
const hasS3Fields = S3_FIELD_NAMES.every(s3Field => {
const field = fields[s3Field];
try {
const type = getType(field.type);
return field && isScalarType(type) && type.name === 'String';
} catch (e) {
return false;
}
});
return hasS3Fields && fieldName === 'S3Object';
}
return false;
}
Example #2
Source File: index.ts From amplify-codegen with Apache License 2.0 | 6 votes |
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 #3
Source File: index.ts From amplify-codegen with Apache License 2.0 | 6 votes |
addTypeUsed(type: GraphQLType) {
if (this.typesUsedSet.has(type)) return;
if (
isEnumType(type) ||
isUnionType(type) ||
isInputObjectType(type) ||
isInterfaceType(type) ||
isObjectType(type) ||
(isScalarType(type) && !isSpecifiedScalarType(type))
) {
this.typesUsedSet.add(type);
}
if (isInterfaceType(type) || isUnionType(type)) {
for (const concreteType of this.schema.getPossibleTypes(type)) {
this.addTypeUsed(getNamedType(concreteType));
}
}
if (isInputObjectType(type)) {
for (const field of Object.values(type.getFields())) {
this.addTypeUsed(getNamedType(field.type));
}
}
if (isObjectType(type)) {
for (const fieldType of type.getInterfaces()) {
this.addTypeUsed(getNamedType(fieldType));
}
for (const field of Object.values(type.getFields())) {
this.addTypeUsed(getNamedType(field.type));
}
}
}
Example #4
Source File: serializeToJSON.ts From amplify-codegen with Apache License 2.0 | 6 votes |
function serializeType(type: GraphQLType) {
if (isEnumType(type)) {
return serializeEnumType(type);
} else if (isUnionType(type)) {
return serializeUnionType(type);
} else if (isInputObjectType(type)) {
return serializeInputObjectType(type);
} else if (isObjectType(type)) {
return serializeObjectType(type);
} else if (isInterfaceType(type)) {
return serializeInterfaceType(type);
} else if (isScalarType(type)) {
return serializeScalarType(type);
} else {
throw new Error(`Unexpected GraphQL type: ${type}`);
}
}
Example #5
Source File: renderResponseTypes.ts From genql with MIT License | 6 votes |
renderResponseTypes = (
schema: GraphQLSchema,
ctx: RenderContext,
) => {
let typeMap = schema.getTypeMap()
if (ctx.config?.sortProperties) {
typeMap = sortKeys(typeMap)
}
ctx.addCodeBlock(
renderScalarTypes(
ctx,
Object.values(typeMap).filter((type): type is GraphQLScalarType =>
isScalarType(type),
),
),
)
for (const name in typeMap) {
if (excludedTypes.includes(name)) continue
const type = typeMap[name]
if (isEnumType(type)) enumType(type, ctx)
if (isUnionType(type)) unionType(type, ctx)
if (isObjectType(type)) objectType(type, ctx)
if (isInterfaceType(type)) interfaceType(type, ctx)
}
const aliases = [
{ type: schema.getQueryType(), name: 'Query' },
{ type: schema.getMutationType(), name: 'Mutation' },
{ type: schema.getSubscriptionType(), name: 'Subscription' },
]
.map(renderAlias)
.filter(Boolean)
.join('\n')
ctx.addCodeBlock(aliases)
}
Example #6
Source File: no-scalar-result-type-on-mutation.ts From graphql-eslint with MIT License | 5 votes |
rule: GraphQLESLintRule = {
meta: {
type: 'suggestion',
hasSuggestions: true,
docs: {
category: 'Schema',
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
requiresSchema: true,
examples: [
{
title: 'Incorrect',
code: /* GraphQL */ `
type Mutation {
createUser: Boolean
}
`,
},
{
title: 'Correct',
code: /* GraphQL */ `
type Mutation {
createUser: User!
}
`,
},
],
},
schema: [],
},
create(context) {
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
const mutationType = schema.getMutationType();
if (!mutationType) {
return {};
}
const selector = [
`:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}]`,
'> FieldDefinition > .gqlType Name',
].join(' ');
return {
[selector](node: GraphQLESTreeNode<NameNode>) {
const typeName = node.value;
const graphQLType = schema.getType(typeName);
if (isScalarType(graphQLType)) {
context.report({
node,
message: `Unexpected scalar result type \`${typeName}\`.`,
suggest: [
{
desc: `Remove \`${typeName}\``,
fix: fixer => fixer.remove(node as any),
},
],
});
}
},
};
},
}
Example #7
Source File: getFields.ts From amplify-codegen with Apache License 2.0 | 5 votes |
export default function getFields(
field: GraphQLField<any, any>,
schema: GraphQLSchema,
depth: number = 2,
options: GQLDocsGenOptions
): GQLTemplateField {
const fieldType: GQLConcreteType = getType(field.type);
const renderS3FieldFragment = options.useExternalFragmentForS3Object && isS3Object(fieldType);
const subFields = !renderS3FieldFragment && (isObjectType(fieldType) || isInterfaceType(fieldType)) ? fieldType.getFields() : [];
const subFragments: any = isInterfaceType(fieldType) || isUnionType(fieldType) ? schema.getPossibleTypes(fieldType) : {};
if (depth < 1 && !(isScalarType(fieldType) || isEnumType(fieldType))) {
return;
}
const fields: Array<GQLTemplateField> = Object.keys(subFields)
.map(fieldName => {
const subField = subFields[fieldName];
return getFields(subField, schema, adjustDepth(subField, depth), options);
})
.filter(f => f);
const fragments: Array<GQLTemplateFragment> = Object.keys(subFragments)
.map(fragment => getFragment(subFragments[fragment], schema, depth, fields, null, false, options))
.filter(f => f);
// Special treatment for S3 input
// Swift SDK needs S3 Object to have fragment
if (renderS3FieldFragment) {
fragments.push(getFragment(fieldType as GraphQLObjectType, schema, depth, [], 'S3Object', true, options));
}
// if the current field is an object and none of the subfields are included, don't include the field itself
if (!(isScalarType(fieldType) || isEnumType(fieldType)) && fields.length === 0 && fragments.length === 0 && !renderS3FieldFragment) {
return;
}
return {
name: field.name,
fields,
fragments,
hasBody: !!(fields.length || fragments.length),
};
}
Example #8
Source File: objectType.ts From genql with MIT License | 5 votes |
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 #9
Source File: renderTyping.ts From genql with MIT License | 5 votes |
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 #10
Source File: objectType.ts From genql with MIT License | 5 votes |
objectType = (
type: GraphQLObjectType | GraphQLInterfaceType,
ctx: RenderContext,
) => {
let fields = type.getFields()
if (ctx.config?.sortProperties) {
fields = sortKeys(fields)
}
let fieldStrings = Object.keys(fields).map((fieldName) => {
const field = fields[fieldName]
const types: string[] = []
const resolvedType = getNamedType(field.type)
const resolvable = !(
isEnumType(resolvedType) || isScalarType(resolvedType)
)
const argsPresent = field.args.length > 0
const argsString = toArgsString(field)
const argsOptional = !argsString.match(/[^?]:/)
if (argsPresent) {
if (resolvable) {
types.push(`[${argsString},${requestTypeName(resolvedType)}]`)
} else {
types.push(`[${argsString}]`)
}
}
if (!argsPresent || argsOptional) {
if (resolvable) {
types.push(`${requestTypeName(resolvedType)}`)
} else {
types.push('boolean | number')
}
}
return `${fieldComment(field)}${field.name}?: ${types.join(' | ')}`
})
if (isInterfaceType(type) && ctx.schema) {
let interfaceProperties = ctx.schema
.getPossibleTypes(type)
.map((t) => `on_${t.name}?: ${requestTypeName(t)}`)
if (ctx.config?.sortProperties) {
interfaceProperties = interfaceProperties.sort()
}
fieldStrings = fieldStrings.concat(interfaceProperties)
}
fieldStrings.push('__typename?: boolean | number')
fieldStrings.push('__scalar?: boolean | number')
// add indentation
fieldStrings = fieldStrings.map((x) =>
x
.split('\n')
.filter(Boolean)
.map((l) => INDENTATION + l)
.join('\n'),
)
ctx.addCodeBlock(
`${typeComment(type)}export interface ${requestTypeName(
type,
)}{\n${fieldStrings.join('\n')}\n}`,
)
}
Example #11
Source File: renderTypeMap.ts From genql with MIT License | 5 votes |
renderTypeMap = (schema: GraphQLSchema, ctx: RenderContext) => {
// remove fields key,
// remove the Type.type and Type.args, replace with [type, args]
// reverse args.{name}
// Args type is deduced and added only when the concrete type is different from type name, remove the scalar field and replace with a top level scalars array field.
const result: TypeMap<string> = {
scalars: [],
types: {},
}
Object.keys(schema.getTypeMap())
.filter((t) => !excludedTypes.includes(t))
.map((t) => schema.getTypeMap()[t])
.map((t) => {
if (isObjectType(t) || isInterfaceType(t) || isInputObjectType(t))
result.types[t.name] = objectType(t, ctx)
else if (isUnionType(t)) result.types[t.name] = unionType(t, ctx)
else if (isScalarType(t) || isEnumType(t)) {
result.scalars.push(t.name)
result.types[t.name] = {}
}
})
// change names of query, mutation on schemas that chose different names (hasura)
const q = schema.getQueryType()
if (q?.name && q?.name !== 'Query') {
delete result.types[q.name]
result.types.Query = objectType(q, ctx)
// result.Query.name = 'Query'
}
const m = schema.getMutationType()
if (m?.name && m.name !== 'Mutation') {
delete result.types[m.name]
result.types.Mutation = objectType(m, ctx)
// result.Mutation.name = 'Mutation'
}
const s = schema.getSubscriptionType()
if (s?.name && s.name !== 'Subscription') {
delete result.types[s.name]
result.types.Subscription = objectType(s, ctx)
// result.Subscription.name = 'Subscription'
}
ctx.addCodeBlock(
JSON.stringify(replaceTypeNamesWithIndexes(result), null, 4),
)
}
Example #12
Source File: relay-arguments.ts From graphql-eslint with MIT License | 4 votes |
rule: GraphQLESLintRule<[RelayArgumentsConfig], true> = {
meta: {
type: 'problem',
docs: {
category: 'Schema',
description: [
'Set of rules to follow Relay specification for Arguments.',
'',
'- A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both',
'',
'Forward pagination arguments',
'',
'- `first` takes a non-negative integer',
'- `after` takes the Cursor type',
'',
'Backward pagination arguments',
'',
'- `last` takes a non-negative integer',
'- `before` takes the Cursor type',
].join('\n'),
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
examples: [
{
title: 'Incorrect',
code: /* GraphQL */ `
type User {
posts: PostConnection
}
`,
},
{
title: 'Correct',
code: /* GraphQL */ `
type User {
posts(after: String, first: Int, before: String, last: Int): PostConnection
}
`,
},
],
isDisabledForAllConfig: true,
},
messages: {
[MISSING_ARGUMENTS]:
'A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both.',
},
schema: {
type: 'array',
maxItems: 1,
items: {
type: 'object',
additionalProperties: false,
minProperties: 1,
properties: {
includeBoth: {
type: 'boolean',
default: true,
description: 'Enforce including both forward and backward pagination arguments',
},
},
},
},
},
create(context) {
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
const { includeBoth = true } = context.options[0] || {};
return {
'FieldDefinition > .gqlType Name[value=/Connection$/]'(node: GraphQLESTreeNode<NameNode>) {
let fieldNode = node.parent;
while (fieldNode.kind !== Kind.FIELD_DEFINITION) {
fieldNode = fieldNode.parent as GraphQLESTreeNode<FieldDefinitionNode>;
}
const args = Object.fromEntries(fieldNode.arguments.map(argument => [argument.name.value, argument]));
const hasForwardPagination = Boolean(args.first && args.after);
const hasBackwardPagination = Boolean(args.last && args.before);
if (!hasForwardPagination && !hasBackwardPagination) {
context.report({
node: fieldNode.name,
messageId: MISSING_ARGUMENTS,
});
return;
}
function checkField(typeName: 'String' | 'Int', argumentName: 'first' | 'last' | 'after' | 'before'): void {
const argument = args[argumentName];
const hasArgument = Boolean(argument);
let type = argument as any;
if (hasArgument && type.gqlType.kind === Kind.NON_NULL_TYPE) {
type = type.gqlType;
}
const isAllowedNonNullType =
hasArgument &&
type.gqlType.kind === Kind.NAMED_TYPE &&
(type.gqlType.name.value === typeName ||
(typeName === 'String' && isScalarType(schema.getType(type.gqlType.name.value))));
if (!isAllowedNonNullType) {
const returnType = typeName === 'String' ? 'String or Scalar' : typeName;
context.report({
node: (argument || fieldNode).name,
message: hasArgument
? `Argument \`${argumentName}\` must return ${returnType}.`
: `Field \`${fieldNode.name.value}\` must contain an argument \`${argumentName}\`, that return ${returnType}.`,
});
}
}
if (includeBoth || args.first || args.after) {
checkField('Int', 'first');
checkField('String', 'after');
}
if (includeBoth || args.last || args.before) {
checkField('Int', 'last');
checkField('String', 'before');
}
},
};
},
}
Example #13
Source File: relay-edge-types.ts From graphql-eslint with MIT License | 4 votes |
rule: GraphQLESLintRule<[EdgeTypesConfig], true> = {
meta: {
type: 'problem',
docs: {
category: 'Schema',
description: [
'Set of rules to follow Relay specification for Edge types.',
'',
"- A type that is returned in list form by a connection type's `edges` field is considered by this spec to be an Edge type",
'- Edge type must be an Object type',
'- Edge type must contain a field `node` that return either Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types. Notably, this field cannot return a list',
'- Edge type must contain a field `cursor` that return either String, Scalar, or a non-null wrapper around one of those types',
'- Edge type name must end in "Edge" _(optional)_',
"- Edge type's field `node` must implement `Node` interface _(optional)_",
'- A list type should only wrap an edge type _(optional)_',
].join('\n'),
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
isDisabledForAllConfig: true,
requiresSchema: true,
examples: [
{
title: 'Correct',
code: /* GraphQL */ `
type UserConnection {
edges: [UserEdge]
pageInfo: PageInfo!
}
`,
},
],
},
messages: {
[MESSAGE_MUST_BE_OBJECT_TYPE]: 'Edge type must be an Object type.',
[MESSAGE_MISSING_EDGE_SUFFIX]: 'Edge type must have "Edge" suffix.',
[MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE]: 'A list type should only wrap an edge type.',
[MESSAGE_SHOULD_IMPLEMENTS_NODE]: "Edge type's field `node` must implement `Node` interface.",
},
schema: {
type: 'array',
maxItems: 1,
items: {
type: 'object',
additionalProperties: false,
minProperties: 1,
properties: {
withEdgeSuffix: {
type: 'boolean',
default: true,
description: 'Edge type name must end in "Edge".',
},
shouldImplementNode: {
type: 'boolean',
default: true,
description: "Edge type's field `node` must implement `Node` interface.",
},
listTypeCanWrapOnlyEdgeType: {
type: 'boolean',
default: true,
description: 'A list type should only wrap an edge type.',
},
},
},
},
},
create(context) {
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
const edgeTypes = getEdgeTypes(schema);
const options: EdgeTypesConfig = {
withEdgeSuffix: true,
shouldImplementNode: true,
listTypeCanWrapOnlyEdgeType: true,
...context.options[0],
};
const isNamedOrNonNullNamed = (node: GraphQLESTreeNode<TypeNode>): boolean =>
node.kind === Kind.NAMED_TYPE || (node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.NAMED_TYPE);
const checkNodeField = (node: GraphQLESTreeNode<ObjectTypeDefinitionNode>): void => {
const nodeField = node.fields.find(field => field.name.value === 'node');
const message =
'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
if (!nodeField) {
context.report({
node: node.name,
message: `Edge type must contain a field \`node\` that ${message}`,
});
} else if (!isNamedOrNonNullNamed(nodeField.gqlType)) {
context.report({ node: nodeField.name, message: `Field \`node\` must ${message}` });
} else if (options.shouldImplementNode) {
const nodeReturnTypeName = getTypeName(nodeField.gqlType.rawNode());
const type = schema.getType(nodeReturnTypeName);
if (!isObjectType(type)) {
return;
}
const implementsNode = type.astNode.interfaces.some(n => n.name.value === 'Node');
if (!implementsNode) {
context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE });
}
}
};
const checkCursorField = (node: GraphQLESTreeNode<ObjectTypeDefinitionNode>): void => {
const cursorField = node.fields.find(field => field.name.value === 'cursor');
const message = 'return either a String, Scalar, or a non-null wrapper wrapper around one of those types.';
if (!cursorField) {
context.report({
node: node.name,
message: `Edge type must contain a field \`cursor\` that ${message}`,
});
return;
}
const typeName = getTypeName(cursorField.rawNode());
if (
!isNamedOrNonNullNamed(cursorField.gqlType) ||
(typeName !== 'String' && !isScalarType(schema.getType(typeName)))
) {
context.report({ node: cursorField.name, message: `Field \`cursor\` must ${message}` });
}
};
const listeners: GraphQLESLintRuleListener<true> = {
':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType Name'(
node: GraphQLESTreeNode<NameNode>
) {
const type = schema.getType(node.value);
if (!isObjectType(type)) {
context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
}
},
':matches(ObjectTypeDefinition, ObjectTypeExtension)'(node: GraphQLESTreeNode<ObjectTypeDefinitionNode>) {
const typeName = node.name.value;
if (edgeTypes.has(typeName)) {
checkNodeField(node);
checkCursorField(node);
if (options.withEdgeSuffix && !typeName.endsWith('Edge')) {
context.report({ node: node.name, messageId: MESSAGE_MISSING_EDGE_SUFFIX });
}
}
},
};
if (options.listTypeCanWrapOnlyEdgeType) {
listeners['FieldDefinition > .gqlType'] = (node: GraphQLESTreeNode<TypeNode>) => {
if (
node.kind === Kind.LIST_TYPE ||
(node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.LIST_TYPE)
) {
const typeName = getTypeName(node.rawNode());
if (!edgeTypes.has(typeName)) {
context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE });
}
}
};
}
return listeners;
},
}
Example #14
Source File: relay-page-info.ts From graphql-eslint with MIT License | 4 votes |
rule: GraphQLESLintRule = {
meta: {
type: 'problem',
docs: {
category: 'Schema',
description: [
'Set of rules to follow Relay specification for `PageInfo` object.',
'',
'- `PageInfo` must be an Object type',
'- `PageInfo` must contain fields `hasPreviousPage` and `hasNextPage`, that return non-null Boolean',
'- `PageInfo` must contain fields `startCursor` and `endCursor`, that return either String or Scalar, which can be null if there are no results',
].join('\n'),
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
examples: [
{
title: 'Correct',
code: /* GraphQL */ `
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
}
`,
},
],
isDisabledForAllConfig: true,
requiresSchema: true,
},
messages: {
[MESSAGE_MUST_EXIST]: 'The server must provide a `PageInfo` object.',
[MESSAGE_MUST_BE_OBJECT_TYPE]: '`PageInfo` must be an Object type.',
},
schema: [],
},
create(context) {
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
if (process.env.NODE_ENV === 'test' || !hasPageInfoChecked) {
const pageInfoType = schema.getType('PageInfo');
if (!pageInfoType) {
context.report({
loc: REPORT_ON_FIRST_CHARACTER,
messageId: MESSAGE_MUST_EXIST,
});
}
hasPageInfoChecked = true;
}
return {
[notPageInfoTypesSelector](node) {
context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
},
'ObjectTypeDefinition[name.value=PageInfo]'(node: GraphQLESTreeNode<ObjectTypeDefinitionNode>) {
const fieldMap = Object.fromEntries(node.fields.map(field => [field.name.value, field]));
const checkField = (
fieldName: 'hasPreviousPage' | 'hasNextPage' | 'startCursor' | 'endCursor',
typeName: 'Boolean' | 'String'
): void => {
const field = fieldMap[fieldName];
let isAllowedType = false;
if (field) {
const type = field.gqlType;
if (typeName === 'Boolean') {
isAllowedType =
type.kind === Kind.NON_NULL_TYPE &&
type.gqlType.kind === Kind.NAMED_TYPE &&
type.gqlType.name.value === 'Boolean';
} else if (type.kind === Kind.NAMED_TYPE) {
isAllowedType = type.name.value === 'String' || isScalarType(schema.getType(type.name.value));
}
}
if (!isAllowedType) {
const returnType =
typeName === 'Boolean'
? 'non-null Boolean'
: 'either String or Scalar, which can be null if there are no results';
context.report({
node: field ? field.name : node.name,
message: field
? `Field \`${fieldName}\` must return ${returnType}.`
: `\`PageInfo\` must contain a field \`${fieldName}\`, that return ${returnType}.`,
});
}
};
checkField('hasPreviousPage', 'Boolean');
checkField('hasNextPage', 'Boolean');
checkField('startCursor', 'String');
checkField('endCursor', 'String');
},
};
},
}
Example #15
Source File: addExecutionLogicToComposer.ts From graphql-mesh with MIT License | 4 votes |
export async function addExecutionLogicToComposer(
schemaComposer: SchemaComposer,
{
fetch: globalFetch,
logger,
operations,
operationHeaders,
baseUrl,
pubsub: globalPubsub,
queryParams,
}: AddExecutionLogicToComposerOptions
) {
logger.debug(`Attaching execution logic to the schema`);
for (const operationConfig of operations) {
const { httpMethod, rootTypeName, fieldName } = getOperationMetadata(operationConfig);
const operationLogger = logger.child(`${rootTypeName}.${fieldName}`);
const interpolationStrings: string[] = [
...Object.values(operationHeaders || {}),
...Object.values(queryParams || {}),
baseUrl,
];
const rootTypeComposer = schemaComposer[rootTypeName];
const field = rootTypeComposer.getField(fieldName);
if (isPubSubOperationConfig(operationConfig)) {
field.description = operationConfig.description || `PubSub Topic: ${operationConfig.pubsubTopic}`;
field.subscribe = (root, args, context, info) => {
const pubsub = context?.pubsub || globalPubsub;
if (!pubsub) {
return new GraphQLError(`You should have PubSub defined in either the config or the context!`);
}
const interpolationData = { root, args, context, info, env: process.env };
const pubsubTopic = stringInterpolator.parse(operationConfig.pubsubTopic, interpolationData);
operationLogger.debug(`=> Subscribing to pubSubTopic: ${pubsubTopic}`);
return pubsub.asyncIterator(pubsubTopic);
};
field.resolve = root => {
operationLogger.debug('Received ', root, ' from ', operationConfig.pubsubTopic);
return root;
};
interpolationStrings.push(operationConfig.pubsubTopic);
} else if (operationConfig.path) {
if (process.env.DEBUG) {
field.description = `
***Original Description***: ${operationConfig.description || '(none)'}
***Method***: ${operationConfig.method}
***Base URL***: ${baseUrl}
***Path***: ${operationConfig.path}
`;
} else {
field.description = operationConfig.description;
}
field.resolve = async (root, args, context) => {
operationLogger.debug(`=> Resolving`);
const interpolationData = { root, args, context, env: process.env };
const interpolatedBaseUrl = stringInterpolator.parse(baseUrl, interpolationData);
const interpolatedPath = stringInterpolator.parse(operationConfig.path, interpolationData);
let fullPath = urlJoin(interpolatedBaseUrl, interpolatedPath);
const headers = {
...operationHeaders,
...operationConfig?.headers,
};
for (const headerName in headers) {
headers[headerName] = stringInterpolator.parse(headers[headerName], interpolationData);
}
const requestInit: RequestInit = {
method: httpMethod,
headers,
};
if (queryParams) {
const interpolatedQueryParams: Record<string, any> = {};
for (const queryParamName in queryParams) {
interpolatedQueryParams[queryParamName] = stringInterpolator.parse(
queryParams[queryParamName],
interpolationData
);
}
const queryParamsString = qsStringify(interpolatedQueryParams, { indices: false });
fullPath += fullPath.includes('?') ? '&' : '?';
fullPath += queryParamsString;
}
// Handle binary data
if ('binary' in operationConfig) {
const binaryUpload = await args.input;
if (isFileUpload(binaryUpload)) {
const readable = binaryUpload.createReadStream();
const chunks: number[] = [];
for await (const chunk of readable) {
for (const byte of chunk) {
chunks.push(byte);
}
}
requestInit.body = new Uint8Array(chunks);
const [, contentType] = Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
if (!contentType) {
headers['content-type'] = binaryUpload.mimetype;
}
}
requestInit.body = binaryUpload;
} else {
if (operationConfig.requestBaseBody != null) {
args.input = args.input || {};
for (const key in operationConfig.requestBaseBody) {
const configValue = operationConfig.requestBaseBody[key];
if (typeof configValue === 'string') {
const value = stringInterpolator.parse(configValue, interpolationData);
lodashSet(args.input, key, value);
} else {
args.input[key] = configValue;
}
}
}
// Resolve union input
const input = (args.input = resolveDataByUnionInputType(
cleanObject(args.input),
field.args?.input?.type?.getType(),
schemaComposer
));
if (input != null) {
switch (httpMethod) {
case 'GET':
case 'HEAD':
case 'CONNECT':
case 'OPTIONS':
case 'TRACE': {
fullPath += fullPath.includes('?') ? '&' : '?';
fullPath += qsStringify(input, { indices: false });
break;
}
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE': {
const [, contentType] =
Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
if (contentType?.startsWith('application/x-www-form-urlencoded')) {
requestInit.body = qsStringify(input, { indices: false });
} else {
requestInit.body = JSON.stringify(input);
}
break;
}
default:
return createError(`Unknown HTTP Method: ${httpMethod}`, {
url: fullPath,
method: httpMethod,
});
}
}
}
// Delete unused queryparams
const [actualPath, queryString] = fullPath.split('?');
if (queryString) {
const queryParams = qsParse(queryString);
const cleanedQueryParams = cleanObject(queryParams);
fullPath = actualPath + '?' + qsStringify(cleanedQueryParams, { indices: false });
}
operationLogger.debug(`=> Fetching `, fullPath, `=>`, requestInit);
const fetch: typeof globalFetch = context?.fetch || globalFetch;
if (!fetch) {
return createError(`You should have fetch defined in either the config or the context!`, {
url: fullPath,
method: httpMethod,
});
}
const response = await fetch(fullPath, requestInit);
// If return type is a file
if (field.type.getTypeName() === 'File') {
return response.blob();
}
const responseText = await response.text();
operationLogger.debug(`=> Received`, {
headers: response.headers,
text: responseText,
});
let responseJson: any;
try {
responseJson = JSON.parse(responseText);
} catch (error) {
const returnNamedGraphQLType = getNamedType(field.type.getType());
// The result might be defined as scalar
if (isScalarType(returnNamedGraphQLType)) {
operationLogger.debug(` => Return type is not a JSON so returning ${responseText}`);
return responseText;
} else if (response.status === 204) {
responseJson = {};
} else {
return createError(`Unexpected response`, {
url: fullPath,
method: httpMethod,
responseText,
error,
});
}
}
if (!response.status.toString().startsWith('2')) {
const returnNamedGraphQLType = getNamedType(field.type.getType());
if (!isUnionType(returnNamedGraphQLType)) {
return createError(`HTTP Error: ${response.status}`, {
url: fullPath,
method: httpMethod,
...(response.statusText ? { status: response.statusText } : {}),
responseJson,
});
}
}
operationLogger.debug(`Returning `, responseJson);
// Sometimes API returns an array but the return type is not an array
const isListReturnType = isListTypeOrNonNullListType(field.type.getType());
const isArrayResponse = Array.isArray(responseJson);
if (isListReturnType && !isArrayResponse) {
operationLogger.debug(`Response is not array but return type is list. Normalizing the response`);
responseJson = [responseJson];
}
if (!isListReturnType && isArrayResponse) {
operationLogger.debug(`Response is array but return type is not list. Normalizing the response`);
responseJson = responseJson[0];
}
const addResponseMetadata = (obj: any) => {
return {
...obj,
$url: fullPath,
$method: httpMethod,
$request: {
query: {
...obj,
...args,
...args.input,
},
path: {
...obj,
...args,
},
header: requestInit.headers,
},
$response: {
url: fullPath,
method: httpMethod,
status: response.status,
statusText: response.statusText,
body: obj,
},
};
};
operationLogger.debug(`Adding response metadata to the response object`);
return Array.isArray(responseJson)
? responseJson.map(obj => addResponseMetadata(obj))
: addResponseMetadata(responseJson);
};
interpolationStrings.push(...Object.values(operationConfig.headers || {}));
interpolationStrings.push(operationConfig.path);
if ('links' in operationConfig) {
for (const linkName in operationConfig.links) {
const linkObj = operationConfig.links[linkName];
const typeTC = schemaComposer.getOTC(field.type.getTypeName());
typeTC.addFields({
[linkName]: () => {
const targetField = schemaComposer.Query.getField(linkObj.fieldName);
return {
...targetField,
args: {},
description: linkObj.description || targetField.description,
resolve: (root, args, context, info) =>
linkResolver(linkObj.args, targetField.resolve, root, args, context, info),
};
},
});
}
} else if ('responseByStatusCode' in operationConfig) {
const unionOrSingleTC = schemaComposer.getAnyTC(getNamedType(field.type.getType()));
const types = 'getTypes' in unionOrSingleTC ? unionOrSingleTC.getTypes() : [unionOrSingleTC];
const statusCodeOneOfIndexMap =
(unionOrSingleTC.getExtension('statusCodeOneOfIndexMap') as Record<string, number>) || {};
for (const statusCode in operationConfig.responseByStatusCode) {
const responseConfig = operationConfig.responseByStatusCode[statusCode];
for (const linkName in responseConfig.links) {
const typeTCThunked = types[statusCodeOneOfIndexMap[statusCode] || 0];
const typeTC = schemaComposer.getOTC(typeTCThunked.getTypeName());
typeTC.addFields({
[linkName]: () => {
const linkObj = responseConfig.links[linkName];
const targetField = schemaComposer.Query.getField(linkObj.fieldName);
return {
...targetField,
args: {},
description: linkObj.description || targetField.description,
resolve: (root, args, context, info) =>
linkResolver(linkObj.args, targetField.resolve, root, args, context, info),
};
},
});
}
}
}
}
const { args: globalArgs } = parseInterpolationStrings(interpolationStrings, operationConfig.argTypeMap);
rootTypeComposer.addFieldArgs(fieldName, globalArgs);
}
logger.debug(`Building the executable schema.`);
return schemaComposer;
}
Example #16
Source File: getComposerFromSchema.test.ts From graphql-mesh with MIT License | 4 votes |
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);
});
});