From 1bd2571a25088c4eb2c09a1c72b17a179549c20a Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Thu, 19 Mar 2020 17:27:54 -0400 Subject: [PATCH 1/4] Move naming conventions to an interface --- config/naming.go | 40 ++++++++++++++++++ endpoint/endpoint.go | 19 +++++---- graphql/keyspace.go | 20 ++++----- graphql/keyspace_schema.go | 36 ++++++++-------- graphql/routes.go | 47 +++++++++++++------- graphql/schema.go | 87 +++++++++++++++++++++----------------- graphql/table.go | 29 ++++++------- graphql/updater.go | 13 +++--- main.go | 4 +- 9 files changed, 181 insertions(+), 114 deletions(-) create mode 100644 config/naming.go diff --git a/config/naming.go b/config/naming.go new file mode 100644 index 0000000..65e14e6 --- /dev/null +++ b/config/naming.go @@ -0,0 +1,40 @@ +package config + +import "github.com/iancoleman/strcase" + +type NamingConvention interface { + ToCQLColumn(name string) string + ToCQLTable(name string) string + + ToGraphQLField(name string) string + ToGraphQLFieldPrefix(prefix string, name string) string + + ToGraphQLType(name string) string +} + +type defaultNaming struct { +} + +func NewDefaultNaming() *defaultNaming { + return &defaultNaming{} +} + +func (n *defaultNaming) ToCQLColumn(name string) string { + return strcase.ToSnake(name) +} + +func (n *defaultNaming) ToCQLTable(name string) string { + return strcase.ToSnake(name) +} + +func (n *defaultNaming) ToGraphQLField(name string) string { + return strcase.ToLowerCamel(name) +} + +func (n *defaultNaming) ToGraphQLFieldPrefix(prefix string, name string) string { + return strcase.ToLowerCamel(prefix) + strcase.ToCamel(name) +} + +func (n *defaultNaming) ToGraphQLType(name string) string { + return strcase.ToCamel(name) +} diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index be07e7b..86a3332 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -1,6 +1,7 @@ package endpoint // TODO: Change package name? import ( + "github.com/riptano/data-endpoints/config" "github.com/riptano/data-endpoints/db" "github.com/riptano/data-endpoints/graphql" "time" @@ -12,35 +13,35 @@ type DataEndpointConfig struct { DbPassword string ExcludedKeyspaces []string SchemaUpdateInterval time.Duration + Naming config.NamingConvention } type DataEndpoint struct { - db *db.Db - cfg DataEndpointConfig + graphQLRouteGen *graphql.RouteGenerator } func NewEndpointConfig(hosts ...string) *DataEndpointConfig { return &DataEndpointConfig{ DbHosts: hosts, SchemaUpdateInterval: 10 * time.Second, + Naming: config.NewDefaultNaming(), } } func (cfg *DataEndpointConfig) NewEndpoint() (*DataEndpoint, error) { - db, err := db.NewDb(cfg.DbUsername, cfg.DbPassword, cfg.DbHosts...) + dbClient, err := db.NewDb(cfg.DbUsername, cfg.DbPassword, cfg.DbHosts...) if err != nil { return nil, err } return &DataEndpoint{ - db: db, - cfg: *cfg, + graphQLRouteGen: graphql.NewRouteGenerator(dbClient, cfg.ExcludedKeyspaces, cfg.SchemaUpdateInterval, cfg.Naming), }, nil } -func (pnt *DataEndpoint) RoutesGql(pattern string) ([]graphql.Route, error) { - return graphql.Routes(pattern, pnt.cfg.ExcludedKeyspaces, pnt.db, pnt.cfg.SchemaUpdateInterval) +func (e *DataEndpoint) RoutesGraphQL(pattern string) ([]graphql.Route, error) { + return e.graphQLRouteGen.Routes(pattern) } -func (pnt *DataEndpoint) RoutesKeyspaceGql(pattern string, ksName string) ([]graphql.Route, error) { - return graphql.RoutesKeyspace(pattern, ksName, pnt.db, pnt.cfg.SchemaUpdateInterval) +func (e *DataEndpoint) RoutesKeyspaceGraphQL(pattern string, ksName string) ([]graphql.Route, error) { + return e.graphQLRouteGen.RoutesKeyspace(pattern, ksName) } diff --git a/graphql/keyspace.go b/graphql/keyspace.go index 735cc70..d9a87dc 100644 --- a/graphql/keyspace.go +++ b/graphql/keyspace.go @@ -46,11 +46,11 @@ var keyspaceType = graphql.NewObject(graphql.ObjectConfig{ }, }) -func BuildKeyspaceSchema(dbClient *db.Db) (graphql.Schema, error) { +func (sg *SchemaGenerator) BuildKeyspaceSchema() (graphql.Schema, error) { return graphql.NewSchema( graphql.SchemaConfig{ - Query: buildKeyspaceQuery(dbClient), - Mutation: buildKeyspaceMutation(dbClient), + Query: sg.buildKeyspaceQuery(), + Mutation: sg.buildKeyspaceMutation(), }) } @@ -83,7 +83,7 @@ func buildKeyspaceValue(keyspace *gocql.KeyspaceMetadata) ksValue { return ksValue{keyspace.Name, dcs} } -func buildKeyspaceQuery(dbClient *db.Db) *graphql.Object { +func (sg *SchemaGenerator) buildKeyspaceQuery() *graphql.Object { return graphql.NewObject(graphql.ObjectConfig{ Name: "KeyspaceQuery", Fields: graphql.Fields{ @@ -96,7 +96,7 @@ func buildKeyspaceQuery(dbClient *db.Db) *graphql.Object { }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { ksName := params.Args["name"].(string) - keyspace, err := dbClient.Keyspace(ksName) + keyspace, err := sg.dbClient.Keyspace(ksName) if err != nil { return nil, err } @@ -107,14 +107,14 @@ func buildKeyspaceQuery(dbClient *db.Db) *graphql.Object { "keyspaces": &graphql.Field{ Type: graphql.NewList(keyspaceType), Resolve: func(params graphql.ResolveParams) (interface{}, error) { - ksNames, err := dbClient.Keyspaces() + ksNames, err := sg.dbClient.Keyspaces() if err != nil { return nil, err } ksValues := make([]ksValue, 0) for _, ksName := range ksNames { - keyspace, err := dbClient.Keyspace(ksName) + keyspace, err := sg.dbClient.Keyspace(ksName) if err != nil { return nil, err } @@ -128,7 +128,7 @@ func buildKeyspaceQuery(dbClient *db.Db) *graphql.Object { }) } -func buildKeyspaceMutation(dbClient *db.Db) *graphql.Object { +func (sg *SchemaGenerator) buildKeyspaceMutation() *graphql.Object { return graphql.NewObject(graphql.ObjectConfig{ Name: "KeyspaceMutation", Fields: graphql.Fields{ @@ -156,7 +156,7 @@ func buildKeyspaceMutation(dbClient *db.Db) *graphql.Object { if err != nil { return nil, err } - return dbClient.CreateKeyspace(ksName, dcReplicas, db.NewQueryOptions().WithUserOrRole(userOrRole)) + return sg.dbClient.CreateKeyspace(ksName, dcReplicas, db.NewQueryOptions().WithUserOrRole(userOrRole)) }, }, "dropKeyspace": &graphql.Field{ @@ -173,7 +173,7 @@ func buildKeyspaceMutation(dbClient *db.Db) *graphql.Object { if err != nil { return nil, err } - return dbClient.DropKeyspace(ksName, db.NewQueryOptions().WithUserOrRole(userOrRole)) + return sg.dbClient.DropKeyspace(ksName, db.NewQueryOptions().WithUserOrRole(userOrRole)) }, }, }, diff --git a/graphql/keyspace_schema.go b/graphql/keyspace_schema.go index c6b6f6b..5a1344d 100644 --- a/graphql/keyspace_schema.go +++ b/graphql/keyspace_schema.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/gocql/gocql" "github.com/graphql-go/graphql" - "github.com/iancoleman/strcase" + "github.com/riptano/data-endpoints/config" ) type KeyspaceGraphQLSchema struct { @@ -38,36 +38,36 @@ var inputMutationOptions = graphql.NewInputObject(graphql.InputObjectConfig{ }, }) -func (s *KeyspaceGraphQLSchema) BuildTypes(keyspace *gocql.KeyspaceMetadata) error { - s.buildOrderEnums(keyspace) - s.buildTableTypes(keyspace) - s.buildResultTypes(keyspace) +func (s *KeyspaceGraphQLSchema) BuildTypes(keyspace *gocql.KeyspaceMetadata, naming config.NamingConvention) error { + s.buildOrderEnums(keyspace, naming) + s.buildTableTypes(keyspace, naming) + s.buildResultTypes(keyspace, naming) return nil } -func (s *KeyspaceGraphQLSchema) buildOrderEnums(keyspace *gocql.KeyspaceMetadata) { +func (s *KeyspaceGraphQLSchema) buildOrderEnums(keyspace *gocql.KeyspaceMetadata, naming config.NamingConvention) { s.orderEnums = make(map[string]*graphql.Enum, len(keyspace.Tables)) for _, table := range keyspace.Tables { values := make(map[string]*graphql.EnumValueConfig, len(table.Columns)) for _, column := range table.Columns { - values[strcase.ToCamel(column.Name)+"_ASC"] = &graphql.EnumValueConfig{ - Value: column.Name + "_ASC", + values[naming.ToGraphQLField(column.Name)+"_ASC"] = &graphql.EnumValueConfig{ + Value: column.Name + "_ASC", Description: fmt.Sprintf("Order %s by %s in a scending order", table.Name, column.Name), } - values[strcase.ToCamel(column.Name)+"_DESC"] = &graphql.EnumValueConfig{ + values[naming.ToGraphQLField(naming.ToGraphQLField(column.Name))+"_DESC"] = &graphql.EnumValueConfig{ Value: column.Name + "_DESC", Description: fmt.Sprintf("Order %s by %s in descending order", table.Name, column.Name), } } s.orderEnums[table.Name] = graphql.NewEnum(graphql.EnumConfig{ - Name: strcase.ToCamel(table.Name + "Order"), + Name: naming.ToGraphQLType(table.Name + "Order"), Values: values, }) } } -func (s *KeyspaceGraphQLSchema) buildTableTypes(keyspace *gocql.KeyspaceMetadata) { +func (s *KeyspaceGraphQLSchema) buildTableTypes(keyspace *gocql.KeyspaceMetadata, naming config.NamingConvention) { s.tableValueTypes = make(map[string]*graphql.Object, len(keyspace.Tables)) s.tableScalarInputTypes = make(map[string]*graphql.InputObject, len(keyspace.Tables)) s.tableOperatorInputTypes = make(map[string]*graphql.InputObject, len(keyspace.Tables)) @@ -78,7 +78,7 @@ func (s *KeyspaceGraphQLSchema) buildTableTypes(keyspace *gocql.KeyspaceMetadata inputOperatorFields := graphql.InputObjectConfigFieldMap{} for name, column := range table.Columns { - fieldName := strcase.ToLowerCamel(name) + fieldName := naming.ToGraphQLField(name) fieldType := buildType(column.Type) fields[fieldName] = &graphql.Field{Type: fieldType} inputFields[fieldName] = &graphql.InputObjectFieldConfig{Type: fieldType} @@ -88,23 +88,23 @@ func (s *KeyspaceGraphQLSchema) buildTableTypes(keyspace *gocql.KeyspaceMetadata } s.tableValueTypes[table.Name] = graphql.NewObject(graphql.ObjectConfig{ - Name: strcase.ToCamel(table.Name), + Name: naming.ToGraphQLType(table.Name), Fields: fields, }) s.tableScalarInputTypes[table.Name] = graphql.NewInputObject(graphql.InputObjectConfig{ - Name: strcase.ToCamel(table.Name) + "Input", + Name: naming.ToGraphQLType(table.Name) + "Input", Fields: inputFields, }) s.tableOperatorInputTypes[table.Name] = graphql.NewInputObject(graphql.InputObjectConfig{ - Name: strcase.ToCamel(table.Name) + "FilterInput", + Name: naming.ToGraphQLType(table.Name) + "FilterInput", Fields: inputOperatorFields, }) } } -func (s *KeyspaceGraphQLSchema) buildResultTypes(keyspace *gocql.KeyspaceMetadata) { +func (s *KeyspaceGraphQLSchema) buildResultTypes(keyspace *gocql.KeyspaceMetadata, naming config.NamingConvention) { s.resultSelectTypes = make(map[string]*graphql.Object, len(keyspace.Tables)) s.resultUpdateTypes = make(map[string]*graphql.Object, len(keyspace.Tables)) @@ -116,7 +116,7 @@ func (s *KeyspaceGraphQLSchema) buildResultTypes(keyspace *gocql.KeyspaceMetadat } s.resultSelectTypes[table.Name] = graphql.NewObject(graphql.ObjectConfig{ - Name: strcase.ToCamel(table.Name + "Result"), + Name: naming.ToGraphQLType(table.Name + "Result"), Fields: graphql.Fields{ "pageState": {Type: graphql.String}, "values": {Type: graphql.NewList(graphql.NewNonNull(itemType))}, @@ -124,7 +124,7 @@ func (s *KeyspaceGraphQLSchema) buildResultTypes(keyspace *gocql.KeyspaceMetadat }) s.resultUpdateTypes[table.Name] = graphql.NewObject(graphql.ObjectConfig{ - Name: strcase.ToCamel(table.Name + "MutationResult"), + Name: naming.ToGraphQLType(table.Name + "MutationResult"), Fields: graphql.Fields{ "applied": {Type: graphql.NewNonNull(graphql.Boolean)}, "value": {Type: itemType}, diff --git a/graphql/routes.go b/graphql/routes.go index c42efaa..e65c5b1 100644 --- a/graphql/routes.go +++ b/graphql/routes.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "github.com/graphql-go/graphql" + "github.com/riptano/data-endpoints/config" "github.com/riptano/data-endpoints/db" "net/http" "path" @@ -19,6 +20,13 @@ var systemKeyspaces = []string{ type executeQueryFunc func(query string, ctx context.Context) *graphql.Result +type RouteGenerator struct { + dbClient *db.Db + ksExcluded []string + updateInterval time.Duration + schemaGen *SchemaGenerator +} + type Route struct { Method string Pattern string @@ -33,25 +41,34 @@ type requestBody struct { Query string `json:"query"` } -func Routes(prefixPattern string, ksExcluded []string, db *db.Db, updateInterval time.Duration) ([]Route, error) { - ksNames, err := db.Keyspaces() +func NewRouteGenerator(dbClient *db.Db, ksExcluded []string, updateInterval time.Duration, naming config.NamingConvention) *RouteGenerator { + return &RouteGenerator{ + dbClient: dbClient, + ksExcluded: ksExcluded, + updateInterval: updateInterval, + schemaGen: NewSchemaGenerator(dbClient, naming), + } +} + +func (rg *RouteGenerator) Routes(prefixPattern string) ([]Route, error) { + ksNames, err := rg.dbClient.Keyspaces() if err != nil { return nil, fmt.Errorf("unable to retrieve keyspace names: %s", err) } - routes := make([]Route, 0, len(ksNames) + 1) + routes := make([]Route, 0, len(ksNames)+1) - ksManageRoutes, err := RoutesKeyspaceManagement(prefixPattern, db) + ksManageRoutes, err := rg.RoutesKeyspaceManagement(prefixPattern) if err != nil { return nil, err } routes = append(routes, ksManageRoutes...) for _, ksName := range ksNames { - if isKeyspaceExcluded(ksName, systemKeyspaces) || isKeyspaceExcluded(ksName, ksExcluded) { + if isKeyspaceExcluded(ksName, systemKeyspaces) || isKeyspaceExcluded(ksName, rg.ksExcluded) { continue } - ksRoutes, err := RoutesKeyspace(path.Join(prefixPattern, ksName), ksName, db, updateInterval) + ksRoutes, err := rg.RoutesKeyspace(path.Join(prefixPattern, ksName), ksName) if err != nil { return nil, err } @@ -61,8 +78,8 @@ func Routes(prefixPattern string, ksExcluded []string, db *db.Db, updateInterval return routes, nil } -func RoutesKeyspaceManagement(pattern string, db *db.Db) ([]Route, error) { - schema, err := BuildKeyspaceSchema(db) +func (rg *RouteGenerator) RoutesKeyspaceManagement(pattern string) ([]Route, error) { + schema, err := rg.schemaGen.BuildKeyspaceSchema() if err != nil { return nil, fmt.Errorf("unable to build graphql schema for keyspace management: %s", err) } @@ -71,8 +88,8 @@ func RoutesKeyspaceManagement(pattern string, db *db.Db) ([]Route, error) { }), nil } -func RoutesKeyspace(pattern string, ksName string, db *db.Db, updateInterval time.Duration) ([]Route, error) { - updater, err := NewUpdater(ksName, db, updateInterval) +func (rg *RouteGenerator) RoutesKeyspace(pattern string, ksName string) ([]Route, error) { + updater, err := NewUpdater(rg.schemaGen, ksName, rg.updateInterval) if err != nil { return nil, fmt.Errorf("unable to build graphql schema for keyspace '%s': %s", ksName, err) } @@ -92,17 +109,17 @@ func isKeyspaceExcluded(ksName string, ksExcluded []string) bool { } func routesForSchema(pattern string, execute executeQueryFunc) []Route { - return []Route { + return []Route{ { - Method: http.MethodGet, + Method: http.MethodGet, Pattern: pattern, HandlerFunc: func(w http.ResponseWriter, r *http.Request) { - result:= execute(r.URL.Query().Get("query"), r.Context()) + result := execute(r.URL.Query().Get("query"), r.Context()) json.NewEncoder(w).Encode(result) }, }, { - Method: http.MethodPost, + Method: http.MethodPost, Pattern: pattern, HandlerFunc: func(w http.ResponseWriter, r *http.Request) { if r.Body == nil { @@ -128,7 +145,7 @@ func executeQuery(query string, ctx context.Context, schema graphql.Schema) *gra result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, - Context: ctx, + Context: ctx, }) if len(result.Errors) > 0 { fmt.Printf("wrong result, unexpected errors: %v", result.Errors) diff --git a/graphql/schema.go b/graphql/schema.go index 8d0db57..36bedbc 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -3,12 +3,12 @@ package graphql import ( "fmt" "github.com/mitchellh/mapstructure" + "github.com/riptano/data-endpoints/config" "github.com/riptano/data-endpoints/types" "strings" "github.com/gocql/gocql" "github.com/graphql-go/graphql" - "github.com/iancoleman/strcase" "github.com/riptano/data-endpoints/db" ) @@ -19,6 +19,11 @@ const updatePrefix = "update" const AuthUserOrRole = "userOrRole" +type SchemaGenerator struct { + dbClient *db.Db + naming config.NamingConvention +} + func buildType(typeInfo gocql.TypeInfo) graphql.Output { switch typeInfo.Type() { case gocql.TypeInt, gocql.TypeTinyInt, gocql.TypeSmallInt: @@ -40,10 +45,17 @@ func buildType(typeInfo gocql.TypeInfo) graphql.Output { } } -func buildQueriesFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) graphql.Fields { +func NewSchemaGenerator(dbClient *db.Db, naming config.NamingConvention) *SchemaGenerator { + return &SchemaGenerator{ + dbClient: dbClient, + naming: naming, + } +} + +func (sg *SchemaGenerator) buildQueriesFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) graphql.Fields { fields := graphql.Fields{} for name, table := range tables { - fields[strcase.ToLowerCamel(name)] = &graphql.Field{ + fields[sg.naming.ToGraphQLField(name)] = &graphql.Field{ Type: schema.resultSelectTypes[table.Name], Args: graphql.FieldConfigArgument{ "data": {Type: graphql.NewNonNull(schema.tableScalarInputTypes[table.Name])}, @@ -53,7 +65,7 @@ func buildQueriesFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql. Resolve: resolve, } - fields[strcase.ToLowerCamel(name)+"Filter"] = &graphql.Field{ + fields[sg.naming.ToGraphQLField(name)+"Filter"] = &graphql.Field{ Type: schema.resultSelectTypes[table.Name], Args: graphql.FieldConfigArgument{ "filter": {Type: graphql.NewNonNull(schema.tableOperatorInputTypes[table.Name])}, @@ -79,18 +91,18 @@ func buildQueriesFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql. return fields } -func buildQuery(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) *graphql.Object { +func (sg *SchemaGenerator) buildQuery(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) *graphql.Object { return graphql.NewObject( graphql.ObjectConfig{ Name: "TableQuery", - Fields: buildQueriesFields(schema, tables, resolve), + Fields: sg.buildQueriesFields(schema, tables, resolve), }) } -func buildMutationFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) graphql.Fields { +func (sg *SchemaGenerator) buildMutationFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) graphql.Fields { fields := graphql.Fields{} for name, table := range tables { - fields[insertPrefix+strcase.ToCamel(name)] = &graphql.Field{ + fields[sg.naming.ToGraphQLFieldPrefix(insertPrefix, name)] = &graphql.Field{ Type: schema.resultUpdateTypes[table.Name], Args: graphql.FieldConfigArgument{ "data": {Type: graphql.NewNonNull(schema.tableScalarInputTypes[table.Name])}, @@ -100,7 +112,7 @@ func buildMutationFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql Resolve: resolve, } - fields[deletePrefix+strcase.ToCamel(name)] = &graphql.Field{ + fields[sg.naming.ToGraphQLFieldPrefix(deletePrefix, name)] = &graphql.Field{ Type: schema.resultUpdateTypes[table.Name], Args: graphql.FieldConfigArgument{ "data": {Type: graphql.NewNonNull(schema.tableScalarInputTypes[table.Name])}, @@ -141,45 +153,45 @@ func buildMutationFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql return fields } -func buildMutation(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolveFn graphql.FieldResolveFn) *graphql.Object { +func (sg *SchemaGenerator) buildMutation(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolveFn graphql.FieldResolveFn) *graphql.Object { return graphql.NewObject( graphql.ObjectConfig{ Name: "TableMutation", - Fields: buildMutationFields(schema, tables, resolveFn), + Fields: sg.buildMutationFields(schema, tables, resolveFn), }) } // Build GraphQL schema for tables in the provided keyspace metadata -func BuildSchema(keyspaceName string, dbClient *db.Db) (graphql.Schema, error) { - keyspace, err := dbClient.Keyspace(keyspaceName) +func (sg *SchemaGenerator) BuildSchema(keyspaceName string) (graphql.Schema, error) { + keyspace, err := sg.dbClient.Keyspace(keyspaceName) if err != nil { return graphql.Schema{}, err } keyspaceSchema := &KeyspaceGraphQLSchema{} - if err := keyspaceSchema.BuildTypes(keyspace); err != nil { + if err := keyspaceSchema.BuildTypes(keyspace, sg.naming); err != nil { return graphql.Schema{}, err } return graphql.NewSchema( graphql.SchemaConfig{ - Query: buildQuery(keyspaceSchema, keyspace.Tables, queryFieldResolver(keyspace, dbClient)), - Mutation: buildMutation(keyspaceSchema, keyspace.Tables, mutationFieldResolver(keyspace, dbClient)), + Query: sg.buildQuery(keyspaceSchema, keyspace.Tables, sg.queryFieldResolver(keyspace)), + Mutation: sg.buildMutation(keyspaceSchema, keyspace.Tables, sg.mutationFieldResolver(keyspace)), }, ) } -func queryFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) graphql.FieldResolveFn { +func (sg *SchemaGenerator) queryFieldResolver(keyspace *gocql.KeyspaceMetadata) graphql.FieldResolveFn { return func(params graphql.ResolveParams) (interface{}, error) { fieldName := params.Info.FieldName switch fieldName { case "table": - return getTable(keyspace, params.Args) + return sg.getTable(keyspace, params.Args) case "tables": - return getTables(keyspace) + return sg.getTables(keyspace) default: var table *gocql.TableMetadata - table, tableFound := keyspace.Tables[strcase.ToSnake(fieldName)] + table, tableFound := keyspace.Tables[sg.naming.ToCQLTable(fieldName)] var data map[string]interface{} if params.Args["data"] != nil { data = params.Args["data"].(map[string]interface{}) @@ -193,19 +205,19 @@ func queryFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) graph whereClause = make([]types.ConditionItem, 0, len(data)) for key, value := range data { whereClause = append(whereClause, types.ConditionItem{ - Column: strcase.ToSnake(key), + Column: sg.naming.ToCQLColumn(key), Operator: "=", Value: value, }) } } else { if strings.HasSuffix(fieldName, "Filter") { - table, tableFound = keyspace.Tables[strcase.ToSnake(strings.TrimSuffix(fieldName, "Filter"))] + table, tableFound = keyspace.Tables[sg.naming.ToCQLTable(strings.TrimSuffix(fieldName, "Filter"))] if !tableFound { return nil, fmt.Errorf("unable to find table '%s'", params.Info.FieldName) } - whereClause = adaptCondition(data) + whereClause = sg.adaptCondition(data) } } @@ -224,7 +236,7 @@ func queryFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) graph return nil, err } - result, err := dbClient.Select(&db.SelectInfo{ + result, err := sg.dbClient.Select(&db.SelectInfo{ Keyspace: keyspace.Name, Table: table.Name, Where: whereClause, @@ -238,13 +250,13 @@ func queryFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) graph return &types.QueryResult{ PageState: result.PageState(), - Values: adaptResultValues(result.Values()), + Values: sg.adaptResultValues(result.Values()), }, nil } } } -func adaptCondition(data map[string]interface{}) []types.ConditionItem { +func (sg *SchemaGenerator) adaptCondition(data map[string]interface{}) []types.ConditionItem { result := make([]types.ConditionItem, 0, len(data)) for key, value := range data { if value == nil { @@ -254,7 +266,7 @@ func adaptCondition(data map[string]interface{}) []types.ConditionItem { for operatorName, itemValue := range mapValue { result = append(result, types.ConditionItem{ - Column: strcase.ToSnake(key), + Column: sg.naming.ToCQLColumn(key), Operator: cqlOperators[operatorName], Value: itemValue, }) @@ -263,13 +275,12 @@ func adaptCondition(data map[string]interface{}) []types.ConditionItem { return result } -func adaptResultValues(values []map[string]interface{}) []map[string]interface{} { +func (sg *SchemaGenerator) adaptResultValues(values []map[string]interface{}) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(values)) - // TODO: Use naming conventions for _, item := range values { resultItem := make(map[string]interface{}) for k, v := range item { - resultItem[strcase.ToLowerCamel(k)] = v + resultItem[sg.naming.ToGraphQLField(k)] = v } result = append(result, resultItem) } @@ -277,23 +288,23 @@ func adaptResultValues(values []map[string]interface{}) []map[string]interface{} return result } -func mutationFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) graphql.FieldResolveFn { +func (sg *SchemaGenerator) mutationFieldResolver(keyspace *gocql.KeyspaceMetadata) graphql.FieldResolveFn { return func(params graphql.ResolveParams) (interface{}, error) { fieldName := params.Info.FieldName switch fieldName { case "createTable": - return createTable(dbClient, keyspace.Name, params) + return sg.createTable(keyspace.Name, params) case "dropTable": - return dropTable(dbClient, keyspace.Name, params) + return sg.dropTable(keyspace.Name, params) default: operation, typeName := mutationPrefix(fieldName) - if table, ok := keyspace.Tables[strcase.ToSnake(typeName)]; ok { + if table, ok := keyspace.Tables[sg.naming.ToCQLTable(typeName)]; ok { data := params.Args["data"].(map[string]interface{}) columnNames := make([]string, 0, len(data)) queryParams := make([]interface{}, 0, len(data)) for key, value := range data { - columnNames = append(columnNames, strcase.ToSnake(key)) + columnNames = append(columnNames, sg.naming.ToCQLColumn(key)) queryParams = append(queryParams, value) } @@ -315,7 +326,7 @@ func mutationFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) gr ttl = options["ttl"].(int) } ifNotExists := params.Args["ifNotExists"] == true - return dbClient.Insert(&db.InsertInfo{ + return sg.dbClient.Insert(&db.InsertInfo{ Keyspace: keyspace.Name, Table: table.Name, Columns: columnNames, @@ -326,9 +337,9 @@ func mutationFieldResolver(keyspace *gocql.KeyspaceMetadata, dbClient *db.Db) gr case deletePrefix: var ifCondition []types.ConditionItem if params.Args["ifCondition"] != nil { - ifCondition = adaptCondition(params.Args["ifCondition"].(map[string]interface{})) + ifCondition = sg.adaptCondition(params.Args["ifCondition"].(map[string]interface{})) } - return dbClient.Delete(&db.DeleteInfo{ + return sg.dbClient.Delete(&db.DeleteInfo{ Keyspace: keyspace.Name, Table: table.Name, Columns: columnNames, diff --git a/graphql/table.go b/graphql/table.go index b6c9e9e..2dafbc2 100644 --- a/graphql/table.go +++ b/graphql/table.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/gocql/gocql" "github.com/graphql-go/graphql" - "github.com/iancoleman/strcase" "github.com/mitchellh/mapstructure" "github.com/riptano/data-endpoints/db" ) @@ -182,24 +181,24 @@ var tableType = graphql.NewObject(graphql.ObjectConfig{ }, }) -func getTable(keyspace *gocql.KeyspaceMetadata, args map[string]interface{}) (interface{}, error) { +func (sg *SchemaGenerator) getTable(keyspace *gocql.KeyspaceMetadata, args map[string]interface{}) (interface{}, error) { name := args["name"].(string) - table := keyspace.Tables[strcase.ToSnake(name)] + table := keyspace.Tables[sg.naming.ToCQLTable(name)] if table == nil { return nil, fmt.Errorf("unable to find table '%s'", name) } return &tableValue{ - Name: strcase.ToCamel(name), - Columns: toColumnValues(table.Columns), + Name: sg.naming.ToGraphQLType(name), + Columns: sg.toColumnValues(table.Columns), }, nil } -func getTables(keyspace *gocql.KeyspaceMetadata) (interface{}, error) { +func (sg *SchemaGenerator) getTables(keyspace *gocql.KeyspaceMetadata) (interface{}, error) { tableValues := make([]*tableValue, 0) for _, table := range keyspace.Tables { tableValues = append(tableValues, &tableValue{ - Name: strcase.ToCamel(table.Name), - Columns: toColumnValues(table.Columns), + Name: sg.naming.ToGraphQLType(table.Name), + Columns: sg.toColumnValues(table.Columns), }) } return tableValues, nil @@ -247,7 +246,7 @@ func decodeClusteringInfo(columns []interface{}) ([]*gocql.ColumnMetadata, error return columnValues, nil } -func createTable(dbClient *db.Db, ksName string, params graphql.ResolveParams) (interface{}, error) { +func (sg *SchemaGenerator) createTable(ksName string, params graphql.ResolveParams) (interface{}, error) { var values []*gocql.ColumnMetadata = nil var clusteringKeys []*gocql.ColumnMetadata = nil args := params.Args @@ -275,7 +274,7 @@ func createTable(dbClient *db.Db, ksName string, params graphql.ResolveParams) ( if err != nil { return nil, err } - return dbClient.CreateTable(&db.CreateTableInfo{ + return sg.dbClient.CreateTable(&db.CreateTableInfo{ Keyspace: ksName, Table: name, PartitionKeys: partitionKeys, @@ -283,13 +282,13 @@ func createTable(dbClient *db.Db, ksName string, params graphql.ResolveParams) ( Values: values}, db.NewQueryOptions().WithUserOrRole(userOrRole)) } -func dropTable(dbClient *db.Db, ksName string, params graphql.ResolveParams) (interface{}, error) { - name := strcase.ToSnake(params.Args["name"].(string)) +func (sg *SchemaGenerator) dropTable(ksName string, params graphql.ResolveParams) (interface{}, error) { + name := sg.naming.ToCQLTable(params.Args["name"].(string)) userOrRole, err := checkAuthUserOrRole(params) if err != nil { return nil, err } - return dbClient.DropTable(&db.DropTableInfo{ + return sg.dbClient.DropTable(&db.DropTableInfo{ Keyspace: ksName, Table: name}, db.NewQueryOptions().WithUserOrRole(userOrRole)) } @@ -370,11 +369,11 @@ func toDbColumnType(info *dataTypeValue) gocql.TypeInfo { return nil } -func toColumnValues(columns map[string]*gocql.ColumnMetadata) []*columnValue { +func (sg *SchemaGenerator) toColumnValues(columns map[string]*gocql.ColumnMetadata) []*columnValue { columnValues := make([]*columnValue, 0) for _, column := range columns { columnValues = append(columnValues, &columnValue{ - Name: strcase.ToLowerCamel(column.Name), + Name: sg.naming.ToGraphQLField(column.Name), Kind: toColumnKind(column.Kind), Type: toColumnType(column.Type), }) diff --git a/graphql/updater.go b/graphql/updater.go index 3a5be94..4e1a23c 100644 --- a/graphql/updater.go +++ b/graphql/updater.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/gocql/gocql" "github.com/graphql-go/graphql" - "github.com/riptano/data-endpoints/db" "os" "sync" "time" @@ -17,8 +16,8 @@ type SchemaUpdater struct { mutex sync.Mutex updateInterval time.Duration schema *graphql.Schema + schemaGen *SchemaGenerator ksName string - dbClient *db.Db schemaVersion gocql.UUID } @@ -29,8 +28,8 @@ func (su *SchemaUpdater) Schema() *graphql.Schema { return su.schema } -func NewUpdater(ksName string, dbClient *db.Db, updateInterval time.Duration) (*SchemaUpdater, error) { - schema, err := BuildSchema(ksName, dbClient) +func NewUpdater(schemaGen *SchemaGenerator, ksName string, updateInterval time.Duration) (*SchemaUpdater, error) { + schema, err := schemaGen.BuildSchema(ksName) if err != nil { return nil, err } @@ -40,8 +39,8 @@ func NewUpdater(ksName string, dbClient *db.Db, updateInterval time.Duration) (* mutex: sync.Mutex{}, updateInterval: updateInterval, schema: &schema, + schemaGen: schemaGen, ksName: ksName, - dbClient: dbClient, } return updater, nil } @@ -49,7 +48,7 @@ func NewUpdater(ksName string, dbClient *db.Db, updateInterval time.Duration) (* func (su *SchemaUpdater) Start() { su.ctx, su.cancel = context.WithCancel(context.Background()) for { - result, err := su.dbClient.Execute("SELECT schema_version FROM system.local", nil) + result, err := su.schemaGen.dbClient.Execute("SELECT schema_version FROM system.local", nil) if err != nil { // TODO: Log error @@ -67,7 +66,7 @@ func (su *SchemaUpdater) Start() { } if shouldUpdate { - schema, err := BuildSchema(su.ksName, su.dbClient) + schema, err := su.schemaGen.BuildSchema(su.ksName) if err != nil { // TODO: Log error fmt.Fprintf(os.Stderr, "error trying to build graphql schema for keyspace '%s': %s", su.ksName, err) diff --git a/main.go b/main.go index 1f5bf78..a08b90e 100644 --- a/main.go +++ b/main.go @@ -35,9 +35,9 @@ func main() { var routes []graphql.Route if singleKsName != "" { // Single keyspace mode (useful for cloud) - routes, err = endpoint.RoutesKeyspaceGql("/graphql", singleKsName) + routes, err = endpoint.RoutesKeyspaceGraphQL("/graphql", singleKsName) } else { - routes, err = endpoint.RoutesGql("/graphql") + routes, err = endpoint.RoutesGraphQL("/graphql") } if err != nil { From 7282f12757a6ea226549632ce8fcef1041c97433 Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Thu, 19 Mar 2020 17:31:29 -0400 Subject: [PATCH 2/4] Add comment about incorrect handling of names like "table2" --- config/naming.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/naming.go b/config/naming.go index 65e14e6..744bb40 100644 --- a/config/naming.go +++ b/config/naming.go @@ -20,10 +20,12 @@ func NewDefaultNaming() *defaultNaming { } func (n *defaultNaming) ToCQLColumn(name string) string { + // TODO: Fix numbers: "Table2" or "table2" --> "table_2" return strcase.ToSnake(name) } func (n *defaultNaming) ToCQLTable(name string) string { + // TODO: Fix numbers: "Table2" or "table2" --> "table_2" return strcase.ToSnake(name) } From 6467b216de54dc89d0735b3906b5cf72d1b5873e Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Thu, 19 Mar 2020 19:45:37 -0400 Subject: [PATCH 3/4] Cleanup --- config/naming.go | 18 +++++++++--------- endpoint/endpoint.go | 2 +- graphql/keyspace_schema.go | 4 ++-- graphql/schema.go | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/config/naming.go b/config/naming.go index 744bb40..9820cd7 100644 --- a/config/naming.go +++ b/config/naming.go @@ -5,19 +5,15 @@ import "github.com/iancoleman/strcase" type NamingConvention interface { ToCQLColumn(name string) string ToCQLTable(name string) string - ToGraphQLField(name string) string - ToGraphQLFieldPrefix(prefix string, name string) string - + ToGraphQLOperation(prefix string, name string) string ToGraphQLType(name string) string + ToGraphQLEnum(name string) string } -type defaultNaming struct { -} +type defaultNaming struct{} -func NewDefaultNaming() *defaultNaming { - return &defaultNaming{} -} +var DefaultNaming = &defaultNaming{} func (n *defaultNaming) ToCQLColumn(name string) string { // TODO: Fix numbers: "Table2" or "table2" --> "table_2" @@ -33,10 +29,14 @@ func (n *defaultNaming) ToGraphQLField(name string) string { return strcase.ToLowerCamel(name) } -func (n *defaultNaming) ToGraphQLFieldPrefix(prefix string, name string) string { +func (n *defaultNaming) ToGraphQLOperation(prefix string, name string) string { return strcase.ToLowerCamel(prefix) + strcase.ToCamel(name) } func (n *defaultNaming) ToGraphQLType(name string) string { return strcase.ToCamel(name) } + +func (n *defaultNaming) ToGraphQLEnum(name string) string { + return strcase.ToCamel(name) +} diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index 86a3332..d35bc48 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -24,7 +24,7 @@ func NewEndpointConfig(hosts ...string) *DataEndpointConfig { return &DataEndpointConfig{ DbHosts: hosts, SchemaUpdateInterval: 10 * time.Second, - Naming: config.NewDefaultNaming(), + Naming: config.DefaultNaming, } } diff --git a/graphql/keyspace_schema.go b/graphql/keyspace_schema.go index 5a1344d..b4c9a85 100644 --- a/graphql/keyspace_schema.go +++ b/graphql/keyspace_schema.go @@ -50,11 +50,11 @@ func (s *KeyspaceGraphQLSchema) buildOrderEnums(keyspace *gocql.KeyspaceMetadata for _, table := range keyspace.Tables { values := make(map[string]*graphql.EnumValueConfig, len(table.Columns)) for _, column := range table.Columns { - values[naming.ToGraphQLField(column.Name)+"_ASC"] = &graphql.EnumValueConfig{ + values[naming.ToGraphQLEnum(column.Name)+"_ASC"] = &graphql.EnumValueConfig{ Value: column.Name + "_ASC", Description: fmt.Sprintf("Order %s by %s in a scending order", table.Name, column.Name), } - values[naming.ToGraphQLField(naming.ToGraphQLField(column.Name))+"_DESC"] = &graphql.EnumValueConfig{ + values[naming.ToGraphQLEnum(column.Name)+"_DESC"] = &graphql.EnumValueConfig{ Value: column.Name + "_DESC", Description: fmt.Sprintf("Order %s by %s in descending order", table.Name, column.Name), } diff --git a/graphql/schema.go b/graphql/schema.go index 36bedbc..4f77ab1 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -55,7 +55,7 @@ func NewSchemaGenerator(dbClient *db.Db, naming config.NamingConvention) *Schema func (sg *SchemaGenerator) buildQueriesFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) graphql.Fields { fields := graphql.Fields{} for name, table := range tables { - fields[sg.naming.ToGraphQLField(name)] = &graphql.Field{ + fields[sg.naming.ToGraphQLOperation("", name)] = &graphql.Field{ Type: schema.resultSelectTypes[table.Name], Args: graphql.FieldConfigArgument{ "data": {Type: graphql.NewNonNull(schema.tableScalarInputTypes[table.Name])}, @@ -65,7 +65,7 @@ func (sg *SchemaGenerator) buildQueriesFields(schema *KeyspaceGraphQLSchema, tab Resolve: resolve, } - fields[sg.naming.ToGraphQLField(name)+"Filter"] = &graphql.Field{ + fields[sg.naming.ToGraphQLOperation("", name)+"Filter"] = &graphql.Field{ Type: schema.resultSelectTypes[table.Name], Args: graphql.FieldConfigArgument{ "filter": {Type: graphql.NewNonNull(schema.tableOperatorInputTypes[table.Name])}, @@ -102,7 +102,7 @@ func (sg *SchemaGenerator) buildQuery(schema *KeyspaceGraphQLSchema, tables map[ func (sg *SchemaGenerator) buildMutationFields(schema *KeyspaceGraphQLSchema, tables map[string]*gocql.TableMetadata, resolve graphql.FieldResolveFn) graphql.Fields { fields := graphql.Fields{} for name, table := range tables { - fields[sg.naming.ToGraphQLFieldPrefix(insertPrefix, name)] = &graphql.Field{ + fields[sg.naming.ToGraphQLOperation(insertPrefix, name)] = &graphql.Field{ Type: schema.resultUpdateTypes[table.Name], Args: graphql.FieldConfigArgument{ "data": {Type: graphql.NewNonNull(schema.tableScalarInputTypes[table.Name])}, @@ -112,7 +112,7 @@ func (sg *SchemaGenerator) buildMutationFields(schema *KeyspaceGraphQLSchema, ta Resolve: resolve, } - fields[sg.naming.ToGraphQLFieldPrefix(deletePrefix, name)] = &graphql.Field{ + fields[sg.naming.ToGraphQLOperation(deletePrefix, name)] = &graphql.Field{ Type: schema.resultUpdateTypes[table.Name], Args: graphql.FieldConfigArgument{ "data": {Type: graphql.NewNonNull(schema.tableScalarInputTypes[table.Name])}, From 918cff6ca85e88eeaaf44d1f8281fcb8e3babefc Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Thu, 19 Mar 2020 20:12:10 -0400 Subject: [PATCH 4/4] Fixes and docs --- config/naming.go | 22 +++++++++++++++++++--- graphql/keyspace_schema.go | 4 ++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/config/naming.go b/config/naming.go index 9820cd7..9d99872 100644 --- a/config/naming.go +++ b/config/naming.go @@ -3,16 +3,28 @@ package config import "github.com/iancoleman/strcase" type NamingConvention interface { + // ToCQLColumn converts a GraphQL/REST name to a CQL column name. ToCQLColumn(name string) string + + // ToCQLColumn converts a GraphQL/REST name to a CQL table name. ToCQLTable(name string) string + + // ToGraphQLField converts a CQL name (typically a column name) to a GraphQL field name. ToGraphQLField(name string) string + + // ToGraphQLOperation converts a CQL name (typically a table name) to a GraphQL operation name. ToGraphQLOperation(prefix string, name string) string + + // ToGraphQLType converts a CQL name (typically a table name) to a GraphQL type name. ToGraphQLType(name string) string - ToGraphQLEnum(name string) string + + // ToGraphQLEnumValue converts a CQL name to a GraphQL enumeration value name. + ToGraphQLEnumValue(name string) string } type defaultNaming struct{} +// Default naming implementation. var DefaultNaming = &defaultNaming{} func (n *defaultNaming) ToCQLColumn(name string) string { @@ -30,13 +42,17 @@ func (n *defaultNaming) ToGraphQLField(name string) string { } func (n *defaultNaming) ToGraphQLOperation(prefix string, name string) string { - return strcase.ToLowerCamel(prefix) + strcase.ToCamel(name) + if prefix == "" { + return strcase.ToLowerCamel(name) + } else { + return strcase.ToLowerCamel(prefix) + strcase.ToCamel(name) + } } func (n *defaultNaming) ToGraphQLType(name string) string { return strcase.ToCamel(name) } -func (n *defaultNaming) ToGraphQLEnum(name string) string { +func (n *defaultNaming) ToGraphQLEnumValue(name string) string { return strcase.ToCamel(name) } diff --git a/graphql/keyspace_schema.go b/graphql/keyspace_schema.go index b4c9a85..08b3a3a 100644 --- a/graphql/keyspace_schema.go +++ b/graphql/keyspace_schema.go @@ -50,11 +50,11 @@ func (s *KeyspaceGraphQLSchema) buildOrderEnums(keyspace *gocql.KeyspaceMetadata for _, table := range keyspace.Tables { values := make(map[string]*graphql.EnumValueConfig, len(table.Columns)) for _, column := range table.Columns { - values[naming.ToGraphQLEnum(column.Name)+"_ASC"] = &graphql.EnumValueConfig{ + values[naming.ToGraphQLEnumValue(column.Name)+"_ASC"] = &graphql.EnumValueConfig{ Value: column.Name + "_ASC", Description: fmt.Sprintf("Order %s by %s in a scending order", table.Name, column.Name), } - values[naming.ToGraphQLEnum(column.Name)+"_DESC"] = &graphql.EnumValueConfig{ + values[naming.ToGraphQLEnumValue(column.Name)+"_DESC"] = &graphql.EnumValueConfig{ Value: column.Name + "_DESC", Description: fmt.Sprintf("Order %s by %s in descending order", table.Name, column.Name), }