Skip to content

gen: Allow cutom go types via annotations #462

@0xjac

Description

@0xjac

Somewhat similar to #429 but more flexible and with keeping type annotations within the Avro schema, it would be great to generate a Go struct with custom types using annotations.

Specifically: add the ability to support specific annotations (go-type, go-key-type) similar to Java's but for go. The Go types would be expected to implement encoding.TextMarshaler/encoding.TextUnmarshaler (taking advantage of #68 and #327).

Example

Schema

record MyRecord {
  @go-type("math/big.Float") string value;
  @go-key-type("go.custom.com/ident.ID4") map<@go-type("math/big.Float") string> balances;
  array<@go-type("math/big.Float") string> values;
  @go-type("github.com/google/btree.BTreeG[int]") array<string> totals;
}
Which results in the following schema
{
  "type" : "record",
  "name" : "MyRecord",
  "fields" : [ {
    "name" : "value",
    "type" : {
      "type" : "string",
      "go-type" : "math/big.Float"
    }
  }, {
    "name" : "balances",
    "type" : {
      "type" : "map",
      "values" : {
        "type" : "string",
        "go-type" : "math/big.Float"
      },
      "go-key-type" : "go.custom.com/ident.ID4"
    }
  }, {
    "name" : "values",
    "type" : {
      "type" : "array",
      "items" : {
        "type" : "string",
        "go-type" : "math/big.Float"
      }
    }
  }, {
    "name" : "totals",
    "type" : {
      "type" : "array",
      "items" : "string",
      "go-type" : "github.com/google/btree.BTreeG[int]"
    }
  } ]
}

Gen cmd

avrogen -p main MyRecord.avsc

Actual Output

package main

// Code generated by avro/gen. DO NOT EDIT.

// MyRecord is a generated struct.
type MyRecord struct {
        Value    string            `avro:"value"`
        Balances map[string]string `avro:"balances"`
        Values   []string          `avro:"values"`
        Totals   []string          `avro:"totals"`
}

Desired Output

package main

import (
	"math/big"

	"github.com/google/btree"
	"go.custom.com/ident"
)

// Code generated by avro/gen. DO NOT EDIT.

// MyRecord is a generated struct.
type MyRecord struct {
	Value    big.Float               `avro:"value" json:"value"`
	Balances map[ident.ID4]big.Float `avro:"balances" json:"balances"`
	Values   []big.Float             `avro:"values" json:"values"`
	Totals   btree.BTreeG[int]       `avro:"totals" json:"totals"`
}

Notes

Potential pain points are:

  1. Extract the actual type and package to import from the fully qualified type in the go-type annotation.
    For complex cases, extra annotations should be considered... SQLC which also allows go type override handles this decently. Taking inspiration from their doc, this would define:
    1. go-type-import: Import path of the package.
    2. go-type-pkg: Package name if it doesn't match the import path.
    3. go-type-name: The actual Go type name
    4. go-type-ptr: Whether to use a pointer or the type directly.

      This could also be done with a ["null", T] union. But there might be some cases where specifying a union in Avro is not desirable, yet a pointer might be useful in Go.)

    5. Accordingly for map key type annotation, the corresponding go-key-type-import, go-key-type-pkg, go-key-type-name, go-key-type-ptr annotations.
  2. Generating clean import statements (without duplicates, in order and formatted correctly). It could be alleviated using goimports.
  3. The last annotation in the example (@go-type("github.com/google/btree.BTreeG[int]") array<string> totals;) is a bit more tricky as it is overwriting the array type. Marshaling to and from that type is not supported as it is not a string type supporting encoding.TextMarshaler/encoding.TextUnmarshaler. Overriding array (and map!) types be ignored (potentially with a warning/error) until marshaling can be handled for array, map or even arbitrary types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions