Skip to content

Commit c0b6df1

Browse files
committed
feat: Added Unix timestamp detection.
1 parent 4746a6f commit c0b6df1

File tree

100 files changed

+1902
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+1902
-121
lines changed

src/libs/OpenApiGenerator.Cli/Commands/GenerateCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ private static async Task HandleAsync(
147147
.Concat([Sources.JsonSerializerContext(data.Converters, data.Types)])
148148
.Concat([Sources.JsonSerializerContextTypes(data.Types)])
149149
.Concat([Sources.Polyfills(settings)])
150+
.Concat([Sources.UnixTimestampJsonConverter(settings)])
150151
.Where(x => !x.IsEmpty)
151152
.ToArray();
152153

src/libs/OpenApiGenerator.Core/Extensions/OpenApiSchemaExtensions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,30 @@ public static bool IsBinary(
9191

9292
return schema.Type == "string" && schema.Format == "binary";
9393
}
94+
95+
public static bool IsUnixTimestamp(
96+
this OpenApiSchema schema)
97+
{
98+
schema = schema ?? throw new ArgumentNullException(nameof(schema));
99+
100+
// Example from OpenAI spec:
101+
// created_at:
102+
// type: integer
103+
// description: The Unix timestamp (in seconds) for when the batch was created.
104+
105+
return (schema.Type == "integer" &&
106+
schema.Format is
107+
// https://github.com/OAI/OpenAPI-Specification/issues/2565
108+
"timestamp" or
109+
"unix-timestamp" or
110+
"unix-time" or
111+
"unix-epoch" or
112+
"epoch") ||
113+
(schema.Type == "integer" &&
114+
schema.Format is
115+
null or
116+
"int64" or
117+
"int32" &&
118+
schema.Description?.ToUpperInvariant().Contains("UNIX TIMESTAMP") == true);
119+
}
94120
}

src/libs/OpenApiGenerator.Core/Generation/Data.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,9 @@ .. includedTags.Select(tag => PropertyData.Default with
296296
})
297297
.Concat(anyOfDatas
298298
.Where(x => x.JsonSerializerType == JsonSerializerType.SystemTextJson)
299-
.Select(x =>
300-
string.IsNullOrWhiteSpace(x.Name)
301-
? $"global::OpenApiGenerator.JsonConverters.{x.SubType}JsonConverterFactory{x.Count}"
302-
: $"global::OpenApiGenerator.JsonConverters.{x.Name}JsonConverter"))
299+
.Select(x => string.IsNullOrWhiteSpace(x.Name)
300+
? $"global::OpenApiGenerator.JsonConverters.{x.SubType}JsonConverterFactory{x.Count}"
301+
: $"global::OpenApiGenerator.JsonConverters.{x.Name}JsonConverter"))
303302
.ToImmutableArray();
304303
for (var i = 0; i < methods.Length; i++)
305304
{
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using OpenApiGenerator.Core.Json;
2+
using OpenApiGenerator.Core.Models;
3+
4+
namespace OpenApiGenerator.Core.Generation;
5+
6+
public static partial class Sources
7+
{
8+
public static string GenerateUnixTimestampJsonConverter(
9+
Settings settings,
10+
CancellationToken cancellationToken = default)
11+
{
12+
if (settings.JsonSerializerType == JsonSerializerType.NewtonsoftJson)
13+
{
14+
return $@"#nullable enable
15+
16+
namespace OpenApiGenerator.JsonConverters
17+
{{
18+
/// <inheritdoc />
19+
public class UnixTimestampJsonConverter : global::Newtonsoft.Json.JsonConverter<global::System.DateTimeOffset>
20+
{{
21+
/// <inheritdoc />
22+
public override global::System.DateTimeOffset ReadJson(
23+
global::Newtonsoft.Json.JsonReader reader,
24+
global::System.Type objectType,
25+
global::System.DateTimeOffset existingValue,
26+
bool hasExistingValue,
27+
global::Newtonsoft.Json.JsonSerializer serializer)
28+
{{
29+
if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Integer)
30+
{{
31+
switch (reader.Value)
32+
{{
33+
case long unixTimestamp:
34+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
35+
case int unixTimestampInt:
36+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
37+
}}
38+
}}
39+
40+
return default;
41+
}}
42+
43+
/// <inheritdoc />
44+
public override void WriteJson(
45+
global::Newtonsoft.Json.JsonWriter writer,
46+
global::System.DateTimeOffset value,
47+
global::Newtonsoft.Json.JsonSerializer serializer)
48+
{{
49+
writer.WriteValue(value.ToUnixTimeSeconds());
50+
}}
51+
}}
52+
}}
53+
";
54+
}
55+
56+
return $@"#nullable enable
57+
58+
namespace OpenApiGenerator.JsonConverters
59+
{{
60+
/// <inheritdoc />
61+
public class UnixTimestampJsonConverter : global::System.Text.Json.Serialization.JsonConverter<global::System.DateTimeOffset>
62+
{{
63+
/// <inheritdoc />
64+
public override global::System.DateTimeOffset Read(
65+
ref global::System.Text.Json.Utf8JsonReader reader,
66+
global::System.Type typeToConvert,
67+
global::System.Text.Json.JsonSerializerOptions options)
68+
{{
69+
if (reader.TokenType == global::System.Text.Json.JsonTokenType.Number)
70+
{{
71+
if (reader.TryGetInt64(out long unixTimestamp))
72+
{{
73+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
74+
}}
75+
if (reader.TryGetInt32(out int unixTimestampInt))
76+
{{
77+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
78+
}}
79+
}}
80+
81+
return default;
82+
}}
83+
84+
/// <inheritdoc />
85+
public override void Write(
86+
global::System.Text.Json.Utf8JsonWriter writer,
87+
global::System.DateTimeOffset value,
88+
global::System.Text.Json.JsonSerializerOptions options)
89+
{{
90+
long unixTimestamp = value.ToUnixTimeSeconds();
91+
writer.WriteNumberValue(unixTimestamp);
92+
}}
93+
}}
94+
}}
95+
";
96+
}
97+
}

