From a998b426c92e7d9f817ddcd3b336cd7b06a7a5c2 Mon Sep 17 00:00:00 2001 From: Ludovic Pourrat <52542520+ludovic-pourrat@users.noreply.github.com> Date: Fri, 17 May 2024 10:25:22 +0200 Subject: [PATCH 1/4] Serialize schemas feature #400 --- schema_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/schema_test.go b/schema_test.go index 7922e11..582bbdf 100644 --- a/schema_test.go +++ b/schema_test.go @@ -41,6 +41,15 @@ func TestParseFiles(t *testing.T) { assert.Equal(t, avro.String, s.Type()) } +func TestSerialize(t *testing.T) { + s, err := avro.ParseFiles("testdata/superhero-part1.avsc", "testdata/superhero-part2.avsc") + + data := avro.Serialize(s) + n, err := avro.Parse(data) + require.NoError(t, err) + assert.Equal(t, avro.Record, n.Type()) +} + func TestParseFiles_FileDoesntExist(t *testing.T) { _, err := avro.ParseFiles("test.something") From 54f8c61417fc3c83249124b0de12b161414a5108 Mon Sep 17 00:00:00 2001 From: Ludovic Pourrat <52542520+ludovic-pourrat@users.noreply.github.com> Date: Fri, 17 May 2024 10:26:00 +0200 Subject: [PATCH 2/4] Serialize schemas feature #400 --- schema.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/schema.go b/schema.go index 6c0018f..5b6b520 100644 --- a/schema.go +++ b/schema.go @@ -136,6 +136,9 @@ type Schema interface { // String returns the canonical form of the schema. String() string + // Resolve returns the resolved canonical form of the schema. + Resolve(cache *SchemaCache) string + // Fingerprint returns the SHA256 fingerprint of the schema. Fingerprint() [32]byte @@ -492,6 +495,11 @@ func (s *PrimitiveSchema) String() string { return `{"type":"` + string(s.typ) + `",` + s.logical.String() + `}` } +// Resolve returns the resolved form of the schema. +func (s *PrimitiveSchema) Resolve(_ *SchemaCache) string { + return s.String() +} + // MarshalJSON marshals the schema to json. func (s *PrimitiveSchema) MarshalJSON() ([]byte, error) { if s.logical == nil && len(s.props) == 0 { @@ -609,8 +617,48 @@ func (s *RecordSchema) String() string { if len(fields) > 0 { fields = fields[:len(fields)-1] } + if len(s.Namespace()) == 0 { + return `{"name":"` + s.FullName() + `","type":"` + typ + `","fields":[` + fields + `]}` + } else { + return `{"namespace":"` + s.Namespace() + `","name":"` + s.Name() + `","type":"` + typ + `","fields":[` + fields + `]}` + } +} + +// Resolve returns the resolved form of the schema. +func (s *RecordSchema) Resolve(cache *SchemaCache) string { + typ := "record" + if s.isError { + typ = "error" + } + + fields := "" + for _, f := range s.fields { + switch lookup := f.typ.(type) { + case *RefSchema: + switch found := lookup.actual.(type) { + case *RecordSchema: + if strings.Contains(found.full, ".") { + fields += `{"name":"` + f.Name() + `","type":` + found.Resolve(cache) + `},` + } else { + fields += f.String() + "," + } + default: + fields += f.String() + "," + } + default: + fields += f.String() + "," + } + } + if len(fields) > 0 { + fields = fields[:len(fields)-1] + } + + if len(s.Namespace()) == 0 { + return `{"name":"` + s.FullName() + `","type":"` + typ + `","fields":[` + fields + `]}` + } else { + return `{"namespace":"` + s.Namespace() + `","name":"` + s.Name() + `","type":"` + typ + `","fields":[` + fields + `]}` + } - return `{"name":"` + s.FullName() + `","type":"` + typ + `","fields":[` + fields + `]}` } // MarshalJSON marshals the schema to json. @@ -966,8 +1014,17 @@ func (s *EnumSchema) String() string { if len(symbols) > 0 { symbols = symbols[:len(symbols)-1] } + if len(s.Namespace()) == 0 { + return `{"name":"` + s.FullName() + `","type":"enum","symbols":[` + symbols + `]}` + } else { + return `{"namespace":"` + s.Namespace() + `","name":"` + s.Name() + `","type":"enum","symbols":[` + symbols + `]}` + } + +} - return `{"name":"` + s.FullName() + `","type":"enum","symbols":[` + symbols + `]}` +// Resolve returns the resolved form of the schema. +func (s *EnumSchema) Resolve(_ *SchemaCache) string { + return s.String() } // MarshalJSON marshals the schema to json. @@ -1060,6 +1117,11 @@ func (s *ArraySchema) String() string { return `{"type":"array","items":` + s.items.String() + `}` } +// Resolve returns the resolved form of the schema. +func (s *ArraySchema) Resolve(_ *SchemaCache) string { + return s.String() +} + // MarshalJSON marshals the schema to json. func (s *ArraySchema) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) @@ -1130,6 +1192,11 @@ func (s *MapSchema) String() string { return `{"type":"map","values":` + s.values.String() + `}` } +// Resolve returns the resolved form of the schema. +func (s *MapSchema) Resolve(_ *SchemaCache) string { + return s.String() +} + // MarshalJSON marshals the schema to json. func (s *MapSchema) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) @@ -1242,6 +1309,19 @@ func (s *UnionSchema) String() string { return `[` + types + `]` } +// Resolve returns the resolved form of the schema. +func (s *UnionSchema) Resolve(cache *SchemaCache) string { + types := "" + for _, typ := range s.types { + types += typ.Resolve(cache) + "," + } + if len(types) > 0 { + types = types[:len(types)-1] + } + + return `[` + types + `]` +} + // MarshalJSON marshals the schema to json. func (s *UnionSchema) MarshalJSON() ([]byte, error) { return jsoniter.Marshal(s.types) @@ -1323,7 +1403,12 @@ func (s *FixedSchema) String() string { logical = "," + s.logical.String() } - return `{"name":"` + s.FullName() + `","type":"fixed","size":` + size + logical + `}` + return `{"namespace":"` + s.Namespace() + `","name":"` + s.Name() + `","type":"fixed","size":` + size + logical + `}` +} + +// Resolve returns the resolved form of the schema. +func (s *FixedSchema) Resolve(_ *SchemaCache) string { + return s.String() } // MarshalJSON marshals the schema to json. @@ -1386,6 +1471,11 @@ func (s *NullSchema) String() string { return `"null"` } +// Resolve returns the resolved form of the schema. +func (s *NullSchema) Resolve(_ *SchemaCache) string { + return s.String() +} + // MarshalJSON marshals the schema to json. func (s *NullSchema) MarshalJSON() ([]byte, error) { return []byte(`"null"`), nil @@ -1433,6 +1523,11 @@ func (s *RefSchema) String() string { return `"` + s.actual.FullName() + `"` } +// Resolve returns the resolved form of the schema. +func (s *RefSchema) Resolve(cache *SchemaCache) string { + return s.actual.Resolve(cache) +} + // MarshalJSON marshals the schema to json. func (s *RefSchema) MarshalJSON() ([]byte, error) { return []byte(`"` + s.actual.FullName() + `"`), nil From 72f62c23b3f480d5ec1d9a772f33b3684ab8ae14 Mon Sep 17 00:00:00 2001 From: Ludovic Pourrat <52542520+ludovic-pourrat@users.noreply.github.com> Date: Fri, 17 May 2024 10:26:35 +0200 Subject: [PATCH 3/4] Serialize schemas feature #400 --- schema_parse.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/schema_parse.go b/schema_parse.go index 2be5a37..539bd9c 100644 --- a/schema_parse.go +++ b/schema_parse.go @@ -20,11 +20,21 @@ func Parse(schema string) (Schema, error) { return ParseBytes([]byte(schema)) } +// Serialize serializes a schema object. +func Serialize(schema Schema) string { + return SerializeWithCache(schema, DefaultSchemaCache) +} + // ParseWithCache parses a schema string using the given namespace and schema cache. func ParseWithCache(schema, namespace string, cache *SchemaCache) (Schema, error) { return ParseBytesWithCache([]byte(schema), namespace, cache) } +// SerializeWithCache serializes a schema using the given namespace and schema cache. +func SerializeWithCache(schema Schema, cache *SchemaCache) string { + return serializeType(schema, cache) +} + // MustParse parses a schema string, panicing if there is an error. func MustParse(schema string) Schema { parsed, err := Parse(schema) From 50ce2241b6f785bcd3bf49269afb05a54c06b11f Mon Sep 17 00:00:00 2001 From: Ludovic Pourrat <52542520+ludovic-pourrat@users.noreply.github.com> Date: Fri, 17 May 2024 11:16:03 +0200 Subject: [PATCH 4/4] Serialize schemas feature #400 --- schema_parse.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema_parse.go b/schema_parse.go index 539bd9c..cedb0fa 100644 --- a/schema_parse.go +++ b/schema_parse.go @@ -85,6 +85,10 @@ func ParseBytesWithCache(schema []byte, namespace string, cache *SchemaCache) (S return derefSchema(s), nil } +func serializeType(schema Schema, cache *SchemaCache) string { + return schema.Resolve(cache) +} + func parseType(namespace string, v any, seen seenCache, cache *SchemaCache) (Schema, error) { switch val := v.(type) { case nil: