diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index cdbde228..3a730e0e 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -811,16 +811,18 @@ public final class StringValue { public let kind: Kind = .stringValue public let loc: Location? public let value: String + public let block: Bool? - init(loc: Location? = nil, value: String) { + init(loc: Location? = nil, value: String, block: Bool? = nil) { self.loc = loc self.value = value + self.block = block } } extension StringValue : Equatable { public static func == (lhs: StringValue, rhs: StringValue) -> Bool { - return lhs.value == rhs.value + return lhs.value == rhs.value && lhs.block == rhs.block } } @@ -1076,11 +1078,13 @@ public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool { public final class SchemaDefinition { public let kind: Kind = .schemaDefinition public let loc: Location? + public let description: StringValue? public let directives: [Directive] public let operationTypes: [OperationTypeDefinition] - init(loc: Location? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { + init(loc: Location? = nil, description: StringValue? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { self.loc = loc + self.description = description self.directives = directives self.operationTypes = operationTypes } @@ -1088,8 +1092,9 @@ public final class SchemaDefinition { extension SchemaDefinition : Equatable { public static func == (lhs: SchemaDefinition, rhs: SchemaDefinition) -> Bool { - return lhs.directives == rhs.directives && - lhs.operationTypes == rhs.operationTypes + return lhs.description == rhs.description && + lhs.directives == rhs.directives && + lhs.operationTypes == rhs.operationTypes } } @@ -1157,11 +1162,13 @@ public func == (lhs: TypeDefinition, rhs: TypeDefinition) -> Bool { public final class ScalarTypeDefinition { public let kind: Kind = .scalarTypeDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let directives: [Directive] - init(loc: Location? = nil, name: Name, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.directives = directives } @@ -1169,7 +1176,8 @@ public final class ScalarTypeDefinition { extension ScalarTypeDefinition : Equatable { public static func == (lhs: ScalarTypeDefinition, rhs: ScalarTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives } } @@ -1177,13 +1185,15 @@ extension ScalarTypeDefinition : Equatable { public final class ObjectTypeDefinition { public let kind: Kind = .objectTypeDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let interfaces: [NamedType] public let directives: [Directive] public let fields: [FieldDefinition] - init(loc: Location? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { self.loc = loc + self.description = description self.name = name self.interfaces = interfaces self.directives = directives @@ -1193,7 +1203,8 @@ public final class ObjectTypeDefinition { extension ObjectTypeDefinition : Equatable { public static func == (lhs: ObjectTypeDefinition, rhs: ObjectTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.interfaces == rhs.interfaces && lhs.directives == rhs.directives && lhs.fields == rhs.fields @@ -1203,13 +1214,15 @@ extension ObjectTypeDefinition : Equatable { public final class FieldDefinition { public let kind: Kind = .fieldDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let arguments: [InputValueDefinition] public let type: Type public let directives: [Directive] - init(loc: Location? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.arguments = arguments self.type = type @@ -1219,7 +1232,8 @@ public final class FieldDefinition { extension FieldDefinition : Equatable { public static func == (lhs: FieldDefinition, rhs: FieldDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.arguments == rhs.arguments && lhs.type == rhs.type && lhs.directives == rhs.directives @@ -1229,13 +1243,15 @@ extension FieldDefinition : Equatable { public final class InputValueDefinition { public let kind: Kind = .inputValueDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let type: Type public let defaultValue: Value? public let directives: [Directive] - init(loc: Location? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.type = type self.defaultValue = defaultValue @@ -1272,6 +1288,7 @@ extension InputValueDefinition : Equatable { public final class InterfaceTypeDefinition { public let kind: Kind = .interfaceTypeDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let interfaces: [NamedType] public let directives: [Directive] @@ -1279,12 +1296,14 @@ public final class InterfaceTypeDefinition { init( loc: Location? = nil, + description: StringValue? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] ) { self.loc = loc + self.description = description self.name = name self.interfaces = interfaces self.directives = directives @@ -1294,7 +1313,8 @@ public final class InterfaceTypeDefinition { extension InterfaceTypeDefinition : Equatable { public static func == (lhs: InterfaceTypeDefinition, rhs: InterfaceTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives && lhs.fields == rhs.fields } @@ -1303,12 +1323,14 @@ extension InterfaceTypeDefinition : Equatable { public final class UnionTypeDefinition { public let kind: Kind = .unionTypeDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let directives: [Directive] public let types: [NamedType] - init(loc: Location? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { self.loc = loc + self.description = description self.name = name self.directives = directives self.types = types @@ -1317,7 +1339,8 @@ public final class UnionTypeDefinition { extension UnionTypeDefinition : Equatable { public static func == (lhs: UnionTypeDefinition, rhs: UnionTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives && lhs.types == rhs.types } @@ -1326,12 +1349,14 @@ extension UnionTypeDefinition : Equatable { public final class EnumTypeDefinition { public let kind: Kind = .enumTypeDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let directives: [Directive] public let values: [EnumValueDefinition] - init(loc: Location? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { self.loc = loc + self.description = description self.name = name self.directives = directives self.values = values @@ -1340,7 +1365,8 @@ public final class EnumTypeDefinition { extension EnumTypeDefinition : Equatable { public static func == (lhs: EnumTypeDefinition, rhs: EnumTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives && lhs.values == rhs.values } @@ -1349,11 +1375,13 @@ extension EnumTypeDefinition : Equatable { public final class EnumValueDefinition { public let kind: Kind = .enumValueDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let directives: [Directive] - init(loc: Location? = nil, name: Name, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.directives = directives } @@ -1361,7 +1389,8 @@ public final class EnumValueDefinition { extension EnumValueDefinition : Equatable { public static func == (lhs: EnumValueDefinition, rhs: EnumValueDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives } } @@ -1369,12 +1398,14 @@ extension EnumValueDefinition : Equatable { public final class InputObjectTypeDefinition { public let kind: Kind = .inputObjectTypeDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let directives: [Directive] public let fields: [InputValueDefinition] - init(loc: Location? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { self.loc = loc + self.description = description self.name = name self.directives = directives self.fields = fields @@ -1383,9 +1414,10 @@ public final class InputObjectTypeDefinition { extension InputObjectTypeDefinition : Equatable { public static func == (lhs: InputObjectTypeDefinition, rhs: InputObjectTypeDefinition) -> Bool { - return lhs.name == rhs.name && - lhs.directives == rhs.directives && - lhs.fields == rhs.fields + return lhs.description == rhs.description && + lhs.name == rhs.name && + lhs.directives == rhs.directives && + lhs.fields == rhs.fields } } @@ -1409,13 +1441,15 @@ extension TypeExtensionDefinition : Equatable { public final class DirectiveDefinition { public let kind: Kind = .directiveDefinition public let loc: Location? + public let description: StringValue? public let name: Name public let arguments: [InputValueDefinition] public let locations: [Name] - init(loc: Location? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { self.loc = loc self.name = name + self.description = description self.arguments = arguments self.locations = locations } @@ -1423,8 +1457,9 @@ public final class DirectiveDefinition { extension DirectiveDefinition : Equatable { public static func == (lhs: DirectiveDefinition, rhs: DirectiveDefinition) -> Bool { - return lhs.name == rhs.name && - lhs.arguments == rhs.arguments && - lhs.locations == rhs.locations + return lhs.description == rhs.description && + lhs.name == rhs.name && + lhs.arguments == rhs.arguments && + lhs.locations == rhs.locations } } diff --git a/Sources/GraphQL/Language/Lexer.swift b/Sources/GraphQL/Language/Lexer.swift index 40cf4e9b..0501aa74 100644 --- a/Sources/GraphQL/Language/Lexer.swift +++ b/Sources/GraphQL/Language/Lexer.swift @@ -91,6 +91,33 @@ final class Lexer { func advance() throws -> Token { return try advanceFunction(self) } + + /** + * Looks ahead and returns the next non-ignored token, but does not change + * the state of Lexer. + */ + func lookahead() throws -> Token { + var startToken = token + let savedLine = self.line + let savedLineStart = self.lineStart + + guard startToken.kind != .eof else { return startToken } + repeat { + startToken = try startToken.next ?? + { + startToken.next = try readToken(lexer: self, prev: startToken) + return startToken.next! + }() + } while startToken.kind == .comment + + // restore these since both `positionAfterWhitespace` & `readBlockString` + // can potentially modify them and commment for `lookahead` says no lexer modification. + // (the latter is true in the canonical js lexer also and is likely a bug) + self.line = savedLine + self.lineStart = savedLineStart + + return startToken + } } /** diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 5f928350..04ec50aa 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -124,6 +124,22 @@ func parseName(lexer: Lexer) throws -> Name { ) } +func peekDescription(lexer: Lexer) -> Bool { + return peek(lexer: lexer, kind: .string) || peek(lexer: lexer, kind: .blockstring) +} + +/** + * Description is optional StringValue + */ + +func parseDescription(lexer: Lexer) throws -> StringValue? { + if peekDescription(lexer: lexer) { + return try parseStringLiteral(lexer: lexer, startToken: lexer.token) + } + return nil +} + + // Implements the parsing rules in the Document section. /** @@ -154,7 +170,7 @@ func parseDefinition(lexer: Lexer) throws -> Definition { if peek(lexer: lexer, kind: .openingBrace) { return try parseOperationDefinition(lexer: lexer) } - + if peek(lexer: lexer, kind: .name) { switch lexer.token.value! { // Note: subscription is an experimental non-spec addition. @@ -163,10 +179,13 @@ func parseDefinition(lexer: Lexer) throws -> Definition { case "fragment": return try parseFragmentDefinition(lexer: lexer) // Note: the Type System IDL is an experimental non-spec addition. - case "schema", "scalar", "type", "interface", "union", "enum", "input", "extend", "directive": return try parseTypeSystemDefinition(lexer: lexer) + case "schema", "scalar", "type", "interface", "union", "enum", "input", "extend", "directive": + return try parseTypeSystemDefinition(lexer: lexer) default: break } + } else if peekDescription(lexer: lexer) { + return try parseTypeSystemDefinition(lexer: lexer) } throw unexpected(lexer: lexer) @@ -449,12 +468,8 @@ func parseValueLiteral(lexer: Lexer, isConst: Bool) throws -> Value { loc: loc(lexer: lexer, startToken: token), value: token.value! ) - case .string: - try lexer.advance(); - return StringValue( - loc: loc(lexer: lexer, startToken: token), - value: token.value! - ) + case .string, .blockstring: + return try parseStringLiteral(lexer: lexer, startToken: token) case .name: if (token.value == "true" || token.value == "false") { try lexer.advance() @@ -539,6 +554,18 @@ func parseObjectField(lexer: Lexer, isConst: Bool) throws -> ObjectField { ) } +/** + * parseStringLiteral + */ + +func parseStringLiteral(lexer: Lexer, startToken: Token) throws -> StringValue { + try lexer.advance(); + return StringValue( + loc: loc(lexer: lexer, startToken: startToken), + value: startToken.value!, + block: startToken.kind == .blockstring + ) +} // Implements the parsing rules in the Directives section. @@ -631,8 +658,12 @@ func parseNamedType(lexer: Lexer) throws -> NamedType { * - InputObjectTypeDefinition */ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { - if peek(lexer: lexer, kind: .name) { - switch lexer.token.value! { + let keywordToken = peekDescription(lexer: lexer) + ? try lexer.lookahead() + : lexer.token + + if keywordToken.kind == .name { + switch keywordToken.value! { case "schema": return try parseSchemaDefinition(lexer: lexer); case "scalar": return try parseScalarTypeDefinition(lexer: lexer); case "type": return try parseObjectTypeDefinition(lexer: lexer); @@ -646,7 +677,7 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { } } - throw unexpected(lexer: lexer) + throw unexpected(lexer: lexer, atToken: keywordToken) } /** @@ -656,6 +687,7 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { */ func parseSchemaDefinition(lexer: Lexer) throws -> SchemaDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "schema") let directives = try parseDirectives(lexer: lexer) let operationTypes = try many( @@ -666,6 +698,7 @@ func parseSchemaDefinition(lexer: Lexer) throws -> SchemaDefinition { ) return SchemaDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, directives: directives, operationTypes: operationTypes ) @@ -688,11 +721,13 @@ func parseOperationTypeDefinition(lexer: Lexer) throws -> OperationTypeDefinitio */ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "scalar") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) return ScalarTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives ) @@ -704,6 +739,7 @@ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { */ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let start = lexer.token; + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "type") let name = try parseName(lexer: lexer); let interfaces = try parseImplementsInterfaces(lexer: lexer); @@ -716,6 +752,7 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { ) return ObjectTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, interfaces: interfaces, directives: directives, @@ -745,6 +782,7 @@ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { */ func parseFieldDefinition(lexer: Lexer) throws -> FieldDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) let name = try parseName(lexer: lexer) let args = try parseArgumentDefs(lexer: lexer) try expect(lexer: lexer, kind: .colon) @@ -752,6 +790,7 @@ func parseFieldDefinition(lexer: Lexer) throws -> FieldDefinition { let directives = try parseDirectives(lexer: lexer) return FieldDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, arguments: args, type: type, @@ -779,6 +818,7 @@ func parseArgumentDefs(lexer: Lexer) throws -> [InputValueDefinition] { */ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) let name = try parseName(lexer: lexer) try expect(lexer: lexer, kind: .colon) let type = try parseTypeReference(lexer: lexer) @@ -792,6 +832,7 @@ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { return InputValueDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, type: type, defaultValue: defaultValue, @@ -804,6 +845,7 @@ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { */ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "interface") let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) @@ -816,6 +858,7 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio ) return InterfaceTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, interfaces: interfaces, directives: directives, @@ -828,6 +871,7 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio */ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { let start = lexer.token; + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "union") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -835,6 +879,7 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { let types = try parseUnionMembers(lexer: lexer) return UnionTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives, types: types @@ -861,6 +906,7 @@ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { */ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { let start = lexer.token; + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "enum"); let name = try parseName(lexer: lexer); let directives = try parseDirectives(lexer: lexer); @@ -872,6 +918,7 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { ) return EnumTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives, values: values @@ -885,10 +932,12 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { */ func parseEnumValueDefinition(lexer: Lexer) throws -> EnumValueDefinition { let start = lexer.token; + let description = try parseDescription(lexer: lexer) let name = try parseName(lexer: lexer); let directives = try parseDirectives(lexer: lexer); return EnumValueDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives ) @@ -899,6 +948,7 @@ func parseEnumValueDefinition(lexer: Lexer) throws -> EnumValueDefinition { */ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "input") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -910,6 +960,7 @@ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefin ) return InputObjectTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives, fields: fields @@ -935,6 +986,7 @@ func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinitio */ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { let start = lexer.token; + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "directive"); try expect(lexer: lexer, kind: .at) let name = try parseName(lexer: lexer); @@ -943,6 +995,7 @@ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { let locations = try parseDirectiveLocations(lexer: lexer); return DirectiveDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, arguments: args, locations: locations diff --git a/Tests/GraphQLTests/LanguageTests/ParserTests.swift b/Tests/GraphQLTests/LanguageTests/ParserTests.swift index 47642c24..87feb914 100644 --- a/Tests/GraphQLTests/LanguageTests/ParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/ParserTests.swift @@ -266,7 +266,7 @@ class ParserTests : XCTestCase { let expected: Value = ListValue( values: [ IntValue(value: "123"), - StringValue(value: "abc") + StringValue(value: "abc", block: false) ] ) diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index a96d3079..fa249e4b 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -9,6 +9,10 @@ func fieldNode(_ name: Name, _ type: Type) -> FieldDefinition { return FieldDefinition(name: name, type: type) } +func fieldNodeWithDescription(_ description: StringValue? = nil, _ name: Name, _ type: Type) -> FieldDefinition { + return FieldDefinition(description: description, name: name, type: type) +} + func typeNode(_ name: String) -> NamedType { return NamedType(name: nameNode(name)) } @@ -17,6 +21,10 @@ func enumValueNode(_ name: String) -> EnumValueDefinition { return EnumValueDefinition(name: nameNode(name)) } +func enumValueWithDescriptionNode(_ description: StringValue?, _ name: String) -> EnumValueDefinition { + return EnumValueDefinition(description: description, name: nameNode(name)) +} + func fieldNodeWithArgs(_ name: Name, _ type: Type, _ args: [InputValueDefinition]) -> FieldDefinition { return FieldDefinition(name: name, arguments: args, type: type) } @@ -25,6 +33,17 @@ func inputValueNode(_ name: Name, _ type: Type, _ defaultValue: Value? = nil) -> return InputValueDefinition(name: name, type: type, defaultValue: defaultValue) } +func inputValueWithDescriptionNode(_ description: StringValue?, + _ name: Name, + _ type: Type, + _ defaultValue: Value? = nil) -> InputValueDefinition { + return InputValueDefinition(description: description, name: name, type: type, defaultValue: defaultValue) +} + +func namedTypeNode(_ name: String ) -> NamedType { + return NamedType(name: nameNode(name)) +} + class SchemaParserTests : XCTestCase { func testSimpleType() throws { let source = "type Hello { world: String }" @@ -376,4 +395,342 @@ class SchemaParserTests : XCTestCase { let source = "input Hello { world(foo: Int): String }" XCTAssertThrowsError(try parse(source: source)) } + + func testSimpleSchema() throws { + let source = "schema { query: Hello }" + let expected = SchemaDefinition( + directives: [], + operationTypes: [ + OperationTypeDefinition( + operation: .query, + type: namedTypeNode("Hello") + ) + ] + ) + let result = try parse(source: source) + XCTAssert(result.definitions[0] == expected) + } + + // Description tests + + func testTypeWithDescription() throws { + let source = #""The Hello type" type Hello { world: String }"# + + let expected = ObjectTypeDefinition( + description: StringValue(value: "The Hello type", block: false), + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + + let result = try parse(source: source) + XCTAssertEqual(result.definitions[0] as! ObjectTypeDefinition, expected, "\n\(dump(result.definitions[0]))\n\(dump(expected))\n") + } + + func testTypeWitMultilinehDescription() throws { + let source = #""" + """ + The Hello type. + Multi-line description + """ + type Hello { + world: String + } + """# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + description: StringValue(value:"The Hello type.\nMulti-line description", block: true), + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testDirectiveDesciption() throws { + let source = #""directive description" directive @Test(a: String = "hello") on FIELD"# + + let expected: Document = Document( + definitions: [ + DirectiveDefinition(loc: nil, + description: StringValue(value: "directive description", block: false), + name: nameNode("Test"), + arguments: [ + inputValueNode( + nameNode("a"), + typeNode("String"), + StringValue(value: "hello", block: false) + ) + ], + locations: [ + nameNode("FIELD") + ]) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testDirectiveMultilineDesciption() throws { + let source = #""" + """ + directive description + """ + directive @Test(a: String = "hello") on FIELD + """# + let expected: Document = Document( + definitions: [ + DirectiveDefinition(loc: nil, + description: StringValue(value: "directive description", block: true), + name: nameNode("Test"), + arguments: [ + inputValueNode( + nameNode("a"), + typeNode("String"), + StringValue(value: "hello", block: false) + ) + ], + locations: [ + nameNode("FIELD") + ]) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleSchemaWithDescription() throws { + let source = #""Hello Schema" schema { query: Hello } "# + + let expected = SchemaDefinition( + description: StringValue(value: "Hello Schema", block: false), + directives: [], + operationTypes: [ + OperationTypeDefinition( + operation: .query, + type: namedTypeNode("Hello") + ) + ] + ) + let result = try parse(source: source) + XCTAssert(result.definitions[0] == expected) + } + + func testScalarWithDescription() throws { + let source = #""Hello Scaler Test" scalar Hello"# + + let expected = Document( + definitions: [ + ScalarTypeDefinition( + description: StringValue(value: "Hello Scaler Test", block: false), + name: nameNode("Hello") + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleInterfaceWithDescription() throws { + let source = #""Hello World Interface" interface Hello { world: String } "# + + let expected = Document( + definitions: [ + InterfaceTypeDefinition( + description: StringValue(value: "Hello World Interface", block: false), + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleUnionWithDescription() throws { + let source = #""Hello World Union!" union Hello = World "# + + let expected = Document( + definitions: [ + UnionTypeDefinition( + description: StringValue(value: "Hello World Union!", block: false), + name: nameNode("Hello"), + types: [ + typeNode("World"), + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSingleValueEnumDescription() throws { + let source = #""Hello World Enum..." enum Hello { WORLD } "# + + let expected = Document( + definitions: [ + EnumTypeDefinition( + description: StringValue(value: "Hello World Enum...", block: false), + name: nameNode("Hello"), + values: [ + enumValueNode("WORLD"), + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleInputObjectWithDescription() throws { + let source = #""Hello Input Object" input Hello { world: String }"# + + let expected = Document( + definitions: [ + InputObjectTypeDefinition( + description: StringValue(value: "Hello Input Object", block: false), + name: nameNode("Hello"), + fields: [ + inputValueNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + // Test Fields and values with optional descriptions + + func testSingleValueEnumWithDescription() throws { + let source = """ + enum Hello { + "world description" + WORLD + "Hello there" + HELLO + } + """ + + let expected = Document( + definitions: [ + EnumTypeDefinition( + name: nameNode("Hello"), + values: [ + enumValueWithDescriptionNode(StringValue(value: "world description", block: false), "WORLD"), + enumValueWithDescriptionNode(StringValue(value: "Hello there", block: false), "HELLO") + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testTypeFieldWithDescription() throws { + let source = #"type Hello { "The world field." world: String } "# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + fieldNodeWithDescription( + StringValue(value: "The world field.", block: false), + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testTypeFieldWithMultilineDescription() throws { + let source = #""" + type Hello { + """ + The world + field. + """ + world: String + } + """# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + fieldNodeWithDescription( + StringValue(value: "The world\nfield.", block: true), + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected, "\(dump(result)) \n\n\(dump(expected))") + } + + + func testSimpleInputObjectFieldWithDescription() throws { + let source = #"input Hello { "World field" world: String }"# + + let expected = Document( + definitions: [ + InputObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + inputValueWithDescriptionNode( + StringValue(value: "World field", block: false), + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + }