From d1e2cb8fe21bc5d29ecb086e5ca0c4b3ff02d9ef Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Mon, 24 Jul 2023 10:39:02 -0400 Subject: [PATCH] feat: swift codegen updates for API.swift tests --- .../__snapshots__/codeGeneration.ts.snap | 426 ++++++++++++++++++ .../test/swift/codeGeneration.ts | 8 + 2 files changed, 434 insertions(+) diff --git a/packages/graphql-types-generator/test/swift/__snapshots__/codeGeneration.ts.snap b/packages/graphql-types-generator/test/swift/__snapshots__/codeGeneration.ts.snap index bef046b2b..dcdd96ac3 100644 --- a/packages/graphql-types-generator/test/swift/__snapshots__/codeGeneration.ts.snap +++ b/packages/graphql-types-generator/test/swift/__snapshots__/codeGeneration.ts.snap @@ -623,6 +623,432 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a }" `; +exports[`Swift code generation #fileHeader should generate a file header 1`] = ` +"// This file was automatically generated and should not be edited. + +#if canImport(AWSAPIPlugin) +import Foundation + +public protocol GraphQLInputValue { +} + +public struct GraphQLVariable { + let name: String + + public init(_ name: String) { + self.name = name + } +} + +extension GraphQLVariable: GraphQLInputValue { +} + +extension JSONEncodable { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { + return jsonValue + } +} + +public typealias GraphQLMap = [String: JSONEncodable?] + +extension Dictionary where Key == String, Value == JSONEncodable? { + public var withNilValuesRemoved: Dictionary { + var filtered = Dictionary(minimumCapacity: count) + for (key, value) in self { + if value != nil { + filtered[key] = value + } + } + return filtered + } +} + +public protocol GraphQLMapConvertible: JSONEncodable { + var graphQLMap: GraphQLMap { get } +} + +public extension GraphQLMapConvertible { + var jsonValue: Any { + return graphQLMap.withNilValuesRemoved.jsonValue + } +} + +public typealias GraphQLID = String + +public protocol GraphQLOperation: AnyObject { + + static var operationString: String { get } + static var requestString: String { get } + static var operationIdentifier: String? { get } + + var variables: GraphQLMap? { get } + + associatedtype Data: GraphQLSelectionSet +} + +public extension GraphQLOperation { + static var requestString: String { + return operationString + } + + static var operationIdentifier: String? { + return nil + } + + var variables: GraphQLMap? { + return nil + } +} + +public protocol GraphQLQuery: GraphQLOperation {} + +public protocol GraphQLMutation: GraphQLOperation {} + +public protocol GraphQLSubscription: GraphQLOperation {} + +public protocol GraphQLFragment: GraphQLSelectionSet { + static var possibleTypes: [String] { get } +} + +public typealias Snapshot = [String: Any?] + +public protocol GraphQLSelectionSet: Decodable { + static var selections: [GraphQLSelection] { get } + + var snapshot: Snapshot { get } + init(snapshot: Snapshot) +} + +extension GraphQLSelectionSet { + public init(from decoder: Decoder) throws { + if let jsonObject = try? APISwiftJSONValue(from: decoder) { + let encoder = JSONEncoder() + let jsonData = try encoder.encode(jsonObject) + let decodedDictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any] + let optionalDictionary = decodedDictionary.mapValues { $0 as Any? } + + self.init(snapshot: optionalDictionary) + } else { + self.init(snapshot: [:]) + } + } +} + +enum APISwiftJSONValue: Codable { + case array([APISwiftJSONValue]) + case boolean(Bool) + case number(Double) + case object([String: APISwiftJSONValue]) + case string(String) + case null + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if let value = try? container.decode([String: APISwiftJSONValue].self) { + self = .object(value) + } else if let value = try? container.decode([APISwiftJSONValue].self) { + self = .array(value) + } else if let value = try? container.decode(Double.self) { + self = .number(value) + } else if let value = try? container.decode(Bool.self) { + self = .boolean(value) + } else if let value = try? container.decode(String.self) { + self = .string(value) + } else { + self = .null + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .array(let value): + try container.encode(value) + case .boolean(let value): + try container.encode(value) + case .number(let value): + try container.encode(value) + case .object(let value): + try container.encode(value) + case .string(let value): + try container.encode(value) + case .null: + try container.encodeNil() + } + } +} + +public protocol GraphQLSelection { +} + +public struct GraphQLField: GraphQLSelection { + let name: String + let alias: String? + let arguments: [String: GraphQLInputValue]? + + var responseKey: String { + return alias ?? name + } + + let type: GraphQLOutputType + + public init(_ name: String, alias: String? = nil, arguments: [String: GraphQLInputValue]? = nil, type: GraphQLOutputType) { + self.name = name + self.alias = alias + + self.arguments = arguments + + self.type = type + } +} + +public indirect enum GraphQLOutputType { + case scalar(JSONDecodable.Type) + case object([GraphQLSelection]) + case nonNull(GraphQLOutputType) + case list(GraphQLOutputType) + + var namedType: GraphQLOutputType { + switch self { + case .nonNull(let innerType), .list(let innerType): + return innerType.namedType + case .scalar, .object: + return self + } + } +} + +public struct GraphQLBooleanCondition: GraphQLSelection { + let variableName: String + let inverted: Bool + let selections: [GraphQLSelection] + + public init(variableName: String, inverted: Bool, selections: [GraphQLSelection]) { + self.variableName = variableName + self.inverted = inverted; + self.selections = selections; + } +} + +public struct GraphQLTypeCondition: GraphQLSelection { + let possibleTypes: [String] + let selections: [GraphQLSelection] + + public init(possibleTypes: [String], selections: [GraphQLSelection]) { + self.possibleTypes = possibleTypes + self.selections = selections; + } +} + +public struct GraphQLFragmentSpread: GraphQLSelection { + let fragment: GraphQLFragment.Type + + public init(_ fragment: GraphQLFragment.Type) { + self.fragment = fragment + } +} + +public struct GraphQLTypeCase: GraphQLSelection { + let variants: [String: [GraphQLSelection]] + let \`default\`: [GraphQLSelection] + + public init(variants: [String: [GraphQLSelection]], default: [GraphQLSelection]) { + self.variants = variants + self.default = \`default\`; + } +} + +public typealias JSONObject = [String: Any] + +public protocol JSONDecodable { + init(jsonValue value: Any) throws +} + +public protocol JSONEncodable: GraphQLInputValue { + var jsonValue: Any { get } +} + +public enum JSONDecodingError: Error, LocalizedError { + case missingValue + case nullValue + case wrongType + case couldNotConvert(value: Any, to: Any.Type) + + public var errorDescription: String? { + switch self { + case .missingValue: + return \\"Missing value\\" + case .nullValue: + return \\"Unexpected null value\\" + case .wrongType: + return \\"Wrong type\\" + case .couldNotConvert(let value, let expectedType): + return \\"Could not convert \\\\\\"\\\\(value)\\\\\\" to \\\\(expectedType)\\" + } + } +} + +extension String: JSONDecodable, JSONEncodable { + public init(jsonValue value: Any) throws { + guard let string = value as? String else { + throw JSONDecodingError.couldNotConvert(value: value, to: String.self) + } + self = string + } + + public var jsonValue: Any { + return self + } +} + +extension Int: JSONDecodable, JSONEncodable { + public init(jsonValue value: Any) throws { + guard let number = value as? NSNumber else { + throw JSONDecodingError.couldNotConvert(value: value, to: Int.self) + } + self = number.intValue + } + + public var jsonValue: Any { + return self + } +} + +extension Float: JSONDecodable, JSONEncodable { + public init(jsonValue value: Any) throws { + guard let number = value as? NSNumber else { + throw JSONDecodingError.couldNotConvert(value: value, to: Float.self) + } + self = number.floatValue + } + + public var jsonValue: Any { + return self + } +} + +extension Double: JSONDecodable, JSONEncodable { + public init(jsonValue value: Any) throws { + guard let number = value as? NSNumber else { + throw JSONDecodingError.couldNotConvert(value: value, to: Double.self) + } + self = number.doubleValue + } + + public var jsonValue: Any { + return self + } +} + +extension Bool: JSONDecodable, JSONEncodable { + public init(jsonValue value: Any) throws { + guard let bool = value as? Bool else { + throw JSONDecodingError.couldNotConvert(value: value, to: Bool.self) + } + self = bool + } + + public var jsonValue: Any { + return self + } +} + +extension RawRepresentable where RawValue: JSONDecodable { + public init(jsonValue value: Any) throws { + let rawValue = try RawValue(jsonValue: value) + if let tempSelf = Self(rawValue: rawValue) { + self = tempSelf + } else { + throw JSONDecodingError.couldNotConvert(value: value, to: Self.self) + } + } +} + +extension RawRepresentable where RawValue: JSONEncodable { + public var jsonValue: Any { + return rawValue.jsonValue + } +} + +extension Optional where Wrapped: JSONDecodable { + public init(jsonValue value: Any) throws { + if value is NSNull { + self = .none + } else { + self = .some(try Wrapped(jsonValue: value)) + } + } +} + +extension Optional: JSONEncodable { + public var jsonValue: Any { + switch self { + case .none: + return NSNull() + case .some(let wrapped as JSONEncodable): + return wrapped.jsonValue + default: + fatalError(\\"Optional is only JSONEncodable if Wrapped is\\") + } + } +} + +extension Dictionary: JSONEncodable { + public var jsonValue: Any { + return jsonObject + } + + public var jsonObject: JSONObject { + var jsonObject = JSONObject(minimumCapacity: count) + for (key, value) in self { + if case let (key as String, value as JSONEncodable) = (key, value) { + jsonObject[key] = value.jsonValue + } else { + fatalError(\\"Dictionary is only JSONEncodable if Value is (and if Key is String)\\") + } + } + return jsonObject + } +} + +extension Array: JSONEncodable { + public var jsonValue: Any { + return map() { element -> (Any) in + if case let element as JSONEncodable = element { + return element.jsonValue + } else { + fatalError(\\"Array is only JSONEncodable if Element is\\") + } + } + } +} + +extension URL: JSONDecodable, JSONEncodable { + public init(jsonValue value: Any) throws { + guard let string = value as? String else { + throw JSONDecodingError.couldNotConvert(value: value, to: URL.self) + } + self.init(string: string)! + } + + public var jsonValue: Any { + return self.absoluteString + } +} + +extension Dictionary { + static func += (lhs: inout Dictionary, rhs: Dictionary) { + lhs.merge(rhs) { (_, new) in new } + } +} + +#elseif canImport(AWSAppSync) +import AWSAppSync +#endif" +`; + exports[`Swift code generation #initializerDeclarationForProperties() should generate initializer for a property 1`] = ` "public init(episode: Episode) { self.episode = episode diff --git a/packages/graphql-types-generator/test/swift/codeGeneration.ts b/packages/graphql-types-generator/test/swift/codeGeneration.ts index 525e3b9f8..a1db58044 100644 --- a/packages/graphql-types-generator/test/swift/codeGeneration.ts +++ b/packages/graphql-types-generator/test/swift/codeGeneration.ts @@ -21,6 +21,14 @@ describe('Swift code generation', () => { return context; } + describe('#fileHeader', () => { + it('should generate a file header', () => { + generator.fileHeader(); + + expect(generator.output).toMatchSnapshot(); + }); + }); + describe('#classDeclarationForOperation()', () => { it(`should generate a class declaration for a query with variables`, () => { const { operations } = compile(`