Skip to content

Instantly share code, notes, and snippets.

@TuSKan
Last active April 4, 2024 00:30
Show Gist options
  • Save TuSKan/35ae72fac0ec6aa9e71a0ffbedd4790f to your computer and use it in GitHub Desktop.
Save TuSKan/35ae72fac0ec6aa9e71a0ffbedd4790f to your computer and use it in GitHub Desktop.
From Go Struct as reflect.Type to Avro Schema
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