<file_sep>package types

import (
	"fmt"

	"github.com/alanctgardner/gogen-avro/generator"
)

const writeFixedMethod = `
func %v(r %v, w io.Writer) error {
	_, err := w.Write(r[:])
	return err
}
`

const readFixedMethod = `
func %v(r io.Reader) (%v, error) {
	var bb %v
	_, err := io.ReadFull(r, bb[:])
	return bb, err
}
`

type FixedDefinition struct {
	name      QualifiedName
	aliases   []QualifiedName
	sizeBytes int
	metadata  map[string]interface{}
}

func (s *FixedDefinition) AvroName() QualifiedName {
	return s.name
}

func (s *FixedDefinition) Aliases() []QualifiedName {
	return s.aliases
}

func (s *FixedDefinition) FieldType() string {
	return s.GoType()
}

func (s *FixedDefinition) GoType() string {
	return generator.ToPublicName(s.name.Name)
}

func (s *FixedDefinition) serializerMethodDef() string {
	return fmt.Sprintf(writeFixedMethod, s.SerializerMethod(), s.GoType())
}

func (s *FixedDefinition) deserializerMethodDef() string {
	return fmt.Sprintf(readFixedMethod, s.DeserializerMethod(), s.GoType(), s.GoType())
}

func (s *FixedDefinition) typeDef() string {
	return fmt.Sprintf("type %v [%v]byte\n", s.GoType(), s.sizeBytes)
}

func (s *FixedDefinition) filename() string {
	return generator.ToSnake(s.GoType()) + ".go"
}

func (s *FixedDefinition) SerializerMethod() string {
	return fmt.Sprintf("write%v", s.FieldType())
}

func (s *FixedDefinition) DeserializerMethod() string {
	return fmt.Sprintf("read%v", s.FieldType())
}

func (s *FixedDefinition) AddStruct(p *generator.Package) {
	p.AddStruct(s.filename(), s.GoType(), s.typeDef())
}

func (s *FixedDefinition) AddSerializer(p *generator.Package) {
	p.AddFunction(UTIL_FILE, "", s.SerializerMethod(), s.serializerMethodDef())
	p.AddImport(UTIL_FILE, "io")
}

func (s *FixedDefinition) AddDeserializer(p *generator.Package) {
	p.AddFunction(UTIL_FILE, "", s.DeserializerMethod(), s.deserializerMethodDef())
	p.AddImport(UTIL_FILE, "io")
}

func (s *FixedDefinition) ResolveReferences(n *Namespace) error {
	return nil
}

func (s *FixedDefinition) Schema(names map[QualifiedName]interface{}) interface{} {
	name := s.name.String()

	// If name already seen
	if v, ok := names[s.name]; ok {
		// v is times name was seen
		ts := v.(int)

		// Add a suffix to the name to avoid name collisions
		name += "_" + fmt.Sprintf("%d", ts)

		// Update the times seen count
		names[s.name] = ts + 1
	}

	// mark name as seen
	names[s.name] = 1

	return mergeMaps(map[string]interface{}{
		"type": "fixed",
		"name": name,
		"size": s.sizeBytes,
	}, s.metadata)
}
<file_sep>package avro

import (
	"fmt"
	"testing"
)

func TestDoubleReferenceSchema(t *testing.T) {
	// If the generator fails to generate code due to the double reference
	// the following snippet would fail to run
	fmt.Println(&RecB{
		RecA1: &RecA{String: "hello"},
		RecA2: &RecA{String: "goodbye"},
	})
}
<file_sep>package avro

import (
	"strings"
	"testing"
)

var (
	IPAddressZero   = IPAddress{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	IPAddressV4Full = IPAddress{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255}
)

func TestUUIDGenerationDeterministic(t *testing.T) {
	u := UUID{
		String:    "",
		Boolean:   true,
		Int:       1,
		Long:      2,
		IPAddress: IPAddressZero,
		Object: &Object{
			Name: "Boop",
		},

		// lists
		StringArray:  []string{""},
		BooleanArray: []bool{true},
		IntArray:     []int32{1, 2},
		LongArray:    []int64{3, 4},

		// unions
		NullableString: UnionNullString{
			String:    "",
			UnionType: UnionNullStringTypeEnumString,
		},
		NullableBoolean: UnionNullBool{
			Bool:      true,
			UnionType: UnionNullBoolTypeEnumBool,
		},
		NullableInt: UnionNullInt{
			Int:       1,
			UnionType: UnionNullIntTypeEnumInt,
		},
		NullableLong: UnionNullLong{
			Long:      2,
			UnionType: UnionNullLongTypeEnumLong,
		},
	}

	// Test that ID generation is deterministic and depends on field values
	idA := u.GenerateID()
	idB := u.GenerateID()

	if idA != idB {
		t.Fatalf("expected ids to be identical")
	}

	// Changing a field value should change the ID
	u.String = "string"
	idC := u.GenerateID()

	if idA == idC {
		t.Fatalf("expected ids to be different")
	}
}

func TestStringSerializer(t *testing.T) {
	if s := stringSerializer("hello"); s != "hello" {
		t.Fatalf("string serializer provided wrong result")
	}

	// list
	expectedResult := "hello" + ArraySeparator + "bye"
	if s := stringSliceSerializer([]string{"hello", "bye"}); s != expectedResult {
		t.Fatalf("string slice serializer provided wrong result")
	}

	// union
	if s := unionNullStringSerializer(UnionNullString{
		String:    "hello",
		UnionType: UnionNullStringTypeEnumString,
	}); s != "hello" {
		t.Fatalf("nullable string serializer provided wrong result")
	}
	if s := unionNullStringSerializer(UnionNullString{
		UnionType: UnionNullStringTypeEnumNull,
	}); s != "" {
		t.Fatalf("nullable string serializer provided wrong result")
	}
}

func TestInt32Serializer(t *testing.T) {
	if s := int32Serializer(1); s != "1" {
		t.Fatalf("int32 serializer provided wrong result")
	}
	if s := int32Serializer(-1); s != "-1" {
		t.Fatalf("int32 serializer provided wrong result")
	}

	// list
	expectedResult := "1" + ArraySeparator + "-1"
	if s := int32SliceSerializer([]int32{1, -1}); s != expectedResult {
		t.Fatalf("int32 slice serializer provided wrong result")
	}

	// union
	if s := unionNullIntSerializer(UnionNullInt{
		Int:       -1,
		UnionType: UnionNullIntTypeEnumInt,
	}); s != "-1" {
		t.Fatalf("nullable int32 serializer provided wrong result")
	}
	if s := unionNullIntSerializer(UnionNullInt{
		UnionType: UnionNullIntTypeEnumNull,
	}); s != "" {
		t.Fatalf("nullable int32 serializer provided wrong result")
	}
}

func TestInt64Serializer(t *testing.T) {
	if s := int64Serializer(1); s != "1" {
		t.Fatalf("int64 serializer provided wrong result")
	}
	if s := int64Serializer(-1); s != "-1" {
		t.Fatalf("int64 serializer provided wrong result")
	}

	// list
	expectedResult := "1" + ArraySeparator + "-1"
	if s := int64SliceSerializer([]int64{1, -1}); s != expectedResult {
		t.Fatalf("int64 slice serializer provided wrong result")
	}

	// union
	if s := unionNullLongSerializer(UnionNullLong{
		Long:      -1,
		UnionType: UnionNullLongTypeEnumLong,
	}); s != "-1" {
		t.Fatalf("nullable int64 serializer provided wrong result")
	}
	if s := unionNullLongSerializer(UnionNullLong{
		UnionType: UnionNullLongTypeEnumNull,
	}); s != "" {
		t.Fatalf("nullable int64 serializer provided wrong result")
	}
}

func TestBooleanSerializer(t *testing.T) {
	if s := boolSerializer(false); s != "false" {
		t.Fatalf("bool serializer provided wrong result")
	}
	if s := boolSerializer(true); s != "true" {
		t.Fatalf("bool serializer provided wrong result")
	}

	// list
	expectedResult := "true" + ArraySeparator + "false"
	if s := boolSliceSerializer([]bool{true, false}); s != expectedResult {
		t.Fatalf("bool slice serializer provided wrong result")
	}

	// union
	if s := unionNullBoolSerializer(UnionNullBool{
		Bool:      true,
		UnionType: UnionNullBoolTypeEnumBool,
	}); s != "true" {
		t.Fatalf("nullable boolean serializer provided wrong result")
	}
	if s := unionNullBoolSerializer(UnionNullBool{
		UnionType: UnionNullBoolTypeEnumNull,
	}); s != "" {
		t.Fatalf("nullable boolean serializer provided wrong result")
	}
}

func TestIPSerializer(t *testing.T) {
	// case IPAddressZero
	expectedResult := strings.Join(
		[]string{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"},
		ArraySeparator,
	)
	if s := ipSerializer(IPAddressZero); s != expectedResult {
		t.Fatalf("ip serialize provided wrong result")
	}

	// case IPAddressV4Full
	expectedResult = strings.Join(
		[]string{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "255", "255", "255", "255", "255", "255"},
		ArraySeparator,
	)
	if s := ipSerializer(IPAddressV4Full); s != expectedResult {
		t.Fatalf("ip serialize provided wrong result")
	}
}
<file_sep>package avro

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"reflect"
	"testing"

	"github.com/linkedin/goavro"
	"github.com/stretchr/testify/assert"
)

/* Round-trip some primitive values through our serializer and goavro to verify */
const fixtureJson = `
[
{"IntField": 1, "LongField": 2, "FloatField": 3.4, "DoubleField": 5.6, "StringField": "789"},
{"IntField": 2147483647, "LongField": 9223372036854775807, "FloatField": 3.402823e+38, "DoubleField": 1.7976931348623157e+308, "StringField": ""},
{"IntField": -2147483647, "LongField": -9223372036854775807, "FloatField": 3.402823e-38, "DoubleField": 2.2250738585072014e-308, "StringField": ""}
]
`

func compareFixtureGoAvro(t *testing.T, actual interface{}, expected interface{}) {
	record := actual.(*goavro.Record)
	value := reflect.ValueOf(expected)
	for i := 0; i < value.NumField(); i++ {
		fieldName := value.Type().Field(i).Name
		structVal := value.Field(i).Interface()
		avroVal, err := record.Get(fieldName)
		if err != nil {
			t.Fatal(err)
		}
		if !reflect.DeepEqual(structVal, avroVal) {
			t.Fatalf("Field %v not equal: %v != %v", fieldName, structVal, avroVal)
		}
	}
}

func TestPrimitiveFixture(t *testing.T) {
	fixtures := make([]PrimitiveTestRecord, 0)
	err := json.Unmarshal([]byte(fixtureJson), &fixtures)
	if err != nil {
		t.Fatal(err)
	}

	schemaJson, err := ioutil.ReadFile("primitives.avsc")
	if err != nil {
		t.Fatal(err)
	}
	codec, err := goavro.NewCodec(string(schemaJson))
	if err != nil {
		t.Fatal(err)
	}
	var buf bytes.Buffer
	for _, f := range fixtures {
		buf.Reset()
		err = f.Serialize(&buf)
		if err != nil {
			t.Fatal(err)
		}
		datum, err := codec.Decode(&buf)
		if err != nil {
			t.Fatal(err)
		}
		compareFixtureGoAvro(t, datum, f)
	}
}

func TestRoundTrip(t *testing.T) {
	fixtures := make([]PrimitiveTestRecord, 0)
	err := json.Unmarshal([]byte(fixtureJson), &fixtures)
	if err != nil {
		t.Fatal(err)
	}
	var buf bytes.Buffer
	for _, f := range fixtures {
		buf.Reset()
		err = f.Serialize(&buf)
		if err != nil {
			t.Fatal(err)
		}
		datum, err := DeserializePrimitiveTestRecord(&buf)
		if err != nil {
			t.Fatal(err)
		}
		assert.Equal(t, *datum, f)
	}
}
<file_sep># Stage 1 - Build binary
FROM golang:1.9.1 AS builder

WORKDIR /go/src/github.com/alanctgardner/gogen-avro
ADD . .

ENV CGO_ENABLED=0

RUN go build -o /out/gogen-avro -v -a -tags netgo -ldflags="-s -w" .

# Stage 2 - Package binary
#
# Note that we cannot use 'FROM scratch' here because this container needs to be
# runnable in Jenkins, which requires 'cat' to exist for 'docker.inside()' to
# work.
FROM alpine:3.6

WORKDIR /app/

COPY --from=builder /out/gogen-avro .
COPY --from=builder /etc/ssl/certs /etc/ssl/certs

ENTRYPOINT ["./gogen-avro"]
<file_sep>package types

import (
	"fmt"

	"github.com/alanctgardner/gogen-avro/generator"
)

// AddUUIDSerializerToPackage will add a file with Serializer functions
// to the generated package code
func AddUUIDSerializerToPackage(pkg *generator.Package, requiredSerializers []string) {
	// Don't create the file if no uuid serializers are required
	if len(requiredSerializers) == 0 {
		return
	}

	fName := "uuid_serializers.go"

	pkg.AddImport(fName, "fmt")
	pkg.AddConstant(fName, "FieldSeparator", string(0x1E))
	pkg.AddConstant(fName, "ArraySeparator", string(0x1F))

	for _, reqSer := range requiredSerializers {
		ser, ok := serializers[reqSer]
		if !ok {
			panic(fmt.Sprintf("uuid serializer for %s not found", reqSer))
		}

		pkg.AddFunction(fName, "", reqSer, ser)
	}
}

var allowedFieldTypes = map[string]bool{
	"string": true, "[]string": true,
	"bool": true, "[]bool": true,
	"byte": true, "[]byte": true,

	// int
	"int": true, "[]int": true,
	"int32": true, "[]int32": true,
	"int64": true, "[]int64": true,

	// ip
	"IPAddress": true,

	// unions
	"UnionNullString":    true,
	"UnionNullInt":       true,
	"UnionNullLong":      true,
	"UnionNullBool":      true,
	"UnionNullIPAddress": true,
}

var typeSerializerFuncs = map[string]string{
	"byte": "byteSerializer", "[]byte": "byteSliceSerializer",
	"bool": "boolSerializer", "[]bool": "boolSliceSerializer",
	"string": "stringSerializer", "[]string": "stringSliceSerializer",

	// int
	"int": "intSerializer", "[]int": "intSliceSerializer",
	"int32": "int32Serializer", "[]int32": "int32SliceSerializer",
	"int64": "int64Serializer", "[]int64": "int64SliceSerializer",

	// IP related
	"IPAddress": "ipSerializer",

	// unions
	"UnionNullString":    "unionNullStringSerializer",
	"UnionNullInt":       "unionNullIntSerializer",
	"UnionNullLong":      "unionNullLongSerializer",
	"UnionNullBool":      "unionNullBoolSerializer",
	"UnionNullIPAddress": "unionNullIPAddressSerializer",
}

var serializers = map[string]string{
	"byte": byteSerializer, "[]byte": byteSliceSerializer,
	"bool": boolSerializer, "[]bool": boolSliceSerializer,
	"string": stringSerializer, "[]string": stringSliceSerializer,

	// int
	"int": intSerializer, "[]int": intSliceSerializer,
	"int32": int32Serializer, "[]int32": int32SliceSerializer,
	"int64": int64Serializer, "[]int64": int64SliceSerializer,

	// IP related
	"IPAddress": ipSerializer,

	// unions
	"UnionNullString":    unionNullStringSerializer,
	"UnionNullInt":       unionNullIntSerializer,
	"UnionNullLong":      unionNullLongSerializer,
	"UnionNullBool":      unionNullBoolSerializer,
	"UnionNullIPAddress": unionNullIPAddressSerializer,
}

// byte

var byteSerializer = `
	func byteSerializer(v byte) string {
		return fmt.Sprintf("%d", v)
	}
`

var byteSliceSerializer = `
	func byteSliceSerializer(vs []byte) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += fmt.Sprintf("%d", v)
		}
		return out
	}
`

// string

var stringSerializer = `
	func stringSerializer(v string) string {
		return v
	}
`

var stringSliceSerializer = `
	func stringSliceSerializer(vs []string) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += v
		}
		return out
	}
`

// bool

var boolSerializer = `
	func boolSerializer(v bool) string {
		return fmt.Sprintf("%v", v)
	}
`

var boolSliceSerializer = `
	func boolSliceSerializer(vs []bool) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += fmt.Sprintf("%v", v)
		}
		return out
	}
`

// int, int32, int64

var intSerializer = `
	func intSerializer(v int) string {
		return fmt.Sprintf("%d", v)
	}
`

var int32Serializer = `
	func int32Serializer(v int32) string {
		return fmt.Sprintf("%d", v)
	}
`

var int64Serializer = `
	func int64Serializer(v int64) string {
		return fmt.Sprintf("%d", v)
	}
`

var intSliceSerializer = `
	func intSliceSerializer(vs []int) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += fmt.Sprintf("%d", v)
		}
		return out
	}
`

var int32SliceSerializer = `
	func int32SliceSerializer(vs []int32) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += fmt.Sprintf("%d", v)
		}
		return out
	}
`

var int64SliceSerializer = `
	func int64SliceSerializer(vs []int64) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += fmt.Sprintf("%d", v)
		}
		return out
	}
`

// ip

var ipSerializer = `
	func ipSerializer(vs IPAddress) string {
		out := ""
		for i, v := range vs {
			if i != 0 {
				out += ArraySeparator
			}
			out += fmt.Sprintf("%d", v)
		}
		return out
	}
`

// unions

var unionNullStringSerializer = `
	func unionNullStringSerializer(un UnionNullString) string {
		if un.UnionType == UnionNullStringTypeEnumString {
			return un.String
		}
		return ""
	}
`

var unionNullIntSerializer = `
	func unionNullIntSerializer(un UnionNullInt) string {
		if un.UnionType == UnionNullIntTypeEnumInt {
			return fmt.Sprintf("%d", un.Int)
		}
		return ""
	}
`

var unionNullLongSerializer = `
	func unionNullLongSerializer(un UnionNullLong) string {
		if un.UnionType == UnionNullLongTypeEnumLong {
			return fmt.Sprintf("%d", un.Long)
		}
		return ""
	}
`

var unionNullBoolSerializer = `
	func unionNullBoolSerializer(un UnionNullBool) string {
		if un.UnionType == UnionNullBoolTypeEnumBool {
			return fmt.Sprintf("%v", un.Bool)
		}
		return ""
	}
`

var unionNullIPAddressSerializer = `
	func unionNullIPAddressSerializer(un UnionNullIPAddress) string {
		if un.UnionType == UnionNullIPAddressTypeEnumIPAddress {
			out := ""
			for i, v := range un.IPAddress {
				if i != 0 {
					out += ArraySeparator
				}
				out += fmt.Sprintf("%d", v)
			}
			return out
		}
		return ""
	}
`
<file_sep>package types

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/alanctgardner/gogen-avro/generator"
	mapstruct "github.com/alanctgardner/gogen-avro/mapstruct"
	"github.com/serenize/snaker"
)

