Skip to content

Commit fa84506

Browse files
committed
add support for Avro Logical types
1 parent 37ee652 commit fa84506

File tree

16 files changed

+448
-12
lines changed

16 files changed

+448
-12
lines changed

src/LEGO.AsyncAPI.Readers/V2/AsyncApiAvroSchemaDeserializer.cs

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
namespace LEGO.AsyncAPI.Readers
44
{
55
using System;
6+
using System.Threading;
67
using LEGO.AsyncAPI.Exceptions;
78
using LEGO.AsyncAPI.Models;
9+
using LEGO.AsyncAPI.Models.Avro.LogicalTypes;
810
using LEGO.AsyncAPI.Readers.Exceptions;
911
using LEGO.AsyncAPI.Readers.ParseNodes;
1012
using LEGO.AsyncAPI.Writers;
@@ -68,6 +70,60 @@ public class AsyncApiAvroSchemaDeserializer
6870
{ "types", (a, n) => a.Types = n.CreateList(LoadSchema) },
6971
};
7072

73+
private static readonly FixedFieldMap<AvroDecimal> DecimalFixedFields = new()
74+
{
75+
{ "type", (a, n) => { } },
76+
{ "logicalType", (a, n) => { } },
77+
{ "precision", (a, n) => a.Precision = int.Parse(n.GetScalarValue()) },
78+
{ "scale", (a, n) => a.Scale = int.Parse(n.GetScalarValue()) },
79+
};
80+
81+
private static readonly FixedFieldMap<AvroUUID> UUIDFixedFields = new()
82+
{
83+
{ "type", (a, n) => { } },
84+
{ "logicalType", (a, n) => { } },
85+
};
86+
87+
private static readonly FixedFieldMap<AvroDate> DateFixedFields = new()
88+
{
89+
{ "type", (a, n) => { } },
90+
{ "logicalType", (a, n) => { } },
91+
};
92+
93+
private static readonly FixedFieldMap<AvroTimeMillis> TimeMillisFixedFields = new()
94+
{
95+
{ "type", (a, n) => { } },
96+
{ "logicalType", (a, n) => { } },
97+
};
98+
99+
private static readonly FixedFieldMap<AvroTimeMicros> TimeMicrosFixedFields = new()
100+
{
101+
{ "type", (a, n) => { } },
102+
{ "logicalType", (a, n) => { } },
103+
};
104+
105+
private static readonly FixedFieldMap<AvroTimestampMillis> TimestampMillisFixedFields = new()
106+
{
107+
{ "type", (a, n) => { } },
108+
{ "logicalType", (a, n) => { } },
109+
};
110+
111+
private static readonly FixedFieldMap<AvroTimestampMicros> TimestampMicrosFixedFields = new()
112+
{
113+
{ "type", (a, n) => { } },
114+
{ "logicalType", (a, n) => { } },
115+
};
116+
117+
private static readonly FixedFieldMap<AvroDuration> DurationFixedFields = new()
118+
{
119+
{ "type", (a, n) => { } },
120+
{ "logicalType", (a, n) => { } },
121+
{ "name", (a, n) => a.Name = n.GetScalarValue() },
122+
{ "namespace", (a, n) => a.Namespace = n.GetScalarValue() },
123+
{ "aliases", (a, n) => a.Aliases = n.CreateSimpleList(n2 => n2.GetScalarValue()) },
124+
{ "size", (a, n) => { } },
125+
};
126+
71127
private static readonly PatternFieldMap<AvroRecord> RecordMetadataPatternFields =
72128
new()
73129
{
@@ -110,6 +166,54 @@ public class AsyncApiAvroSchemaDeserializer
110166
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
111167
};
112168

