Skip to content

Commit 714e5c8

Browse files
yevgenypatserezrokahhermanschaaf
authored
feat(codegen): Add WithTypeTransformer (#157)
This is take two of #139 Trying to get right the `WithTypeTransformer` so we wont have to change it in the near future as it's heavilly used external API. I've also added error handling to all functions as we were basically ignoring all of them and printing to the screen is not the right approach catching those locally and in CI. --- Use the following steps to ensure your PR is ready to be reviewed - [ ] Read the [contribution guidelines](../blob/main/CONTRIBUTING.md) 🧑‍🎓 - [ ] Run `go fmt` to format your code 🖊 - [ ] Lint your changes via `golangci-lint run` 🚨 (install golangci-lint [here](https://golangci-lint.run/usage/install/#local-installation)) - [ ] Update or add tests 🧪 - [ ] Ensure the status checks below are successful ✅ Co-authored-by: erezrokah <[email protected]> Co-authored-by: Herman Schaaf <[email protected]>
1 parent 66ce593 commit 714e5c8

File tree

3 files changed

+87
-20
lines changed

3 files changed

+87
-20
lines changed

codegen/golang.go

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"unicode"
1212

1313
"github.com/cloudquery/plugin-sdk/schema"
14+
"github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog/v2"
1415
"github.com/iancoleman/strcase"
1516
)
1617

@@ -19,9 +20,11 @@ type TableOptions func(*TableDefinition)
1920
//go:embed templates/*.go.tpl
2021
var TemplatesFS embed.FS
2122