const recordStructDefTemplate = `type %v struct {
%v
}
`

const recordSchemaTemplate = `func (r %v) Schema() string {
 return %v
}
`

const recordStructPublicSerializerTemplate = `
func (r %v) Serialize(w io.Writer) error {
	return %v(r, w)
}
`

const recordStructDeserializerTemplate = `
func %v(r io.Reader) (%v, error) {
	var str = &%v{}
	var err error
	%v
	return str, nil
}
`

const recordStructPublicDeserializerTemplate = `
func %v(r io.Reader) (%v, error) {
	return %v(r)
}
`

type RecordDefinition struct {
	name     QualifiedName
	version  int
	aliases  []QualifiedName
	fields   []Field
	metadata map[string]interface{}
}

func (r *RecordDefinition) AvroName() QualifiedName {
	return r.name
}

func (r *RecordDefinition) Aliases() []QualifiedName {
	return r.aliases
}

func (r *RecordDefinition) GoType() string {
	return fmt.Sprintf("*%v", r.FieldType())
}

func (r *RecordDefinition) FieldType() string {
	return generator.ToPublicName(r.name.Name)
}

func (r *RecordDefinition) structFields() string {
	var fieldDefinitions string
	for _, f := range r.fields {
		fieldDefinitions += fmt.Sprintf("%v %v\n", f.GoName(), f.GoType())
	}
	return fieldDefinitions
}