169+
private static readonly PatternFieldMap<AvroDecimal> DecimalMetadataPatternFields =
170+
new()
171+
{
172+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
173+
};
174+
175+
private static readonly PatternFieldMap<AvroUUID> UUIDMetadataPatternFields =
176+
new()
177+
{
178+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
179+
};
180+
181+
private static readonly PatternFieldMap<AvroDate> DateMetadataPatternFields =
182+
new()
183+
{
184+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
185+
};
186+
187+
private static readonly PatternFieldMap<AvroTimeMillis> TimeMillisMetadataPatternFields =
188+
new()
189+
{
190+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
191+
};
192+
193+
private static readonly PatternFieldMap<AvroTimeMicros> TimeMicrosMetadataPatternFields =
194+
new()
195+
{
196+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
197+
};
198+
199+
private static readonly PatternFieldMap<AvroTimestampMillis> TimestampMillisMetadataPatternFields =
200+
new()
201+
{
202+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
203+
};
204+
205+
private static readonly PatternFieldMap<AvroTimestampMicros> TimestampMicrosMetadataPatternFields =
206+
new()
207+
{
208+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
209+
};
210+
211+
private static readonly PatternFieldMap<AvroDuration> DurationMetadataPatternFields =
212+
new()
213+
{
214+
{ s => s.StartsWith(string.Empty), (a, p, n) => a.Metadata[p] = n.CreateAny() },
215+
};
216+
113217
public static AvroSchema LoadSchema(ParseNode node)
114218
{
115219
if (node is ValueNode valueNode)
@@ -141,8 +245,13 @@ public static AvroSchema LoadSchema(ParseNode node)
141245
};
142246
}
143247

144-
var type = mapNode["type"]?.Value.GetScalarValue();
248+
var isLogicalType = mapNode["logicalType"] != null;
249+
if (isLogicalType)
250+
{
251+
return LoadLogicalType(mapNode);
252+
}
145253

254+
var type = mapNode["type"]?.Value.GetScalarValue();
146255
switch (type)
147256
{
148257
case "record":
@@ -177,6 +286,48 @@ public static AvroSchema LoadSchema(ParseNode node)
177286
throw new AsyncApiReaderException("Invalid node type");
178287
}
179288

