Last active
April 4, 2024 00:30
-
-
Save TuSKan/35ae72fac0ec6aa9e71a0ffbedd4790f to your computer and use it in GitHub Desktop.
From Go Struct as reflect.Type to Avro Schema
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func StructToSchema(t reflect.Type, tags ...reflect.StructTag) (avro.Schema, error) { | |
var schFields []*avro.Field | |
switch t.Kind() { | |
case reflect.Struct: | |
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { | |
return avro.NewPrimitiveSchema(avro.Long, avro.NewPrimitiveLogicalSchema(avro.TimestampMillis)), nil | |
} | |
if t.Implements(reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()) { | |
subtype := strings.Split(t.String(), ".") | |
return avro.NewPrimitiveSchema(avro.String, nil, avro.WithProps(map[string]any{"subtype": strings.ToLower(subtype[len(subtype)-1])})), nil | |
} | |
for i := 0; i < t.NumField(); i++ { | |
f := t.Field(i) | |
s, err := StructToSchema(f.Type, f.Tag) | |
if err != nil { | |
return nil, fmt.Errorf("StructToSchema: %w", err) | |
} | |
fName := f.Tag.Get("avro") | |
if len(fName) == 0 { | |
fName = strcase.ToSnake(f.Name) | |
} else if fName == "-" { | |
continue | |
} | |
schField, err := avro.NewField(fName, s, avro.WithDefault(avroDefaultField(s))) | |
if err != nil { | |
return nil, fmt.Errorf("avro.NewField: %w", err) | |
} | |
schFields = append(schFields, schField) | |
} | |
name := strcase.ToSnake(t.Name()) | |
if len(name) == 0 { | |
name = "anonymous" | |
} | |
return avro.NewRecordSchema(name, "", schFields) | |
case reflect.Map: | |
s, err := StructToSchema(t.Elem(), tags...) | |
if err != nil { | |
return nil, fmt.Errorf("StructToSchema: %w", err) | |
} | |
return avro.NewMapSchema(s), nil | |
case reflect.Slice, reflect.Array: | |
if t.Elem().Kind() == reflect.Uint8 { | |
if strings.Contains(strings.ToLower(t.Elem().String()), "decimal") { | |
return avro.NewPrimitiveSchema(avro.Bytes, avro.NewPrimitiveLogicalSchema(avro.Decimal)), nil | |
} | |
if strings.Contains(strings.ToLower(t.Elem().String()), "uuid") { | |
return avro.NewPrimitiveSchema(avro.String, avro.NewPrimitiveLogicalSchema(avro.UUID)), nil | |
} | |
return avro.NewPrimitiveSchema(avro.Bytes, nil), nil | |
} | |
s, err := StructToSchema(t.Elem(), tags...) | |
if err != nil { | |
return nil, fmt.Errorf("StructToSchema: %w", err) | |
} | |
return avro.NewArraySchema(s), nil | |
case reflect.Pointer: | |
n := avro.NewPrimitiveSchema(avro.Null, nil) | |
s, err := StructToSchema(t.Elem(), tags...) | |
if err != nil { | |
return nil, fmt.Errorf("StructToSchema: %w", err) | |
} | |
union, err := avro.NewUnionSchema([]avro.Schema{n, s}) | |
if err != nil { | |
return nil, fmt.Errorf("avro.NewUnionSchema: %v, type: %s", err, s.String()) | |
} | |
return union, nil | |
case reflect.Bool: | |
return avro.NewPrimitiveSchema(avro.Boolean, nil), nil | |
case reflect.Uint8, reflect.Int8: | |
return avro.NewPrimitiveSchema(avro.Bytes, nil), nil | |
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint16, reflect.Uint32: | |
if strings.Contains(strings.ToLower(t.String()), "date") { | |
return avro.NewPrimitiveSchema(avro.Int, avro.NewPrimitiveLogicalSchema(avro.Date)), nil | |
} | |
if strings.Contains(strings.ToLower(t.String()), "time") { | |
return avro.NewPrimitiveSchema(avro.Int, avro.NewPrimitiveLogicalSchema(avro.TimeMillis)), nil | |
} | |
return avro.NewPrimitiveSchema(avro.Int, nil), nil | |
case reflect.Int64, reflect.Uint64: | |
if strings.Contains(strings.ToLower(t.String()), "duration") { | |
return avro.NewPrimitiveSchema(avro.Fixed, avro.NewPrimitiveLogicalSchema(avro.Duration)), nil | |
} | |
return avro.NewPrimitiveSchema(avro.Long, nil), nil | |
case reflect.Float32: | |
return avro.NewPrimitiveSchema(avro.Float, nil), nil | |
case reflect.Float64: | |
return avro.NewPrimitiveSchema(avro.Double, nil), nil | |
case reflect.String: | |
return avro.NewPrimitiveSchema(avro.String, nil), nil | |
default: | |
return nil, fmt.Errorf("unknown type %s", t.Kind().String()) | |
} | |
func avroDefaultField(s avro.Schema) any { | |
switch s.Type() { | |
case avro.String, avro.Bytes, avro.Enum, avro.Fixed: | |
return "" | |
case avro.Boolean: | |
return false | |
case avro.Int: | |
return int(0) | |
case avro.Long: | |
return int64(0) | |
case avro.Float: | |
return float32(0.0) | |
case avro.Double: | |
return float64(0.0) | |
case avro.Map: | |
return make(map[string]any) | |
case avro.Array: | |
return []any{} | |
default: | |
return nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment