feat : improve the generated introspection schema and avoid the chirino/graphql api leaking through the core api. (#53)
This commit is contained in:
parent
0a02bde219
commit
2a32c179ba
28
core/api.go
28
core/api.go
|
@ -93,7 +93,6 @@ type SuperGraph struct {
|
|||
anonExists bool
|
||||
qc *qcode.Compiler
|
||||
pc *psql.Compiler
|
||||
Engine *graphql.Engine
|
||||
}
|
||||
|
||||
// NewSuperGraph creates the SuperGraph struct, this involves querying the database to learn its
|
||||
|
@ -125,10 +124,6 @@ func NewSuperGraph(conf *Config, db *sql.DB) (*SuperGraph, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := sg.initGraphQLEgine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(conf.SecretKey) != 0 {
|
||||
sk := sha256.Sum256([]byte(conf.SecretKey))
|
||||
conf.SecretKey = ""
|
||||
|
@ -167,13 +162,18 @@ func (sg *SuperGraph) GraphQL(c context.Context, query string, vars json.RawMess
|
|||
|
||||
// use the chirino/graphql library for introspection queries
|
||||
// disabled when allow list is enforced
|
||||
if !sg.conf.UseAllowList &&
|
||||
res.op == qcode.QTQuery &&
|
||||
res.name == "IntrospectionQuery" {
|
||||
if !sg.conf.UseAllowList && res.name == "IntrospectionQuery" {
|
||||
engine, err := sg.createGraphQLEgine()
|
||||
if err != nil {
|
||||
res.Error = err.Error()
|
||||
return &res, err
|
||||
}
|
||||
|
||||
r := sg.Engine.ExecuteOne(&graphql.EngineRequest{Query: query})
|
||||
r := engine.ExecuteOne(&graphql.EngineRequest{Query: query})
|
||||
res.Data = r.Data
|
||||
|
||||
if r.Error() != nil {
|
||||
res.Error = r.Error().Error()
|
||||
}
|
||||
return &res, r.Error()
|
||||
}
|
||||
|
||||
|
@ -198,3 +198,11 @@ func (sg *SuperGraph) GraphQL(c context.Context, query string, vars json.RawMess
|
|||
|
||||
return &ct.res, nil
|
||||
}
|
||||
|
||||
func (sg *SuperGraph) GraphQLSchema() (string, error) {
|
||||
engine, err := sg.createGraphQLEgine()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return engine.Schema.String(), nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/chirino/graphql"
|
||||
|
@ -24,15 +26,17 @@ var typeMap map[string]string = map[string]string{
|
|||
"boolean": "Boolean",
|
||||
}
|
||||
|
||||
func (sg *SuperGraph) initGraphQLEgine() error {
|
||||
sg.Engine = graphql.New()
|
||||
engineSchema := sg.Engine.Schema
|
||||
func (sg *SuperGraph) createGraphQLEgine() (*graphql.Engine, error) {
|
||||
engine := graphql.New()
|
||||
engineSchema := engine.Schema
|
||||
dbSchema := sg.schema
|
||||
|
||||
sanitizeForGraphQLSchema := func(value string) string {
|
||||
// TODO:
|
||||
return value
|
||||
engineSchema.Parse(`
|
||||
enum OrderDirection {
|
||||
asc
|
||||
desc
|
||||
}
|
||||
`)
|
||||
|
||||
gqltype := func(col psql.DBColumn) schema.Type {
|
||||
typeName := typeMap[strings.ToLower(col.Type)]
|
||||
|
@ -59,106 +63,409 @@ func (sg *SuperGraph) initGraphQLEgine() error {
|
|||
engineSchema.EntryPoints[schema.Query] = query
|
||||
engineSchema.EntryPoints[schema.Mutation] = mutation
|
||||
|
||||
validGraphQLIdentifierRegex := regexp.MustCompile(`^[A-Za-z_][A-Za-z_0-9]*$`)
|
||||
|
||||
scalarExpressionTypesNeeded := map[string]bool{}
|
||||
tableNames := dbSchema.GetTableNames()
|
||||
for _, table := range tableNames {
|
||||
|
||||
ti, err := dbSchema.GetTable(table)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ti.IsSingular {
|
||||
continue
|
||||
}
|
||||
|
||||
pti, err := dbSchema.GetTable(ti.Plural)
|
||||
if err != nil {
|
||||
return err
|
||||
singularName := ti.Singular
|
||||
if !validGraphQLIdentifierRegex.MatchString(singularName) {
|
||||
return nil, errors.New("table name is not a valid GraphQL identifier: " + singularName)
|
||||
}
|
||||
pluralName := ti.Plural
|
||||
if !validGraphQLIdentifierRegex.MatchString(pluralName) {
|
||||
return nil, errors.New("table name is not a valid GraphQL identifier: " + pluralName)
|
||||
}
|
||||
|
||||
outputType := &schema.Object{
|
||||
Name: sanitizeForGraphQLSchema(ti.Singular) + "Output",
|
||||
Name: singularName + "Output",
|
||||
Fields: schema.FieldList{},
|
||||
}
|
||||
engineSchema.Types[outputType.Name] = outputType
|
||||
|
||||
inputType := &schema.InputObject{
|
||||
Name: sanitizeForGraphQLSchema(ti.Singular) + "Input",
|
||||
Name: singularName + "Input",
|
||||
Fields: schema.InputValueList{},
|
||||
}
|
||||
engineSchema.Types[inputType.Name] = inputType
|
||||
|
||||
orderByType := &schema.InputObject{
|
||||
Name: singularName + "OrderBy",
|
||||
Fields: schema.InputValueList{},
|
||||
}
|
||||
engineSchema.Types[orderByType.Name] = orderByType
|
||||
|
||||
expressionTypeName := singularName + "Expression"
|
||||
expressionType := &schema.InputObject{
|
||||
Name: expressionTypeName,
|
||||
Fields: schema.InputValueList{
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "and"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionTypeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "or"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionTypeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "not"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionTypeName}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
engineSchema.Types[expressionType.Name] = expressionType
|
||||
|
||||
for _, col := range ti.Columns {
|
||||
colName := col.Name
|
||||
if !validGraphQLIdentifierRegex.MatchString(colName) {
|
||||
return nil, errors.New("column name is not a valid GraphQL identifier: " + colName)
|
||||
}
|
||||
|
||||
colType := gqltype(col)
|
||||
nullableColType := ""
|
||||
if x, ok := colType.(*schema.NonNull); ok {
|
||||
nullableColType = x.OfType.(*schema.TypeName).Ident.Text
|
||||
} else {
|
||||
nullableColType = colType.(*schema.TypeName).Ident.Text
|
||||
}
|
||||
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: sanitizeForGraphQLSchema(col.Name),
|
||||
Type: gqltype(col),
|
||||
Name: colName,
|
||||
Type: colType,
|
||||
})
|
||||
inputType.Fields = append(inputType.Fields, &schema.InputValue{
|
||||
Name: schema.Ident{Text: sanitizeForGraphQLSchema(col.Name)},
|
||||
Type: gqltype(col),
|
||||
|
||||
// If it's a numeric type...
|
||||
if nullableColType == "Float" || nullableColType == "Int" {
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "avg_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "count_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "max_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "min_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "stddev_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "stddev_pop_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "stddev_samp_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "variance_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "var_pop_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
outputType.Fields = append(outputType.Fields, &schema.Field{
|
||||
Name: "var_samp_" + colName,
|
||||
Type: colType,
|
||||
})
|
||||
}
|
||||
|
||||
engineSchema.Types[outputType.Name] = outputType
|
||||
engineSchema.Types[inputType.Name] = inputType
|
||||
inputType.Fields = append(inputType.Fields, &schema.InputValue{
|
||||
Name: schema.Ident{Text: colName},
|
||||
Type: colType,
|
||||
})
|
||||
orderByType.Fields = append(orderByType.Fields, &schema.InputValue{
|
||||
Name: schema.Ident{Text: colName},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "OrderDirection"}}},
|
||||
})
|
||||
|
||||
scalarExpressionTypesNeeded[nullableColType] = true
|
||||
|
||||
expressionType.Fields = append(expressionType.Fields, &schema.InputValue{
|
||||
Name: schema.Ident{Text: colName},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: nullableColType + "Expression"}}},
|
||||
})
|
||||
}
|
||||
|
||||
outputTypeName := &schema.TypeName{Ident: schema.Ident{Text: outputType.Name}}
|
||||
inputTypeName := &schema.TypeName{Ident: schema.Ident{Text: inputType.Name}}
|
||||
pluralOutputTypeName := &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: outputType.Name}}}}}
|
||||
pluralInputTypeName := &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: inputType.Name}}}}}
|
||||
|
||||
args := schema.InputValueList{}
|
||||
args := schema.InputValueList{
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: "To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."},
|
||||
Name: schema.Ident{Text: "order_by"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: orderByType.Name}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "where"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: expressionType.Name}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "limit"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "offset"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "first"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "last"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Int"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "before"},
|
||||
Type: &schema.TypeName{Ident: schema.Ident{Text: "String"}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "after"},
|
||||
Type: &schema.TypeName{Ident: schema.Ident{Text: "String"}},
|
||||
},
|
||||
}
|
||||
if ti.PrimaryCol != nil {
|
||||
t := gqltype(*ti.PrimaryCol)
|
||||
if _, ok := t.(*schema.NonNull); !ok {
|
||||
t = &schema.NonNull{OfType: t}
|
||||
}
|
||||
arg := &schema.InputValue{
|
||||
args = append(args, &schema.InputValue{
|
||||
Desc: &schema.Description{Text: "Finds the record by the primary key"},
|
||||
Name: schema.Ident{Text: "id"},
|
||||
Type: t,
|
||||
})
|
||||
}
|
||||
args = append(args, arg)
|
||||
|
||||
if ti.TSVCol != nil {
|
||||
args = append(args, &schema.InputValue{
|
||||
Desc: &schema.Description{Text: "Performs full text search using a TSV index"},
|
||||
Name: schema.Ident{Text: "search"},
|
||||
Type: &schema.TypeName{Ident: schema.Ident{Text: "String!"}},
|
||||
})
|
||||
}
|
||||
|
||||
query.Fields = append(query.Fields, &schema.Field{
|
||||
Name: sanitizeForGraphQLSchema(ti.Singular),
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: singularName,
|
||||
Type: outputTypeName,
|
||||
Args: args,
|
||||
})
|
||||
query.Fields = append(query.Fields, &schema.Field{
|
||||
Name: sanitizeForGraphQLSchema(pti.Plural),
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: pluralName,
|
||||
Type: pluralOutputTypeName,
|
||||
Args: args,
|
||||
})
|
||||
|
||||
mutation.Fields = append(mutation.Fields, &schema.Field{
|
||||
Name: sanitizeForGraphQLSchema(ti.Singular),
|
||||
Args: append(args, schema.InputValueList{
|
||||
mutationArgs := schema.InputValueList{
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "insert"},
|
||||
Type: inputTypeName,
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "inserts"},
|
||||
Type: pluralInputTypeName,
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "update"},
|
||||
Type: inputTypeName,
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "updates"},
|
||||
Type: pluralInputTypeName,
|
||||
},
|
||||
}...),
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "upsert"},
|
||||
Type: inputTypeName,
|
||||
},
|
||||
&schema.InputValue{
|
||||
Desc: &schema.Description{Text: ""},
|
||||
Name: schema.Ident{Text: "upserts"},
|
||||
Type: pluralInputTypeName,
|
||||
},
|
||||
}
|
||||
mutation.Fields = append(mutation.Fields, &schema.Field{
|
||||
Name: singularName,
|
||||
Args: append(args, mutationArgs...),
|
||||
Type: outputType,
|
||||
})
|
||||
mutation.Fields = append(mutation.Fields, &schema.Field{
|
||||
Name: pluralName,
|
||||
Args: append(args, mutationArgs...),
|
||||
Type: outputType,
|
||||
})
|
||||
|
||||
}
|
||||
err := engineSchema.ResolveTypes()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
for typeName, _ := range scalarExpressionTypesNeeded {
|
||||
expressionType := &schema.InputObject{
|
||||
Name: typeName + "Expression",
|
||||
Fields: schema.InputValueList{
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "eq"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "equals"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "neq"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "not_equals"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "gt"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "greater_than"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "lt"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "lesser_than"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "gte"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "greater_or_equals"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "lte"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "lesser_or_equals"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "in"},
|
||||
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "nin"},
|
||||
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "not_in"},
|
||||
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
|
||||
},
|
||||
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "like"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "nlike"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "not_like"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "ilike"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "nilike"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "not_ilike"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "similar"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "nsimilar"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "not_similar"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "has_key"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "has_key_any"},
|
||||
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "has_key_all"},
|
||||
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "contains"},
|
||||
Type: &schema.NonNull{OfType: &schema.List{OfType: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: typeName}}}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "contained_in"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "String"}}},
|
||||
},
|
||||
&schema.InputValue{
|
||||
Name: schema.Ident{Text: "is_null"},
|
||||
Type: &schema.NonNull{OfType: &schema.TypeName{Ident: schema.Ident{Text: "Boolean"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
engineSchema.Types[expressionType.Name] = expressionType
|
||||
}
|
||||
|
||||
sg.Engine.Resolver = resolvers.Func(func(request *resolvers.ResolveRequest, next resolvers.Resolution) resolvers.Resolution {
|
||||
err := engineSchema.ResolveTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
engine.Resolver = resolvers.Func(func(request *resolvers.ResolveRequest, next resolvers.Resolution) resolvers.Resolution {
|
||||
resolver := resolvers.MetadataResolver.Resolve(request, next)
|
||||
if resolver != nil {
|
||||
return resolver
|
||||
|
@ -170,5 +477,5 @@ func (sg *SuperGraph) initGraphQLEgine() error {
|
|||
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
return engine, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/dosco/super-graph/core"
|
||||
|
@ -152,67 +153,23 @@ func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) {
|
|||
|
||||
t.Run("schema introspection", func(t *testing.T) {
|
||||
before(t)
|
||||
// fmt.Println(sg.Engine.Schema.String())
|
||||
assert.Equal(t, `type Mutation {
|
||||
line_item(id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!):line_itemOutput
|
||||
product(id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!):productOutput
|
||||
user(id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!):userOutput
|
||||
}
|
||||
type Query {
|
||||
line_item(id:Int!):line_itemOutput
|
||||
line_items(id:Int!):[line_itemOutput!]!
|
||||
product(id:Int!):productOutput
|
||||
products(id:Int!):[productOutput!]!
|
||||
user(id:Int!):userOutput
|
||||
users(id:Int!):[userOutput!]!
|
||||
}
|
||||
input line_itemInput {
|
||||
id:Int!
|
||||
price:Float
|
||||
product:Int
|
||||
quantity:Int
|
||||
}
|
||||
type line_itemOutput {
|
||||
id:Int!
|
||||
price:Float
|
||||
product:Int
|
||||
quantity:Int
|
||||
}
|
||||
input productInput {
|
||||
id:Int!
|
||||
name:String
|
||||
weight:Float
|
||||
}
|
||||
type productOutput {
|
||||
id:Int!
|
||||
name:String
|
||||
weight:Float
|
||||
}
|
||||
input userInput {
|
||||
full_name:String
|
||||
id:Int!
|
||||
}
|
||||
type userOutput {
|
||||
full_name:String
|
||||
id:Int!
|
||||
}
|
||||
schema {
|
||||
mutation: Mutation
|
||||
query: Query
|
||||
}
|
||||
`, sg.Engine.Schema.String())
|
||||
schema, err := sg.GraphQLSchema()
|
||||
require.NoError(t, err)
|
||||
// Uncomment the following line if you need to regenerate the expected schema.
|
||||
// ioutil.WriteFile("../introspection.graphql", []byte(schema), 0644)
|
||||
expected, err := ioutil.ReadFile("../introspection.graphql")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(expected), schema)
|
||||
})
|
||||
|
||||
res, err := sg.GraphQL(ctx, introspectionQuery, json.RawMessage(``))
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(res.Data),
|
||||
`{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":null,"types":`)
|
||||
assert.Contains(t, string(res.Data),
|
||||
`{"kind":"OBJECT","name":"Mutation","description":null,"fields":[{"name":"line_item","description":null`)
|
||||
}
|
||||
|
||||
const introspectionQuery = `
|
||||
query {
|
||||
query IntrospectionQuery {
|
||||
__schema {
|
||||
queryType { name }
|
||||
mutationType { name }
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
input FloatExpression {
|
||||
contained_in:String!
|
||||
contains:[Float!]!
|
||||
eq:Float!
|
||||
equals:Float!
|
||||
greater_or_equals:Float!
|
||||
greater_than:Float!
|
||||
gt:Float!
|
||||
gte:Float!
|
||||
has_key:Float!
|
||||
has_key_all:[Float!]!
|
||||
has_key_any:[Float!]!
|
||||
ilike:String!
|
||||
in:[Float!]!
|
||||
is_null:Boolean!
|
||||
lesser_or_equals:Float!
|
||||
lesser_than:Float!
|
||||
like:String!
|
||||
lt:Float!
|
||||
lte:Float!
|
||||
neq:Float!
|
||||
nilike:String!
|
||||
nin:[Float!]!
|
||||
nlike:String!
|
||||
not_equals:Float!
|
||||
not_ilike:String!
|
||||
not_in:[Float!]!
|
||||
not_like:String!
|
||||
not_similar:String!
|
||||
nsimilar:String!
|
||||
similar:String!
|
||||
}
|
||||
input IntExpression {
|
||||
contained_in:String!
|
||||
contains:[Int!]!
|
||||
eq:Int!
|
||||
equals:Int!
|
||||
greater_or_equals:Int!
|
||||
greater_than:Int!
|
||||
gt:Int!
|
||||
gte:Int!
|
||||
has_key:Int!
|
||||
has_key_all:[Int!]!
|
||||
has_key_any:[Int!]!
|
||||
ilike:String!
|
||||
in:[Int!]!
|
||||
is_null:Boolean!
|
||||
lesser_or_equals:Int!
|
||||
lesser_than:Int!
|
||||
like:String!
|
||||
lt:Int!
|
||||
lte:Int!
|
||||
neq:Int!
|
||||
nilike:String!
|
||||
nin:[Int!]!
|
||||
nlike:String!
|
||||
not_equals:Int!
|
||||
not_ilike:String!
|
||||
not_in:[Int!]!
|
||||
not_like:String!
|
||||
not_similar:String!
|
||||
nsimilar:String!
|
||||
similar:String!
|
||||
}
|
||||
type Mutation {
|
||||
line_item(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!, upsert:line_itemInput, upserts:[line_itemInput!]!
|
||||
):line_itemOutput
|
||||
line_items(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!, insert:line_itemInput, inserts:[line_itemInput!]!, update:line_itemInput, updates:[line_itemInput!]!, upsert:line_itemInput, upserts:[line_itemInput!]!
|
||||
):line_itemOutput
|
||||
product(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!, upsert:productInput, upserts:[productInput!]!
|
||||
):productOutput
|
||||
products(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!, insert:productInput, inserts:[productInput!]!, update:productInput, updates:[productInput!]!, upsert:productInput, upserts:[productInput!]!
|
||||
):productOutput
|
||||
user(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!, upsert:userInput, upserts:[userInput!]!
|
||||
):userOutput
|
||||
users(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!, insert:userInput, inserts:[userInput!]!, update:userInput, updates:[userInput!]!, upsert:userInput, upserts:[userInput!]!
|
||||
):userOutput
|
||||
}
|
||||
enum OrderDirection {
|
||||
asc
|
||||
desc
|
||||
}
|
||||
type Query {
|
||||
line_item(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!
|
||||
):line_itemOutput
|
||||
line_items(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:line_itemOrderBy!, where:line_itemExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!
|
||||
):[line_itemOutput!]!
|
||||
product(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!
|
||||
):productOutput
|
||||
products(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:productOrderBy!, where:productExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!
|
||||
):[productOutput!]!
|
||||
user(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!
|
||||
):userOutput
|
||||
users(
|
||||
"To sort or ordering results just use the order_by argument. This can be combined with where, search, etc to build complex queries to fit you needs."
|
||||
order_by:userOrderBy!, where:userExpression!, limit:Int!, offset:Int!, first:Int!, last:Int!, before:String, after:String,
|
||||
"Finds the record by the primary key"
|
||||
id:Int!
|
||||
):[userOutput!]!
|
||||
}
|
||||
input StringExpression {
|
||||
contained_in:String!
|
||||
contains:[String!]!
|
||||
eq:String!
|
||||
equals:String!
|
||||
greater_or_equals:String!
|
||||
greater_than:String!
|
||||
gt:String!
|
||||
gte:String!
|
||||
has_key:String!
|
||||
has_key_all:[String!]!
|
||||
has_key_any:[String!]!
|
||||
ilike:String!
|
||||
in:[String!]!
|
||||
is_null:Boolean!
|
||||
lesser_or_equals:String!
|
||||
lesser_than:String!
|
||||
like:String!
|
||||
lt:String!
|
||||
lte:String!
|
||||
neq:String!
|
||||
nilike:String!
|
||||
nin:[String!]!
|
||||
nlike:String!
|
||||
not_equals:String!
|
||||
not_ilike:String!
|
||||
not_in:[String!]!
|
||||
not_like:String!
|
||||
not_similar:String!
|
||||
nsimilar:String!
|
||||
similar:String!
|
||||
}
|
||||
input line_itemExpression {
|
||||
and:line_itemExpression!
|
||||
id:IntExpression!
|
||||
not:line_itemExpression!
|
||||
or:line_itemExpression!
|
||||
price:FloatExpression!
|
||||
product:IntExpression!
|
||||
quantity:IntExpression!
|
||||
}
|
||||
input line_itemInput {
|
||||
id:Int!
|
||||
price:Float
|
||||
product:Int
|
||||
quantity:Int
|
||||
}
|
||||
input line_itemOrderBy {
|
||||
id:OrderDirection!
|
||||
price:OrderDirection!
|
||||
product:OrderDirection!
|
||||
quantity:OrderDirection!
|
||||
}
|
||||
type line_itemOutput {
|
||||
avg_id:Int!
|
||||
avg_price:Float
|
||||
avg_product:Int
|
||||
avg_quantity:Int
|
||||
count_id:Int!
|
||||
count_price:Float
|
||||
count_product:Int
|
||||
count_quantity:Int
|
||||
id:Int!
|
||||
max_id:Int!
|
||||
max_price:Float
|
||||
max_product:Int
|
||||
max_quantity:Int
|
||||
min_id:Int!
|
||||
min_price:Float
|
||||
min_product:Int
|
||||
min_quantity:Int
|
||||
price:Float
|
||||
product:Int
|
||||
quantity:Int
|
||||
stddev_id:Int!
|
||||
stddev_pop_id:Int!
|
||||
stddev_pop_price:Float
|
||||
stddev_pop_product:Int
|
||||
stddev_pop_quantity:Int
|
||||
stddev_price:Float
|
||||
stddev_product:Int
|
||||
stddev_quantity:Int
|
||||
stddev_samp_id:Int!
|
||||
stddev_samp_price:Float
|
||||
stddev_samp_product:Int
|
||||
stddev_samp_quantity:Int
|
||||
var_pop_id:Int!
|
||||
var_pop_price:Float
|
||||
var_pop_product:Int
|
||||
var_pop_quantity:Int
|
||||
var_samp_id:Int!
|
||||
var_samp_price:Float
|
||||
var_samp_product:Int
|
||||
var_samp_quantity:Int
|
||||
variance_id:Int!
|
||||
variance_price:Float
|
||||
variance_product:Int
|
||||
variance_quantity:Int
|
||||
}
|
||||
input productExpression {
|
||||
and:productExpression!
|
||||
id:IntExpression!
|
||||
name:StringExpression!
|
||||
not:productExpression!
|
||||
or:productExpression!
|
||||
weight:FloatExpression!
|
||||
}
|
||||
input productInput {
|
||||
id:Int!
|
||||
name:String
|
||||
weight:Float
|
||||
}
|
||||
input productOrderBy {
|
||||
id:OrderDirection!
|
||||
name:OrderDirection!
|
||||
weight:OrderDirection!
|
||||
}
|
||||
type productOutput {
|
||||
avg_id:Int!
|
||||
avg_weight:Float
|
||||
count_id:Int!
|
||||
count_weight:Float
|
||||
id:Int!
|
||||
max_id:Int!
|
||||
max_weight:Float
|
||||
min_id:Int!
|
||||
min_weight:Float
|
||||
name:String
|
||||
stddev_id:Int!
|
||||
stddev_pop_id:Int!
|
||||
stddev_pop_weight:Float
|
||||
stddev_samp_id:Int!
|
||||
stddev_samp_weight:Float
|
||||
stddev_weight:Float
|
||||
var_pop_id:Int!
|
||||
var_pop_weight:Float
|
||||
var_samp_id:Int!
|
||||
var_samp_weight:Float
|
||||
variance_id:Int!
|
||||
variance_weight:Float
|
||||
weight:Float
|
||||
}
|
||||
input userExpression {
|
||||
and:userExpression!
|
||||
full_name:StringExpression!
|
||||
id:IntExpression!
|
||||
not:userExpression!
|
||||
or:userExpression!
|
||||
}
|
||||
input userInput {
|
||||
full_name:String
|
||||
id:Int!
|
||||
}
|
||||
input userOrderBy {
|
||||
full_name:OrderDirection!
|
||||
id:OrderDirection!
|
||||
}
|
||||
type userOutput {
|
||||
avg_id:Int!
|
||||
count_id:Int!
|
||||
full_name:String
|
||||
id:Int!
|
||||
max_id:Int!
|
||||
min_id:Int!
|
||||
stddev_id:Int!
|
||||
stddev_pop_id:Int!
|
||||
stddev_samp_id:Int!
|
||||
var_pop_id:Int!
|
||||
var_samp_id:Int!
|
||||
variance_id:Int!
|
||||
}
|
||||
schema {
|
||||
mutation: Mutation
|
||||
query: Query
|
||||
}
|
Loading…
Reference in New Issue