22-
func valueToSchemaType(v reflect.Type) (schema.ValueType, error) {
23+
func DefaultGoTypeToSchemaType(v reflect.Type) (schema.ValueType, error) {
2324
k := v.Kind()
2425
switch k {
26+
case reflect.Pointer:
27+
return DefaultGoTypeToSchemaType(v.Elem())
2528
case reflect.String:
2629
return schema.TypeString, nil
2730
case reflect.Bool:
@@ -39,8 +42,6 @@ func valueToSchemaType(v reflect.Type) (schema.ValueType, error) {
3942
return schema.TypeTimestamp, nil
4043
}
4144
return schema.TypeJSON, nil
42-
case reflect.Pointer:
43-
return valueToSchemaType(v.Elem())
4445
case reflect.Slice:
4546
switch v.Elem().Kind() {
4647
case reflect.String:
@@ -56,7 +57,11 @@ func valueToSchemaType(v reflect.Type) (schema.ValueType, error) {
5657
}
5758
}
5859

59-
func WithNameTransformer(transformer func(field reflect.StructField) string) TableOptions {
60+
func DefaultTypeTransformer(v reflect.StructField) (schema.ValueType, error) {
61+
return DefaultGoTypeToSchemaType(v.Type)
62+
}
63+
64+
func WithNameTransformer(transformer NameTransformer) TableOptions {
6065
return func(t *TableDefinition) {
6166
t.nameTransformer = transformer
6267
}
@@ -81,23 +86,37 @@ func WithUnwrapFieldsStructs(fields []string) TableOptions {
8186
}
8287
}
8388

84-
// Unwrap all fields that are embedded structs (1 level deep only)
89+
// WithUnwrapAllEmbeddedStructs unwraps all fields that are embedded structs (1 level deep only)
8590
func WithUnwrapAllEmbeddedStructs() TableOptions {
8691
return func(t *TableDefinition) {
8792
t.unwrapAllEmbeddedStructFields = true
8893
}
8994
}
9095

91-
func DefaultTransformer(field reflect.StructField) string {
96+
// WithLogger
97+
func WithLogger(logger zerolog.Logger) TableOptions {
98+
return func(t *TableDefinition) {
99+
t.logger = logger
100+
}
101+
}
102+
103+
// WithValueTypeTransformer sets a function that can override the schema type for specific fields. Return `schema.TypeInvalid` to fall back to default behavior.
104+
func WithTypeTransformer(transformer TypeTransformer) TableOptions {
105+
return func(t *TableDefinition) {
106+
t.typeTransformer = transformer
107+
}
108+
}
109+
110+
func DefaultTransformer(field reflect.StructField) (string, error) {
92111
name := field.Name
93112
if jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]; len(jsonTag) > 0 {
94113
// return empty string if the field is not related api response
95114
if jsonTag == "-" {
96-
return ""
115+
return "", nil
97116
}
98117
name = jsonTag
99118
}
100-
return strcase.ToSnake(name)
119+
return strcase.ToSnake(name), nil
101120
}
102121

103122
func sliceContains(arr []string, s string) bool {
@@ -139,27 +158,40 @@ func (t *TableDefinition) ignoreField(field reflect.StructField) bool {
139158
return len(field.Name) == 0 || unicode.IsLower(rune(field.Name[0])) || sliceContains(t.skipFields, field.Name)
140159
}
141160

142-
func (t *TableDefinition) addColumnFromField(field reflect.StructField, parent *reflect.StructField) {
161+
func (t *TableDefinition) addColumnFromField(field reflect.StructField, parent *reflect.StructField) error {
143162
if t.ignoreField(field) {
144-
return
163+
return nil
145164
}
146165

147-
columnType, err := valueToSchemaType(field.Type)
166+
columnType, err := t.typeTransformer(field)
148167
if err != nil {
149-
fmt.Printf("skipping field %s on table %s, got err: %v\n", field.Name, t.Name, err)
150-
return
168+
return fmt.Errorf("failed to transform type for field %s: %w", field.Name, err)
169+
}
170+
171+
if columnType == schema.TypeInvalid {
172+
columnType, err = DefaultTypeTransformer(field)
173+
if err != nil {
174+
return fmt.Errorf("failed to transform type for field %s: %w", field.Name, err)
175+
}
151176
}
152177

153178
// generate a PathResolver to use by default
154179
pathResolver := fmt.Sprintf(`schema.PathResolver("%s")`, field.Name)
155-
name := t.nameTransformer(field)
180+
name, err := t.nameTransformer(field)
181+
if err != nil {
182+
return fmt.Errorf("failed to transform field name for field %s: %w", field.Name, err)
183+
}
156184
// skip field if there is no name
157185
if name == "" {
158-
return
186+
return nil
159187
}
160188
if parent != nil {
161189
pathResolver = fmt.Sprintf(`schema.PathResolver("%s.%s")`, parent.Name, field.Name)
162-
name = t.nameTransformer(*parent) + "_" + name
190+
parentName, err := t.nameTransformer(*parent)
191+
if err != nil {
192+
return fmt.Errorf("failed to transform field name for parent field %s: %w", parent.Name, err)
193+
}
194+
name = fmt.Sprintf("%s_%s", parentName, name)
163195
}
164196

165197
column := ColumnDefinition{
@@ -168,13 +200,15 @@ func (t *TableDefinition) addColumnFromField(field reflect.StructField, parent *
168200
Resolver: pathResolver,
169201
}
170202
t.Columns = append(t.Columns, column)
203+
return nil
171204
}
172205

173206
// NewTableFromStruct creates a new TableDefinition from a struct by inspecting its fields
174207
func NewTableFromStruct(name string, obj interface{}, opts ...TableOptions) (*TableDefinition, error) {
175208
t := &TableDefinition{
176209
Name: name,
177210
nameTransformer: DefaultTransformer,
211+
typeTransformer: DefaultTypeTransformer,
178212
}
179213
for _, opt := range opts {
180214
opt(t)
@@ -201,10 +235,14 @@ func NewTableFromStruct(name string, obj interface{}, opts ...TableOptions) (*Ta
201235
parent = &field
202236
}
203237
for _, f := range unwrappedFields {
204-
t.addColumnFromField(f, parent)
238+
if err := t.addColumnFromField(f, parent); err != nil {
239+
return nil, fmt.Errorf("failed to add column from field %s: %w", f.Name, err)
240+
}
205241
}
206242
} else {
207-
t.addColumnFromField(field, nil)
243+
if err := t.addColumnFromField(field, nil); err != nil {
244+
return nil, fmt.Errorf("failed to add column for field %s: %w", field.Name, err)
245+
}
208246
}
209247
}
210248

codegen/golang_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package codegen
33
import (
44
"bytes"
55
"fmt"
6+
"reflect"
67
"testing"
78
"time"
89

@@ -48,6 +49,10 @@ type testStructWithNonEmbeddedStruct struct {
4849
NonEmbedded *embeddedStruct
4950
}
5051

52+
type testStructWithCustomType struct {
53+
TimeCol time.Time `json:"time_col,omitempty"`
54+
}
55+
5156
var expectedColumns = []ColumnDefinition{
5257
{
5358
Name: "int_col",
@@ -156,13 +161,31 @@ func TestTableFromGoStruct(t *testing.T) {
156161
want: expectedTestTableEmbeddedStruct,
157162
},
158163
{
159-
name: "should_unwrap_specific_structs_when_option_is_set",
164+
name: "should unwrap specific structs when option is set",
160165
args: args{
161166
testStruct: testStructWithNonEmbeddedStruct{},
162167
options: []TableOptions{WithUnwrapFieldsStructs([]string{"NonEmbedded"})},
163168
},
164169
want: expectedTestTableNonEmbeddedStruct,
165170
},
171+
{
172+
name: "should override schema type when option is set",
173+
args: args{
174+
testStruct: testStructWithCustomType{},
175+
options: []TableOptions{WithTypeTransformer(func(t reflect.StructField) (schema.ValueType, error) {
176+
switch t.Type {
177+
case reflect.TypeOf(time.Time{}), reflect.TypeOf(&time.Time{}):
178+
return schema.TypeJSON, nil
179+
default:
180+
return schema.TypeInvalid, nil
181+
}
182+
})},
183+
},
184+
want: TableDefinition{Name: "test_struct",
185+
// We expect the time column to be of type JSON, since we override the type of `time.Time` to be JSON
186+
Columns: ColumnDefinitions{{Name: "time_col", Type: schema.TypeJSON, Resolver: `schema.PathResolver("TimeCol")`}},
187+
nameTransformer: DefaultTransformer},
188+
},
166189
}
167190

168191
for _, tt := range tests {

codegen/table.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ import (
44
"reflect"
55

66
"github.com/cloudquery/plugin-sdk/schema"
7+
"github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog/v2"
78
)
89

910
type ResourceDefinition struct {
1011
Name string
1112
Table *TableDefinition
1213
}
1314

15+
type NameTransformer func(reflect.StructField) (string, error)
16+
type TypeTransformer func(reflect.StructField) (schema.ValueType, error)
17+
1418
type TableDefinition struct {
1519
Name string
1620
Columns ColumnDefinitions
@@ -22,11 +26,13 @@ type TableDefinition struct {
2226
Multiplex string
2327
PostResourceResolver string
2428
PreResourceResolver string
25-
nameTransformer func(reflect.StructField) string
29+
nameTransformer NameTransformer
30+
typeTransformer TypeTransformer
2631
skipFields []string
2732
extraColumns ColumnDefinitions
2833
structFieldsToUnwrap []string
2934
unwrapAllEmbeddedStructFields bool
35+
logger zerolog.Logger
3036
}
3137

3238
type ColumnDefinitions []ColumnDefinition

0 commit comments

Comments
 (0)