289+
private static AvroSchema LoadLogicalType(MapNode mapNode)
290+
{
291+
var type = mapNode["logicalType"]?.Value.GetScalarValue();
292+
switch (type)
293+
{
294+
case "decimal":
295+
var @decimal = new AvroDecimal();
296+
mapNode.ParseFields(ref @decimal, DecimalFixedFields, DecimalMetadataPatternFields);
297+
return @decimal;
298+
case "uuid":
299+
var uuid = new AvroUUID();
300+
mapNode.ParseFields(ref uuid, UUIDFixedFields, UUIDMetadataPatternFields);
301+
return uuid;
302+
case "date":
303+
var date = new AvroDate();
304+
mapNode.ParseFields(ref date, DateFixedFields, DateMetadataPatternFields);
305+
return date;
306+
case "time-millis":
307+
var timeMillis = new AvroTimeMillis();
308+
mapNode.ParseFields(ref timeMillis, TimeMillisFixedFields, TimeMillisMetadataPatternFields);
309+
return timeMillis;
310+
case "time-micros":
311+
var timeMicros = new AvroTimeMicros();
312+
mapNode.ParseFields(ref timeMicros, TimeMicrosFixedFields, TimeMicrosMetadataPatternFields);
313+
return timeMicros;
314+
case "timestamp-millis":
315+
var timestampMillis = new AvroTimestampMillis();
316+
mapNode.ParseFields(ref timestampMillis, TimestampMillisFixedFields, TimestampMillisMetadataPatternFields);
317+
return timestampMillis;
318+
case "timestamp-micros":
319+
var timestampMicros = new AvroTimestampMicros();
320+
mapNode.ParseFields(ref timestampMicros, TimestampMicrosFixedFields, TimestampMicrosMetadataPatternFields);
321+
return timestampMicros;
322+
case "duration":
323+
var duration = new AvroDuration();
324+
mapNode.ParseFields(ref duration, DurationFixedFields, DurationMetadataPatternFields);
325+
return duration;
326+
default:
327+
throw new AsyncApiException($"Unsupported type: {type}");
328+
}
329+
}
330+
180331
private static AvroField LoadField(ParseNode node)
181332
{
182333
var mapNode = node.CheckMapNode("field");

src/LEGO.AsyncAPI/Models/Avro/AvroMap.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace LEGO.AsyncAPI.Models
44
{
5-
using System;
65
using System.Collections.Generic;
76
using System.Linq;
87
using LEGO.AsyncAPI.Writers;

src/LEGO.AsyncAPI/Models/Avro/AvroPrimitive.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ public AvroPrimitive(AvroPrimitiveType type)
2020
this.Type = type.GetDisplayName();
2121
}
2222

23-
public AvroPrimitive()
24-
{
25-
}
26-
2723
public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
2824
{
2925
writer.WriteValue(this.Type);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
public class AvroDate : AvroLogicalType
6+
{
7+
public AvroDate()
8+
: base(AvroPrimitiveType.Int)
9+
{
10+
}
11+
12+
public override LogicalType LogicalType => LogicalType.Date;
13+
}
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
using LEGO.AsyncAPI.Writers;
6+
7+
public class AvroDecimal : AvroLogicalType
8+
{
9+
public AvroDecimal()
10+
: base(AvroPrimitiveType.Bytes)
11+
{
12+
}
13+
14+
public override LogicalType LogicalType => LogicalType.Decimal;
15+
16+
public int? Scale { get; set; }
17+
18+
public int? Precision { get; set; }
19+
20+
public override void SerializeV2Core(IAsyncApiWriter writer)
21+
{
22+
writer.WriteOptionalProperty("scale", this.Scale);
23+
writer.WriteOptionalProperty("precision", this.Precision);
24+
}
25+
}
26+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
using System.Linq;
6+
using LEGO.AsyncAPI.Writers;
7+
8+
public class AvroDuration : AvroFixed
9+
{
10+
public LogicalType LogicalType { get; } = LogicalType.Duration;
11+
12+
public new int Size { get; } = 12;
13+
14+
public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
15+
{
16+
writer.WriteStartObject();
17+
writer.WriteOptionalProperty("type", this.Type);
18+
writer.WriteOptionalProperty("logicalType", this.LogicalType.GetDisplayName());
19+
writer.WriteRequiredProperty("name", this.Name);
20+
writer.WriteOptionalProperty("namespace", this.Namespace);
21+
writer.WriteOptionalCollection("aliases", this.Aliases, (w, s) => w.WriteValue(s));
22+
writer.WriteRequiredProperty("size", this.Size);
23+
if (this.Metadata.Any())
24+
{
25+
foreach (var item in this.Metadata)
26+
{
27+
writer.WritePropertyName(item.Key);
28+
if (item.Value == null)
29+
{
30+
writer.WriteNull();
31+
}
32+
else
33+
{
34+
writer.WriteAny(item.Value);
35+
}
36+
}
37+
}
38+
39+
writer.WriteEndObject();
40+
}
41+
}
42+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
using System.Linq;
6+
using LEGO.AsyncAPI.Writers;
7+
8+
public abstract class AvroLogicalType : AvroPrimitive
9+
{
10+
protected AvroLogicalType(AvroPrimitiveType type)
11+
: base(type)
12+
{
13+
}
14+
15+
public abstract LogicalType LogicalType { get; }
16+
17+
public virtual void SerializeV2Core(IAsyncApiWriter writer)
18+
{
19+
}
20+
21+
public override void SerializeV2WithoutReference(IAsyncApiWriter writer)
22+
{
23+
writer.WriteStartObject();
24+
writer.WriteOptionalProperty("type", this.Type);
25+
writer.WriteOptionalProperty("logicalType", this.LogicalType.GetDisplayName());
26+
27+
this.SerializeV2Core(writer);
28+
29+
if (this.Metadata.Any())
30+
{
31+
foreach (var item in this.Metadata)
32+
{
33+
writer.WritePropertyName(item.Key);
34+
if (item.Value == null)
35+
{
36+
writer.WriteNull();
37+
}
38+
else
39+
{
40+
writer.WriteAny(item.Value);
41+
}
42+
}
43+
}
44+
45+
writer.WriteEndObject();
46+
}
47+
}
48+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
public class AvroTimeMicros : AvroLogicalType
6+
{
7+
public AvroTimeMicros()
8+
: base(AvroPrimitiveType.Long)
9+
{
10+
}
11+
12+
public override LogicalType LogicalType => LogicalType.Time_Micros;
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
public class AvroTimeMillis : AvroLogicalType
6+
{
7+
public AvroTimeMillis()
8+
: base(AvroPrimitiveType.Int)
9+
{
10+
}
11+
12+
public override LogicalType LogicalType => LogicalType.Time_Millis;
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) The LEGO Group. All rights reserved.
2+
3+
namespace LEGO.AsyncAPI.Models.Avro.LogicalTypes
4+
{
5+
public class AvroTimestampMicros : AvroLogicalType
6+
{
7+
public AvroTimestampMicros()
8+
: base(AvroPrimitiveType.Long)
9+
{
10+
}
11+
12+
public override LogicalType LogicalType => LogicalType.Timestamp_Micros;
13+
}
14+
}

0 commit comments

Comments
 (0)