diff --git a/Package.swift b/Package.swift index 667543b4..92f893e6 100644 --- a/Package.swift +++ b/Package.swift @@ -8,34 +8,25 @@ let package = Package( .iOS(.v15), .macOS(.v10_15), .tvOS(.v13), - .watchOS(.v6) + .watchOS(.v6), ], products: [ // SwiftGraphQL .library(name: "SwiftGraphQL", targets: ["SwiftGraphQL"]), .library(name: "SwiftGraphQLClient", targets: ["SwiftGraphQLClient"]), - .library(name: "SwiftGraphQLCodegen", targets: ["SwiftGraphQLCodegen"]), - // CLI - .executable( name: "swift-graphql", targets: ["SwiftGraphQLCLI"]), // Utilities .library(name: "GraphQL", targets: ["GraphQL"]), - .library(name: "GraphQLAST", targets: ["GraphQLAST"]), - .library(name: "GraphQLWebSocket", targets: ["GraphQLWebSocket"]) + .library(name: "GraphQLWebSocket", targets: ["GraphQLWebSocket"]), ], dependencies: [ // .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-format", "508.0.0"..<"510.0.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), .package(url: "https://github.com/daltoniam/Starscream.git", from: "4.0.5"), - .package(url: "https://github.com/dominicegginton/Spinner", from: "2.0.0"), .package(url: "https://github.com/JohnSundell/Files", from: "4.0.0"), - .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"), ], targets: [ // Spec .target(name: "GraphQL", dependencies: [], path: "Sources/GraphQL"), - .target(name: "GraphQLAST", dependencies: [], path: "Sources/GraphQLAST"), .target( name: "GraphQLWebSocket", dependencies: [ @@ -46,12 +37,14 @@ let package = Package( path: "Sources/GraphQLWebSocket", exclude: ["README.md"] ), - + // SwiftGraphQL - + .target( name: "SwiftGraphQL", - dependencies: ["GraphQL", "SwiftGraphQLUtils"], + dependencies: [ + "GraphQL", + ], path: "Sources/SwiftGraphQL" ), .target( @@ -64,50 +57,22 @@ let package = Package( ], path: "Sources/SwiftGraphQLClient" ), - .target( - name: "SwiftGraphQLCodegen", - dependencies: [ - "GraphQLAST", - .product(name: "SwiftFormat", package: "swift-format"), - .product(name: "SwiftFormatConfiguration", package: "swift-format"), - "SwiftGraphQLUtils" - ], - path: "Sources/SwiftGraphQLCodegen" - ), - .target(name: "SwiftGraphQLUtils", dependencies: [], path: "Sources/SwiftGraphQLUtils"), - - // Executables - - .executableTarget( - name: "SwiftGraphQLCLI", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - "Files", - "Spinner", - "SwiftGraphQLCodegen", - "Yams", - ], - path: "Sources/SwiftGraphQLCLI" - ), - + // Tests - + .testTarget( name: "SwiftGraphQLTests", dependencies: [ "Files", "GraphQL", - "GraphQLAST", "GraphQLWebSocket", - "SwiftGraphQLCodegen", "SwiftGraphQL", "SwiftGraphQLClient", - "SwiftGraphQLUtils", ], path: "Tests", exclude: [ - "SwiftGraphQLCodegenTests/Integration/schema.json", - "SwiftGraphQLCodegenTests/Integration/swiftgraphql.yml", + "SwiftGraphQLTests/Integration/schema.json", + "SwiftGraphQLTests/Integration/swiftgraphql.yml", ] ) ] diff --git a/Sources/GraphQLAST/Errors.swift b/Sources/GraphQLAST/Errors.swift deleted file mode 100644 index f84d357f..00000000 --- a/Sources/GraphQLAST/Errors.swift +++ /dev/null @@ -1,16 +0,0 @@ - - -/// Errors that occur during the parsing of the schema. -public enum ParsingError: Error { - /// Schema references a type that doesn't exist in the schema. - case unknownType(String) - - /// Schema references an unknown scalar. - case unknownScalar(String) - - /// Schema references an object that is not in the schema. - case unknownObject(String) - - /// Schema references an unknown input object. - case unknownInputObject(String) -} diff --git a/Sources/GraphQLAST/Introspection.swift b/Sources/GraphQLAST/Introspection.swift deleted file mode 100644 index 23d0501a..00000000 --- a/Sources/GraphQLAST/Introspection.swift +++ /dev/null @@ -1,249 +0,0 @@ -import Foundation - -/* - We use the common introspection Query to construct the library. - You can find remaining utility types that represent the result - of the schema introspection inside AST folder. - - I've namespaced every GraphQL and GraphQL schema related values - and functions to GraphQL enum. - */ - -// MARK: - Introspection Query - -/// IntrospectionQuery that you should use to fetch data. -/// -/// - Note: If you use a different introspection query, GraphQLAST might not be able to -/// correctly parse it. -public let introspectionQuery: String = """ -query IntrospectionQuery($includeDeprecated: Boolean = true) { - __schema { - queryType { name } - mutationType { name } - subscriptionType { name } - types { - ...FullType - } - } -} - -fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: $includeDeprecated) { - ...Field - } - inputFields { - ...InputValue - } - interfaces { - ...TypeRef - } - enumValues(includeDeprecated: $includeDeprecated) { - ...EnumValue - } - possibleTypes { - ...TypeRef - } -} - -fragment Field on __Field { - name - description - args { - ...InputValue - } - type { - ...TypeRef - } - isDeprecated - deprecationReason -} - -fragment InputValue on __InputValue { - name - description - type { - ...TypeRef - } - defaultValue -} - -fragment EnumValue on __EnumValue { - name - description - isDeprecated - deprecationReason -} - - - -fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } -} -""" - -// MARK: - Parser - -/// Decodes the received schema representation into Swift abstract type. -public func parse(_ data: Data) throws -> Schema { - let decoder = JSONDecoder() - let result = try decoder.decode(Reponse.self, from: data) - - return result.data.schema -} - -// MARK: - Internals - -/// Represents a GraphQL response. -private struct Reponse: Decodable { - public let data: T -} - -extension Reponse: Equatable where T: Equatable {} - -/// Represents introspection query return type in GraphQL response. -private struct IntrospectionQuery: Decodable, Equatable { - public let schema: Schema - - enum CodingKeys: String, CodingKey { - case schema = "__schema" - } -} - -// MARK: - Loader - -/// Fetches a schema from the provided endpoint using introspection query. -func fetch(from endpoint: URL, withHeaders headers: [String: String] = [:]) throws -> Data { - /* Compose a request. */ - var request = URLRequest(url: endpoint) - - for header in headers { - request.setValue(header.value, forHTTPHeaderField: header.key) - } - - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue("*/*", forHTTPHeaderField: "Accept") - request.httpMethod = "POST" - - let payload: [String: Any] = [ - "query": introspectionQuery, - "variables": [String: Any](), - "operationName": "IntrospectionQuery" - ] - - request.httpBody = try! JSONSerialization.data( - withJSONObject: payload, - options: JSONSerialization.WritingOptions() - ) - - /* Semaphore */ - let semaphore = DispatchSemaphore(value: 0) - var result: Result? - - /* Load the schema. */ - URLSession.shared.dataTask(with: request) { data, response, error in - /* Check for errors. */ - if let error = error { - result = .failure(.error(error)) - semaphore.signal() - return - } - - guard let httpResponse = response as? HTTPURLResponse else { - result = .failure(.unknown) - semaphore.signal() - return - } - - guard (200 ... 299).contains(httpResponse.statusCode) else { - result = .failure(.statusCode(httpResponse.statusCode)) - semaphore.signal() - return - } - - /* Save JSON to file. */ - if let data = data { - result = .success(data) - semaphore.signal() - return - } - }.resume() - - // Result - _ = semaphore.wait(wallTimeout: .distantFuture) - - switch result { - case let .success(data): - return data - case let .failure(error): - throw error - default: - throw IntrospectionError.unknown - } -} - -public enum IntrospectionError: Error { - - /// There was an error during the execution. - case error(Error) - - /// Request received a bad status code from the server. - case statusCode(Int) - - /// We don;t know what caused the error, but something unexpected happened. - case unknown - - /// Error that signifies that there's no content at the provided file path. - case emptyfile -} - -// MARK: - Extension - -public extension Schema { - - /// Downloads a schema from the provided endpoint or a local file. - /// - /// - NOTE: The function is going to load from the local path if the URL is missing a scheme or has a `file` scheme. - init(from endpoint: URL, withHeaders headers: [String: String] = [:]) throws { - - let introspection: Data - if endpoint.isFileURL { - introspection = try Data(contentsOf: endpoint) - } else { - introspection = try fetch(from: endpoint, withHeaders: headers) - } - - self = try parse(introspection) - } -} diff --git a/Sources/GraphQLAST/InvertedTypeRef.swift b/Sources/GraphQLAST/InvertedTypeRef.swift deleted file mode 100644 index cffce4f3..00000000 --- a/Sources/GraphQLAST/InvertedTypeRef.swift +++ /dev/null @@ -1,89 +0,0 @@ -import Foundation - -/// Inverted type reference represents a reference using nullable references -/// instead of required ones. This makes some calculations easier. -public indirect enum InvertedTypeRef { - case named(Type) - case nullable(InvertedTypeRef) - case list(InvertedTypeRef) - - // MARK: - Calculated properties - - /// Returns a nullable instance of self. - public var nullable: InvertedTypeRef { - self.inverted.nullable.inverted - } - - /// Returns a non nullable instance of self. - public var nonNullable: InvertedTypeRef { - switch self { - case let .nullable(subref): - return subref - default: - return self - } - } -} - -public extension InvertedTypeRef { - /// Returns the bottom most named type in reference. - var namedType: Type { - switch self { - case let .named(type): - return type - case let .nullable(subRef), let .list(subRef): - return subRef.namedType - } - } -} - -extension InvertedTypeRef: Equatable where Type: Equatable {} - -// MARK: - Conversion - -public extension TypeRef { - /// Turns a regular reference into an inverted reference that presents - /// fields as regular (i.e. required) or nullable instead of regular (i.e. nulalble) or required. - var inverted: InvertedTypeRef { - switch self { - case let .named(named): - return .nullable(.named(named)) - case let .list(ref): - return .nullable(.list(ref.inverted)) - case let .nonNull(ref): - /* Remove nullable wrapper. */ - switch ref.inverted { - case let .nullable(subRef): - return subRef - default: - return ref.inverted - } - } - } -} - -public extension InvertedTypeRef { - /// Turns an inverted reference back into a regular reference. - var inverted: TypeRef { - switch self { - case let .named(named): - return .nonNull(.named(named)) - case let .list(ref): - return .nonNull(.list(ref.inverted)) - case let .nullable(ref): - switch ref.inverted { - /* Remove nonnullable wrapper. */ - case let .nonNull(subRef): - return subRef - default: - return ref.inverted - } - } - } -} - -// MARK: - Type Alias - -public typealias InvertedNamedTypeRef = InvertedTypeRef -public typealias InvertedOutputTypeRef = InvertedTypeRef -public typealias InvertedInputTypeRef = InvertedTypeRef diff --git a/Sources/GraphQLAST/Schema.swift b/Sources/GraphQLAST/Schema.swift deleted file mode 100644 index 042b5fe0..00000000 --- a/Sources/GraphQLAST/Schema.swift +++ /dev/null @@ -1,171 +0,0 @@ -import Foundation - -// MARK: - Schema - -public struct Schema: Decodable, Equatable { - /// Collection of all types in the schema. - public let types: [NamedType] - - private let _query: String - private let _mutation: String? - private let _subscription: String? - - // MARK: - Initializer - - public init(types: [NamedType], query: String, mutation: String? = nil, subscription: String? = nil) { - self.types = types - - _query = query - _mutation = mutation - _subscription = subscription - } - - // MARK: - Decoder - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - types = try container.decode([NamedType].self, forKey: .types) - _query = try container.decode(_Operation.self, forKey: .query).name - _mutation = try container.decode(_Operation?.self, forKey: .mutation)?.name - _subscription = try container.decode(_Operation?.self, forKey: .subscription)?.name - } - - private enum CodingKeys: String, CodingKey { - case types - case query = "queryType" - case mutation = "mutationType" - case subscription = "subscriptionType" - } - - private struct _Operation: Codable { - var name: String - } -} - -// MARK: - Accessors - -public extension Schema { - - /// Searches for a type with a given name. - func type(name: String) -> NamedType? { - types.first(where: { $0.name == name }) - } - - /// Searches for an object with a given name. - func object(name: String) -> ObjectType? { - objects.first(where: { $0.name == name }) - } - - /// Searches for an input object with a given name. - func inputObject(name: String) -> InputObjectType? { - inputObjects.first(where: { $0.name == name }) - } - - /// Returns a scalar type with a given name if it exists. - func scalar(name: String) -> ScalarType? { - scalars.first(where: { $0.name == name }) - } - - // MARK: - Operations - - /// Query operation type in the schema. - var query: Operation { - .query(object(name: _query)!) - } - - /// Mutation operation type in the schema. - var mutation: Operation? { - _mutation - .flatMap { object(name: $0) } - .flatMap { .mutation($0) } - } - - /// Subscription operation type in the schema. - var subscription: Operation? { - _subscription - .flatMap { object(name: $0) } - .flatMap { .subscription($0) } - } - - /// Returns operation types in the schema. - var operations: [Operation] { - [query, mutation, subscription].compactMap { $0 } - } - - // MARK: - Scalars - - /// Returns every scalar referenced in the schema. - var scalars: [ScalarType] { - self.types.compactMap { - switch $0 { - case .scalar(let scalar): - return scalar - default: - return nil - } - } - } - - // MARK: - Named types - - /// Returns object definitions from schema. - var objects: [ObjectType] { - types.compactMap { - switch $0 { - case let .object(type) where !type.isInternal: - return type - default: - return nil - } - } - } - - /// Returns object definitions from schema. - var interfaces: [InterfaceType] { - types.compactMap { - switch $0 { - case let .interface(type) where !type.isInternal: - return type - default: - return nil - } - } - } - - /// Returns object definitions from schema. - var unions: [UnionType] { - types.compactMap { - switch $0 { - case let .union(type) where !type.isInternal: - return type - default: - return nil - } - } - } - - /// Returns enumerator definitions in schema. - var enums: [EnumType] { - types.compactMap { - switch $0 { - case let .enum(type) where !type.isInternal: - return type - default: - return nil - } - } - } - - /// Returns input object definitions from schema. - var inputObjects: [InputObjectType] { - types.compactMap { - switch $0 { - case let .inputObject(type) where !type.isInternal: - return type - default: - return nil - } - } - } -} diff --git a/Sources/GraphQLAST/Type.swift b/Sources/GraphQLAST/Type.swift deleted file mode 100644 index d93c3fe6..00000000 --- a/Sources/GraphQLAST/Type.swift +++ /dev/null @@ -1,216 +0,0 @@ -import Foundation - -public enum Operation { - case query(ObjectType) - case mutation(ObjectType) - case subscription(ObjectType) - - public var type: ObjectType { - switch self { - case let .query(type), let .mutation(type), let .subscription(type): - return type - } - } -} - -// MARK: - Named Type Protocol - -public enum NamedTypeKind: String, Codable, Equatable { - case scalar = "SCALAR" - case object = "OBJECT" - case interface = "INTERFACE" - case union = "UNION" - case enumeration = "ENUM" - case inputObject = "INPUT_OBJECT" -} - -public protocol NamedTypeProtocol { - var kind: NamedTypeKind { get } - var name: String { get } - var description: String? { get } -} - -public extension NamedTypeProtocol { - var isInternal: Bool { - name.starts(with: "__") - } -} - -// MARK: - Named Types - -public struct ScalarType: NamedTypeProtocol, Decodable, Hashable { - public var kind: NamedTypeKind = .scalar - public let name: String - public let description: String? -} - -public struct ObjectType: NamedTypeProtocol, Decodable, Equatable { - public var kind: NamedTypeKind = .object - public let name: String - public let description: String? - - public let fields: [Field] - public let interfaces: [InterfaceTypeRef]? - -} - -extension ObjectType { - public init( - name: String, - description: String?, - fields: [Field], - interfaces: [InterfaceTypeRef]? - ) { - self.name = name - self.description = description - self.fields = fields - self.interfaces = interfaces - } -} - -public struct InterfaceType: NamedTypeProtocol, Decodable, Equatable { - public var kind: NamedTypeKind = .interface - public let name: String - public let description: String? - - public let fields: [Field] - public let interfaces: [InterfaceTypeRef]? - public let possibleTypes: [ObjectTypeRef] -} - -extension InterfaceType { - public init( - name: String, - description: String?, - fields: [Field], - interfaces: [InterfaceTypeRef]?, - possibleTypes: [ObjectTypeRef] - ) { - self.name = name - self.description = description - self.fields = fields - self.interfaces = interfaces - self.possibleTypes = possibleTypes - } -} - -public struct UnionType: NamedTypeProtocol, Decodable, Equatable { - public var kind: NamedTypeKind = .union - public let name: String - public let description: String? - - public let possibleTypes: [ObjectTypeRef] -} - -extension UnionType { - public init( - name: String, - description: String?, - possibleTypes: [ObjectTypeRef] - ) { - self.name = name - self.description = description - self.possibleTypes = possibleTypes - } -} - -public struct EnumType: NamedTypeProtocol, Decodable, Equatable { - public var kind: NamedTypeKind = .enumeration - public let name: String - public let description: String? - - public let enumValues: [EnumValue] -} - -extension EnumType { - public init( - name: String, - description: String?, - values: [EnumValue] - ) { - self.name = name - self.description = description - self.enumValues = values - } -} - -public struct InputObjectType: NamedTypeProtocol, Decodable, Equatable { - public var kind: NamedTypeKind = .inputObject - public let name: String - public let description: String? - - public let inputFields: [InputValue] -} - -extension InputObjectType { - public init( - name: String, - description: String?, - fields: [InputValue] - ) { - self.name = name - self.description = description - self.inputFields = fields - } -} - -// MARK: - Collection Types - -public enum NamedType: Equatable { - case scalar(ScalarType) - case object(ObjectType) - case interface(InterfaceType) - case union(UnionType) - case `enum`(EnumType) - case inputObject(InputObjectType) - - /// Returns a name of the named type. - public var name: String { - switch self { - case let .enum(`enum`): - return `enum`.name - case let .inputObject(io): - return io.name - case let .interface(interface): - return interface.name - case let .object(object): - return object.name - case let .scalar(scalar): - return scalar.name - case let .union(union): - return union.name - } - } -} - -extension NamedType: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(NamedTypeKind.self, forKey: .kind) - - switch kind { - case .scalar: - let value = try ScalarType(from: decoder) - self = .scalar(value) - case .object: - let value = try ObjectType(from: decoder) - self = .object(value) - case .interface: - let value = try InterfaceType(from: decoder) - self = .interface(value) - case .union: - let value = try UnionType(from: decoder) - self = .union(value) - case .enumeration: - let value = try EnumType(from: decoder) - self = .enum(value) - case .inputObject: - let value = try InputObjectType(from: decoder) - self = .inputObject(value) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - } -} diff --git a/Sources/GraphQLAST/TypeRef.swift b/Sources/GraphQLAST/TypeRef.swift deleted file mode 100644 index ebf2d896..00000000 --- a/Sources/GraphQLAST/TypeRef.swift +++ /dev/null @@ -1,316 +0,0 @@ -import Foundation - -// MARK: - Types - -public enum IntrospectionTypeKind: String, Codable, Equatable { - case scalar = "SCALAR" - case object = "OBJECT" - case interface = "INTERFACE" - case union = "UNION" - case enumeration = "ENUM" - case inputObject = "INPUT_OBJECT" - case list = "LIST" - case nonNull = "NON_NULL" -} - -// MARK: - Reference Type - -/// Represents a GraphQL type reference. -public indirect enum TypeRef { - case named(Type) - case list(TypeRef) - case nonNull(TypeRef) - - // MARK: - Calculated properties - - /// Returns the non nullable self. - public var nonNullable: TypeRef { - inverted.nonNullable.inverted - } - - /// Makes the type optional. - public var nullable: TypeRef { - switch self { - case let .nonNull(subref): - return subref - default: - return self - } - } -} - -// MARK: - Possible Type References - -public enum NamedRef: Equatable { - case scalar(String) - case object(String) - case interface(String) - case union(String) - case `enum`(String) - case inputObject(String) - - /// Returns a name of the referenced type. - public var name: String { - switch self { - case let .scalar(name), let - .object(name), let - .interface(name), let - .union(name), let - .enum(name), let - .inputObject(name): - return name - } - } -} - -public enum ObjectRef: Equatable { - case object(String) - - public var name: String { - switch self { - case let .object(name): - return name - } - } -} - -public enum InterfaceRef: Equatable { - case interface(String) - - public var name: String { - switch self { - case let .interface(name): - return name - } - } -} - -public enum OutputRef: Equatable { - case scalar(String) - case object(String) - case interface(String) - case union(String) - case `enum`(String) - - public var name: String { - switch self { - case let .scalar(name), let - .object(name), let - .interface(name), let - .union(name), let - .enum(name): - return name - } - } -} - -public enum InputRef: Equatable { - case scalar(String) - case `enum`(String) - case inputObject(String) -} - -// MARK: - Extensions - -extension TypeRef: Decodable where Type: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(IntrospectionTypeKind.self, forKey: .kind) - - switch kind { - case .list: - let ref = try container.decode(TypeRef.self, forKey: .ofType) - self = .list(ref) - case .nonNull: - let ref = try container.decode(TypeRef.self, forKey: .ofType) - self = .nonNull(ref) - case .scalar, .object, .interface, .union, .enumeration, .inputObject: - let named = try Type(from: decoder) - self = .named(named) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - case ofType - } -} - -extension TypeRef: Equatable where Type: Equatable {} - -public extension TypeRef { - /// Returns the bottom most named type in reference. - var namedType: Type { - switch self { - case let .named(type): - return type - case let .nonNull(subRef), let .list(subRef): - return subRef.namedType - } - } -} - -extension NamedRef: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(NamedTypeKind.self, forKey: .kind) - let name = try container.decode(String.self, forKey: .name) - - switch kind { - case .scalar: - self = .scalar(name) - case .object: - self = .object(name) - case .interface: - self = .interface(name) - case .union: - self = .union(name) - case .enumeration: - self = .enum(name) - case .inputObject: - self = .inputObject(name) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - case name - } -} - -extension ObjectRef: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(NamedTypeKind.self, forKey: .kind) - let name = try container.decode(String.self, forKey: .name) - - switch kind { - case .object: - self = .object(name) - default: - throw DecodingError.typeMismatch( - OutputRef.self, - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Couldn't decode output object." - ) - ) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - case name - } -} - -extension InterfaceRef: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(NamedTypeKind.self, forKey: .kind) - let name = try container.decode(String.self, forKey: .name) - - switch kind { - case .interface: - self = .interface(name) - default: - throw DecodingError.typeMismatch( - OutputRef.self, - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Couldn't decode output object." - ) - ) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - case name - } -} - -extension OutputRef: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(NamedTypeKind.self, forKey: .kind) - let name = try container.decode(String.self, forKey: .name) - - switch kind { - case .scalar: - self = .scalar(name) - case .object: - self = .object(name) - case .interface: - self = .interface(name) - case .union: - self = .union(name) - case .enumeration: - self = .enum(name) - default: - throw DecodingError.typeMismatch( - OutputRef.self, - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Couldn't decode output object." - ) - ) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - case name - } -} - -extension InputRef: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let kind = try container.decode(NamedTypeKind.self, forKey: .kind) - let name = try container.decode(String.self, forKey: .name) - - switch kind { - case .scalar: - self = .scalar(name) - case .enumeration: - self = .enum(name) - case .inputObject: - self = .inputObject(name) - default: - throw DecodingError.typeMismatch( - InputRef.self, - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Couldn't decode output object." - ) - ) - } - } - - private enum CodingKeys: String, CodingKey { - case kind - case name - } -} - -// MARK: - Type Alias - -public typealias NamedTypeRef = TypeRef -public typealias OutputTypeRef = TypeRef -public typealias InputTypeRef = TypeRef -public typealias ObjectTypeRef = TypeRef -public typealias InterfaceTypeRef = TypeRef - -public extension ObjectTypeRef { - var name: String { - switch self { - case let .named(ref): - return ref.name - case let .list(ref): - return ref.namedType.name - case let .nonNull(ref): - return ref.namedType.name - } - } -} diff --git a/Sources/GraphQLAST/Value.swift b/Sources/GraphQLAST/Value.swift deleted file mode 100644 index 1958c102..00000000 --- a/Sources/GraphQLAST/Value.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Foundation - -/// Represents a single GraphQL field. -public struct Field: Decodable, Equatable { - public let name: String - public let description: String? - public let args: [InputValue] - public let type: OutputTypeRef - public let isDeprecated: Bool - public let deprecationReason: String? - - public init( - name: String, - description: String?, - args: [InputValue], - type: OutputTypeRef, - isDeprecated: Bool, - deprecationReason: String? - ) { - self.name = name - self.description = description - self.args = args - self.type = type - self.isDeprecated = isDeprecated - self.deprecationReason = deprecationReason - } -} - -/// Represents a GraphQL type that may be used as an input value. -public struct InputValue: Decodable, Equatable { - public let name: String - public let description: String? - public let type: InputTypeRef - - public init( - name: String, - description: String?, - type: InputTypeRef - ) { - self.name = name - self.description = description - self.type = type - } -} - -/// Represents a GraphQL enumerator case. -public struct EnumValue: Codable, Equatable { - public let name: String - public let description: String? - public let isDeprecated: Bool - public let deprecationReason: String? - - public init( - name: String, - description: String?, - isDeprecated: Bool, - deprecationReason: String? - ) { - self.name = name - self.description = description - self.isDeprecated = isDeprecated - self.deprecationReason = deprecationReason - } -} - diff --git a/Sources/SwiftGraphQL/Document/Field.swift b/Sources/SwiftGraphQL/Document/Field.swift index 9d3bac0f..12eacdd6 100644 --- a/Sources/SwiftGraphQL/Document/Field.swift +++ b/Sources/SwiftGraphQL/Document/Field.swift @@ -1,5 +1,4 @@ import Foundation -import SwiftGraphQLUtils /// GraphQLField represents a value in the selection. It contains information about /// what the selection should be and what the selected types are. diff --git a/Sources/SwiftGraphQLUtils/Extensions/String+Case.swift b/Sources/SwiftGraphQL/Extensions/String+Case.swift similarity index 100% rename from Sources/SwiftGraphQLUtils/Extensions/String+Case.swift rename to Sources/SwiftGraphQL/Extensions/String+Case.swift diff --git a/Sources/SwiftGraphQLCLI/main.swift b/Sources/SwiftGraphQLCLI/main.swift deleted file mode 100755 index 23d25412..00000000 --- a/Sources/SwiftGraphQLCLI/main.swift +++ /dev/null @@ -1,210 +0,0 @@ -import ArgumentParser -import Files -import Foundation -import GraphQLAST -import Spinner -import SwiftGraphQLCodegen -import System -import Yams - -SwiftGraphQLCLI.main() - -// MARK: - CLI - -struct SwiftGraphQLCLI: ParsableCommand { - // MARK: - Parameters - - @Argument(help: "GraphQL server endpoint or local file path from the current location.") - var endpoint: String - - @Option(help: "Relative path from CWD to your YML config file.") - var config: String? - - @Option(name: .shortAndLong, help: "Relative path from CWD to the output file.") - var output: String? - - @Option( - name: .shortAndLong, - help: "Custom headers to include in the request in format \"Header: Value\"" - ) - var header: [String] = [] - - // MARK: - Configuration - - static var configuration = CommandConfiguration( - commandName: "swift-graphql" - ) - - // MARK: - Main - - func run() throws { - print("Generating SwiftGraphQL Selection 🚀") - - // Make sure we get a valid endpoint file or URL endpoint. - guard var url = URL(string: endpoint) else { - SwiftGraphQLCLI.exit(withError: .endpoint) - } - - // Covnert relative URLs to absolute ones. - if url.scheme == nil { - guard #available(macOS 12, *) else { - SwiftGraphQLCLI.exit(withError: .legacy) - } - - var cwd = FilePath(FileManager.default.currentDirectoryPath) - if endpoint.starts(with: "/") { - cwd = FilePath("/") - } - - guard let fileurl = URL(cwd.appending(endpoint)) else { - SwiftGraphQLCLI.exit(withError: .endpoint) - } - url = fileurl - } - - // Load configuration if config path is present, otherwise use default. - let config: Config - - if let configPath = self.config { - let raw = try Folder.current.file(at: configPath).read() - config = try Config(from: raw) - } else { - config = Config() - } - - if !self.header.isEmpty { - print("Adding headers to your request:") - } - - // Add headers to the request. - var headers: [String: String] = [:] - for header in self.header { - // Each header is split into two parts on the `: ` - // separator as in cURL spec. - let parts = header.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true) - guard parts.count == 2 else { - SwiftGraphQLCLI.exit(withError: .header) - } - - let name = String(parts[0]) - let value = parts[1].trimmingCharacters(in: CharacterSet.whitespaces) - headers[name] = value - - print(" - \(name): \(value)") - } - - // Fetch the schema. - let loadSchemaSpinner = Spinner(.dots, "Fetching GraphQL Schema") - loadSchemaSpinner.start() - let schema: Schema - do { - schema = try Schema(from: url, withHeaders: headers) - } catch(let err) { - print(err.localizedDescription) - SwiftGraphQLCLI.exit(withError: .unreachable) - } - - loadSchemaSpinner.success("Schema loaded!") - - // Generate the code. - let generateCodeSpinner = Spinner(.dots, "Generating API") - generateCodeSpinner.start() - - let scalars = ScalarMap(scalars: config.scalars) - let generator = GraphQLCodegen(scalars: scalars) - let code: String - - do { - code = try generator.generate(schema: schema) - generateCodeSpinner.success("API generated successfully!") - } catch CodegenError.formatting(let err) { - generateCodeSpinner.error(err.localizedDescription) - SwiftGraphQLCLI.exit(withError: .formatting) - } catch IntrospectionError.emptyfile, IntrospectionError.unknown { - SwiftGraphQLCLI.exit(withError: .introspection) - } catch IntrospectionError.error(let err) { - generateCodeSpinner.error(err.localizedDescription) - SwiftGraphQLCLI.exit(withError: .introspection) - } catch { - SwiftGraphQLCLI.exit(withError: .unknown) - } - - // Write to target file or stdout. - if let outputPath = output { - try Folder.current.createFile(at: outputPath).write(code) - } else { - FileHandle.standardOutput.write(code.data(using: .utf8)!) - } - - let analyzeSchemaSpinner = Spinner(.dots, "Analyzing Schema") - analyzeSchemaSpinner.start() - - // Warn about the unused scalars. - let ignoredScalars = try schema.missing(from: scalars) - guard !ignoredScalars.isEmpty else { - analyzeSchemaSpinner.success("SwiftGraphQL Ready!") - return - } - - analyzeSchemaSpinner.stop() - - let message = """ - Your schema contains some unknown scalars: - - \(ignoredScalars.map { " - \($0)" }.joined(separator: "\n")) - - Add them to the config to get better type support! - """ - print(message) - } -} - -// MARK: - Configuraiton - - -/** - Configuration file specification for `swiftgraphql.yml`. - - ```yml - scalars: - Date: DateTime - ``` - */ -struct Config: Codable, Equatable { - /// Key-Value dictionary of scalar mappings. - let scalars: [String: String] - - // MARK: - Initializers - - /// Creates an empty configuration instance. - init() { - self.scalars = [:] - } - - /// Tries to decode the configuration from a string. - init(from data: Data) throws { - let decoder = YAMLDecoder() - self = try decoder.decode(Config.self, from: data) - } -} - -// MARK: - Errors - -enum SwiftGraphQLGeneratorError: String, Error { - case endpoint = "Invalid endpoint!" - case legacy = "Please update your MacOS to use schema from a file." - case formatting = "There was an error formatting the code. Make sure your Swift version (i.e. `swift-format`) matches the `swift-format` version. If you need any help, don't hesitate to open an issue and include the log above!" - case unknown = "Something unexpected happened. Please open an issue and we'll help you out!" - case introspection = "Couldn't introspect the schema." - case header = "Invalid header format. Use `Header: Value` to specify a single header." - case unreachable = "Couldn't reach GraphQL server at given endpoint." -} - -extension String: Error {} - -extension ParsableCommand { - /// Exits the program with an internal error. - static func exit(withError error: SwiftGraphQLGeneratorError? = nil) -> Never { - Self.exit(withError: error?.rawValue) - } -} diff --git a/Sources/SwiftGraphQLCodegen/Context.swift b/Sources/SwiftGraphQLCodegen/Context.swift deleted file mode 100644 index 4cf9c393..00000000 --- a/Sources/SwiftGraphQLCodegen/Context.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import GraphQLAST - -/// A type that is passed down to every field that requires contextual information. -struct Context { - - /// Currently processed schema. - var schema: Schema - - /// Available scalars on the client. - var scalars: ScalarMap -} diff --git a/Sources/SwiftGraphQLCodegen/Error.swift b/Sources/SwiftGraphQLCodegen/Error.swift deleted file mode 100644 index d56e34c8..00000000 --- a/Sources/SwiftGraphQLCodegen/Error.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -/// Errors that might occur during the code-generation phase. -public enum CodegenError: Error { - - /// There was a problem with the formatting of the library. - case formatting(Error) -} diff --git a/Sources/SwiftGraphQLCodegen/Extensions/Collection+Extensions.swift b/Sources/SwiftGraphQLCodegen/Extensions/Collection+Extensions.swift deleted file mode 100644 index 02e8e9ab..00000000 --- a/Sources/SwiftGraphQLCodegen/Extensions/Collection+Extensions.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -extension Collection { - /// Returns a list of unique items. - /// - /// - Note: Item's uniqueness is determined by the hash value of that item. - /// If there are more items with the same hash, we use the last one. - func unique(by: (Element) -> T) -> [Element] where T: Hashable { - var dict = [T: Element]() - - for item in self { - dict[by(item)] = item - } - - return [Element](dict.values) - } - - /// Returns a mapped instance of each value. - func indexMap(fn: ((offset: Int, element: Self.Element)) throws -> T) rethrows -> [T] { - try enumerated().map(fn) - } -} diff --git a/Sources/SwiftGraphQLCodegen/Extensions/Schema+Extensions.swift b/Sources/SwiftGraphQLCodegen/Extensions/Schema+Extensions.swift deleted file mode 100644 index 2e96f152..00000000 --- a/Sources/SwiftGraphQLCodegen/Extensions/Schema+Extensions.swift +++ /dev/null @@ -1,11 +0,0 @@ -import GraphQLAST - -extension Schema { - - /// Returns a list of missing scalars that are used in the schema, but not supported in - /// the provided scalar map. - public func missing(from scalars: ScalarMap) throws -> [String] { - let missing = self.scalars.filter { !scalars.supported.contains($0.name) } - return missing.map { $0.name } - } -} diff --git a/Sources/SwiftGraphQLCodegen/Extensions/String+Extensions.swift b/Sources/SwiftGraphQLCodegen/Extensions/String+Extensions.swift deleted file mode 100644 index 3395a6f8..00000000 --- a/Sources/SwiftGraphQLCodegen/Extensions/String+Extensions.swift +++ /dev/null @@ -1,114 +0,0 @@ -import Foundation -import SwiftFormat -import SwiftFormatConfiguration - -extension String { - /// Formats the given Swift source code. - /// - /// - NOTE: Make sure your Swift version (i..e `swift --version`, matches the toolchain - /// version of Swift Format. Read more about it at https://github.com/apple/swift-format#matching-swift-format-to-your-swift-version. - func format() throws -> String { - let trimmed = trimmingCharacters( - in: CharacterSet.newlines.union(.whitespaces) - ) - - let formatter = SwiftFormatter(configuration: Configuration()) - - var output = "" - - do { - try formatter.format(source: trimmed, assumingFileURL: nil, to: &output) - } catch(let err) { - throw CodegenError.formatting(err) - } - - let blanks = CharacterSet.newlines.union(CharacterSet.whitespaces) - return output.trimmingCharacters(in: blanks) - } - - /// Adds backticks on reserved words. - /// - /// - NOTE: Function arguments don't need to be normalized. - var normalize: String { - if reservedWords.contains(self) { - return "`\(self)`" - } - return self - } -} - -// https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html -let reservedWords = [ - /* Keywords used in delcarations. */ - "associatedtype", - "class", - "deinit", - "enum", - "extension", - "fileprivate", - "func", - "import", - "init", - "inout", - "internal", - "let", - "open", - "operator", - "private", - "protocol", - "public", - "rethrows", - "static", - "struct", - "subscript", - "typealias", - "var", - /* Keywords used in statements */ - "break", - "case", - "continue", - "default", - "defer", - "do", - "else", - "fallthrough", - "for", - "guard", - "if", - "in", - "repeat", - "return", - "switch", - "where", - "while", - /* Keywords used in expressions and types */ - "as", - "Any", - "catch", - "false", - "is", - "nil", - "super", - "self", - "Self", - "throw", - "throws", - "true", - "try", - /* Booleans */ - "not", - "and", - "or", - /* Keywords used in patterns */ - "_", - // NOTE: There are other reserved keywords, but they aren't important in this context. -] - - -extension Collection where Element == String { - - /// Returns a string showing each string in new line. - var lines: String { - joined(separator: "\n") - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator.swift b/Sources/SwiftGraphQLCodegen/Generator.swift deleted file mode 100644 index e15062cf..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator.swift +++ /dev/null @@ -1,92 +0,0 @@ -import Foundation -import GraphQLAST - -/// Structure that holds methods for SwiftGraphQL query-builder generation. -public struct GraphQLCodegen { - - /// Map of supported scalars. - private let scalars: ScalarMap - - // MARK: - Initializer - - public init(scalars: ScalarMap) { - self.scalars = scalars - } - - // MARK: - Methods - - /// Generates a SwiftGraphQL Selection File (i.e. the code that tells how to define selections). - public func generate(schema: Schema) throws -> String { - let context = Context(schema: schema, scalars: self.scalars) - - let subscription = schema.operations.first { $0.isSubscription }?.type.name - - // Code Parts - let operations = schema.operations.map { $0.declaration() } - let objectDefinitions = try schema.objects.map { object in - try object.declaration( - objects: schema.objects, - context: context, - alias: object.name != subscription - ) - } - - let staticFieldSelection = try schema.objects.map { object in - try object.statics(context: context) - } - - let interfaceDefinitions = try schema.interfaces.map { - try $0.declaration(objects: schema.objects, context: context) - } - - let unionDefinitions = try schema.unions.map { - try $0.declaration(objects: schema.objects, context: context) - } - - let enumDefinitions = schema.enums.map { $0.declaration } - - let inputObjectDefinitions = try schema.inputObjects.map { - try $0.declaration(context: context) - } - - // API - let code = """ - // This file was auto-generated using maticzav/swift-graphql. DO NOT EDIT MANUALLY! - import Foundation - import GraphQL - import SwiftGraphQL - - // MARK: - Operations - public enum Operations {} - \(operations.lines) - - // MARK: - Objects - public enum Objects {} - \(objectDefinitions.lines) - \(staticFieldSelection.lines) - - // MARK: - Interfaces - public enum Interfaces {} - \(interfaceDefinitions.lines) - - // MARK: - Unions - public enum Unions {} - \(unionDefinitions.lines) - - // MARK: - Enums - public enum Enums {} - \(enumDefinitions.lines) - - // MARK: - Input Objects - - /// Utility pointer to InputObjects. - public typealias Inputs = InputObjects - - public enum InputObjects {} - \(inputObjectDefinitions.lines) - """ - - let formatted = try code.format() - return formatted - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Codable.swift b/Sources/SwiftGraphQLCodegen/Generator/Codable.swift deleted file mode 100644 index 52c28bb5..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Codable.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - - -/// Structure protocol outlines anything that might be made codable (e.g. GraphQL Objects, Interfaces and Unions). -/// -/// To decode a response we first decode values to an intermediate type. We first decode the key -/// that references a result and use the engraved in the alias to further decode the result. The result is -/// saved into a HashMap structure that groups fields with the same type. -protocol Structure { - - /// Name of the GraphQL type that corresponds to this structure - var name: String { get } - - /// Fields that are shared between all types in the structure. - var fields: [Field] { get } - - /// References to the type those fields may be part of. - var possibleTypes: [ObjectTypeRef] { get } -} - -// MARK: - Decoder - -private extension Collection where Element == ObjectTypeRef { - - /// Returns an enumerator that we use to decode typename field. - func typenamesEnum() -> String { - let types = self - .map { "case \($0.name.camelCasePreservingSurroundingUnderscores.normalize) = \"\($0.name)\"" } - .joined(separator: "\n") - - return """ - public enum TypeName: String, Codable { - \(types) - } - """ - } -} - -extension OutputRef { - - /// Returns an internal reference to the given output type ref. - func type(scalars: ScalarMap) throws -> String { - switch self { - case let .scalar(scalar): - return try scalars.scalar(scalar) - case let .enum(enm): - return "Enums.\(enm.pascalCase)" - case let .object(type): - return "Objects.\(type.pascalCase)" - case let .interface(type): - return "Interfaces.\(type.pascalCase)" - case let .union(type): - return "Unions.\(type.pascalCase)" - } - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Enum.swift b/Sources/SwiftGraphQLCodegen/Generator/Enum.swift deleted file mode 100644 index 24f58ba0..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Enum.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -extension EnumType { - - /// Represents the enum structure. - var declaration: String { - """ - extension Enums { - \(docs) - public enum \(name.pascalCase): String, CaseIterable, Codable { - \(values) - } - } - - extension Enums.\(name.pascalCase): GraphQLScalar { - \(decode) - - \(mock) - } - """ - } - - // MARK: - Definition - - private var docs: String { - (description ?? name).split(separator: "\n").map { "/// \($0)" }.joined(separator: "\n") - } - - /// Represents possible enum cases. - private var values: String { - enumValues.map { $0.declaration }.joined(separator: "\n") - } - - // MARK: - GraphQL Scalar - - private var decode: String { - return """ - public init(from data: AnyCodable) throws { - switch data.value { - case let string as String: - if let value = Enums.\(self.name.pascalCase)(rawValue: string) { - self = value - } else { - throw ScalarDecodingError.unknownEnumCase(value: string) - } - default: - throw ScalarDecodingError.unexpectedScalarType( - expected: "\(self.name)", - received: data.value - ) - } - } - """ - } - - /// Mock value declaration. - private var mock: String { - let value = self.enumValues.first! - return "public static var mockValue = Self.\(value.name.camelCasePreservingSurroundingUnderscores.normalize)" - } -} - -// MARK: - EnumValue - -extension EnumValue { - - /// Returns an enum case definition. - fileprivate var declaration: String { - """ - \(docs) - case \(name.camelCasePreservingSurroundingUnderscores.normalize) = "\(name)" - """ - } - - private var docs: String { - if let description = self.description { - return description.split(separator: "\n").map { "/// \($0)" }.joined(separator: "\n") - } - return "" - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Field.swift b/Sources/SwiftGraphQLCodegen/Generator/Field.swift deleted file mode 100644 index 63ee4a5e..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Field.swift +++ /dev/null @@ -1,367 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -/* - We use functions - selections - to construct a query. - - Each selection function contains a selection part that is responsible for - telling the Selection about its existance and a decoder part that - checks for the result and returns it as a function value. - - The last part of the function is a mock value which we use as a placeholder - of the return value on the first run when we collect the selection. - */ - -// MARK: - Function Definition - -/* - This section contains functions that we use to generate the selection - function itself. - */ - -extension Collection where Element == Field { - - /// Returns dynamic selection function for every field in the collection. - func getDynamicSelections(parent: String, context: Context) throws -> String { - try self.map { try $0.getDynamicSelection(parent: parent, context: context) } - .joined(separator: "\n") - } - - /// Returns static selection function for every field in the collection. - func getStaticSelections(for type: ObjectType, context: Context) throws -> String { - try self.map { try $0.getStaticSelection(for: type, context: context) }.joined(separator: "\n") - } -} - -extension Field { - - /// Returns a function that may be used to create dynamic selection (i.e. a special subcase of a type) using SwiftGraphQL. - func getDynamicSelection(parent: String, context: Context) throws -> String { - let parameters = try fParameters(context: context) - let output = try type.dynamicReturnType(context: context) - - let code = """ - \(docs) - \(availability) - public func \(fName)\(parameters) throws -> \(output) { - \(self.selection(parent: parent)) - self.__select(field) - - switch self.__state { - case .decoding: - \(try self.decoder(parent: parent, context: context)) - case .selecting: - return \(try type.mock(context: context)) - } - } - """ - - return code - } - - /// Returns a function that may be used to select a single field in the object. - func getStaticSelection(for type: ObjectType, context: Context) throws -> String { - let parameters = try fParameters(context: context) - let typelock = "Objects.\(type.name.pascalCase)" - let returnType = try self.type.dynamicReturnType(context: context) - let args = self.args.arguments(field: self, context: context) - - let code = """ - \(docs) - \(availability) - public static func \(fName)\(parameters) -> Selection<\(returnType), \(typelock)> { - Selection<\(returnType), \(typelock)> { - try $0.\(fName)\(args) - } - } - """ - - return code - } - - private var docs: String { - if let description = self.description { - return description.split(separator: "\n").map { "/// \($0)" }.joined(separator: "\n") - } - return "" - } - - private var availability: String { - if isDeprecated { - // NOTE: It's possible that a string contains double-quoted characters in deprecation reason. - // http://spec.graphql.org/October2021/#sec-Language.Directives - let message = deprecationReason?.replacingOccurrences(of: "\"", with: "\\\"") ?? "" - if message.contains("\n") { - return "@available(*, deprecated, message: \"\"\"\n\(message)\n\"\"\")" - } else { - return "@available(*, deprecated, message: \"\(message)\")" - } - } - return "" - } - - private var fName: String { - name.camelCasePreservingSurroundingUnderscores.normalize - } - - private func fParameters(context: Context) throws -> String { - try args.parameters(field: self, context: context, typelock: type.type(for: typelock)) - } - - /// Returns a typelock value for this field. - private var typelock: String { - switch type.namedType { - case let .object(typelock): - return "Objects.\(typelock.pascalCase)" - case let .interface(typelock): - return "Interfaces.\(typelock.pascalCase)" - case let .union(typelock): - return "Unions.\(typelock.pascalCase)" - default: - return "" - } - } -} - -// MARK: - Parameters - -/* - This section contains function used to generate function parameters. - Some functions here rely on function from InputObject file. - */ - -private extension Collection where Element == InputValue { - /// Returns a function parameter definition. - func parameters(field: Field, context: Context, typelock: String) throws -> String { - // We only return parameters when given scalars. If the function is referencing another type, - // however, we also generate a generic type and add arguments. - let params = try map { try $0.parameter(context: context) }.joined(separator: ", ") - - switch field.type.namedType { - case .scalar, .enum: - return "(\(params))" - default: - if isEmpty { - return "(selection: Selection)" - } - return "(\(params), selection: Selection)" - } - } - - /// Returns a one-to-one argument mapping. - func arguments(field: Field, context: Context) -> String { - let args = self - .map { $0.name.camelCasePreservingSurroundingUnderscores }.map { "\($0): \($0.normalize)" } - .joined(separator: ", ") - - switch field.type.namedType { - case .scalar, .enum: - return "(\(args))" - default: - if isEmpty { - return "(selection: selection)" - } - return "(\(args), selection: selection)" - } - } -} - -extension InputValue { - /// Generates a function parameter for this input value. - fileprivate func parameter(context: Context) throws -> String { - "\(name.camelCasePreservingSurroundingUnderscores.normalize): \(try type.type(scalars: context.scalars)) \(self.default)" - } - - /// Returns the default value of the parameter. - private var `default`: String { - switch type.inverted { - case .nullable: - return "= .init()" - default: - return "" - } - } -} - -// MARK: - Selection - -/* - This section contains function that we use to generate parts of the code - that tell SwiftGraphQL how to construct the query. - */ - -private extension Field { - - /// Generates an internal leaf definition used for composing selection set. - func selection(parent: String) -> String { - switch type.namedType { - case .scalar, .enum: - return """ - let field = GraphQLField.leaf( - field: \"\(name)\", - parent: \"\(parent)\", - arguments: [ \(args.arguments) ] - ) - """ - case .interface, .object, .union: - return """ - let field = GraphQLField.composite( - field: "\(name)", - parent: "\(parent)", - type: "\(self.type.namedType.name)", - arguments: [ \(args.arguments) ], - selection: selection.__selection() - ) - """ - } - } -} - -private extension Collection where Element == InputValue { - /// Returns a list of SwiftGraphQL Argument definitions that SwiftGraphQL accepts to create a GraphQL query. - var arguments: String { - map { $0.argument }.joined(separator: ",") - } -} - -private extension InputValue { - /// Returns a SwiftGraphQL Argument definition for a given input value. - var argument: String { - #"Argument(name: "\#(name)", type: "\#(type.argument)", value: \#(name.camelCasePreservingSurroundingUnderscores.normalize))"# - } -} - -extension InputTypeRef { - /// Generates an argument definition that we use to make selection using the client. - var argument: String { - /* - We use this variable recursively on list and null references. - */ - switch self { - case let .named(named): - switch named { - case let .enum(name), let .inputObject(name), let .scalar(name): - return name - } - case let .list(subref): - return "[\(subref.argument)]" - case let .nonNull(subref): - return "\(subref.argument)!" - } - } -} - -// MARK: - Decoders - -/* - This section contains functions that we use to generate decoders - for a selection. - */ - -private extension Field { - - /// Returns selection decoder for this field. - func decoder(parent: String, context: Context) throws -> String { - let internalType = try self.type.namedType.type(scalars: context.scalars) - let wrappedType = self.type.type(for: internalType) - - switch self.type.namedType { - case .scalar, .enum: - return "return try self.__decode(field: field.alias!) { try \(wrappedType)(from: $0) }" - - case .interface, .object, .union: - return "return try self.__decode(field: field.alias!) { try selection.__decode(data: $0) }" - } - } -} - -// MARK: - Mocking - -/* - This section contains functions that we use to create mock - values for a given field. - */ - -extension OutputTypeRef { - /// Generates mock data for this output ref. - func mock(context: Context) throws -> String { - switch self.namedType { - case .scalar(let scalar): - let type = try context.scalars.scalar(scalar) - return mock(value: "\(type).mockValue") - case .enum(let enm): - return mock(value: "Enums.\(enm.pascalCase).mockValue") - case .interface, .object, .union: - return "try selection.__mock()" - } - } - - /// Returns a mock value wrapped according to ref. - private func mock(value: String) -> String { - self.inverted.mock(value: value) - } -} - -extension InvertedOutputTypeRef { - - /// Returns a mock value wrapped according to ref. - func mock(value: String) -> String { - switch self { - case .named: - return value - case .list: - return "[]" - case .nullable: - return "nil" - } - } - -} - -// MARK: - Output Types - -/* - This section contains functions that we use to generate return - types of fields. - */ - -private extension OutputTypeRef { - - /// Returns a return type of a referrable type. - func dynamicReturnType(context: Context) throws -> String { - switch namedType { - case let .scalar(scalar): - let scalar = try context.scalars.scalar(scalar) - return type(for: scalar) - case let .enum(enm): - return type(for: "Enums.\(enm.pascalCase)") - case .interface, .object, .union: - return "T" - } - } - -} - -extension TypeRef { - - /// Returns a wrapped instance of a given type respecting the reference. - func type(for name: String) -> String { - inverted.type(for: name) - } -} - -extension InvertedTypeRef { - - /// Returns a wrapped instance of a given type respecting the reference. - func type(for name: String) -> String { - switch self { - case .named: - return name - case let .list(subref): - return "[\(subref.type(for: name))]" - case let .nullable(subref): - return "\(subref.type(for: name))?" - } - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Fragments.swift b/Sources/SwiftGraphQLCodegen/Generator/Fragments.swift deleted file mode 100644 index 2cf279f7..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Fragments.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -extension Collection where Element == ObjectTypeRef { - - /// Returns a function that may create fragment selection for a type of a given interface or union. - /// - /// - parameter name: Name of the fragment this selection is for. - /// - parameter objects: List of all objects in the scheama. - func selection(name type: String, objects: [ObjectType]) -> String { - """ - extension Fields where TypeLock == \(type) { - public func on(\(parameters)) throws -> T { - self.__select([\(selection(interface: type))]) - - switch self.__state { - case .decoding(let data): - let typename = try self.__decode(field: "__typename") { $0.value as? String } - switch typename { - \(self.decoders(objects: objects)) - default: - throw ObjectDecodingError.unknownInterfaceType(interface: "\(type)", typename: typename) - } - case .selecting: - return try \(mock).__mock() - } - } - } - """ - } - - private var parameters: String { - map { $0.parameter }.joined(separator: ", ") - } - - /// Creates a field selection variables for the given interface. - /// - /// - parameter interace: The name of the union or interface. - private func selection(interface: String) -> String { - map { $0.fragment(interface: interface) }.joined(separator: ",\n") - } - - /// Functions used to decode response values. - private func decoders(objects: [ObjectType]) -> String { - map { $0.decoder(objects: objects) }.lines - } - - /// Type used to - private var mock: String { - self.first!.namedType.name.camelCasePreservingSurroundingUnderscores - } -} - -private extension ObjectTypeRef { - /// Returns a parameter definition for a given type reference. - var parameter: String { - "\(namedType.name.camelCasePreservingSurroundingUnderscores): Selection" - } - - /// Returns a SwiftGraphQL Fragment selection. - func fragment(interface: String) -> String { - #"GraphQLField.fragment(type: "\#(namedType.name)", interface: "\#(interface)", selection: \#(namedType.name.camelCasePreservingSurroundingUnderscores).__selection())"# - } - - /// Returns a decoder for a fragment. - /// - /// - parameter objects: List of all objects that appear in the schema. - func decoder(objects: [ObjectType]) -> String { - let name = namedType.name - return """ - case "\(name)": - return try \(name.camelCasePreservingSurroundingUnderscores).__decode(data: data) - """ - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/InputObject.swift b/Sources/SwiftGraphQLCodegen/Generator/InputObject.swift deleted file mode 100644 index 71e3b640..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/InputObject.swift +++ /dev/null @@ -1,157 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -// MARK: - Struct Definition - -extension InputObjectType { - /// Returns the code that represents a particular InputObjectType in our schema. It contains - /// an encoder as well as the function used to add values into it. - func declaration(context: Context) throws -> String { - return """ - extension InputObjects { - public struct \(self.name.pascalCase): Encodable, Hashable { - - \(try self.inputFields.map { try $0.declaration(context: context) }.joined(separator: "\n")) - - public init( - \(try self.inputFields.map { - try $0.initDeclaration(context: context, isLast: $0 == self.inputFields.last) - }.joined(separator: ",\n")) - ) { - \(try self.inputFields.map { - try $0.initFields(context: context) - }.joined(separator: "\n")) - } - - \(self.inputFields.encoder) - - \(self.inputFields.codingKeys) - } - } - """ - } -} - -// MARK: - Fields - -/* - This section contains functions that we use to generate field definitions - of an input object. - */ - -extension InputValue { - /// Returns a declaration of the input value (i.e. the property definition, docs and default value. - fileprivate func declaration(context: Context) throws -> String { - """ - \(docs) - public var \(name.camelCasePreservingSurroundingUnderscores.normalize): \(try type.type(scalars: context.scalars)) - """ - } - - fileprivate func initDeclaration(context: Context, isLast: Bool) throws -> String { - """ - \(name.camelCasePreservingSurroundingUnderscores.normalize): \(try type.type(scalars: context.scalars))\(self.default) - """ - } - - fileprivate func initFields(context: Context) throws -> String { - """ - self.\(name.camelCasePreservingSurroundingUnderscores.normalize) = \(name.camelCasePreservingSurroundingUnderscores.normalize) - """ - } - - private var docs: String { - if let description = self.description { - return description.split(separator: "\n").map { "/// \($0)" }.joined(separator: "\n") - } - return "" - } - - /// The default value if the value is nullable. - private var `default`: String { - switch type.inverted { - case .nullable: - return " = .init()" - default: - return "" - } - } -} - -extension InputTypeRef { - /// Returns an internal type for a given input type ref. - func type(scalars: ScalarMap) throws -> String { - try inverted.type(scalars: scalars) - } -} - -extension InvertedInputTypeRef { - /// Returns an internal type for a given input type ref. - func type(scalars: ScalarMap) throws -> String { - switch self { - case let .named(named): - switch named { - case let .scalar(scalar): - return try scalars.scalar(scalar) - case let .enum(enm): - return "Enums.\(enm.pascalCase)" - case let .inputObject(inputObject): - return "InputObjects.\(inputObject.pascalCase)" - } - case let .list(subref): - return "[\(try subref.type(scalars: scalars))]" - case let .nullable(subref): - return "OptionalArgument<\(try subref.type(scalars: scalars))>" - } - } -} - -// MARK: - Codable - -/* - This section contains functions that we use to make an input object - conform to codable protocol. - */ - -private extension Collection where Element == InputValue { - /// Generates encoder function for an input object. - var encoder: String { - """ - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - \(map { $0.encoder }.joined(separator: "\n")) - } - """ - } - - /// Returns a codingkeys enumerator that we can use to create a codable out of our type. - var codingKeys: String { - """ - public enum CodingKeys: String, CodingKey { - \(map { $0.codingKey }.joined(separator: "\n")) - } - """ - } -} - -private extension InputValue { - /// Returns an encoder for this input value. - var encoder: String { - let key = name.camelCasePreservingSurroundingUnderscores.normalize - - switch type.inverted { - case .nullable: - // Only encode nullables when they have a value. - return "if \(key).hasValue { try container.encode(\(key), forKey: .\(key)) }" - default: - // Always encode keys that are not optional. - return "try container.encode(\(key), forKey: .\(key))" - } - } - - /// Returns a coding key for this input value. - var codingKey: String { - "case \(name.camelCasePreservingSurroundingUnderscores.normalize) = \"\(name)\"" - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Interface.swift b/Sources/SwiftGraphQLCodegen/Generator/Interface.swift deleted file mode 100644 index d5d79236..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Interface.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -/* - This file contains the code that we use to generate interfaces. - Interfaces generate a selection for common fields as well as discriminating - function that allows developers to select fields from a union type. - */ - -extension InterfaceType: Structure {} - -extension InterfaceType { - - /// Returns a code that represents an interface. - /// - /// - parameter objects: List of all objects in the schema. - func declaration(objects: [ObjectType], context: Context) throws -> String { - let name = self.name.pascalCase - let fields = try self.fields.getDynamicSelections(parent: self.name, context: context) - - return """ - extension Interfaces { - public struct \(name) {} - } - - extension Fields where TypeLock == Interfaces.\(name) { - \(fields) - } - - \(possibleTypes.selection(name: "Interfaces.\(name)", objects: objects)) - - extension Selection where T == Never, TypeLock == Never { - public typealias \(name) = Selection - } - """ - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Object.swift b/Sources/SwiftGraphQLCodegen/Generator/Object.swift deleted file mode 100644 index 30968eb3..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Object.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -extension ObjectType: Structure { - var possibleTypes: [ObjectTypeRef] { - [ObjectTypeRef.named(ObjectRef.object(name))] - } -} - -extension ObjectType { - - /// Creates deifnitions used by SwiftGraphQL to make selection and decode a particular object. - /// - /// - parameter objects: All objects in the schema. - /// - parameter alias: Tells whether the generated code should include utility `Selection.Type` alias. - func declaration(objects: [ObjectType], context: Context, alias: Bool = true) throws -> String { - let apiName = self.name.pascalCase - let selection = try self.fields.getDynamicSelections(parent: self.name, context: context) - - var code = """ - extension Objects { - public struct \(apiName) {} - } - - extension Fields where TypeLock == Objects.\(apiName) { - \(selection) - } - - """ - - guard alias else { - return code - } - - // Adds utility alias for the selection. - code.append(""" - extension Selection where T == Never, TypeLock == Never { - public typealias \(apiName) = Selection - } - """) - - return code - } - - /// Generates utility code that may be used to select a single field from the object using a static function. - /// - /// - parameter alias: Tells whether the code should include utility reference in `Selection.Type`. - func statics(context: Context) throws -> String { - let name = self.name.pascalCase - let selections = try self.fields.getStaticSelections(for: self, context: context) - - let code = """ - extension Objects.\(name) { - \(selections) - } - """ - - return code - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Operation.swift b/Sources/SwiftGraphQLCodegen/Generator/Operation.swift deleted file mode 100644 index 1cfe59ff..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Operation.swift +++ /dev/null @@ -1,44 +0,0 @@ -import GraphQLAST -import SwiftGraphQLUtils - -extension Operation { - - /// Tells whether the operation is a subscription. - var isSubscription: Bool { - switch self { - case .subscription: - return true - default: - return false - } - } - - /// Returns a definition of an operation. - func declaration() -> String { - """ - extension Objects.\(type.name.pascalCase): \(self.protocol) { - public static var operation: GraphQLOperationKind { .\(operation) } - } - """ - } - - private var operation: String { - switch self { - case .query: - return "query" - case .mutation: - return "mutation" - case .subscription: - return "subscription" - } - } - - private var `protocol`: String { - switch self { - case .query, .mutation: - return "GraphQLHttpOperation" - case .subscription: - return "GraphQLWebSocketOperation" - } - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Scalar.swift b/Sources/SwiftGraphQLCodegen/Generator/Scalar.swift deleted file mode 100644 index 83985c0c..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Scalar.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation - -/// Structure that maps GraphQL scalars to their Swift types. -public struct ScalarMap: Equatable { - /// Dictionary of Swift scalars indexed by the scalar name in GraphQL schema. - private var definitions: [String: String] - - public init() { - self.definitions = [:] - } - - public init(scalars: [String: String]) { - self.definitions = scalars - } - - /// Converts a scalar from the GraphQL schema to a Swift type. - func scalar(_ name: String) throws -> String { - if let mapping = self.definitions[name] { - return mapping - } - - if let mapping = ScalarMap.builtin[name] { - return mapping - } - - return "AnyCodable" - } - - /// List of GraphQL scaars that the scalar map supports. - var supported: Set { - let keys = Array(self.definitions.keys) + Array(ScalarMap.builtin.keys) - let scalars = Set(keys) - - return scalars - } - - /// A map of built-in scalars where Swift types are indexed by the GraphQL type. - static private var builtin: [String: String] { - [ - "ID": "String", - "String": "String", - "Int": "Int", - "Boolean": "Bool", - "Float": "Double", - ] - } -} diff --git a/Sources/SwiftGraphQLCodegen/Generator/Union.swift b/Sources/SwiftGraphQLCodegen/Generator/Union.swift deleted file mode 100644 index 6055baae..00000000 --- a/Sources/SwiftGraphQLCodegen/Generator/Union.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation -import GraphQLAST -import SwiftGraphQLUtils - -/* - This file contains code used to generate unions. - Unions consist of an overarching structure definition and - */ - -extension UnionType: Structure { - var fields: [Field] { - [] // Unions come with no predefined fields. - } -} - -extension UnionType { - - /// Returns a declaration of the union type that we add to the generated file. - /// - parameter objects: - func declaration(objects: [ObjectType], context: Context) throws -> String { - let name = self.name.pascalCase - let selections = possibleTypes.selection(name: "Unions.\(name)", objects: objects) - - return """ - extension Unions { - public struct \(name) {} - } - - \(selections) - - extension Selection where T == Never, TypeLock == Never { - public typealias \(name) = Selection - } - """ - } -} diff --git a/Tests/GraphQLASTTests/ASTIntrospectionTests.swift b/Tests/GraphQLASTTests/ASTIntrospectionTests.swift deleted file mode 100644 index 50f052c9..00000000 --- a/Tests/GraphQLASTTests/ASTIntrospectionTests.swift +++ /dev/null @@ -1,14 +0,0 @@ -@testable import GraphQLAST -import XCTest - -final class ASTIntrospectionTests: XCTestCase { - /// Test that it's possible to load schema from a URL. - func testLoadSchemaFromURL() throws { - let url = URL(string: "http://127.0.0.1:4000/graphql")! - let schema = try Schema(from: url) - - /* Tests */ - - XCTAssertNotNil(schema) - } -} diff --git a/Tests/GraphQLASTTests/ASTTests.swift b/Tests/GraphQLASTTests/ASTTests.swift deleted file mode 100644 index 75a3868b..00000000 --- a/Tests/GraphQLASTTests/ASTTests.swift +++ /dev/null @@ -1,83 +0,0 @@ -@testable import GraphQLAST -import XCTest - -final class ASTTests: XCTestCase { - /* Schema */ - - func testDecodeSchema() throws { - let json = """ - { - "data": { - "__schema": { - "queryType": { - "name": "Query" - }, - "mutationType": null, - "subscriptionType": null, - "types": [ - { - "kind": "OBJECT", - "name": "Human", - "description": "A humanoid creature in the Star Wars universe.", - "fields": [ - { - "name": "name", - "description": "The name of the character", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [ - { - "kind": "INTERFACE", - "name": "Character", - "ofType": null - } - ], - "enumValues": null, - "possibleTypes": null - } - ] - } - } - } - """ - - /* Decode */ - - let value = try parse(json.data(using: .utf8)!) - let expected = Schema( - types: [ - .object(ObjectType( - name: "Human", - description: "A humanoid creature in the Star Wars universe.", - fields: [ - Field( - name: "name", - description: "The name of the character", - args: [], - type: .named(.scalar("String")), - isDeprecated: false, - deprecationReason: nil - ), - ], - interfaces: [ - .named(.interface("Character")), - ] - )), - ], - query: "Query" - ) - - /* Tests */ - - XCTAssertEqual(value, expected) - } -} diff --git a/Tests/GraphQLASTTests/ASTTypeRef+InvertedTests.swift b/Tests/GraphQLASTTests/ASTTypeRef+InvertedTests.swift deleted file mode 100644 index b836f01f..00000000 --- a/Tests/GraphQLASTTests/ASTTypeRef+InvertedTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -@testable import GraphQLAST -import XCTest - -final class InvertedTypeRefTests: XCTestCase { - func testInversion() { - // [String]! - let string = NamedType.scalar(ScalarType(name: "ID", description: nil)) - let typeRef = TypeRef.nonNull(.list(.list(.named(string)))) - let iTypeRef = InvertedTypeRef.list(.list(.nullable(.named(string)))) - - XCTAssertEqual(typeRef.inverted.inverted, typeRef) - XCTAssertEqual(iTypeRef.inverted.inverted, iTypeRef) - } - - func testNullability() { - let string = NamedType.scalar(ScalarType(name: "ID", description: nil)) - - // String! - - XCTAssertEqual( - TypeRef.nonNull(.named(string)).inverted, - InvertedTypeRef.named(string) - ) - } -} diff --git a/Tests/GraphQLASTTests/ASTTypeRefTests.swift b/Tests/GraphQLASTTests/ASTTypeRefTests.swift deleted file mode 100644 index b6b2c7f1..00000000 --- a/Tests/GraphQLASTTests/ASTTypeRefTests.swift +++ /dev/null @@ -1,148 +0,0 @@ -@testable import GraphQLAST -import XCTest - -final class ASTTypeRefTests: XCTestCase { - // MARK: - Named Ref - - func testNamedRef() { - /* Data */ - let json = """ - [ - { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Human", - "ofType": null - }, - { - "kind": "INTERFACE", - "name": "Node", - "ofType": null - }, - { - "kind": "UNION", - "name": "Character", - "ofType": null - }, - { - "kind": "ENUM", - "name": "Episode", - "ofType": null - }, - { - "kind": "INPUT_OBJECT", - "name": "HumanParams", - "ofType": null - } - ] - """ - - /* Decoder */ - - let values = try! JSONDecoder().decode([NamedTypeRef].self, from: json.data(using: .utf8)!) - - /* Tests */ - - XCTAssertEqual(values, - [ - NamedTypeRef.named(.scalar("String")), - NamedTypeRef.named(.object("Human")), - NamedTypeRef.named(.interface("Node")), - NamedTypeRef.named(.union("Character")), - NamedTypeRef.named(.enum("Episode")), - NamedTypeRef.named(.inputObject("HumanParams")), - ]) - } - - // MARK: - Output Ref - - func testOutputRef() { - /* Data */ - let json = """ - [ - { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Human", - "ofType": null - }, - { - "kind": "INTERFACE", - "name": "Node", - "ofType": null - }, - { - "kind": "UNION", - "name": "Character", - "ofType": null - }, - { - "kind": "ENUM", - "name": "Episode", - "ofType": null - } - ] - """ - - /* Decoder */ - - let values = try! JSONDecoder().decode([OutputTypeRef].self, from: json.data(using: .utf8)!) - - /* Tests */ - - XCTAssertEqual(values, - [ - OutputTypeRef.named(.scalar("String")), - OutputTypeRef.named(.object("Human")), - OutputTypeRef.named(.interface("Node")), - OutputTypeRef.named(.union("Character")), - OutputTypeRef.named(.enum("Episode")), - ]) - } - - // MARK: - Input Ref - - func testInputdRef() { - /* Data */ - let json = """ - [ - { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - { - "kind": "ENUM", - "name": "Episode", - "ofType": null - }, - { - "kind": "INPUT_OBJECT", - "name": "HumanParams", - "ofType": null - } - ] - """ - - /* Decoder */ - - let values = try! JSONDecoder().decode([InputTypeRef].self, from: json.data(using: .utf8)!) - - /* Tests */ - - XCTAssertEqual(values, - [ - InputTypeRef.named(.scalar("String")), - InputTypeRef.named(.enum("Episode")), - InputTypeRef.named(.inputObject("HumanParams")), - ]) - } -} diff --git a/Tests/GraphQLASTTests/ASTTypeTests.swift b/Tests/GraphQLASTTests/ASTTypeTests.swift deleted file mode 100644 index 83d18864..00000000 --- a/Tests/GraphQLASTTests/ASTTypeTests.swift +++ /dev/null @@ -1,235 +0,0 @@ -@testable import GraphQLAST -import XCTest - -final class ASTTypeTests: XCTestCase { - // MARK: - Scalar - - func testScalarType() { - let json = """ - { - "kind": "SCALAR", - "name": "String", - "description": "It's string!", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - } - """ - - /* Tests */ - let value = try! JSONDecoder().decode(NamedType.self, from: json.data(using: .utf8)!) - let expected = NamedType.scalar( - ScalarType( - name: "String", - description: "It's string!" - ) - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - Object - - func testObjectType() { - let json = """ - { - "kind": "OBJECT", - "name": "Query", - "description": null, - "fields": [], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - } - """ - - /* Tests */ - let value = try! JSONDecoder().decode(NamedType.self, from: json.data(using: .utf8)!) - let expected = NamedType.object( - ObjectType( - name: "Query", - description: nil, - fields: [], - interfaces: [] - ) - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - Interface - - func testInterfaceType() { - let json = """ - { - "kind": "INTERFACE", - "name": "Character", - "description": null, - "fields": [], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "Droid", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Human", - "ofType": null - } - ] - } - """ - - /* Tests */ - let value = try! JSONDecoder().decode(NamedType.self, from: json.data(using: .utf8)!) - let expected = NamedType.interface( - InterfaceType( - name: "Character", - description: nil, - fields: [], - interfaces: [], - possibleTypes: [ - .named(.object("Droid")), - .named(.object("Human")), - ] - ) - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - Union - - func testUnionType() { - let json = """ - { - "kind": "UNION", - "name": "Union", - "description": null, - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "TypeOne", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "TypeTwo", - "ofType": null - } - ] - } - """ - - /* Tests */ - let value = try! JSONDecoder().decode(NamedType.self, from: json.data(using: .utf8)!) - let expected = NamedType.union( - UnionType( - name: "Union", - description: nil, - possibleTypes: [ - .named(.object("TypeOne")), - .named(.object("TypeTwo")), - ] - ) - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - Enum - - func testEnumType() { - let json = """ - { - "kind": "ENUM", - "name": "Enum", - "description": "It's an ENUM!", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "enumValue", - "description": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - } - - """ - - /* Tests */ - let value = try! JSONDecoder().decode(NamedType.self, from: json.data(using: .utf8)!) - let expected = NamedType.enum( - EnumType( - name: "Enum", - description: "It's an ENUM!", - enumValues: [ - .init(name: "enumValue", description: nil, isDeprecated: false, deprecationReason: nil), - ] - ) - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - Input Object - - func testInputObjectType() { - let json = """ - { - "kind": "INPUT_OBJECT", - "name": "InputObject", - "description": null, - "fields": null, - "inputFields": [ - { - "name": "inputField", - "description": "", - "type": { - "kind": "ENUM", - "name": "order_by", - "ofType": null - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - } - """ - - /* Tests */ - let value = try! JSONDecoder().decode(NamedType.self, from: json.data(using: .utf8)!) - let expected = NamedType.inputObject( - InputObjectType( - name: "InputObject", - description: nil, - inputFields: [ - .init( - name: "inputField", - description: "", - type: .named(.enum("order_by")) - ), - ] - ) - ) - - XCTAssertEqual(value, expected) - } -} diff --git a/Tests/GraphQLASTTests/ASTValueTests.swift b/Tests/GraphQLASTTests/ASTValueTests.swift deleted file mode 100644 index 799d1a76..00000000 --- a/Tests/GraphQLASTTests/ASTValueTests.swift +++ /dev/null @@ -1,116 +0,0 @@ -@testable import GraphQLAST -import XCTest - -final class ASTValueTests: XCTestCase { - // MARK: - Field - - func testDecodeField() { - /* Data */ - let data = """ - { - "name": "hero", - "description": "The character", - "args": [ - { - "name": "id", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "defaultValue": null - } - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - """.data(using: .utf8)! - - /* Decoder */ - - let value = try! JSONDecoder().decode(Field.self, from: data) - - /* Tests */ - - let expected = Field( - name: "hero", - description: "The character", - args: [ - InputValue( - name: "id", - description: nil, - type: .named(.scalar("ID")) - ), - ], - type: .named(.scalar("String")), - isDeprecated: false, - deprecationReason: nil - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - InputValue - - func testDecodeInputValue() { - /* Data */ - let data = """ - { - "name": "id", - "description": null, - "type": { - "kind": "ENUM", - "name": "order_by", - "ofType": null - }, - "defaultValue": null - } - - """.data(using: .utf8)! - - /* Tests */ - let value = try! JSONDecoder().decode(InputValue.self, from: data) - let expected = InputValue( - name: "id", - description: nil, - type: .named(.enum("order_by")) - ) - - XCTAssertEqual(value, expected) - } - - // MARK: - EnumValue - - func testDecodeEnumValue() { - /* Data */ - let data = """ - { - "name": "NEWHOPE", - "description": "Released in 1977.", - "isDeprecated": false, - "deprecationReason": null - } - """.data(using: .utf8)! - - /* Decoder */ - - let value = try! JSONDecoder().decode(EnumValue.self, from: data) - - /* Tests */ - - let expected = EnumValue( - name: "NEWHOPE", - description: "Released in 1977.", - isDeprecated: false, - deprecationReason: nil - ) - - XCTAssertEqual(value, expected) - } -} diff --git a/Tests/SwiftGraphQLCodegenTests/Extensions/Context+Extensions.swift b/Tests/SwiftGraphQLCodegenTests/Extensions/Context+Extensions.swift deleted file mode 100644 index ca0ac344..00000000 --- a/Tests/SwiftGraphQLCodegenTests/Extensions/Context+Extensions.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation -import GraphQLAST -@testable import SwiftGraphQLCodegen - -extension Context { - /// Creates a mocking context that may be used in tests containing provided scalars. - static func from(scalars: [String: String]) -> Context { - let schema = Schema( - types: [ - .object(ObjectType(name: "Query", description: nil, fields: [], interfaces: nil)) - ], - query: "Query", - mutation: nil, - subscription: nil - ) - return Context(schema: schema, scalars: ScalarMap(scalars: scalars)) - } -} diff --git a/Tests/SwiftGraphQLCodegenTests/Generator/EnumTests.swift b/Tests/SwiftGraphQLCodegenTests/Generator/EnumTests.swift deleted file mode 100644 index 17bcb1b7..00000000 --- a/Tests/SwiftGraphQLCodegenTests/Generator/EnumTests.swift +++ /dev/null @@ -1,81 +0,0 @@ -@testable import GraphQLAST -@testable import SwiftGraphQLCodegen - -import XCTest - -final class EnumTests: XCTestCase { - func testGenerateEnum() throws { - /* Declaration */ - - let type = EnumType( - name: "Episodes", - description: "Collection of all StarWars episodes.\nEarliest trilogy.", - enumValues: [ - EnumValue( - name: "NEWHOPE", - description: "Released in 1977.", - isDeprecated: false, - deprecationReason: nil - ), - EnumValue( - name: "EMPIRE", - description: "Introduced Yoda.\nConsidered the best.", - isDeprecated: false, - deprecationReason: nil - ), - EnumValue( - name: "JEDI", - description: "Released in 1983.", - isDeprecated: true, - deprecationReason: "Was too good." - ), - EnumValue( - name: "_SKYWALKER__", - description: nil, - isDeprecated: true, - deprecationReason: nil - ), - ] - ) - - let generated = try type.declaration.format() - - generated.assertInlineSnapshot(matching: """ - extension Enums { - /// Collection of all StarWars episodes. - /// Earliest trilogy. - public enum Episodes: String, CaseIterable, Codable { - /// Released in 1977. - case newhope = "NEWHOPE" - /// Introduced Yoda. - /// Considered the best. - case empire = "EMPIRE" - /// Released in 1983. - case jedi = "JEDI" - - case _skywalker__ = "_SKYWALKER__" - } - } - - extension Enums.Episodes: GraphQLScalar { - public init(from data: AnyCodable) throws { - switch data.value { - case let string as String: - if let value = Enums.Episodes(rawValue: string) { - self = value - } else { - throw ScalarDecodingError.unknownEnumCase(value: string) - } - default: - throw ScalarDecodingError.unexpectedScalarType( - expected: "Episodes", - received: data.value - ) - } - } - - public static var mockValue = Self.newhope - } - """) - } -} diff --git a/Tests/SwiftGraphQLCodegenTests/Generator/FieldTests.swift b/Tests/SwiftGraphQLCodegenTests/Generator/FieldTests.swift deleted file mode 100644 index 6cbe24b8..00000000 --- a/Tests/SwiftGraphQLCodegenTests/Generator/FieldTests.swift +++ /dev/null @@ -1,598 +0,0 @@ -@testable import GraphQLAST -@testable import SwiftGraphQLCodegen -import XCTest - -final class FieldTests: XCTestCase { - - func testFieldDocs() throws { - let field = Field( - name: "id", - description: "Object identifier.\nMultiline.", - args: [], - type: .named(.scalar("ID")), - isDeprecated: true, - deprecationReason: "Use ID instead." - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - /// Object identifier. - /// Multiline. - @available(*, deprecated, message: "Use ID instead.") - public func id() throws -> String? { - let field = GraphQLField.leaf( - field: "id", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String?(from: $0) } - case .selecting: - return nil - } - } - """) - } - - func testDeprecatedFieldWithEscapeCharacters() throws { - let field = Field( - name: "id", - description: "Object identifier.\nMultiline.", - args: [], - type: .named(.scalar("ID")), - isDeprecated: true, - deprecationReason: "Use \"ID\" instead." - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - /// Object identifier. - /// Multiline. - @available(*, deprecated, message: "Use \\\"ID\\\" instead.") - public func id() throws -> String? { - let field = GraphQLField.leaf( - field: "id", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String?(from: $0) } - case .selecting: - return nil - } - } - """) - } - - func testMultilineDeprecationReason() throws { - let field = Field( - name: "id", - description: "Deprecation\nMultiline.", - args: [], - type: .named(.scalar("ID")), - isDeprecated: true, - deprecationReason: """ - Use ID instead. - See: for more detail. - """ - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - /// Deprecation - /// Multiline. - @available( - *, deprecated, - message: \"\"\"Use ID instead. - See: for more detail.\"\"\" - ) - public func id() throws -> String? { - let field = GraphQLField.leaf( - field: "id", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String?(from: $0) } - case .selecting: - return nil - } - } - """) - } - - // MARK: - Scalar - - func testScalarField() throws { - let field = Field( - name: "id", - description: nil, - args: [], - type: .nonNull(.named(.scalar("ID"))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func id() throws -> String { - let field = GraphQLField.leaf( - field: "id", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String(from: $0) } - case .selecting: - return String.mockValue - } - } - """) - } - - func testNullableScalarField() throws { - let field = Field( - name: "id", - description: nil, - args: [], - type: .named(.scalar("ID")), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func id() throws -> String? { - let field = GraphQLField.leaf( - field: "id", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String?(from: $0) } - case .selecting: - return nil - } - } - """) - } - - func testListScalarField() throws { - let field = Field( - name: "ids", - description: nil, - args: [], - type: .list(.nonNull(.named(.scalar("ID")))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func ids() throws -> [String]? { - let field = GraphQLField.leaf( - field: "ids", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try [String]?(from: $0) } - case .selecting: - return nil - } - } - """) - } - - func testGenearateNonNullableListScalarField() throws { - let field = Field( - name: "ids", - description: nil, - args: [], - type: .nonNull(.list(.nonNull(.named(.scalar("ID"))))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func ids() throws -> [String] { - let field = GraphQLField.leaf( - field: "ids", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try [String](from: $0) } - case .selecting: - return [] - } - } - """) - } - - // MARK: - Enumerators - - func testEnumField() throws { - let field = Field( - name: "episode", - description: nil, - args: [], - type: .nonNull(.named(.enum("Episode"))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func episode() throws -> Enums.Episode { - let field = GraphQLField.leaf( - field: "episode", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try Enums.Episode(from: $0) } - case .selecting: - return Enums.Episode.mockValue - } - } - """) - } - - func testNullableEnumField() throws { - let field = Field( - name: "episode", - description: nil, - args: [], - type: .named(.enum("Episode")), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func episode() throws -> Enums.Episode? { - let field = GraphQLField.leaf( - field: "episode", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try Enums.Episode?(from: $0) } - case .selecting: - return nil - } - } - """) - } - - func testNullableListEnumField() throws { - let field = Field( - name: "episode", - description: nil, - args: [], - type: .nonNull(.list(.named(.enum("Episode")))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func episode() throws -> [Enums.Episode?] { - let field = GraphQLField.leaf( - field: "episode", - parent: "TestType", - arguments: [] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try [Enums.Episode?](from: $0) } - case .selecting: - return [] - } - } - """) - } - - // MARK: - Selections - - func testSelectionField() throws { - let field = Field( - name: "hero", - description: nil, - args: [], - type: .nonNull(.named(.object("Hero"))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func hero(selection: Selection) throws -> T { - let field = GraphQLField.composite( - field: "hero", - parent: "TestType", - type: "Hero", - arguments: [], - selection: selection.__selection() - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try selection.__decode(data: $0) } - case .selecting: - return try selection.__mock() - } - } - """) - } - - func testNullableSelectionField() throws { - let field = Field( - name: "hero", - description: nil, - args: [], - type: .named(.object("Hero")), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func hero(selection: Selection) throws -> T { - let field = GraphQLField.composite( - field: "hero", - parent: "TestType", - type: "Hero", - arguments: [], - selection: selection.__selection() - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try selection.__decode(data: $0) } - case .selecting: - return try selection.__mock() - } - } - """) - } - - func testListSelectionField() throws { - let field = Field( - name: "hero", - description: nil, - args: [], - type: .nonNull(.list(.nonNull(.named(.object("Hero"))))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func hero(selection: Selection) throws -> T { - let field = GraphQLField.composite( - field: "hero", - parent: "TestType", - type: "Hero", - arguments: [], - selection: selection.__selection() - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try selection.__decode(data: $0) } - case .selecting: - return try selection.__mock() - } - } - """) - } - - // MARK: - Arguments - - func testFieldWithScalarArgument() throws { - let field = Field( - name: "hero", - description: nil, - args: [ - InputValue( - name: "id", - description: nil, - type: .nonNull(.named(.scalar("ID"))) - ), - ], - type: .nonNull(.named(.scalar("ID"))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func hero(id: String) throws -> String { - let field = GraphQLField.leaf( - field: "hero", - parent: "TestType", - arguments: [Argument(name: "id", type: "ID!", value: id)] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String(from: $0) } - case .selecting: - return String.mockValue - } - } - """) - } - - func testFieldWithOptionalArgument() throws { - let field = Field( - name: "hero", - description: nil, - args: [ - InputValue( - name: "id", - description: nil, - type: .named(.scalar("ID")) - ), - ], - type: .nonNull(.named(.scalar("ID"))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func hero(id: OptionalArgument = .init()) throws -> String { - let field = GraphQLField.leaf( - field: "hero", - parent: "TestType", - arguments: [Argument(name: "id", type: "ID", value: id)] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String(from: $0) } - case .selecting: - return String.mockValue - } - } - """) - } - - func testFieldWithInputObjectArgument() throws { - let field = Field( - name: "hero", - description: nil, - args: [ - InputValue( - name: "id", - description: nil, - type: .nonNull(.named(.inputObject("Input"))) - ), - ], - type: .nonNull(.named(.scalar("ID"))), - isDeprecated: false, - deprecationReason: nil - ) - - let generated = try field.getDynamicSelection( - parent: "TestType", - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - public func hero(id: InputObjects.Input) throws -> String { - let field = GraphQLField.leaf( - field: "hero", - parent: "TestType", - arguments: [Argument(name: "id", type: "Input!", value: id)] - ) - self.__select(field) - - switch self.__state { - case .decoding: - return try self.__decode(field: field.alias!) { try String(from: $0) } - case .selecting: - return String.mockValue - } - } - """) - } -} diff --git a/Tests/SwiftGraphQLCodegenTests/Generator/InputObjectTests.swift b/Tests/SwiftGraphQLCodegenTests/Generator/InputObjectTests.swift deleted file mode 100644 index 3c54b1dc..00000000 --- a/Tests/SwiftGraphQLCodegenTests/Generator/InputObjectTests.swift +++ /dev/null @@ -1,107 +0,0 @@ -@testable import GraphQLAST -@testable import SwiftGraphQLCodegen -import XCTest - -final class InputObjectTests: XCTestCase { - - func testInputObjectField() throws { - let type = InputObjectType( - name: "InputObject", - description: nil, - inputFields: [ - /* Scalar, Docs */ - InputValue( - name: "id", - description: "Field description.\nMultiline.", - type: .nonNull(.named(.inputObject("AnotherInputObject"))) - ), - /* Scalar, Docs */ - InputValue( - name: "input_value", - description: nil, - type: .named(.scalar("ID")) - ), - ] - ) - - let generated = try type.declaration( - context: Context.from(scalars: ["ID": "ID"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - extension InputObjects { - public struct InputObject: Encodable, Hashable { - - /// Field description. - /// Multiline. - public var id: InputObjects.AnotherInputObject - - public var inputValue: OptionalArgument - - public init( - id: InputObjects.AnotherInputObject, - inputValue: OptionalArgument = .init() - ) { - self.id = id - self.inputValue = inputValue - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - if inputValue.hasValue { try container.encode(inputValue, forKey: .inputValue) } - } - - public enum CodingKeys: String, CodingKey { - case id = "id" - case inputValue = "input_value" - } - } - } - """) - } - - func testEnumField() throws { - let type = InputObjectType( - name: "InputObject", - description: nil, - inputFields: [ - /* Scalar, Docs */ - InputValue( - name: "id", - description: "Field description.", - type: .nonNull(.named(.enum("ENUM"))) - ), - ] - ) - - let generated = try type.declaration( - context: Context.from(scalars: ["ID": "String"]) - ).format() - - generated.assertInlineSnapshot(matching: """ - extension InputObjects { - public struct InputObject: Encodable, Hashable { - - /// Field description. - public var id: Enums.Enum - - public init( - id: Enums.Enum - ) { - self.id = id - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - } - - public enum CodingKeys: String, CodingKey { - case id = "id" - } - } - } - """) - } -} diff --git a/Tests/SwiftGraphQLCodegenTests/Integration/Scalars.swift b/Tests/SwiftGraphQLCodegenTests/Integration/Scalars.swift deleted file mode 100644 index 67d086ae..00000000 --- a/Tests/SwiftGraphQLCodegenTests/Integration/Scalars.swift +++ /dev/null @@ -1,38 +0,0 @@ -import GraphQL -import Foundation -import SwiftGraphQL - -extension Date: GraphQLScalar { - public init(from codable: AnyCodable) throws { - switch codable.value { - case let value as String: - let dateFormatter = DateFormatter() - - dateFormatter.calendar = Calendar(identifier: .iso8601) - dateFormatter.locale = Locale(identifier: "en_US_POSIX") - dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" - - if let date = dateFormatter.date(from: value) { - self = date - } else { - throw DateFormattingError.couldNotParse - } - default: - throw DateFormattingError.unexpectedType - } - } - - public static var mockValue: Date { - Date(timeIntervalSince1970: 0) - } -} - -enum DateFormattingError: Error { - - /// Could not parse the error from the string. - case couldNotParse - - /// Expected a string but got a different scalar. - case unexpectedType -} diff --git a/Tests/SwiftGraphQLCodegenTests/Utils/TestSnapshot.swift b/Tests/SwiftGraphQLCodegenTests/Utils/TestSnapshot.swift deleted file mode 100644 index 060e18a2..00000000 --- a/Tests/SwiftGraphQLCodegenTests/Utils/TestSnapshot.swift +++ /dev/null @@ -1,79 +0,0 @@ -import Foundation -import XCTest - -extension String { - - /// Creates an inline snapshot of a the result. - func assertInlineSnapshot( - matching: String? = nil, - file: StaticString = #file, - line: UInt = #line - ) { - guard matching == nil else { - XCTAssertEqual(self, matching) - return - } - - let sourcepath = URL(fileURLWithPath: "\(file)") - - let source = try! String(contentsOf: sourcepath) - let lines = source.split(omittingEmptySubsequences: false, whereSeparator: { $0.isNewline }) - - var codeWithInlineTest: [String] = [] - - for index in lines.indices { - // Line values start with 1, indexes start with 0. - let normalizedIndex = index + 1 - - let content = String(lines[index]) - - if normalizedIndex < line { - codeWithInlineTest.append(content) - } - - if normalizedIndex == line { - let indentation = content.countLeft(characters: CharacterSet.whitespaces) + 3 - - codeWithInlineTest.append(content.replacingOccurrences( - of: ".assertInlineSnapshot()", - with: #".assertInlineSnapshot(matching: """"# - )) - codeWithInlineTest.append(contentsOf: self - .replacingOccurrences(of: #"""""#, with: #"\"\"\""#) - .split(separator: "\n", omittingEmptySubsequences: false) - .map { String($0) } - .map { $0.indent(by: indentation) }) - codeWithInlineTest.append(#"""")"#.indent(by: indentation)) - } - - if normalizedIndex > line { - codeWithInlineTest.append(content) - } - } - - let code = codeWithInlineTest.joined(separator: "\n") - try! code.write(to: sourcepath, atomically: true, encoding: .utf8) - - XCTFail("Wrote Inline Snapshot", file: file, line: line) - } - - /// Returns the number of characters before reaching a character that's not in a set going left-to-right. - private func countLeft(characters: CharacterSet) -> Int { - var count = 0 - - for char in self { - if char.unicodeScalars.allSatisfy({ characters.contains($0) }) { - count += 1 - } else { - return count - } - } - - return count - } - - /// Returns an indented string by n spaces in front. - private func indent(by spaces: Int) -> String { - "\(String(repeating: " ", count: spaces))\(self)" - } -} diff --git a/Tests/SwiftGraphQLCodegenTests/Integration/API.swift b/Tests/SwiftGraphQLTests/Integration/API.swift similarity index 100% rename from Tests/SwiftGraphQLCodegenTests/Integration/API.swift rename to Tests/SwiftGraphQLTests/Integration/API.swift diff --git a/Tests/SwiftGraphQLTests/Integration/Scalars.swift b/Tests/SwiftGraphQLTests/Integration/Scalars.swift new file mode 100644 index 00000000..51657442 --- /dev/null +++ b/Tests/SwiftGraphQLTests/Integration/Scalars.swift @@ -0,0 +1,38 @@ +import GraphQL +import Foundation +import SwiftGraphQL + +extension Date: GraphQLScalar { + public init(from codable: AnyCodable) throws { + switch codable.value { + case let value as String: + let dateFormatter = DateFormatter() + + dateFormatter.calendar = Calendar(identifier: .iso8601) + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" + + if let date = dateFormatter.date(from: value) { + self = date + } else { + throw DateFormattingError.couldNotParse + } + default: + throw DateFormattingError.unexpectedType + } + } + + public static var mockValue: Date { + Date(timeIntervalSince1970: 0) + } +} + +enum DateFormattingError: Error { + + /// Could not parse the error from the string. + case couldNotParse + + /// Expected a string but got a different scalar. + case unexpectedType +} diff --git a/Tests/SwiftGraphQLCodegenTests/Integration/Selection.swift b/Tests/SwiftGraphQLTests/Integration/Selection.swift similarity index 100% rename from Tests/SwiftGraphQLCodegenTests/Integration/Selection.swift rename to Tests/SwiftGraphQLTests/Integration/Selection.swift diff --git a/Tests/SwiftGraphQLCodegenTests/Integration/schema.json b/Tests/SwiftGraphQLTests/Integration/schema.json similarity index 100% rename from Tests/SwiftGraphQLCodegenTests/Integration/schema.json rename to Tests/SwiftGraphQLTests/Integration/schema.json diff --git a/Tests/SwiftGraphQLCodegenTests/Integration/swiftgraphql.yml b/Tests/SwiftGraphQLTests/Integration/swiftgraphql.yml similarity index 100% rename from Tests/SwiftGraphQLCodegenTests/Integration/swiftgraphql.yml rename to Tests/SwiftGraphQLTests/Integration/swiftgraphql.yml diff --git a/Tests/SwiftGraphQLUtilsTests/Extensions/String+CaseTests.swift b/Tests/SwiftGraphQLUtilsTests/Extensions/String+CaseTests.swift index 00f93380..db936799 100644 --- a/Tests/SwiftGraphQLUtilsTests/Extensions/String+CaseTests.swift +++ b/Tests/SwiftGraphQLUtilsTests/Extensions/String+CaseTests.swift @@ -1,4 +1,4 @@ -@testable import SwiftGraphQLUtils +@testable import SwiftGraphQL import XCTest final class StringExtensionsTest: XCTestCase { diff --git a/server/docker-compose.yml b/server/docker-compose.yml new file mode 100644 index 00000000..3d827bca --- /dev/null +++ b/server/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3' +name: graphqlserver +services: + server: + image: node:latest + working_dir: /root + entrypoint: ["/bin/bash", "-c"] + ports: + - 4000:4000 + command: + - | + npm --version + npm install + npm run start + volumes: + - ./:/root/