From 1610f0fb4e9c1c8d85631380f09299bc18f89d56 Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:17:36 -0400 Subject: [PATCH] feat: swift codegen updates for API.swift --- .../src/swift/codeGeneration.ts | 565 ++++++++++++++++++ 1 file changed, 565 insertions(+) diff --git a/packages/graphql-types-generator/src/swift/codeGeneration.ts b/packages/graphql-types-generator/src/swift/codeGeneration.ts index e9937552f..103727775 100644 --- a/packages/graphql-types-generator/src/swift/codeGeneration.ts +++ b/packages/graphql-types-generator/src/swift/codeGeneration.ts @@ -120,7 +120,11 @@ export class SwiftAPIGenerator extends SwiftGenerator { fileHeader() { this.printOnNewline('// This file was automatically generated and should not be edited.'); this.printNewline(); + this.printOnNewline('#if canImport(AWSAPIPlugin)'); + this.print(appsyncCompatibilityExtensionTypes); + this.printOnNewline('#elseif canImport(AWSAppSync)'); this.printOnNewline('import AWSAppSync'); + this.printOnNewline('#endif'); } classDeclarationForOperation(operation: Operation) { @@ -819,3 +823,564 @@ export class SwiftAPIGenerator extends SwiftGenerator { }); } } + +const appsyncCompatibilityExtensionTypes = ` +import Foundation +/// Represents an error encountered during the execution of a GraphQL operation. +/// +/// - SeeAlso: [The Response Format section in the GraphQL specification](https://facebook.github.io/graphql/#sec-Response-Format) +public struct GraphQLError: Error { + private let object: JSONObject + + init(_ object: JSONObject) { + self.object = object + } + + init(_ message: String) { + self.init(["message": message]) + } + + /// GraphQL servers may provide additional entries as they choose to produce more helpful or machineā€readable errors. + public subscript(key: String) -> Any? { + return object[key] + } + + /// A description of the error. + public var message: String { + return self["message"] as! String + } + + /// A list of locations in the requested GraphQL document associated with the error. + public var locations: [Location]? { + return (self["locations"] as? [JSONObject])?.map(Location.init) + } + + /// Represents a location in a GraphQL document. + public struct Location { + /// The line number of a syntax element. + public let line: Int + /// The column number of a syntax element. + public let column: Int + + init(_ object: JSONObject) { + line = object["line"] as! Int + column = object["column"] as! Int + } + } +} + +public protocol GraphQLInputValue { + func evaluate(with variables: [String: JSONEncodable]?) throws -> Any +} + +public struct GraphQLVariable { + let name: String + + public init(_ name: String) { + self.name = name + } +} + +extension GraphQLVariable: GraphQLInputValue { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { + guard let value = variables?[name] else { + throw GraphQLError("Variable \\(name) was not provided.") + } + return value.jsonValue + } +} + +extension JSONEncodable { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { + return jsonValue + } +} + +extension Dictionary: GraphQLInputValue { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { + return try evaluate(with: variables) as JSONObject + } +} + +extension Dictionary { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> JSONObject { + var jsonObject = JSONObject(minimumCapacity: count) + for (key, value) in self { + if case let (key as String, value as GraphQLInputValue) = (key, value) { + let evaluatedValue = try value.evaluate(with: variables) + if !(evaluatedValue is NSNull) { + jsonObject[key] = evaluatedValue + } + } else { + fatalError("Dictionary is only GraphQLInputValue if Value is (and if Key is String)") + } + } + return jsonObject + } +} + +extension Array: GraphQLInputValue { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { + return try evaluate(with: variables) as [Any] + } +} + +extension Array { + public func evaluate(with variables: [String: JSONEncodable]?) throws -> [Any] { + var jsonArray = [Any]() + jsonArray.reserveCapacity(count) + for (value) in self { + if case let (value as GraphQLInputValue) = value { + jsonArray.append(try value.evaluate(with: variables)) + } else { + fatalError("Array is only GraphQLInputValue if Element is") + } + } + return jsonArray + } +} + +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 extension GraphQLMapConvertible { + func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { + return try graphQLMap.evaluate(with: variables) + } +} + +public typealias GraphQLID = String + +public protocol GraphQLOperation: AnyObject { + static var rootCacheKey: String { get } + + 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 extension GraphQLQuery { + static var rootCacheKey: String { return "QUERY_ROOT" } +} + +public protocol GraphQLMutation: GraphQLOperation {} +public extension GraphQLMutation { + static var rootCacheKey: String { return "MUTATION_ROOT" } +} + +public protocol GraphQLSubscription: GraphQLOperation {} + +public extension GraphQLSubscription { + static var rootCacheKey: String { return "SUBSCRIPTION_ROOT" } +} + +public protocol GraphQLFragment: GraphQLSelectionSet { + static var possibleTypes: [String] { get } +} + +public extension GraphQLOperation { + static func getResponseGraphQLSelections() -> [GraphQLSelection] { + return Data.selections + } +} + +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? JSONValue(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: [:]) + } + } +} + +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 + } + + func cacheKey(with variables: [String: JSONEncodable]?) throws -> String { + if let argumentValues = try arguments?.evaluate(with: variables), !argumentValues.isEmpty { + let argumentsKey = orderIndependentKey(for: argumentValues) + return "\\(name)(\\(argumentsKey))" + } else { + return name + } + } +} + +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 + } + } +} + +private func orderIndependentKey(for object: JSONObject) -> String { + return object.sorted { $0.key < $1.key }.map { + if let object = $0.value as? JSONObject { + return "[\\($0.key):\\(orderIndependentKey(for: object))]" + } else { + return "\\($0.key):\\($0.value)" + } + }.joined(separator: ",") +} + +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)" + } + } +} + +enum JSONValue { + case array([JSONValue]) + case boolean(Bool) + case number(Double) + case object([String: JSONValue]) + case string(String) + case null +} + +extension JSONValue: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if let value = try? container.decode([String: JSONValue].self) { + self = .object(value) + } else if let value = try? container.decode([JSONValue].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 + } + } + + public 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 final class JSONSerializationFormat { + public class func serialize(value: JSONEncodable) throws -> Data { + return try JSONSerialization.data(withJSONObject: value.jsonValue, options: []) + } + + public class func deserialize(data: Data) throws -> Any { + return try JSONSerialization.jsonObject(with: data, options: []) + } +} + +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 + } +} +`; \ No newline at end of file