src/libs/OpenApiGenerator.Core/Generation/Sources.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ public static FileWithName AnyOfJsonConverterFactory(
100100
Text: GenerateAnyOfJsonConverterFactory(anyOf, cancellationToken: cancellationToken));
101101
}
102102

103+
public static FileWithName UnixTimestampJsonConverter(
104+
Settings settings,
105+
CancellationToken cancellationToken = default)
106+
{
107+
return new FileWithName(
108+
Name: "JsonConverters.UnixTimestamp.g.cs",
109+
Text: GenerateUnixTimestampJsonConverter(settings, cancellationToken: cancellationToken));
110+
}
111+
103112
public static FileWithName JsonSerializerContextTypes(
104113
EquatableArray<TypeData> types,
105114
CancellationToken cancellationToken = default)

src/libs/OpenApiGenerator.Core/Models/TypeData.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public readonly record struct TypeData(
1414
bool IsDateTime,
1515
bool IsBinary,
1616
bool IsValueType,
17+
bool IsUnixTimestamp,
1718
int AnyOfCount,
1819
int OneOfCount,
1920
int AllOfCount,
@@ -35,6 +36,7 @@ public readonly record struct TypeData(
3536
IsDateTime: false,
3637
IsBinary: false,
3738
IsValueType: false,
39+
IsUnixTimestamp: false,
3840
AnyOfCount: 0,
3941
OneOfCount: 0,
4042
AllOfCount: 0,
@@ -61,15 +63,18 @@ CSharpTypeWithoutNullability is "string" or "int" or "long" or "float" or "doubl
6163
IsAnyOf ||
6264
IsEnum;
6365

64-
public string ConverterType => IsEnum || ((AnyOfCount > 0 || OneOfCount > 0 || AllOfCount > 0) && IsComponent)
65-
? $"global::OpenApiGenerator.JsonConverters.{ShortCSharpTypeWithoutNullability}JsonConverter"
66-
: AnyOfCount > 0
67-
? $"global::OpenApiGenerator.JsonConverters.AnyOfJsonConverterFactory{AnyOfCount}"
68-
: OneOfCount > 0
69-
? $"global::OpenApiGenerator.JsonConverters.OneOfJsonConverterFactory{OneOfCount}"
70-
: AllOfCount > 0
71-
? $"global::OpenApiGenerator.JsonConverters.AllOfJsonConverterFactory{AllOfCount}"
72-
: string.Empty;
66+
public string ConverterType =>
67+
IsUnixTimestamp
68+
? "global::OpenApiGenerator.JsonConverters.UnixTimestampJsonConverter"
69+
: IsEnum || ((AnyOfCount > 0 || OneOfCount > 0 || AllOfCount > 0) && IsComponent)
70+
? $"global::OpenApiGenerator.JsonConverters.{ShortCSharpTypeWithoutNullability}JsonConverter"
71+
: AnyOfCount > 0
72+
? $"global::OpenApiGenerator.JsonConverters.AnyOfJsonConverterFactory{AnyOfCount}"
73+
: OneOfCount > 0
74+
? $"global::OpenApiGenerator.JsonConverters.OneOfJsonConverterFactory{OneOfCount}"
75+
: AllOfCount > 0
76+
? $"global::OpenApiGenerator.JsonConverters.AllOfJsonConverterFactory{AllOfCount}"
77+
: string.Empty;
7378

7479
public static TypeData FromSchemaContext(SchemaContext context)
7580
{
@@ -149,6 +154,7 @@ Default with
149154
IsDate: context.Schema.IsDate(),
150155
IsDateTime: context.Schema.IsDateTime(),
151156
IsBinary: context.Schema.IsBinary(),
157+
IsUnixTimestamp: context.Schema.IsUnixTimestamp(),
152158
AnyOfCount: context.Schema.AnyOf?.Count ?? 0,
153159
OneOfCount: context.Schema.OneOf?.Count ?? 0,
154160
AllOfCount: context.Schema.AllOf?.Count ?? 0,
@@ -188,6 +194,8 @@ public static string GetCSharpType(SchemaContext context, SchemaContext? additio
188194

189195
var (type, reference) = (context.Schema.Type, context.Schema.Format) switch
190196
{
197+
(_, _) when context.Schema.IsUnixTimestamp() => ("global::System.DateTimeOffset", false),
198+
191199
(_, _) when context.Schema.IsAnyOf() && context.IsComponent => ($"global::{context.Settings.Namespace}.{context.Id}", true),
192200
(_, _) when context.Schema.IsOneOf() && context.IsComponent => ($"global::{context.Settings.Namespace}.{context.Id}", true),
193201
(_, _) when context.Schema.IsAllOf() && context.IsComponent => ($"global::{context.Settings.Namespace}.{context.Id}", true),

src/libs/OpenApiGenerator.Core/OpenApiGenerator.Core.csproj

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

33
<PropertyGroup>
44
<TargetFrameworks>net4.6.2;netstandard2.0;net6.0;net8.0</TargetFrameworks>
5-
<NoWarn>$(NoWarn);CA1031;CA1307;CA1724;CA1056;CA1054;CA1865;CA1847;CA2227</NoWarn>
5+
<NoWarn>$(NoWarn);CA1031;CA1307;CA1724;CA1056;CA1054;CA1865;CA1847;CA2227;CA1862</NoWarn>
66
</PropertyGroup>
77

88
<ItemGroup Label="Global Usings">

src/libs/OpenApiGenerator/SdkGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2222
.SelectAndReportExceptions((x, c) => Sources.Polyfills(x, c)
2323
.AsFileWithName(), context, Id)
2424
.AddSource(context);
25+
settings
26+
.SelectAndReportExceptions((x, c) => Sources.UnixTimestampJsonConverter(x, c)
27+
.AsFileWithName(), context, Id)
28+
.AddSource(context);
2529

2630
var data = context.AdditionalTextsProvider
2731
.Where(static text => text.Path.EndsWith(".yaml", StringComparison.InvariantCultureIgnoreCase) ||
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//HintName: JsonConverters.UnixTimestamp.g.cs
2+
#nullable enable
3+
4+
namespace OpenApiGenerator.JsonConverters
5+
{
6+
/// <inheritdoc />
7+
public class UnixTimestampJsonConverter : global::Newtonsoft.Json.JsonConverter<global::System.DateTimeOffset>
8+
{
9+
/// <inheritdoc />
10+
public override global::System.DateTimeOffset ReadJson(
11+
global::Newtonsoft.Json.JsonReader reader,
12+
global::System.Type objectType,
13+
global::System.DateTimeOffset existingValue,
14+
bool hasExistingValue,
15+
global::Newtonsoft.Json.JsonSerializer serializer)
16+
{
17+
if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Integer)
18+
{
19+
switch (reader.Value)
20+
{
21+
case long unixTimestamp:
22+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
23+
case int unixTimestampInt:
24+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
25+
}
26+
}
27+
28+
return default;
29+
}
30+
31+
/// <inheritdoc />
32+
public override void WriteJson(
33+
global::Newtonsoft.Json.JsonWriter writer,
34+
global::System.DateTimeOffset value,
35+
global::Newtonsoft.Json.JsonSerializer serializer)
36+
{
37+
writer.WriteValue(value.ToUnixTimeSeconds());
38+
}
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//HintName: JsonConverters.UnixTimestamp.g.cs
2+
#nullable enable
3+
4+
namespace OpenApiGenerator.JsonConverters
5+
{
6+
/// <inheritdoc />
7+
public class UnixTimestampJsonConverter : global::System.Text.Json.Serialization.JsonConverter<global::System.DateTimeOffset>
8+
{
9+
/// <inheritdoc />
10+
public override global::System.DateTimeOffset Read(
11+
ref global::System.Text.Json.Utf8JsonReader reader,
12+
global::System.Type typeToConvert,
13+
global::System.Text.Json.JsonSerializerOptions options)
14+
{
15+
if (reader.TokenType == global::System.Text.Json.JsonTokenType.Number)
16+
{
17+
if (reader.TryGetInt64(out long unixTimestamp))
18+
{
19+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
20+
}
21+
if (reader.TryGetInt32(out int unixTimestampInt))
22+
{
23+
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
24+
}
25+
}
26+
27+
return default;
28+
}
29+
30+
/// <inheritdoc />
31+
public override void Write(
32+
global::System.Text.Json.Utf8JsonWriter writer,
33+
global::System.DateTimeOffset value,
34+
global::System.Text.Json.JsonSerializerOptions options)
35+
{
36+
long unixTimestamp = value.ToUnixTimeSeconds();
37+
writer.WriteNumberValue(unixTimestamp);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)