func (r *RecordDefinition) fieldSerializers() string {
	serializerMethods := "var err error\n"
	for _, f := range r.fields {
		serializerMethods += fmt.Sprintf("err = %v(r.%v, w)\nif err != nil {return err}\n", f.SerializerMethod(), f.GoName())
	}
	return serializerMethods
}

func (r *RecordDefinition) fieldDeserializers() string {
	deserializerMethods := ""
	for _, f := range r.fields {
		deserializerMethods += fmt.Sprintf("str.%v, err = %v(r)\nif err != nil {return nil, err}\n", f.GoName(), f.DeserializerMethod())
	}
	return deserializerMethods
}

func (r *RecordDefinition) structDefinition() string {
	return fmt.Sprintf(recordStructDefTemplate, r.FieldType(), r.structFields())
}

func (r *RecordDefinition) serializerMethodDef() string {
	return fmt.Sprintf("func %v(r %v, w io.Writer) error {\n%v\nreturn nil\n}", r.SerializerMethod(), r.GoType(), r.fieldSerializers())
}

func (r *RecordDefinition) deserializerMethodDef() string {
	return fmt.Sprintf(recordStructDeserializerTemplate, r.DeserializerMethod(), r.GoType(), r.FieldType(), r.fieldDeserializers())
}

func (r *RecordDefinition) SerializerMethod() string {
	return fmt.Sprintf("write%v", r.FieldType())
}

