diff --git a/graphql/e2e/custom_logic/custom_logic_test.go b/graphql/e2e/custom_logic/custom_logic_test.go index 59368a763e3..b76ac03dfef 100644 --- a/graphql/e2e/custom_logic/custom_logic_test.go +++ b/graphql/e2e/custom_logic/custom_logic_test.go @@ -223,7 +223,7 @@ func TestServerShouldAllowForwardHeaders(t *testing.T) { id: ID! name: String! } - type Movie @remote { + type Movie { id: ID! name: String! @custom(http: { url: "http://mock:8888/movieName", @@ -235,6 +235,7 @@ func TestServerShouldAllowForwardHeaders(t *testing.T) { method: "POST", forwardHeaders: ["User-Id", "X-App-Token"] }) + foo: String } type Query { @@ -2098,9 +2099,9 @@ func TestRestCustomLogicInDeepNestedField(t *testing.T) { type SearchTweets { id: ID! text: String! - user: User + user: User } - + type User { id: ID! screen_name: String! @id @@ -2110,12 +2111,12 @@ func TestRestCustomLogicInDeepNestedField(t *testing.T) { }) tweets: [SearchTweets] @hasInverse(field: user) } - + type RemoteUser @remote { id: ID! name: String } - + type Followers@remote{ users: [RemoteUser] } diff --git a/graphql/schema/dgraph_schemagen_test.yml b/graphql/schema/dgraph_schemagen_test.yml index e5f1cb4ffae..13d4b52771a 100644 --- a/graphql/schema/dgraph_schemagen_test.yml +++ b/graphql/schema/dgraph_schemagen_test.yml @@ -8,6 +8,7 @@ schemas: } type P { id: ID! + q: A } output: | type A { @@ -15,7 +16,9 @@ schemas: } A.p: uid . type P { + P.q } + P.q: uid . - name: "Scalar list" @@ -54,6 +57,7 @@ schemas: } type P { id: ID! + name: String } output: | type X { @@ -61,7 +65,9 @@ schemas: } X.p: [uid] . type P { + P.name } + P.name: string . - name: "Scalar types" diff --git a/graphql/schema/gqlschema_test.yml b/graphql/schema/gqlschema_test.yml index cc3841465bb..d24b44ef4f2 100644 --- a/graphql/schema/gqlschema_test.yml +++ b/graphql/schema/gqlschema_test.yml @@ -9,6 +9,7 @@ invalid_schemas: } errlist: [ {"message":"Fields id1, id2 and id3 are listed as IDs for type P, but a type can have only one ID field. Pick a single field as the ID for type P.", "locations":[{"line":2, "column":3}, {"line":3, "column":3}, {"line":4, "column":3}]}, + {"message":"Type P; is invalid, a type must have atleast one field that is not of ID! type and doesn't have @custom directive.", "locations":[{"line":1, "column":6}]} ] - @@ -44,6 +45,7 @@ invalid_schemas: input: | type A { f: [ID] + name: String } errlist: [ {"message": "Type A; Field f: ID lists are invalid.", "locations": [{"line":2, "column": 3}]} @@ -239,9 +241,11 @@ invalid_schemas: input: | type X { id: ID! @search + name: String } type Y { id: ID! @search(by: [term]) + name: String } errlist: [ {"message": "Type X; Field id: has the @search directive but fields of type ID can't @@ -250,7 +254,7 @@ invalid_schemas: {"message": "Type Y; Field id: has the @search directive but the argument term doesn't apply to field type ID. Search by term applies to fields of type String. Fields of type ID can't have the @search directive.", - "locations":[{"line":5, "column":12}]} + "locations":[{"line":6, "column":12}]} ] - @@ -758,19 +762,22 @@ invalid_schemas: input: | type Author { id: ID! + name: String } input UpdateAuthorInput { id: ID! + name: String } errlist: [ {"message": "UpdateAuthorInput is a reserved word, so you can't declare an input type with this name. Pick a different name for the input type.", - "locations":[{"line":4, "column":7}]}, + "locations":[{"line":5, "column":7}]}, ] - name: "@custom query can't have same name as the query generated for other types" input: | type Author { id: ID! + name: String } type Query { @@ -778,13 +785,14 @@ invalid_schemas: } errlist: [ {"message": "getAuthor is a reserved word, so you can't declare a query with this name. Pick a different name for the query.", - "locations":[{"line":6, "column":3}]}, + "locations":[{"line":7, "column":3}]}, ] - name: "@custom mutation can't have same name as the mutation generated for other types" input: | type Author { id: ID! + name: String } type Mutation { @@ -792,13 +800,14 @@ invalid_schemas: } errlist: [ {"message": "addAuthor is a reserved word, so you can't declare a mutation with this name. Pick a different name for the mutation.", - "locations":[{"line":6, "column":3}]}, + "locations":[{"line":7, "column":3}]}, ] - name: "@custom directive with extra arguments" input: | type Author { id: ID! + name: String } type Query { @@ -806,13 +815,14 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: has 2 arguments for @custom directive, it should contain exactly 1 argument.", - "locations":[{"line":6, "column":32}]}, + "locations":[{"line":7, "column":32}]}, ] - name: "@custom directive without http argument" input: | type Author { id: ID! + name: String } type Query { @@ -820,7 +830,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: http argument for @custom directive should not be empty.", - "locations":[{"line":6, "column":32}]}, + "locations":[{"line":7, "column":32}]}, ] - @@ -828,6 +838,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -835,7 +846,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; url field inside @custom directive is invalid.", - "locations":[{"line":6, "column":52}]}, + "locations":[{"line":7, "column":52}]}, ] - @@ -843,6 +854,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -850,7 +862,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; url path inside @custom directive uses an argument idm that is not defined.", - "locations":[{"line":6, "column":52}]}, + "locations":[{"line":7, "column":52}]}, ] - @@ -858,6 +870,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -865,7 +878,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; url path inside @custom directive uses an argument id that can be null.", - "locations":[{"line":6, "column":52}]}, + "locations":[{"line":7, "column":52}]}, ] - @@ -873,6 +886,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -880,7 +894,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; url query inside @custom directive uses an argument idm that is not defined.", - "locations":[{"line":6, "column":52}]}, + "locations":[{"line":7, "column":52}]}, ] - @@ -888,6 +902,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -895,7 +910,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; method field inside @custom directive can only be GET/POST/PUT/PATCH/DELETE.", - "locations":[{"line":6, "column":82}]}, + "locations":[{"line":7, "column":82}]}, ] - @@ -903,6 +918,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -910,7 +926,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; mode field inside @custom directive can't be present on Query/Mutation.", - "locations":[{"line":6, "column":94}]}, + "locations":[{"line":7, "column":94}]}, ] - @@ -918,15 +934,17 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Post { id: ID! + name: String! author: Author! @custom(http: {url: "http://google.com/", method: "GET", mode: RANDOM}) } errlist: [ {"message": "Type Post; Field author; mode field inside @custom directive can only be SINGLE/BATCH.", - "locations":[{"line":7, "column":82}]}, + "locations":[{"line":9, "column":82}]}, ] - @@ -934,10 +952,12 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Post { id: ID! + name: String! author: Author! @custom(http: { url: "http://google.com?a=$id", method: "GET", @@ -945,7 +965,7 @@ invalid_schemas: } errlist: [ {"message": "Type Post; Field author; has parameters in url inside @custom directive while mode is BATCH, url can't contain parameters if mode is BATCH.", - "locations":[{"line":8, "column":11}]}, + "locations":[{"line":10, "column":11}]}, ] - @@ -953,6 +973,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -964,7 +985,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; has parameters in url along with graphql field inside @custom directive, url can't contain parameters if graphql field is present.", - "locations":[{"line":6, "column":32}]}, + "locations":[{"line":7, "column":32}]}, ] @@ -973,6 +994,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -984,7 +1006,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; has method GET while graphql field is also present inside @custom directive, method can only be POST if graphql field is present.", - "locations":[{"line":6, "column":32}]}, + "locations":[{"line":7, "column":32}]}, ] - @@ -992,6 +1014,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1004,7 +1027,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; has both body and graphql field inside @custom directive, they can't be present together.", - "locations":[{"line":6, "column":32}]}, + "locations":[{"line":7, "column":32}]}, ] - @@ -1012,6 +1035,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1023,7 +1047,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; body template inside @custom directive could not be parsed.", - "locations":[{"line":9, "column":12}]}, + "locations":[{"line":10, "column":12}]}, ] - @@ -1031,6 +1055,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1042,7 +1067,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1; body template inside @custom directive uses an argument idm that is not defined.", - "locations":[{"line":9, "column":12}]}, + "locations":[{"line":10, "column":12}]}, ] - @@ -1050,6 +1075,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1061,7 +1087,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found 0 operations, it can have exactly one operation.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1069,6 +1095,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { getAuthor1(id: ID): Author! @custom(http: { @@ -1079,7 +1106,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: unable to parse graphql in @custom directive because: Unexpected Name \"garbage\"", - "locations":[{"line":8, "column":15}]}, + "locations":[{"line":9, "column":15}]}, ] - @@ -1087,6 +1114,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String! } type Query { @@ -1098,7 +1126,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found 2 operations, it can have exactly one operation.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1106,6 +1134,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String! } type Query { @@ -1117,7 +1146,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found `subscription` operation, it can only have query/mutation.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1125,6 +1154,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String! } type Query { @@ -1136,7 +1166,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found operation with name `opName`, it can't have a name.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1144,6 +1174,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String! } type Query { @@ -1155,7 +1186,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found operation with directives, it can't have any directives.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1163,6 +1194,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1174,7 +1206,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found 2 fields inside operation `query`, it can have exactly one field.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1182,6 +1214,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1193,7 +1226,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found mutation `getAuthor` with alias `authors`, it can't have any alias.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1201,6 +1234,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1212,7 +1246,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: unable to parse graphql in @custom directive because: Expected Name, found :", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1220,6 +1254,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1231,7 +1266,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found query `getAuthor` with directives, it can't have any directives.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1239,6 +1274,7 @@ invalid_schemas: input: | type Author { id: ID! + name: String } type Query { @@ -1250,7 +1286,7 @@ invalid_schemas: } errlist: [ {"message": "Type Query; Field getAuthor1: inside graphql in @custom directive, found query `getAuthor` with a selection set, it can't have any selection set.", - "locations":[{"line":9, "column":15}]}, + "locations":[{"line":10, "column":15}]}, ] - @@ -1269,6 +1305,7 @@ invalid_schemas: graphql: "query { getAuthor(postId: {id: $id}) }", body: "{id: $id}" }) + name: String } errlist: [ {"message": "Type Post; Field author: inside graphql in @custom directive, for BATCH mode, query `getAuthor` can have only one argument whose value should be a variable.", @@ -1284,6 +1321,7 @@ invalid_schemas: method: "GET", body: "{ abc: $foo }" }) + name: String } errlist: [ {"message": "Type Author; Field id; custom directive not allowed on field of type ID! or @@ -1301,6 +1339,7 @@ invalid_schemas: method: "GET", body: "{ id: $id }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; custom directive not allowed on field of type ID! or field with @id directive.", @@ -1317,6 +1356,7 @@ invalid_schemas: method: "GET", body: "{ abc: $name, id: $id }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; @custom directive, body template can't require itself.", @@ -1333,6 +1373,7 @@ invalid_schemas: method: "GET", body: "{ abc: $abc }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; @custom directive, body template must use fields defined within the type, found `abc`.", @@ -1410,6 +1451,7 @@ invalid_schemas: method: "POST", graphql: "query($id: ID!, $name: String!) { getName(abc: $name, id: $id) }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; @custom directive, graphql can't require itself.", @@ -1426,6 +1468,7 @@ invalid_schemas: method: "POST", graphql: "query($id: ID!, $abc: String!) { getName(obj: [{abc: $abc}], id: $id) }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; @custom directive, graphql must use fields defined within the type, found `abc`.", @@ -1444,6 +1487,7 @@ invalid_schemas: graphql: "query ($abc: [AuthorInput]) { getName(obj: $abc) }" body: "{abc: $abc, id: $id}" }) + bar: String } errlist: [ {"message": "Type Author; Field name; @custom directive, body template must use fields defined within the type, found `abc`.", @@ -1521,6 +1565,7 @@ invalid_schemas: method: "GET", body: "{ abc: $abc, jam: $foo }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; @custom directive is only allowed on fields where the type @@ -1538,6 +1583,7 @@ invalid_schemas: method: "GET", body: "{ id: $id }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; custom directive not allowed along with @search directive.", @@ -1554,6 +1600,7 @@ invalid_schemas: method: "GET", body: "{ id: $id }" }) + bar: String } errlist: [ {"message": "Type Author; Field name; custom directive not allowed along with @dgraph directive.", @@ -1805,6 +1852,35 @@ invalid_schemas: "locations":[{"line":7, "column":15}]}, ] + - + name: "type can't just have ID! type field" + input: | + type Author { + id: ID! + } + + errlist: [ + {"message": "Type Author; is invalid, a type must have atleast one field that is not of ID! type and doesn't have @custom directive.", + "locations":[{"line":1, "column":6}]}, + ] + + - + name: "types must have field which is not of ID! type and doesn't have @custom directive" + input: | + type Author { + id: ID! + name: String! @custom(http: { + url: "http://google.com/", + method: "POST", + graphql: "query ($id: ID!) { getAuthor(id: $id) }", + skipIntrospection: true + }) + } + errlist: [ + {"message": "Type Author; is invalid, a type must have atleast one field that is not of ID! type and doesn't have @custom directive.", + "locations":[{"line":1, "column":6}]}, + ] + - name: "@auth on interface" input: | interface X @auth( @@ -2059,4 +2135,4 @@ valid_schemas: graphql: "query ($id: ID!, $age: Int!) { getAuthor(id: $id, age: $age) }", skipIntrospection: true }) - } \ No newline at end of file + } diff --git a/graphql/schema/rules.go b/graphql/schema/rules.go index 13f152040df..8dbd41eac0b 100644 --- a/graphql/schema/rules.go +++ b/graphql/schema/rules.go @@ -38,7 +38,7 @@ func init() { schemaValidations = append(schemaValidations, dgraphDirectivePredicateValidation) typeValidations = append(typeValidations, idCountCheck, dgraphDirectiveTypeValidation, - passwordDirectiveValidation, conflictingDirectiveValidation) + passwordDirectiveValidation, conflictingDirectiveValidation, nonIdFieldsCheck) fieldValidations = append(fieldValidations, listValidityCheck, fieldArgumentCheck, fieldNameCheck, isValidFieldForList, hasAuthDirective) @@ -524,6 +524,39 @@ func dgraphDirectiveTypeValidation(typ *ast.Definition) *gqlerror.Error { return nil } +// A type should have other fields apart from fields of +// 1. Type ID! +// 2. Fields with @custom directive. +// to be a valid type. Otherwise its not possible to add objects of that type. +func nonIdFieldsCheck(typ *ast.Definition) *gqlerror.Error { + if isQueryOrMutation(typ.Name) || typ.Kind == ast.Enum || typ.Kind == ast.Interface || + typ.Kind == ast.InputObject { + return nil + } + + // We don't generate mutations for remote types, so we skip this check for them. + remote := typ.Directives.ForName(remoteDirective) + if remote != nil { + return nil + } + + hasNonIdField := false + for _, field := range typ.Fields { + custom := field.Directives.ForName(customDirective) + if isIDField(typ, field) || custom != nil { + continue + } + hasNonIdField = true + break + } + + if !hasNonIdField { + return gqlerror.ErrorPosf(typ.Position, "Type %s; is invalid, a type must have atleast "+ + "one field that is not of ID! type and doesn't have @custom directive.", typ.Name) + } + return nil +} + func idCountCheck(typ *ast.Definition) *gqlerror.Error { var idFields []*ast.FieldDefinition var idDirectiveFields []*ast.FieldDefinition