Skip to content

Commit

Permalink
Add graphql support
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinJoiner committed Mar 26, 2024
1 parent b1e31fa commit a7061db
Show file tree
Hide file tree
Showing 16 changed files with 2,146 additions and 49 deletions.
6 changes: 6 additions & 0 deletions cmd/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/DIMO-Network/model-garage/internal/codegen"
"github.com/DIMO-Network/model-garage/internal/codegen/clickhouse"
"github.com/DIMO-Network/model-garage/internal/codegen/convert"
"github.com/DIMO-Network/model-garage/internal/codegen/graphql"
"github.com/DIMO-Network/model-garage/internal/codegen/model"
)

Expand Down Expand Up @@ -46,4 +47,9 @@ func main() {
if err != nil {
log.Fatalf("failed to generate convert file: %v", err)
}

err = graphql.Generate(tmplData, *outputDir)
if err != nil {
log.Fatalf("failed to generate graphql file: %v", err)
}
}
6 changes: 6 additions & 0 deletions internal/codegen/clickhouse/goTable.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ func {{ .ModelName }}ColNames() []string {
{{- end }}
}
}
// {{ .ModelName }}JSONName2CHName maps the JSON field names to the Clickhouse column names.
var {{ .ModelName }}JSONName2CHName = map[string]string{
{{- range .Signals }}
"{{ .JSONName }}": "{{ .CHName }}",
{{- end }}
}
88 changes: 88 additions & 0 deletions internal/codegen/graphql/dimo.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
A point in time, encoded per RFC-3999. Typically these will be in second precision,
just like the blockchain, and in UTC.
"""
scalar Time

"""
This directive on an input object indicates that a client must specify one of the
fields of the object and no others. Typically used for lookups.
"""
directive @oneOf on INPUT_OBJECT

"""
The root query type for the GraphQL schema.
"""
type Query {
"""
View a particular node.
"""
node(
"""
The ID of the node.
"""
id: ID!
): Node

dimo(dimoID: ID!, timeStamp: Time!): Dimo

dimos(
"""
The number of dimos to retrieve.
Mutually exclusive with `last`.
"""
first: Int
"""
A cursor for pagination. Retrieve dimos after this cursor.
"""
after: String
"""
The number of dimos to retrieve from the end of the list.
Mutually exclusive with `first`.
"""
last: Int
"""
A cursor for pagination. Retrieve dimos before this cursor.
"""
before: String
"""
Filter the dimos based on specific criteria.
"""
filterBy: DimosFilter
): DimoConnection
}

"""
The DimosFilter input is used to specify filtering criteria for querying dimos.
Dimos must match all of the specified criteria.
"""
input DimosFilter {
tokenID: ID!
since: Time!
until: Time!
}

# Shared Types
interface Node {
id: ID!
}

type Dimo implements Node {
id: ID!
tokenID: ID!
timeStamp: Time!
value: String!
}

type DimoConnection {
totalCount: Int!
edges: [DimoEdge!]!
pageInfo: PageInfo!
}

type PageInfo {
startCursor: String
endCursor: String
hasPreviousPage: Boolean!
hasNextPage: Boolean!
}
13 changes: 13 additions & 0 deletions internal/codegen/graphql/goTable.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Code generated by "model-garage" DO NOT EDIT.
package {{ .PackageName }}

import (
_ "embed"
)

// {{ .ModelName }} is the SQL query to create a clickhouse table for the {{ .ModelName }} model.
//
//go:embed {{ schemaFile }}
var {{ .ModelName }}GraphSchema string


9 changes: 9 additions & 0 deletions internal/codegen/graphql/gql.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type {{ lower .ModelName }} implments Node {
{{- range .Signals }}
"""
{{ .Desc }}
"""
{{ .JSONName }}: {{ .GQLType }}
@requiresPrivilege(privileges: {VehicleNonLocationData: null})
{{- end }}
}
97 changes: 97 additions & 0 deletions internal/codegen/graphql/graphql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Package graphql provides the Graphql table generation functionality for converting VSPEC signals to Go structs and Graphql tables.
package graphql

import (
"bytes"
_ "embed"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"

"github.com/DIMO-Network/model-garage/internal/codegen"
)

// graphqlFileName is the name of the Graphql table file that will be generated.
var graphqlFileName = "%s-gql.graphql"

var goGraphqlFileName = "%s-gql.go"

//go:embed gql.tmpl
var graphqlTableTemplate string

//go:embed goTable.tmpl
var goGraphqlTableTemplate string

// Generate creates a new Graphql table file.
func Generate(tmplData *codegen.TemplateData, outputDir string) error {
setFileNamesFrom(tmplData.ModelName)

// create a new Graphql table template.
graphqlTableTmpl, err := createGraphqlTableTemplate()
if err != nil {
return err
}

// execute the Graphql table template directly to a file.
tablePath := filepath.Clean((filepath.Join(outputDir, graphqlFileName)))
graphqlTableOutputFile, err := os.Create(tablePath)
if err != nil {
return fmt.Errorf("error creating Graphql table output file: %w", err)
}
defer func() {
if cerr := graphqlTableOutputFile.Close(); err == nil && cerr != nil {
err = cerr
}
}()

err = graphqlTableTmpl.Execute(graphqlTableOutputFile, &tmplData)
if err != nil {
return fmt.Errorf("error executing Graphql table template: %w", err)
}

// create a new go Graphql table template.
goGraphqlTableTmpl, err := createGoGraphqlTableTemplate()
if err != nil {
return err
}
var outBuf bytes.Buffer
if err = goGraphqlTableTmpl.Execute(&outBuf, &tmplData); err != nil {
return fmt.Errorf("error executing go Graphql table template: %w", err)
}
filePath := filepath.Clean(filepath.Join(outputDir, goGraphqlFileName))
err = codegen.FormatAndWriteToFile(outBuf.Bytes(), filePath)
if err != nil {
return fmt.Errorf("error writing file: %w", err)
}

return nil
}

func setFileNamesFrom(modelName string) {
lowerName := strings.ToLower(modelName)
graphqlFileName = fmt.Sprintf(graphqlFileName, lowerName)
goGraphqlFileName = fmt.Sprintf(goGraphqlFileName, lowerName)
}

func createGraphqlTableTemplate() (*template.Template, error) {
tmpl, err := template.New("graphqlTableTemplate").Funcs(template.FuncMap{
"escapeDesc": func(desc string) string { return strings.ReplaceAll(desc, `'`, `\'`) },
"lower": strings.ToLower,
}).Parse(graphqlTableTemplate)
if err != nil {
return nil, fmt.Errorf("error parsing Graphql table template: %w", err)
}
return tmpl, nil
}

func createGoGraphqlTableTemplate() (*template.Template, error) {
tmpl, err := template.New("goGraphqlTableTemplate").Funcs(template.FuncMap{
"schemaFile": func() string { return graphqlFileName },
}).Parse(goGraphqlTableTemplate)
if err != nil {
return nil, fmt.Errorf("error parsing Graphql table template: %w", err)
}
return tmpl, nil
}
2 changes: 1 addition & 1 deletion internal/codegen/model/vehicle.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ type {{ .ModelName }} struct {
{{- range .Signals }}
{{ if .Deprecated }}// Deprecated field{{ end -}}
// {{ .GOName }} {{ .Desc }}
{{ .GOName }} {{ .GOType }} `ch:"{{ .CHName }}" json:"{{ .CHName }},omitempty"`
{{ .GOName }} {{ .GOType }} `ch:"{{ .CHName }}" json:"{{ .JSONName }},omitempty"`
{{- end }}
}
49 changes: 43 additions & 6 deletions internal/codegen/signal.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ type SignalInfo struct {
Deprecated bool

// Derived
IsArray bool
GOName string
BaseGoType string
BaseCHType string
CHName string
Conversion *ConversionInfo
IsArray bool
GOName string
CHName string
JSONName string
BaseGoType string
BaseCHType string
BaseGQLType string
Conversion *ConversionInfo
}

// ConversionInfo contains the conversion information for a field.
Expand All @@ -56,6 +58,7 @@ type DefinitionInfo struct {
VspecName string `json:"vspecName"`
ClickHouseType string `json:"clickHouseType"`
GoType string `json:"goType"`
GQLType string `json:"gqlType"`
}

// Definitions is a map of definitions from clickhouse Name to definition info.
Expand Down Expand Up @@ -104,9 +107,11 @@ func NewSignalInfo(record []string) *SignalInfo {
// if this is not a branch type, we can convert it to default golang and clickhouse types
sig.BaseGoType = goTypeFromVSPEC(baseType)
sig.BaseCHType = chTypeFromVSPEC(baseType)
sig.BaseGQLType = gqlTypeFromVSPEC(baseType)
}
sig.GOName = goName(sig.Name)
sig.CHName = chName(sig.Name)
sig.JSONName = JSONName(sig.Name)

return sig
}
Expand All @@ -119,6 +124,9 @@ func (s *SignalInfo) MergeWithDefinition(definition *DefinitionInfo) {
if definition.GoType != "" {
s.BaseGoType = definition.GoType
}
if definition.GQLType != "" {
s.BaseGQLType = definition.GQLType
}
if definition.IsArray != nil {
s.IsArray = *definition.IsArray
}
Expand Down Expand Up @@ -146,6 +154,14 @@ func (s *SignalInfo) CHType() string {
return s.BaseCHType
}

// GQLType returns the graphql type of the signal.
func (s *SignalInfo) GQLType() string {
if s.IsArray {
return "[" + s.BaseGQLType + "]"
}
return s.BaseGQLType
}

func goName(name string) string {
return nonAlphaNum.ReplaceAllString(removePrefix(name), "")
}
Expand All @@ -154,6 +170,11 @@ func chName(name string) string {
return strings.ReplaceAll(removePrefix(name), ".", "_")
}

func JSONName(name string) string {
n := goName(name)
// lowercase the first letter
return strings.ToLower(n[:1]) + n[1:]
}
func removePrefix(name string) string {
idx := strings.IndexByte(name, '.')
if idx != -1 {
Expand Down Expand Up @@ -209,3 +230,19 @@ func chTypeFromVSPEC(baseType string) string {
return "String"
}
}

// gqlTypeFromVSPEC converts vspec type to graphql types.
func gqlTypeFromVSPEC(baseType string) string {
switch baseType {
case "uint8", "int8", "uint16", "int16", "uint32", "int32":
return "Int"
case "string":
return "String"
case "boolean":
return "Boolean"
case "float", "double", "uint64", "int64":
return "Float"
default:
return "String"
}
}
Loading

0 comments on commit a7061db

Please sign in to comment.