func (r *RecordDefinition) DeserializerMethod() string {
	return fmt.Sprintf("read%v", r.FieldType())
}

func (r *RecordDefinition) publicDeserializerMethod() string {
	return fmt.Sprintf("Deserialize%v", r.FieldType())
}

func (r *RecordDefinition) publicSerializerMethodDef() string {
	return fmt.Sprintf(recordStructPublicSerializerTemplate, r.GoType(), r.SerializerMethod())
}

func (r *RecordDefinition) publicDeserializerMethodDef() string {
	return fmt.Sprintf(recordStructPublicDeserializerTemplate, r.publicDeserializerMethod(), r.GoType(), r.DeserializerMethod())
}

func (r *RecordDefinition) filename() string {
	return generator.ToSnake(r.FieldType()) + ".go"
}

func (r *RecordDefinition) schemaMethod() string {
	schemaJson, _ := json.Marshal(r.Schema(make(map[QualifiedName]interface{})))
	return fmt.Sprintf(recordSchemaTemplate, r.GoType(), strconv.Quote(string(schemaJson)))
}

func (r *RecordDefinition) AddStruct(p *generator.Package) {
	// Import guard, to avoid circular dependencies
	if !p.HasStruct(r.filename(), r.GoType()) {
		p.AddStruct(r.filename(), r.GoType(), r.structDefinition())
		for _, f := range r.fields {
			f.AddStruct(p)
		}
		p.AddFunction(r.filename(), r.GoType(), "Schema", r.schemaMethod())

		// For Records we also want to add other utility methods.
		r.AddGenerateID(p)
		r.AddSchemaVersion(p)
		r.AddSendStats(p)
	}
}

