github.com/dgraph-io/dgraph@v1.2.8/graphql/schema/schemagen.go (about) 1 /* 2 * Copyright 2019 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package schema 18 19 import ( 20 "fmt" 21 "strings" 22 23 "github.com/pkg/errors" 24 "github.com/vektah/gqlparser/ast" 25 "github.com/vektah/gqlparser/gqlerror" 26 "github.com/vektah/gqlparser/parser" 27 "github.com/vektah/gqlparser/validator" 28 ) 29 30 // A Handler can produce valid GraphQL and Dgraph schemas given an input of 31 // types and relationships 32 type Handler interface { 33 DGSchema() string 34 GQLSchema() string 35 } 36 37 type handler struct { 38 input string 39 originalDefs []string 40 completeSchema *ast.Schema 41 dgraphSchema string 42 } 43 44 // FromString builds a GraphQL Schema from input string, or returns any parsing 45 // or validation errors. 46 func FromString(schema string) (Schema, error) { 47 // validator.Prelude includes a bunch of predefined types which help with schema introspection 48 // queries, hence we include it as part of the schema. 49 doc, gqlErr := parser.ParseSchemas(validator.Prelude, &ast.Source{Input: schema}) 50 if gqlErr != nil { 51 return nil, errors.Wrap(gqlErr, "while parsing GraphQL schema") 52 } 53 54 gqlSchema, gqlErr := validator.ValidateSchemaDocument(doc) 55 if gqlErr != nil { 56 return nil, errors.Wrap(gqlErr, "while validating GraphQL schema") 57 } 58 59 return AsSchema(gqlSchema), nil 60 } 61 62 func (s *handler) GQLSchema() string { 63 return Stringify(s.completeSchema, s.originalDefs) 64 } 65 66 func (s *handler) DGSchema() string { 67 return s.dgraphSchema 68 } 69 70 // NewHandler processes the input schema. If there are no errors, it returns 71 // a valid Handler, otherwise it returns nil and an error. 72 func NewHandler(input string) (Handler, error) { 73 if input == "" { 74 return nil, gqlerror.Errorf("No schema specified") 75 } 76 77 // The input schema contains just what's required to describe the types, 78 // relationships and searchability - but that's not enough to define a 79 // valid GraphQL schema: e.g. we allow an input schema file like 80 // 81 // type T { 82 // f: Int @search 83 // } 84 // 85 // But, that's not valid GraphQL unless there's also definitions of scalars 86 // (Int, String, etc) and definitions of the directives (@search, etc). 87 // We don't want to make the user have those in their file and then we have 88 // to check that they've made the right definitions, etc, etc. 89 // 90 // So we parse the original input of just types and relationships and 91 // run a validation to make sure it only contains things that it should. 92 // To that we add all the scalars and other definitions we always require. 93 // 94 // Then, we GraphQL validate to make sure their definitions plus our additions 95 // is GraphQL valid. At this point we know the definitions are GraphQL valid, 96 // but we need to check if it makes sense to our layer. 97 // 98 // The next final validation ensures that the definitions are made 99 // in such a way that our GraphQL API will be able to interpret the schema 100 // correctly. 101 // 102 // Then we can complete the process by adding in queries and mutations etc. to 103 // make the final full GraphQL schema. 104 105 doc, gqlErr := parser.ParseSchemas(validator.Prelude, &ast.Source{Input: input}) 106 if gqlErr != nil { 107 return nil, gqlerror.List{gqlErr} 108 } 109 110 gqlErrList := preGQLValidation(doc) 111 if gqlErrList != nil { 112 return nil, gqlErrList 113 } 114 115 defns := make([]string, 0, len(doc.Definitions)) 116 for _, defn := range doc.Definitions { 117 if defn.BuiltIn { 118 continue 119 } 120 defns = append(defns, defn.Name) 121 } 122 123 expandSchema(doc) 124 125 sch, gqlErr := validator.ValidateSchemaDocument(doc) 126 if gqlErr != nil { 127 return nil, gqlerror.List{gqlErr} 128 } 129 130 gqlErrList = postGQLValidation(sch, defns) 131 if gqlErrList != nil { 132 return nil, gqlErrList 133 } 134 135 dgSchema := genDgSchema(sch, defns) 136 completeSchema(sch, defns) 137 138 return &handler{ 139 input: input, 140 dgraphSchema: dgSchema, 141 completeSchema: sch, 142 originalDefs: defns, 143 }, nil 144 } 145 146 func getAllSearchIndexes(val *ast.Value) []string { 147 res := make([]string, len(val.Children)) 148 149 for i, child := range val.Children { 150 res[i] = supportedSearches[child.Value.Raw].dgIndex 151 } 152 153 return res 154 } 155 156 func typeName(def *ast.Definition) string { 157 name := def.Name 158 dir := def.Directives.ForName(dgraphDirective) 159 if dir == nil { 160 return name 161 } 162 typeArg := dir.Arguments.ForName(dgraphTypeArg) 163 if typeArg == nil { 164 return name 165 } 166 return typeArg.Value.Raw 167 } 168 169 // fieldName returns the dgraph predicate corresponding to a field. 170 // If the field had a dgraph directive, then it returns the value of the name field otherwise 171 // it returns typeName + "." + fieldName. 172 func fieldName(def *ast.FieldDefinition, typName string) string { 173 name := typName + "." + def.Name 174 dir := def.Directives.ForName(dgraphDirective) 175 if dir == nil { 176 return name 177 } 178 predArg := dir.Arguments.ForName(dgraphPredArg) 179 if predArg == nil { 180 return name 181 } 182 return predArg.Value.Raw 183 } 184 185 // genDgSchema generates Dgraph schema from a valid graphql schema. 186 func genDgSchema(gqlSch *ast.Schema, definitions []string) string { 187 var typeStrings []string 188 189 for _, key := range definitions { 190 def := gqlSch.Types[key] 191 switch def.Kind { 192 case ast.Object, ast.Interface: 193 typName := typeName(def) 194 var typeDef, preds strings.Builder 195 fmt.Fprintf(&typeDef, "type %s {\n", typName) 196 for _, f := range def.Fields { 197 if f.Type.Name() == "ID" { 198 continue 199 } 200 201 typName = typeName(def) 202 // This field could have originally been defined in an interface that this type 203 // implements. If we get a parent interface, then we should prefix the field name 204 // with it instead of def.Name. 205 parentInt := parentInterface(gqlSch, def, f.Name) 206 if parentInt != nil { 207 typName = typeName(parentInt) 208 } 209 fname := fieldName(f, typName) 210 211 var prefix, suffix string 212 if f.Type.Elem != nil { 213 prefix = "[" 214 suffix = "]" 215 } 216 217 var typStr string 218 switch gqlSch.Types[f.Type.Name()].Kind { 219 case ast.Object: 220 typStr = fmt.Sprintf("%suid%s", prefix, suffix) 221 222 fmt.Fprintf(&typeDef, " %s: %s\n", fname, typStr) 223 if parentInt == nil { 224 fmt.Fprintf(&preds, "%s: %s .\n", fname, typStr) 225 } 226 case ast.Scalar: 227 typStr = fmt.Sprintf( 228 "%s%s%s", 229 prefix, scalarToDgraph[f.Type.Name()], suffix, 230 ) 231 232 indexStr := "" 233 upsertStr := "" 234 search := f.Directives.ForName(searchDirective) 235 id := f.Directives.ForName(idDirective) 236 if id != nil { 237 upsertStr = "@upsert " 238 } 239 240 if search != nil { 241 arg := search.Arguments.ForName(searchArgs) 242 if arg != nil { 243 indexes := getAllSearchIndexes(arg.Value) 244 indexes = addHashIfRequired(f, indexes) 245 indexStr = fmt.Sprintf(" @index(%s)", strings.Join(indexes, ", ")) 246 } else { 247 indexStr = fmt.Sprintf(" @index(%s)", defaultSearches[f.Type.Name()]) 248 } 249 } else if id != nil { 250 indexStr = fmt.Sprintf(" @index(hash)") 251 } 252 253 fmt.Fprintf(&typeDef, " %s: %s\n", fname, typStr) 254 if parentInt == nil { 255 fmt.Fprintf(&preds, "%s: %s%s %s.\n", fname, typStr, indexStr, upsertStr) 256 } 257 case ast.Enum: 258 typStr = fmt.Sprintf("%s%s%s", prefix, "string", suffix) 259 260 indexStr := " @index(hash)" 261 search := f.Directives.ForName(searchDirective) 262 if search != nil { 263 arg := search.Arguments.ForName(searchArgs) 264 if arg != nil { 265 indexes := getAllSearchIndexes(arg.Value) 266 indexStr = fmt.Sprintf(" @index(%s)", strings.Join(indexes, ", ")) 267 } 268 } 269 fmt.Fprintf(&typeDef, " %s: %s\n", fname, typStr) 270 if parentInt == nil { 271 fmt.Fprintf(&preds, "%s: %s%s .\n", fname, typStr, indexStr) 272 } 273 } 274 } 275 fmt.Fprintf(&typeDef, "}\n") 276 277 typeStrings = append( 278 typeStrings, 279 fmt.Sprintf("%s%s", typeDef.String(), preds.String()), 280 ) 281 } 282 } 283 284 return strings.Join(typeStrings, "") 285 }