Skip to content

Commit 7ca8009

Browse files
authored
fix: Use field name in json type schema if json tag is missing (#2011)
I'm working on validating some data (internal issue cloudquery/cloudquery-issues#2971) using the json type schema and noticed a bug (noticeable on AWS where structs don't have json tags). We should not use the name transformer to get the name as it defaults to `ToSnake` when a json tag is missing. Instead we should mimic the JSON marshaling default behavior to use the field name as is. A better approach can be to create an instance of the type, marshal it to JSON, then use that for the names. Open to ideas how to do that using reflection, with a caveat that we might need to initialize it using non zero values otherwise those can be omitted ---
1 parent da2da87 commit 7ca8009

File tree

4 files changed

+47
-6
lines changed

4 files changed

+47
-6
lines changed

transformers/name.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,16 @@ func DefaultNameTransformer(field reflect.StructField) (string, error) {
2626
return defaultCaser.ToSnake(name), nil
2727
}
2828

29+
func DefaultJSONColumnSchemaNameTransformer(field reflect.StructField) (string, error) {
30+
name := field.Name
31+
if jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]; len(jsonTag) > 0 {
32+
// return empty string if the field is not related api response
33+
if jsonTag == "-" {
34+
return "", nil
35+
}
36+
return jsonTag, nil
37+
}
38+
return name, nil
39+
}
40+
2941
var _ NameTransformer = DefaultNameTransformer

transformers/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ func WithNameTransformer(transformer NameTransformer) StructTransformerOption {
3131
}
3232
}
3333

34+
// WithJSONSchemaNameTransformer overrides how column name will be determined.
35+
// DefaultJSONColumnSchemaNameTransformer is used as the default.
36+
func WithJSONSchemaNameTransformer(transformer NameTransformer) StructTransformerOption {
37+
return func(t *structTransformer) {
38+
t.jsonSchemaNameTransformer = transformer
39+
}
40+
}
41+
3442
// WithTypeTransformer overrides how column type will be determined.
3543
// DefaultTypeTransformer is used as the default.
3644
func WithTypeTransformer(transformer TypeTransformer) StructTransformerOption {

transformers/struct.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type structTransformer struct {
2929
pkFieldsFound []string
3030
pkComponentFields []string
3131
pkComponentFieldsFound []string
32+
jsonSchemaNameTransformer NameTransformer
3233

3334
maxJSONTypeSchemaDepth int
3435
}
@@ -194,11 +195,12 @@ func (t *structTransformer) addColumnFromField(field reflect.StructField, parent
194195

195196
func TransformWithStruct(st any, opts ...StructTransformerOption) schema.Transform {
196197
t := &structTransformer{
197-
nameTransformer: DefaultNameTransformer,
198-
typeTransformer: DefaultTypeTransformer,
199-
resolverTransformer: DefaultResolverTransformer,
200-
ignoreInTestsTransformer: DefaultIgnoreInTestsTransformer,
201-
maxJSONTypeSchemaDepth: DefaultMaxJSONTypeSchemaDepth,
198+
nameTransformer: DefaultNameTransformer,
199+
typeTransformer: DefaultTypeTransformer,
200+
resolverTransformer: DefaultResolverTransformer,
201+
ignoreInTestsTransformer: DefaultIgnoreInTestsTransformer,
202+
jsonSchemaNameTransformer: DefaultJSONColumnSchemaNameTransformer,
203+
maxJSONTypeSchemaDepth: DefaultMaxJSONTypeSchemaDepth,
202204
}
203205
for _, opt := range opts {
204206
opt(t)
@@ -284,7 +286,7 @@ func (t *structTransformer) fieldToJSONSchema(field reflect.StructField, depth i
284286
if !structField.IsExported() || isTypeIgnored(structField.Type) {
285287
continue
286288
}
287-
name, err := t.nameTransformer(structField)
289+
name, err := t.jsonSchemaNameTransformer(structField)
288290
if err != nil {
289291
continue
290292
}

transformers/struct_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/cloudquery/plugin-sdk/v4/schema"
1313
"github.com/cloudquery/plugin-sdk/v4/types"
1414
"github.com/google/go-cmp/cmp"
15+
"github.com/stretchr/testify/require"
1516
)
1617

1718
type (
@@ -665,6 +666,23 @@ func TestJSONTypeSchema(t *testing.T) {
665666
"item": `{"exported":"utf8"}`,
666667
},
667668
},
669+
{
670+
name: "no json tags",
671+
testStruct: struct {
672+
Tags map[string]string
673+
Item struct {
674+
Name string
675+
Tags map[string]string
676+
FlatItems []string
677+
ComplexItems []struct {
678+
Name string
679+
}
680+
}
681+
}{},
682+
want: map[string]string{
683+
"item": `{"ComplexItems":[{"Name":"utf8"}],"FlatItems":["utf8"],"Name":"utf8","Tags":{"utf8":"utf8"}}`,
684+
},
685+
},
668686
}
669687

670688
for _, tt := range tests {
@@ -684,6 +702,7 @@ func TestJSONTypeSchema(t *testing.T) {
684702
}
685703
for col, schema := range tt.want {
686704
column := table.Column(col)
705+
require.NotNil(t, column, "column %q not found", col)
687706
if diff := cmp.Diff(column.TypeSchema, schema); diff != "" {
688707
t.Fatalf("table does not match expected. diff (-got, +want): %v", diff)
689708
}

0 commit comments

Comments
 (0)