func (r *RecordDefinition) AddSerializer(p *generator.Package) {
	// Import guard, to avoid circular dependencies
	if !p.HasFunction(UTIL_FILE, "", r.SerializerMethod()) {
		p.AddImport(r.filename(), "io")
		p.AddFunction(UTIL_FILE, "", r.SerializerMethod(), r.serializerMethodDef())
		p.AddFunction(r.filename(), r.GoType(), "Serialize", r.publicSerializerMethodDef())
		for _, f := range r.fields {
			f.AddSerializer(p)
		}
	}
}

func (r *RecordDefinition) AddDeserializer(p *generator.Package) {
	// Import guard, to avoid circular dependencies
	if !p.HasFunction(UTIL_FILE, "", r.DeserializerMethod()) {
		p.AddImport(r.filename(), "io")
		p.AddFunction(UTIL_FILE, "", r.DeserializerMethod(), r.deserializerMethodDef())
		p.AddFunction(r.filename(), "", r.publicDeserializerMethod(), r.publicDeserializerMethodDef())
		for _, f := range r.fields {
			f.AddDeserializer(p)
		}
	}
}

func (r *RecordDefinition) ResolveReferences(n *Namespace) error {
	var err error
	for _, f := range r.fields {
		err = f.ResolveReferences(n)
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *RecordDefinition) Schema(names map[QualifiedName]interface{}) interface{} {
	name := r.name.Name

	// If name already seen
	if v, ok := names[r.name]; ok {
		// v is times name was seen
		ts := v.(int)

		// Add a suffix to the name to avoid name collisions
		name += "_" + fmt.Sprintf("%d", ts)

		// Update the times seen count
		names[r.name] = ts + 1
	}

	// mark name as seen
	names[r.name] = 1

	fields := make([]interface{}, 0, len(r.fields))
	for _, f := range r.fields {
		fieldDef := map[string]interface{}{
			"name": f.AvroName(),
			"type": f.Schema(names),
		}
		if f.HasDefault() {
			fieldDef["default"] = f.Default()
		}
		fields = append(fields, fieldDef)
	}
	return mergeMaps(map[string]interface{}{
		"type":   "record",
		"name":   name, // Name field should be unqualified (not including namespace)
		"fields": fields,
	}, r.metadata)
}

// AddGenerateID adds a GenerateID method which creates a uuidV5 from a set of fields
func (r *RecordDefinition) AddGenerateID(p *generator.Package) {
	// Import guard, to avoid circular dependencies
	if !p.HasFunction(r.filename(), "", "GenerateID") {
		p.AddImport(r.filename(), "fmt")
		p.AddImport(r.filename(), "github.com/satori/go.uuid")

		uuidStrDef, requiredSerializers := r.uuidStrDef()

		// Create function definition
		fnDef := fmt.Sprintf(`
			func (r %v) GenerateID() string {
				s := fmt.Sprint(%s)
				return uuid.NewV5(uuid.NamespaceOID, s).String()
			}
		`, r.GoType(), uuidStrDef)

		p.AddFunction(r.filename(), r.GoType(), "GenerateID", fnDef)

		// Add serializers to the output package as required
		AddUUIDSerializerToPackage(p, requiredSerializers)
	}
}

// AddSchemaVersion adds a SchemaVersion method which returns the version of the
// schema.
func (r *RecordDefinition) AddSchemaVersion(p *generator.Package) {
	// Import guard, to avoid circular dependencies
	if p.HasFunction(r.filename(), "", "SchemaVersion") {
		return
	}

	// Create function definition
	fnDef := fmt.Sprintf(`
		func (r %v) SchemaVersion() int {
			return %d
		}
	`, r.GoType(), r.version)

	p.AddFunction(r.filename(), r.GoType(), "SchemaVersion", fnDef)
}

func extractAvailableFields(f Field) map[string]string {
	availableFields := map[string]string{}

	// primitive case
	if _, ok := allowedFieldTypes[f.GoType()]; ok {
		availableFields[f.GoName()] = f.GoType()
		return availableFields
	}

	// union case
	un, ok := f.(*unionField)
	if ok {
		// The second type must be an allowed type
		typ := un.itemType[1].GoType()
		if _, ok := allowedFieldTypes[typ]; ok {
			availableFields[f.GoName()] = f.GoType()
			return availableFields
		}
	}

	// reference type
	ref, ok := f.(*Reference)
	if ok {
		// fixed type
		// pass

		// record type
		rec, ok := ref.def.(*RecordDefinition)
		if ok {
			for _, f := range rec.fields {
				moreAvailableFields := extractAvailableFields(f)

				// update availableFields with moreAvailableFields
				for fn, ft := range moreAvailableFields {
					fullFieldName := fmt.Sprintf("%s.%s", rec.FieldType(), fn)
					availableFields[fullFieldName] = ft
				}
			}
		}
	}

	return availableFields
}

// uuidStrDef generates the fmt.Sprintf compatible input for the AddGenerateID method
// e.g. for uuidKeys = []string{"A", "B"} => `"%v%v", A, B`
// It also returns a list of required uuid serializers
func (r *RecordDefinition) uuidStrDef() (string, []string) {
	type Schema struct {
		UUIDKeys []string `json:"uuid_keys"`
	}

	var schema Schema
	if err := mapstruct.Decode(r.metadata, &schema); err != nil {
		fmt.Printf("failed to decode metadata: %s\n", err)
		return "", nil
	}

	// Extract fields from the schema which can be used for uuid generation
	availableFields := map[string]string{}
	for _, f := range r.fields {
		moreAvailableFields := extractAvailableFields(f)
		for fn, ft := range moreAvailableFields {
			availableFields[fn] = ft
		}
	}

	// uuidToFieldName is an auxiliary function to convert a uuid_key name to
	// an appropriate Go CamelCased name.
	uuidToFieldName := func(uuidKey string) string {
		ps := []string{}
		for _, p := range strings.Split(uuidKey, ".") {
			ps = append(ps, snaker.SnakeToCamel(p))
		}
		return strings.Join(ps, ".")
	}

	type uuidField struct {
		Name string
		Type string
	}

	// decide on which keys are to be used for uuid generation
	fieldsToInclude := []uuidField{}
	for _, uuidKey := range schema.UUIDKeys {
		fName := uuidToFieldName(uuidKey)
		if _, ok := availableFields[fName]; !ok {
			fmt.Printf("Error: can't use %s as a uuid key\n", fName)
			fields := []string{}
			for field := range availableFields {
				fields = append(fields, field)
			}
			fmt.Printf("Error: valid UUID keys %s\n", strings.Join(fields, ", "))
			fmt.Printf("Error: the keys above are shown in Go format (CamelCase), but must be declared in uuid_keys using the Avro format (snake_case).\n")
			os.Exit(1)
		}

		fieldsToInclude = append(fieldsToInclude, uuidField{Name: fName, Type: availableFields[fName]})
	}

	// track the serializers that we need
	requiredSerializers := map[string]bool{}
	for _, fti := range fieldsToInclude {
		requiredSerializers[fti.Type] = true
	}
	requiredSerializersList := []string{}
	for k := range requiredSerializers {
		requiredSerializersList = append(requiredSerializersList, k)
	}

	serializedFields := []string{}
	for _, fti := range fieldsToInclude {
		serializerFn, ok := typeSerializerFuncs[fti.Type]
		if !ok {
			fmt.Printf("Error: no serializer available for %s for use as a uuid key\n", fti.Name)
			os.Exit(1)
		}

		serializedFields = append(serializedFields, fmt.Sprintf("%s(r.%s)", serializerFn, fti.Name))
	}
	strDef := strings.Join(serializedFields, " + FieldSeparator + ")

	return strDef, requiredSerializersList
}

// AddSendStats add a SendStats method which submits stats for this record
func (r *RecordDefinition) AddSendStats(p *generator.Package) {
	// Import guard, to avoid circular dependencies
	if !p.HasFunction(r.filename(), "", "SendStats") {
		metricTagsStrDef := r.metricTagsDef()

		// Only add "fmt" if necessary (only when there are tags)
		if metricTagsStrDef != "" {
			p.AddImport(r.filename(), "fmt")
		}
		p.AddImport(r.filename(), "github.com/securityscorecard/go-stats")

		// Create function definition
		fnDef := fmt.Sprintf(`
			func (r %v) SendStats(statser stats.Statser) {
				statser.Count("%s", 1, stats.Tags{
					%s
				})
			}
		`, r.GoType(), r.name, metricTagsStrDef)

		p.AddFunction(r.filename(), r.GoType(), "SendStats", fnDef)
	}
}

func (r *RecordDefinition) metricTagsDef() string {
	type Schema struct {
		MetricTags []string `json:"metric_tags"`
	}

	var schema Schema
	if err := mapstruct.Decode(r.metadata, &schema); err != nil {
		fmt.Printf("failed to decode metadata: %s\n", err)
		return ""
	}

	strDefParts := []string{}
	for _, tag := range schema.MetricTags {
		part := fmt.Sprintf(
			`"%s": fmt.Sprint(r.%s),`,
			tag, snaker.SnakeToCamel(tag),
		)
		strDefParts = append(strDefParts, part)
	}
	return strings.Join(strDefParts, "\n")
}
<file_sep>package mapstruct

/*
Like [mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) but based on a simple round-trip conversion to json.

### Usage

```go
type Person struct {
  Name string `json:"name"`
}

// We have a map that's compatible with the struct
data := map[string]interface{}{
  "name": "Johnny",
}

// We want to convert the map to the struct
var p Person
if err := Decode(data, &p); err != nil {
  log.Fatal(err)
}
```
*/

import (
	"bytes"
	"encoding/json"
)

func Decode(r map[string]interface{}, v interface{}) error {
	var buf bytes.Buffer

	if err := json.NewEncoder(&buf).Encode(&r); err != nil {
		return err
	}

	if err := json.NewDecoder(&buf).Decode(&v); err != nil {
		return err
	}

	return nil
}
