diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57c92760..f631c315 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,19 +25,13 @@ jobs: macos: name: Build and test on macos-latest - runs-on: macOS-latest + runs-on: macos-latest steps: - - uses: actions/checkout@v2 - - name: Set code coverage path - run: echo "codecov_path=$(swift test --show-codecov-path)" >> $GITHUB_ENV - - name: Test and publish code coverage to Code Climate - uses: paulofaria/codeclimate-action@master - env: - CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} - with: - downloadUrl: https://github.com/paulofaria/test-reporter/releases/download/0.9.0/test-reporter-0.9.0-darwin-amd64 - coverageCommand: swift test --enable-test-discovery --enable-code-coverage - coverageLocations: ${{ env.codecov_path }}:lcov-json + - uses: swift-actions/setup-swift@v1 + - uses: actions/checkout@v3 + - name: Test + run: swift test + # ubuntu-latest is ubuntu-22.04 currently. Swift versions older than 5.7 don't have builds for 22.04. https://www.swift.org/download/ ubuntu-old: name: Build ${{ matrix.swift }} on ${{ matrix.os }} @@ -45,7 +39,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - swift: ["5.4", "5.5", "5.6"] + swift: ["5.5", "5.6"] steps: - uses: swift-actions/setup-swift@v1 with: @@ -60,7 +54,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - swift: ["5.7"] + swift: ["5.7", "5.8"] steps: - uses: swift-actions/setup-swift@v1 with: diff --git a/Sources/GraphQL/Error/GraphQLError.swift b/Sources/GraphQL/Error/GraphQLError.swift index 7dac6154..b1a93353 100644 --- a/Sources/GraphQL/Error/GraphQLError.swift +++ b/Sources/GraphQL/Error/GraphQLError.swift @@ -9,6 +9,7 @@ public struct GraphQLError: Error, Codable { case message case locations case path + case extensions } /** @@ -38,6 +39,12 @@ public struct GraphQLError: Error, Codable { */ public let path: IndexPath + /// Reserved for implementors to add additional information to errors + /// however they see fit, and there are no restrictions on its contents. + /// + /// See: https://spec.graphql.org/October2021/#sel-HAPHRPZCBiCBzG67F + public let extensions: [String: Map] + /** * An array of GraphQL AST Nodes corresponding to this error. */ @@ -65,7 +72,8 @@ public struct GraphQLError: Error, Codable { source: Source? = nil, positions: [Int] = [], path: IndexPath = [], - originalError: Error? = nil + originalError: Error? = nil, + extensions: [String: Map] = [:] ) { self.message = message self.nodes = nodes @@ -92,16 +100,19 @@ public struct GraphQLError: Error, Codable { self.path = path self.originalError = originalError + self.extensions = extensions } public init( message: String, locations: [SourceLocation], - path: IndexPath = [] + path: IndexPath = [], + extensions: [String: Map] = [:] ) { self.message = message self.locations = locations self.path = path + self.extensions = extensions nodes = [] source = nil positions = [] @@ -120,6 +131,7 @@ public struct GraphQLError: Error, Codable { message = try container.decode(String.self, forKey: .message) locations = (try? container.decode([SourceLocation]?.self, forKey: .locations)) ?? [] path = try container.decode(IndexPath.self, forKey: .path) + extensions = try container.decodeIfPresent([String: Map].self, forKey: .extensions) ?? [:] } public func encode(to encoder: Encoder) throws { @@ -132,6 +144,10 @@ public struct GraphQLError: Error, Codable { } try container.encode(path, forKey: .path) + + if !extensions.isEmpty { + try container.encode(extensions, forKey: .extensions) + } } } diff --git a/Sources/GraphQL/Execution/Values.swift b/Sources/GraphQL/Execution/Values.swift index 11c0a666..4ca05b90 100644 --- a/Sources/GraphQL/Execution/Values.swift +++ b/Sources/GraphQL/Execution/Values.swift @@ -149,7 +149,7 @@ func coerceValue(value: Map, type: GraphQLInputType) throws -> Map { } // Convert solitary value into single-value array - return .array([try coerceValue(value: value, type: itemType)]) + return try .array([coerceValue(value: value, type: itemType)]) } if let objectType = type as? GraphQLInputObjectType { diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index bc2cbe0f..1520cbe1 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -1331,7 +1331,7 @@ func many( parse: (Lexer) throws -> T ) throws -> [T] { try expect(lexer: lexer, kind: openKind) - var nodes = [try parse(lexer)] + var nodes = try [parse(lexer)] while try !skip(lexer: lexer, kind: closeKind) { try nodes.append(parse(lexer)) } diff --git a/Sources/GraphQL/Map/GraphQLJSONEncoder.swift b/Sources/GraphQL/Map/GraphQLJSONEncoder.swift index 9800ae7a..a938ee4f 100644 --- a/Sources/GraphQL/Map/GraphQLJSONEncoder.swift +++ b/Sources/GraphQL/Map/GraphQLJSONEncoder.swift @@ -473,11 +473,6 @@ extension JSONEncoderImpl: _SpecialTreatmentEncoder { return .string(url.absoluteString) case let decimal as Decimal: return .number(decimal.description) - case let object as OrderedDictionary< - String, - Encodable - >: // this emits a warning, but it works perfectly - return try wrapObject(object, for: nil) case let date as Date: return try wrapDate(date, for: nil) default: @@ -545,8 +540,6 @@ extension _SpecialTreatmentEncoder { return .string(url.absoluteString) case let decimal as Decimal: return .number(decimal.description) - case let object as OrderedDictionary: - return try wrapObject(object, for: additionalKey) default: let encoder = getEncoder(for: additionalKey) try encodable.encode(to: encoder) diff --git a/Sources/GraphQL/Map/Map.swift b/Sources/GraphQL/Map/Map.swift index aa12840a..84ca2d21 100644 --- a/Sources/GraphQL/Map/Map.swift +++ b/Sources/GraphQL/Map/Map.swift @@ -701,16 +701,16 @@ extension Map: Codable { /// A wrapper for dictionary keys which are Strings or Ints. /// This is copied from Swift core: https://github.com/apple/swift/blob/256a9c5ad96378daa03fa2d5197b4201bf16db27/stdlib/public/core/Codable.swift#L5508 - internal struct _DictionaryCodingKey: CodingKey { - internal let stringValue: String - internal let intValue: Int? + struct _DictionaryCodingKey: CodingKey { + let stringValue: String + let intValue: Int? - internal init?(stringValue: String) { + init?(stringValue: String) { self.stringValue = stringValue intValue = Int(stringValue) } - internal init?(intValue: Int) { + init?(intValue: Int) { stringValue = "\(intValue)" self.intValue = intValue } diff --git a/Sources/GraphQL/Utilities/NIO+Extensions.swift b/Sources/GraphQL/Utilities/NIO+Extensions.swift index 84d01be4..3903fb7f 100644 --- a/Sources/GraphQL/Utilities/NIO+Extensions.swift +++ b/Sources/GraphQL/Utilities/NIO+Extensions.swift @@ -1,5 +1,5 @@ // -// DictionaryFuture.swift +// NIO+Extensions.swift // GraphQL // // Created by Jeff Seibert on 3/9/18. diff --git a/Sources/GraphQL/Utilities/ValueFromAST.swift b/Sources/GraphQL/Utilities/ValueFromAST.swift index 2fc43d2a..0a9288af 100644 --- a/Sources/GraphQL/Utilities/ValueFromAST.swift +++ b/Sources/GraphQL/Utilities/ValueFromAST.swift @@ -65,8 +65,8 @@ func valueFromAST( } // Convert solitary value into single-value array - return .array([ - try valueFromAST( + return try .array([ + valueFromAST( valueAST: valueAST, type: itemType, variables: variables diff --git a/Tests/GraphQLTests/LanguageTests/ParserTests.swift b/Tests/GraphQLTests/LanguageTests/ParserTests.swift index 6eb19fab..065c1c9f 100644 --- a/Tests/GraphQLTests/LanguageTests/ParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/ParserTests.swift @@ -26,7 +26,9 @@ class ParserTests: XCTestCase { XCTAssertEqual(error.locations[0].column, 2) } - XCTAssertThrowsError(try parse(source: "{ ...MissingOn }\nfragment MissingOn Type\n")) { error in + XCTAssertThrowsError(try parse( + source: "{ ...MissingOn }\nfragment MissingOn Type\n" + )) { error in guard let error = error as? GraphQLError else { return XCTFail() } @@ -111,7 +113,9 @@ class ParserTests: XCTestCase { )) } - XCTAssertThrowsError(try parse(source: "type WithImplementsButNoTypes implements {}")) { error in + XCTAssertThrowsError(try parse( + source: "type WithImplementsButNoTypes implements {}" + )) { error in guard let error = error as? GraphQLError else { return XCTFail() } diff --git a/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift b/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift index f2fd1e90..5ae2e781 100644 --- a/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift +++ b/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift @@ -87,7 +87,9 @@ import XCTest type: GraphQLInt ), ], - resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture< + Any? + > in guard let email = emailAny as? Email else { throw GraphQLError( message: "Source is not Email type: \(type(of: emailAny))" @@ -98,7 +100,9 @@ import XCTest inbox: Inbox(emails: db.emails) )) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< + Any? + > in eventLoopGroup.next().makeSucceededFuture(db.publisher.subscribe()) } ), @@ -109,7 +113,9 @@ import XCTest type: GraphQLInt ), ], - resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture< + Any? + > in guard let email = emailAny as? Email else { throw GraphQLError( message: "Source is not Email type: \(type(of: emailAny))" @@ -120,7 +126,9 @@ import XCTest inbox: Inbox(emails: db.emails) )) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< + Any? + > in eventLoopGroup.next().makeSucceededFuture(db.publisher.subscribe()) } ), @@ -192,7 +200,9 @@ import XCTest resolve: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in eventLoopGroup.next().makeSucceededFuture(nil) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< + Any? + > in didResolveImportantEmail = true return eventLoopGroup.next() .makeSucceededFuture(db.publisher.subscribe()) @@ -203,7 +213,9 @@ import XCTest resolve: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in eventLoopGroup.next().makeSucceededFuture(nil) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< + Any? + > in didResolveNonImportantEmail = true return eventLoopGroup.next() .makeSucceededFuture(db.publisher.subscribe())