UNPKG

apollo-codegen-scala

Version:
567 lines (517 loc) 15.3 kB
import { GraphQLError, getNamedType, isCompositeType, GraphQLEnumType, GraphQLInputObjectType, isNonNullType, isEnumType, isInputObjectType, } from "graphql"; import { isTypeProperSuperTypeOf } from "apollo-codegen-core/lib/utilities/graphql"; import { join } from "apollo-codegen-core/lib/utilities/printing"; import { packageDeclaration, objectDeclaration, escapeIdentifierIfNeeded, comment, traitDeclaration, propertyDeclaration, methodDeclaration, } from "./language"; import { traitNameForPropertyName, traitNameForFragmentName, traitNameForInlineFragment, operationClassName, enumCaseName, propertyFromLegacyField, propertyFromInputField, } from "./naming"; import { multilineString } from "./values"; import { possibleTypesForType, typeNameFromGraphQLType } from "./types"; import CodeGenerator from "apollo-codegen-core/lib/utilities/CodeGenerator"; import { LegacyCompilerContext, LegacyOperation, LegacyFragment, LegacyField, LegacyInlineFragment, } from "apollo-codegen-core/lib/compiler/legacyIR"; import { GraphQLType } from "graphql"; import { Property } from "./language"; import { GraphQLCompositeType } from "graphql"; export function generateSource(context: LegacyCompilerContext) { const generator = new CodeGenerator(context); generator.printOnNewline("// @generated"); generator.printOnNewline( "// This file was automatically generated and should not be edited." ); generator.printNewline(); if (context.options.namespace) { packageDeclaration(generator, context.options.namespace); } context.typesUsed.forEach((type) => { typeDeclarationForGraphQLType(generator, type); }); Object.values(context.operations).forEach((operation) => { classDeclarationForOperation(generator, operation); }); Object.values(context.fragments).forEach((fragment) => { traitDeclarationForFragment(generator, fragment); }); return generator.output; } export function classDeclarationForOperation( generator: CodeGenerator<LegacyCompilerContext, any>, { operationName, operationType, rootType, variables, fields, inlineFragments, fragmentSpreads, fragmentsReferenced, source, operationId, }: LegacyOperation ) { let objectName; let protocol; switch (operationType) { case "query": objectName = `${operationClassName(operationName)}Query`; protocol = "com.apollographql.scalajs.GraphQLQuery"; break; case "mutation": objectName = `${operationClassName(operationName)}Mutation`; protocol = "com.apollographql.scalajs.GraphQLMutation"; break; default: throw new GraphQLError(`Unsupported operation type "${operationType}"`); } objectDeclaration( generator, { objectName, superclass: protocol, }, () => { if (source) { generator.printOnNewline("val operationString ="); generator.withIndent(() => { multilineString(generator, source); }); } if (operationId) { operationIdentifier(generator, operationId); } if (fragmentsReferenced && fragmentsReferenced.length > 0) { generator.printNewlineIfNeeded(); generator.printOnNewline( "val requestString: String = { operationString" ); fragmentsReferenced.forEach((fragment) => { generator.print( ` + ${traitNameForFragmentName(fragment)}.fragmentString` ); }); generator.print(" }"); generator.printOnNewline( "val operation = com.apollographql.scalajs.gql(requestString)" ); } else { generator.printOnNewline( "val operation = com.apollographql.scalajs.gql(operationString)" ); } generator.printNewlineIfNeeded(); if (variables && variables.length > 0) { const properties = variables.map(({ name, type }) => { const propertyName = escapeIdentifierIfNeeded(name); const typeName = typeNameFromGraphQLType( generator.context, type, undefined, undefined, true ); const isOptional = !isNonNullType(type); return { name, propertyName, type, typeName, isOptional }; }); dataContainerDeclaration(generator, { name: "Variables", properties, }); } else { generator.printOnNewline("type Variables = Unit"); } traitDeclarationForSelectionSet(generator, { traitName: "Data", parentType: rootType, fields, inlineFragments, fragmentSpreads, }); } ); } export function traitDeclarationForFragment( generator: CodeGenerator<LegacyCompilerContext, any>, { fragmentName, typeCondition, fields, inlineFragments, fragmentSpreads, source, }: LegacyFragment ) { const traitName = traitNameForFragmentName(fragmentName); traitDeclarationForSelectionSet( generator, { traitName, parentType: typeCondition, fields, inlineFragments, fragmentSpreads, }, () => { if (source) { generator.printOnNewline("val fragmentString ="); generator.withIndent(() => { multilineString(generator, source); }); } } ); } export function traitDeclarationForSelectionSet( generator: CodeGenerator<LegacyCompilerContext, any>, { traitName, parentType, fields, inlineFragments, fragmentSpreads, viewableAs, parentFragments, }: { traitName: string; parentType: GraphQLCompositeType; fields: LegacyField[]; inlineFragments?: LegacyInlineFragment[]; fragmentSpreads?: string[]; viewableAs?: { traitName: string; properties: (LegacyField & Property)[]; }; parentFragments?: string[]; }, objectClosure?: () => void ) { const possibleTypes = parentType ? possibleTypesForType(generator.context, parentType) : null; const properties = fields .map((field) => propertyFromLegacyField(generator.context, field, traitName) ) .filter((field) => field.propertyName != "__typename"); const fragmentSpreadSuperClasses = (fragmentSpreads || []).filter( (spread) => { const fragment = generator.context.fragments[spread]; const alwaysDefined = isTypeProperSuperTypeOf( generator.context.schema, fragment.typeCondition, parentType ); return alwaysDefined; } ); // add types and implicit conversions if (inlineFragments && inlineFragments.length > 0) { inlineFragments.forEach((inlineFragment) => { traitDeclarationForSelectionSet(generator, { traitName: traitNameForInlineFragment(inlineFragment), parentType: inlineFragment.typeCondition, fields: inlineFragment.fields, fragmentSpreads: inlineFragment.fragmentSpreads, viewableAs: { traitName, properties, }, }); }); } dataContainerDeclaration(generator, { name: traitName, properties, extraSuperClasses: [ ...(viewableAs ? [viewableAs.traitName] : []), ...(fragmentSpreadSuperClasses || []), ...(parentFragments || []), ], insideCompanion: () => { if (possibleTypes) { generator.printNewlineIfNeeded(); generator.printOnNewline("val possibleTypes = scala.collection.Set("); generator.print( join( Array.from(possibleTypes).map((type) => `"${String(type)}"`), ", " ) ); generator.print(")"); } generator.printNewlineIfNeeded(); generator.printOnNewline( `implicit class ViewExtensions(private val orig: ${traitName}) extends AnyVal` ); generator.withinBlock(() => { if (inlineFragments && inlineFragments.length > 0) { inlineFragments.forEach((inlineFragment) => { const fragClass = traitNameForInlineFragment(inlineFragment); generator.printOnNewline(`def as${inlineFragment.typeCondition}`); generator.print(`: Option[${fragClass}] =`); generator.withinBlock(() => { generator.printOnNewline( `if (${fragClass}.possibleTypes.contains(orig.asInstanceOf[scala.scalajs.js.Dynamic].__typename.asInstanceOf[String])) Some(orig.asInstanceOf[${fragClass}]) else None` ); }); }); } if (fragmentSpreads) { fragmentSpreads.forEach((s) => { const fragment = generator.context.fragments[s]; const alwaysDefined = isTypeProperSuperTypeOf( generator.context.schema, fragment.typeCondition, parentType ); if (!alwaysDefined) { generator.printOnNewline(`def as${s}`); generator.print(`: Option[${s}] =`); generator.withinBlock(() => { generator.printOnNewline( `if (${s}.possibleTypes.contains(orig.asInstanceOf[scala.scalajs.js.Dynamic].__typename.asInstanceOf[String])) Some(orig.asInstanceOf[${s}]) else None` ); }); } }); } }); const fragments = (fragmentSpreads || []).map( (f) => generator.context.fragments[f] ); fields .filter((field) => isCompositeType(getNamedType(field.type))) .forEach((field) => { traitDeclarationForSelectionSet(generator, { traitName: traitNameForPropertyName(field.responseName), parentType: getNamedType(field.type) as GraphQLCompositeType, fields: field.fields || [], inlineFragments: field.inlineFragments, fragmentSpreads: field.fragmentSpreads, parentFragments: fragments .filter((f) => { return f.fields.some( (o) => field.responseName == o.responseName ); }) .map((f) => { return ( traitNameForFragmentName(f.fragmentName) + "." + traitNameForPropertyName(field.responseName) ); }), }); }); if (objectClosure) { objectClosure(); } }, }); } function operationIdentifier( generator: CodeGenerator<LegacyCompilerContext, any>, operationId: string ) { if (!generator.context.options.generateOperationIds) { return; } generator.printNewlineIfNeeded(); generator.printOnNewline( `val operationIdentifier: String = "${operationId}"` ); } export function typeDeclarationForGraphQLType( generator: CodeGenerator<LegacyCompilerContext, any>, type: GraphQLType ) { if (isEnumType(type)) { enumerationDeclaration(generator, type); } else if (isInputObjectType(type)) { traitDeclarationForInputObjectType(generator, type); } } function enumerationDeclaration( generator: CodeGenerator<LegacyCompilerContext, any>, type: GraphQLEnumType ) { const { name, description } = type; const values = type.getValues(); generator.printNewlineIfNeeded(); comment(generator, description || ""); generator.printOnNewline(`object ${name}`); generator.withinBlock(() => { values.forEach((value) => { comment(generator, value.description || ""); generator.printOnNewline( `val ${escapeIdentifierIfNeeded(enumCaseName(value.name))} = "${ value.value }"` ); }); }); generator.printNewline(); } function traitDeclarationForInputObjectType( generator: CodeGenerator<LegacyCompilerContext, any>, type: GraphQLInputObjectType ) { const { name, description } = type; const fields = Object.values(type.getFields()); const properties = fields.map((field) => propertyFromInputField( generator.context, field, generator.context.options.namespace ) ); dataContainerDeclaration(generator, { name, properties, description: description || undefined, }); } function dataContainerDeclaration( generator: CodeGenerator<LegacyCompilerContext, any>, { name, properties, extraSuperClasses, description, insideCompanion, }: { name: string; properties: (Property & { name?: string; responseName?: string; })[]; extraSuperClasses?: string[]; description?: string; insideCompanion?: () => void; } ) { traitDeclaration( generator, { traitName: name, superclasses: ["scala.scalajs.js.Object", ...(extraSuperClasses || [])], annotations: ["scala.scalajs.js.native"], description: description || undefined, }, () => { properties.forEach((p) => { propertyDeclaration(generator, { jsName: p.name || p.responseName, propertyName: p.propertyName, typeName: p.typeName, }); }); } ); objectDeclaration( generator, { objectName: name, }, () => { methodDeclaration( generator, { methodName: "apply", params: properties.map((p) => { return { name: p.propertyName, type: p.typeName, defaultValue: p.isOptional ? "com.apollographql.scalajs.OptionalValue.empty" : "", }; }), }, () => { const propertiesIn = properties .map((p) => `"${p.name || p.responseName}" -> ${p.propertyName}`) .join(", "); generator.printOnNewline( `scala.scalajs.js.Dynamic.literal(${propertiesIn}).asInstanceOf[${name}]` ); } ); methodDeclaration( generator, { methodName: "unapply", params: [ { name: "value", type: name, }, ], }, () => { const propertiesExtracted = properties .map((p) => `value.${p.propertyName}`) .join(", "); generator.printOnNewline(`Some((${propertiesExtracted}))`); } ); generator.printNewlineIfNeeded(); generator.printOnNewline( `implicit class CopyExtensions(private val orig: ${name}) extends AnyVal` ); generator.withinBlock(() => { methodDeclaration( generator, { methodName: "copy", params: properties.map((p) => { return { name: p.propertyName, type: p.typeName, defaultValue: `orig.${p.propertyName}`, }; }), }, () => { const propertiesIn = properties .map((p) => `"${p.name || p.responseName}" -> ${p.propertyName}`) .join(", "); generator.printOnNewline( `scala.scalajs.js.Dynamic.literal(${propertiesIn}).asInstanceOf[${name}]` ); } ); }); if (insideCompanion) { insideCompanion(); } } ); }