UNPKG

@aws-amplify/graphql-types-generator

Version:

Generate API code or type annotations based on a GraphQL schema and statements

634 lines 31.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SwiftAPIGenerator = exports.generateSource = void 0; const path = require("path"); const graphql_1 = require("graphql"); const printing_1 = require("../utilities/printing"); const language_1 = require("./language"); const helpers_1 = require("./helpers"); const s3Wrapper_1 = require("./s3Wrapper"); const appSyncCompatibilityTypes_1 = require("./appSyncCompatibilityTypes"); const graphql_2 = require("../utilities/graphql"); const typeCase_1 = require("../compiler/visitors/typeCase"); const collectFragmentsReferenced_1 = require("../compiler/visitors/collectFragmentsReferenced"); const generateOperationId_1 = require("../compiler/visitors/generateOperationId"); const collectAndMergeFields_1 = require("../compiler/visitors/collectAndMergeFields"); require("../utilities/array"); const complextypes_1 = require("../utilities/complextypes"); function generateSource(context, outputIndividualFiles, only) { const generator = new SwiftAPIGenerator(context); if (outputIndividualFiles) { generator.withinFile(`Types.graphql.swift`, () => { generator.fileHeader(); generator.namespaceDeclaration(context.options.namespace, () => { context.typesUsed.forEach(type => { generator.typeDeclarationForGraphQLType(type); }); }); if (context.options.addS3Wrapper) { generator.printOnNewline(`\n${s3Wrapper_1.s3WrapperCode}`); } }); const inputFilePaths = new Set(); Object.values(context.operations).forEach(operation => { inputFilePaths.add(operation.filePath); }); Object.values(context.fragments).forEach(fragment => { inputFilePaths.add(fragment.filePath); }); for (const inputFilePath of inputFilePaths) { if (only && inputFilePath !== only) continue; generator.withinFile(`${path.basename(inputFilePath)}.swift`, () => { generator.fileHeader(); generator.namespaceExtensionDeclaration(context.options.namespace, () => { Object.values(context.operations).forEach(operation => { if (operation.filePath === inputFilePath) { generator.classDeclarationForOperation(operation); } }); Object.values(context.fragments).forEach(fragment => { if (fragment.filePath === inputFilePath) { generator.structDeclarationForFragment(fragment); } }); }); }); } } else { generator.fileHeader(); generator.namespaceDeclaration(context.options.namespace, () => { context.typesUsed.forEach(type => { generator.typeDeclarationForGraphQLType(type); }); Object.values(context.operations).forEach(operation => { generator.classDeclarationForOperation(operation); }); Object.values(context.fragments).forEach(fragment => { generator.structDeclarationForFragment(fragment); }); }); if (context.options.addS3Wrapper) { generator.printOnNewline(`\n${s3Wrapper_1.s3WrapperCode}`); } } return generator; } exports.generateSource = generateSource; class SwiftAPIGenerator extends language_1.SwiftGenerator { constructor(context) { super(context); this.helpers = new helpers_1.Helpers(context.options); } fileHeader() { this.printOnNewline('// This file was automatically generated and should not be edited.'); this.printNewline(); this.printOnNewline('#if canImport(AWSAPIPlugin)'); this.print(appSyncCompatibilityTypes_1.appSyncCompatibilityTypesCode); this.printOnNewline('#elseif canImport(AWSAppSync)'); this.printOnNewline('import AWSAppSync'); this.printOnNewline('#endif'); } classDeclarationForOperation(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 graphql_1.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 = (0, collectFragmentsReferenced_1.collectFragmentsReferenced)(operation.selectionSet, this.context.fragments); if (this.context.options.generateOperationIds) { const { operationId } = (0, generateOperationId_1.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 = !((0, graphql_1.isNonNullType)(type) || ((0, graphql_1.isListType)(type) && (0, graphql_1.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((0, printing_1.wrap)(`return [`, (0, printing_1.join)(properties.map(({ name, propertyName }) => `"${name}": ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}`), ', ') || ':', `]`)); }); } else { this.initializerDeclarationForProperties([]); } this.structDeclarationForSelectionSet({ structName: 'Data', selectionSet, }); }); } structDeclarationForFragment({ fragmentName, selectionSet, source }) { const structName = this.helpers.structNameForFragmentName(fragmentName); this.structDeclarationForSelectionSet({ structName, adoptedProtocols: ['GraphQLFragment'], selectionSet, }, () => { if (source) { this.printOnNewline('public static let fragmentString ='); this.withIndent(() => { this.multilineString(source); }); } }); } structDeclarationForSelectionSet({ structName, adoptedProtocols = ['GraphQLSelectionSet'], selectionSet, }, before) { const typeCase = (0, typeCase_1.typeCaseForSelectionSet)(selectionSet, this.context.options.mergeInFieldsFromFragmentSpreads); this.structDeclarationForVariant({ structName, adoptedProtocols, variant: typeCase.default, typeCase, }, before, () => { const variants = typeCase.variants.map(this.helpers.propertyFromVariant, this.helpers); for (const variant of variants) { this.propertyDeclarationForVariant(variant); this.structDeclarationForVariant({ structName: variant.structName, variant, }); } }); } structDeclarationForVariant({ structName, adoptedProtocols = ['GraphQLSelectionSet'], variant, typeCase, }, before, after) { this.structDeclaration({ structName, adoptedProtocols }, () => { if (before) { before(); } this.printNewlineIfNeeded(); this.printOnNewline('public static let possibleTypes = ['); this.print((0, printing_1.join)(variant.possibleTypes.map(type => `"${type.name}"`), ', ')); this.print(']'); this.printNewlineIfNeeded(); this.printOnNewline('public static let selections: [GraphQLSelection] = '); if (typeCase) { this.typeCaseInitialization(typeCase); } else { this.selectionSetInitialization(variant); } this.printNewlineIfNeeded(); this.propertyDeclaration({ propertyName: 'snapshot', typeName: 'Snapshot', }); this.printNewlineIfNeeded(); this.printOnNewline('public init(snapshot: Snapshot)'); this.withinBlock(() => { this.printOnNewline(`self.snapshot = snapshot`); }); if (typeCase) { this.initializersForTypeCase(typeCase); } else { this.initializersForVariant(variant); } const fields = (0, collectAndMergeFields_1.collectAndMergeFields)(variant, this.context.options.mergeInFieldsFromFragmentSpreads).map(field => this.helpers.propertyFromField(field)); const fragmentSpreads = variant.fragmentSpreads.map(fragmentSpread => { const isConditional = variant.possibleTypes.some(type => !fragmentSpread.selectionSet.possibleTypes.includes(type)); return this.helpers.propertyFromFragmentSpread(fragmentSpread, isConditional); }); fields.forEach(this.propertyDeclarationForField, this); if (fragmentSpreads.length > 0) { this.printNewlineIfNeeded(); this.printOnNewline(`public var fragments: Fragments`); this.withinBlock(() => { this.printOnNewline('get'); this.withinBlock(() => { this.printOnNewline(`return Fragments(snapshot: snapshot)`); }); this.printOnNewline('set'); this.withinBlock(() => { this.printOnNewline(`snapshot += newValue.snapshot`); }); }); this.structDeclaration({ structName: 'Fragments', }, () => { this.propertyDeclaration({ propertyName: 'snapshot', typeName: 'Snapshot', }); for (const fragmentSpread of fragmentSpreads) { const { propertyName, typeName, structName, isConditional } = fragmentSpread; this.printNewlineIfNeeded(); this.printOnNewline(`public var ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}: ${typeName}`); this.withinBlock(() => { this.printOnNewline('get'); this.withinBlock(() => { if (isConditional) { this.printOnNewline(`if !${structName}.possibleTypes.contains(snapshot["__typename"]! as! String) { return nil }`); } this.printOnNewline(`return ${structName}(snapshot: snapshot)`); }); this.printOnNewline('set'); this.withinBlock(() => { if (isConditional) { this.printOnNewline(`guard let newValue = newValue else { return }`); this.printOnNewline(`snapshot += newValue.snapshot`); } else { this.printOnNewline(`snapshot += newValue.snapshot`); } }); }); } }); } for (const field of fields) { if ((0, graphql_1.isCompositeType)((0, graphql_1.getNamedType)(field.type)) && field.selectionSet) { this.structDeclarationForSelectionSet({ structName: field.structName, selectionSet: field.selectionSet, }); } } if (after) { after(); } }); } initializersForTypeCase(typeCase) { const variants = typeCase.variants; if (variants.length == 0) { this.initializersForVariant(typeCase.default); } else { const remainder = typeCase.remainder; for (const variant of remainder ? [remainder, ...variants] : variants) { this.initializersForVariant(variant, variant === remainder ? undefined : this.helpers.structNameForVariant(variant), false); } } } initializersForVariant(variant, namespace, useInitializerIfPossible = true) { if (useInitializerIfPossible && variant.possibleTypes.length == 1) { const properties = this.helpers.propertiesForSelectionSet(variant); if (!properties) return; this.printNewlineIfNeeded(); this.printOnNewline(`public init`); this.parametersForProperties(properties); this.withinBlock(() => { this.printOnNewline((0, printing_1.wrap)(`self.init(snapshot: [`, (0, printing_1.join)([`"__typename": "${variant.possibleTypes[0]}"`, ...properties.map(this.propertyAssignmentForField, this)], ', ') || ':', `])`)); }); } else { const structName = this.scope.typeName; for (const possibleType of variant.possibleTypes) { const properties = this.helpers.propertiesForSelectionSet({ possibleTypes: [possibleType], selections: variant.selections, }, namespace); if (!properties) continue; this.printNewlineIfNeeded(); this.printOnNewline(`public static func make${possibleType}`); this.parametersForProperties(properties); this.print(` -> ${structName}`); this.withinBlock(() => { this.printOnNewline((0, printing_1.wrap)(`return ${structName}(snapshot: [`, (0, printing_1.join)([`"__typename": "${possibleType}"`, ...properties.map(this.propertyAssignmentForField, this)], ', ') || ':', `])`)); }); } } } propertyAssignmentForField(field) { const { responseKey, propertyName, type } = field; const valueExpression = (0, graphql_1.isCompositeType)((0, graphql_1.getNamedType)(type)) ? this.helpers.mapExpressionForType(type, identifier => `${identifier}.snapshot`, (0, language_1.escapeIdentifierIfNeeded)(propertyName)) : (0, language_1.escapeIdentifierIfNeeded)(propertyName); return `"${responseKey}": ${valueExpression}`; } propertyDeclarationForField(field) { const { responseKey, propertyName, typeName, type, isOptional } = field; const unmodifiedFieldType = (0, graphql_1.getNamedType)(type); this.printNewlineIfNeeded(); this.comment(field.description); this.deprecationAttributes(field.isDeprecated, field.deprecationReason); this.printOnNewline(`public var ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}: ${typeName}`); this.withinBlock(() => { if ((0, graphql_1.isCompositeType)(unmodifiedFieldType)) { const structName = (0, language_1.escapeIdentifierIfNeeded)(this.helpers.structNameForPropertyName(propertyName)); if ((0, graphql_2.isList)(type)) { this.printOnNewline('get'); this.withinBlock(() => { const snapshotTypeName = this.helpers.typeNameFromGraphQLType(type, 'Snapshot', false); let getter; if (isOptional) { getter = `return (snapshot["${responseKey}"] as? ${snapshotTypeName})`; } else { getter = `return (snapshot["${responseKey}"] as! ${snapshotTypeName})`; } getter += this.helpers.mapExpressionForType(type, identifier => `${structName}(snapshot: ${identifier})`); this.printOnNewline(getter); }); this.printOnNewline('set'); this.withinBlock(() => { let newValueExpression = this.helpers.mapExpressionForType(type, identifier => `${identifier}.snapshot`, 'newValue'); this.printOnNewline(`snapshot.updateValue(${newValueExpression}, forKey: "${responseKey}")`); }); } else { this.printOnNewline('get'); this.withinBlock(() => { if (isOptional) { this.printOnNewline(`return (snapshot["${responseKey}"] as? Snapshot).flatMap { ${structName}(snapshot: $0) }`); } else { this.printOnNewline(`return ${structName}(snapshot: snapshot["${responseKey}"]! as! Snapshot)`); } }); this.printOnNewline('set'); this.withinBlock(() => { let newValueExpression; if (isOptional) { newValueExpression = 'newValue?.snapshot'; } else { newValueExpression = 'newValue.snapshot'; } this.printOnNewline(`snapshot.updateValue(${newValueExpression}, forKey: "${responseKey}")`); }); } } else { this.printOnNewline('get'); this.withinBlock(() => { if (isOptional) { this.printOnNewline(`return snapshot["${responseKey}"] as? ${typeName.slice(0, -1)}`); } else { this.printOnNewline(`return snapshot["${responseKey}"]! as! ${typeName}`); } }); this.printOnNewline('set'); this.withinBlock(() => { this.printOnNewline(`snapshot.updateValue(newValue, forKey: "${responseKey}")`); }); } }); } propertyDeclarationForVariant(variant) { const { propertyName, typeName, structName } = variant; this.printNewlineIfNeeded(); this.printOnNewline(`public var ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}: ${typeName}`); this.withinBlock(() => { this.printOnNewline('get'); this.withinBlock(() => { this.printOnNewline(`if !${structName}.possibleTypes.contains(__typename) { return nil }`); this.printOnNewline(`return ${structName}(snapshot: snapshot)`); }); this.printOnNewline('set'); this.withinBlock(() => { this.printOnNewline(`guard let newValue = newValue else { return }`); this.printOnNewline(`snapshot = newValue.snapshot`); }); }); } initializerDeclarationForProperties(properties) { this.printOnNewline(`public init`); this.parametersForProperties(properties); this.withinBlock(() => { properties.forEach(({ propertyName }) => { this.printOnNewline(`self.${propertyName} = ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}`); }); }); } parametersForProperties(properties) { this.print('('); this.print((0, printing_1.join)(properties.map(({ propertyName, typeName, isOptional }) => (0, printing_1.join)([`${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}: ${typeName}`, isOptional && ' = nil'])), ', ')); this.print(')'); } typeCaseInitialization(typeCase) { if (typeCase.variants.length < 1) { this.selectionSetInitialization(typeCase.default); return; } this.print('['); this.withIndent(() => { this.printOnNewline(`GraphQLTypeCase(`); this.withIndent(() => { this.printOnNewline(`variants: [`); this.print(typeCase.variants .flatMap(variant => { const structName = this.helpers.structNameForVariant(variant); return variant.possibleTypes.map(type => `"${type}": ${structName}.selections`); }) .join(', ')); this.print('],'); this.printOnNewline(`default: `); this.selectionSetInitialization(typeCase.default); }); this.printOnNewline(')'); }); this.printOnNewline(']'); } selectionSetInitialization(selectionSet) { this.print('['); this.withIndent(() => { for (const selection of selectionSet.selections) { switch (selection.kind) { case 'Field': { const { name, alias, args, type } = selection; const responseKey = selection.alias || selection.name; const structName = this.helpers.structNameForPropertyName(responseKey); this.printOnNewline(`GraphQLField(`); this.print((0, printing_1.join)([ `"${name}"`, alias ? `alias: "${alias}"` : null, args && args.length && `arguments: ${this.helpers.dictionaryLiteralForFieldArguments(args)}`, `type: ${this.helpers.fieldTypeEnum(type, structName)}`, ], ', ')); this.print('),'); break; } case 'BooleanCondition': this.printOnNewline(`GraphQLBooleanCondition(`); this.print((0, printing_1.join)([`variableName: "${selection.variableName}"`, `inverted: ${selection.inverted}`, 'selections: '], ', ')); this.selectionSetInitialization(selection.selectionSet); this.print('),'); break; case 'TypeCondition': { this.printOnNewline(`GraphQLTypeCondition(`); this.print((0, printing_1.join)([`possibleTypes: [${(0, printing_1.join)(selection.selectionSet.possibleTypes.map(type => `"${type.name}"`), ', ')}]`, 'selections: '], ', ')); this.selectionSetInitialization(selection.selectionSet); this.print('),'); break; } case 'FragmentSpread': { const structName = this.helpers.structNameForFragmentName(selection.fragmentName); this.printOnNewline(`GraphQLFragmentSpread(${structName}.self),`); break; } } } }); this.printOnNewline(']'); } typeDeclarationForGraphQLType(type) { if (type instanceof graphql_1.GraphQLEnumType) { this.enumerationDeclaration(type); } else if (type instanceof graphql_1.GraphQLInputObjectType) { this.structDeclarationForInputObjectType(type); } } enumerationDeclaration(type) { const { name, description } = type; const values = type.getValues(); this.printNewlineIfNeeded(); this.comment(description || undefined); this.printOnNewline(`public enum ${name}: RawRepresentable, Equatable, JSONDecodable, JSONEncodable`); this.withinBlock(() => { this.printOnNewline('public typealias RawValue = String'); values.forEach(value => { this.comment(value.description || undefined); this.deprecationAttributes(value.isDeprecated, value.deprecationReason || undefined); this.printOnNewline(`case ${(0, language_1.escapeIdentifierIfNeeded)(this.helpers.enumCaseName(value.name))}`); }); this.comment('Auto generated constant for unknown enum values'); this.printOnNewline('case unknown(RawValue)'); this.printNewlineIfNeeded(); this.printOnNewline('public init?(rawValue: RawValue)'); this.withinBlock(() => { this.printOnNewline('switch rawValue'); this.withinBlock(() => { values.forEach(value => { this.printOnNewline(`case "${value.value}": self = ${(0, language_1.escapeIdentifierIfNeeded)(this.helpers.enumDotCaseName(value.name))}`); }); this.printOnNewline(`default: self = .unknown(rawValue)`); }); }); this.printNewlineIfNeeded(); this.printOnNewline('public var rawValue: RawValue'); this.withinBlock(() => { this.printOnNewline('switch self'); this.withinBlock(() => { values.forEach(value => { this.printOnNewline(`case ${(0, language_1.escapeIdentifierIfNeeded)(this.helpers.enumDotCaseName(value.name))}: return "${value.value}"`); }); this.printOnNewline(`case .unknown(let value): return value`); }); }); this.printNewlineIfNeeded(); this.printOnNewline(`public static func == (lhs: ${name}, rhs: ${name}) -> Bool`); this.withinBlock(() => { this.printOnNewline('switch (lhs, rhs)'); this.withinBlock(() => { values.forEach(value => { const enumDotCaseName = (0, language_1.escapeIdentifierIfNeeded)(this.helpers.enumDotCaseName(value.name)); const tuple = `(${enumDotCaseName}, ${enumDotCaseName})`; this.printOnNewline(`case ${tuple}: return true`); }); this.printOnNewline(`case (.unknown(let lhsValue), .unknown(let rhsValue)): return lhsValue == rhsValue`); this.printOnNewline(`default: return false`); }); }); }); } structDeclarationForInputObjectType(type) { const { name: structName, description } = type; const adoptedProtocols = ['GraphQLMapConvertible']; const fields = Object.values(type.getFields()); let properties = fields.map(this.helpers.propertyFromInputField, this.helpers); if ((0, complextypes_1.isS3Field)(type)) { properties = [ ...properties, ...(properties.find(p => p.name === 'localUri') ? [] : [ { propertyName: 'localUri', name: 'localUri', typeName: 'String', isOptional: false, description: '', }, ]), ...(properties.find(p => p.name === 'mimeType') ? [] : [ { propertyName: 'mimeType', name: 'mimeType', typeName: 'String', isOptional: false, description: '', }, ]), ]; } this.structDeclaration({ structName, description: description || undefined, adoptedProtocols }, () => { this.printOnNewline(`public var graphQLMap: GraphQLMap`); this.printNewlineIfNeeded(); this.printOnNewline(`public init`); this.print('('); this.print((0, printing_1.join)(properties.map(({ propertyName, typeName, isOptional }) => (0, printing_1.join)([`${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}: ${typeName}`, isOptional && ' = nil'])), ', ')); this.print(')'); this.withinBlock(() => { this.printOnNewline((0, printing_1.wrap)(`graphQLMap = [`, (0, printing_1.join)(properties.map(({ name, propertyName }) => `"${name}": ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}`), ', ') || ':', `]`)); }); for (const { name, propertyName, typeName, description } of properties) { this.printNewlineIfNeeded(); this.comment(description || undefined); this.printOnNewline(`public var ${(0, language_1.escapeIdentifierIfNeeded)(propertyName)}: ${typeName}`); this.withinBlock(() => { this.printOnNewline('get'); this.withinBlock(() => { this.printOnNewline(`return graphQLMap["${name}"] as! ${typeName}`); }); this.printOnNewline('set'); this.withinBlock(() => { this.printOnNewline(`graphQLMap.updateValue(newValue, forKey: "${name}")`); }); }); } }); } } exports.SwiftAPIGenerator = SwiftAPIGenerator; //# sourceMappingURL=codeGeneration.js.map