Skip to content

Commit

Permalink
feat: Added Unix timestamp detection.
Browse files Browse the repository at this point in the history
  • Loading branch information
HavenDV committed Aug 30, 2024
1 parent 4746a6f commit c0b6df1
Show file tree
Hide file tree
Showing 100 changed files with 1,902 additions and 121 deletions.
1 change: 1 addition & 0 deletions src/libs/OpenApiGenerator.Cli/Commands/GenerateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ private static async Task HandleAsync(
.Concat([Sources.JsonSerializerContext(data.Converters, data.Types)])
.Concat([Sources.JsonSerializerContextTypes(data.Types)])
.Concat([Sources.Polyfills(settings)])
.Concat([Sources.UnixTimestampJsonConverter(settings)])
.Where(x => !x.IsEmpty)
.ToArray();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,30 @@ public static bool IsBinary(

return schema.Type == "string" && schema.Format == "binary";
}

public static bool IsUnixTimestamp(
this OpenApiSchema schema)
{
schema = schema ?? throw new ArgumentNullException(nameof(schema));

// Example from OpenAI spec:
// created_at:
// type: integer
// description: The Unix timestamp (in seconds) for when the batch was created.

return (schema.Type == "integer" &&
schema.Format is
// https://github.com/OAI/OpenAPI-Specification/issues/2565
"timestamp" or
"unix-timestamp" or
"unix-time" or
"unix-epoch" or
"epoch") ||
(schema.Type == "integer" &&
schema.Format is
null or
"int64" or
"int32" &&
schema.Description?.ToUpperInvariant().Contains("UNIX TIMESTAMP") == true);
}
}
7 changes: 3 additions & 4 deletions src/libs/OpenApiGenerator.Core/Generation/Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,9 @@ .. includedTags.Select(tag => PropertyData.Default with
})
.Concat(anyOfDatas
.Where(x => x.JsonSerializerType == JsonSerializerType.SystemTextJson)
.Select(x =>
string.IsNullOrWhiteSpace(x.Name)
? $"global::OpenApiGenerator.JsonConverters.{x.SubType}JsonConverterFactory{x.Count}"
: $"global::OpenApiGenerator.JsonConverters.{x.Name}JsonConverter"))
.Select(x => string.IsNullOrWhiteSpace(x.Name)
? $"global::OpenApiGenerator.JsonConverters.{x.SubType}JsonConverterFactory{x.Count}"
: $"global::OpenApiGenerator.JsonConverters.{x.Name}JsonConverter"))
.ToImmutableArray();
for (var i = 0; i < methods.Length; i++)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using OpenApiGenerator.Core.Json;
using OpenApiGenerator.Core.Models;

namespace OpenApiGenerator.Core.Generation;

public static partial class Sources
{
public static string GenerateUnixTimestampJsonConverter(
Settings settings,
CancellationToken cancellationToken = default)
{
if (settings.JsonSerializerType == JsonSerializerType.NewtonsoftJson)
{
return $@"#nullable enable
namespace OpenApiGenerator.JsonConverters
{{
/// <inheritdoc />
public class UnixTimestampJsonConverter : global::Newtonsoft.Json.JsonConverter<global::System.DateTimeOffset>
{{
/// <inheritdoc />
public override global::System.DateTimeOffset ReadJson(
global::Newtonsoft.Json.JsonReader reader,
global::System.Type objectType,
global::System.DateTimeOffset existingValue,
bool hasExistingValue,
global::Newtonsoft.Json.JsonSerializer serializer)
{{
if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Integer)
{{
switch (reader.Value)
{{
case long unixTimestamp:
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
case int unixTimestampInt:
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
}}
}}
return default;
}}
/// <inheritdoc />
public override void WriteJson(
global::Newtonsoft.Json.JsonWriter writer,
global::System.DateTimeOffset value,
global::Newtonsoft.Json.JsonSerializer serializer)
{{
writer.WriteValue(value.ToUnixTimeSeconds());
}}
}}
}}
";
}

return $@"#nullable enable
namespace OpenApiGenerator.JsonConverters
{{
/// <inheritdoc />
public class UnixTimestampJsonConverter : global::System.Text.Json.Serialization.JsonConverter<global::System.DateTimeOffset>
{{
/// <inheritdoc />
public override global::System.DateTimeOffset Read(
ref global::System.Text.Json.Utf8JsonReader reader,
global::System.Type typeToConvert,
global::System.Text.Json.JsonSerializerOptions options)
{{
if (reader.TokenType == global::System.Text.Json.JsonTokenType.Number)
{{
if (reader.TryGetInt64(out long unixTimestamp))
{{
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
}}
if (reader.TryGetInt32(out int unixTimestampInt))
{{
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
}}
}}
return default;
}}
/// <inheritdoc />
public override void Write(
global::System.Text.Json.Utf8JsonWriter writer,
global::System.DateTimeOffset value,
global::System.Text.Json.JsonSerializerOptions options)
{{
long unixTimestamp = value.ToUnixTimeSeconds();
writer.WriteNumberValue(unixTimestamp);
}}
}}
}}
";
}
}
9 changes: 9 additions & 0 deletions src/libs/OpenApiGenerator.Core/Generation/Sources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ public static FileWithName AnyOfJsonConverterFactory(
Text: GenerateAnyOfJsonConverterFactory(anyOf, cancellationToken: cancellationToken));
}

public static FileWithName UnixTimestampJsonConverter(
Settings settings,
CancellationToken cancellationToken = default)
{
return new FileWithName(
Name: "JsonConverters.UnixTimestamp.g.cs",
Text: GenerateUnixTimestampJsonConverter(settings, cancellationToken: cancellationToken));
}

public static FileWithName JsonSerializerContextTypes(
EquatableArray<TypeData> types,
CancellationToken cancellationToken = default)
Expand Down
26 changes: 17 additions & 9 deletions src/libs/OpenApiGenerator.Core/Models/TypeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public readonly record struct TypeData(
bool IsDateTime,
bool IsBinary,
bool IsValueType,
bool IsUnixTimestamp,
int AnyOfCount,
int OneOfCount,
int AllOfCount,
Expand All @@ -35,6 +36,7 @@ public readonly record struct TypeData(
IsDateTime: false,
IsBinary: false,
IsValueType: false,
IsUnixTimestamp: false,
AnyOfCount: 0,
OneOfCount: 0,
AllOfCount: 0,
Expand All @@ -61,15 +63,18 @@ CSharpTypeWithoutNullability is "string" or "int" or "long" or "float" or "doubl
IsAnyOf ||
IsEnum;

public string ConverterType => IsEnum || ((AnyOfCount > 0 || OneOfCount > 0 || AllOfCount > 0) && IsComponent)
? $"global::OpenApiGenerator.JsonConverters.{ShortCSharpTypeWithoutNullability}JsonConverter"
: AnyOfCount > 0
? $"global::OpenApiGenerator.JsonConverters.AnyOfJsonConverterFactory{AnyOfCount}"
: OneOfCount > 0
? $"global::OpenApiGenerator.JsonConverters.OneOfJsonConverterFactory{OneOfCount}"
: AllOfCount > 0
? $"global::OpenApiGenerator.JsonConverters.AllOfJsonConverterFactory{AllOfCount}"
: string.Empty;
public string ConverterType =>
IsUnixTimestamp
? "global::OpenApiGenerator.JsonConverters.UnixTimestampJsonConverter"
: IsEnum || ((AnyOfCount > 0 || OneOfCount > 0 || AllOfCount > 0) && IsComponent)
? $"global::OpenApiGenerator.JsonConverters.{ShortCSharpTypeWithoutNullability}JsonConverter"
: AnyOfCount > 0
? $"global::OpenApiGenerator.JsonConverters.AnyOfJsonConverterFactory{AnyOfCount}"
: OneOfCount > 0
? $"global::OpenApiGenerator.JsonConverters.OneOfJsonConverterFactory{OneOfCount}"
: AllOfCount > 0
? $"global::OpenApiGenerator.JsonConverters.AllOfJsonConverterFactory{AllOfCount}"
: string.Empty;

public static TypeData FromSchemaContext(SchemaContext context)
{
Expand Down Expand Up @@ -149,6 +154,7 @@ Default with
IsDate: context.Schema.IsDate(),
IsDateTime: context.Schema.IsDateTime(),
IsBinary: context.Schema.IsBinary(),
IsUnixTimestamp: context.Schema.IsUnixTimestamp(),
AnyOfCount: context.Schema.AnyOf?.Count ?? 0,
OneOfCount: context.Schema.OneOf?.Count ?? 0,
AllOfCount: context.Schema.AllOf?.Count ?? 0,
Expand Down Expand Up @@ -188,6 +194,8 @@ public static string GetCSharpType(SchemaContext context, SchemaContext? additio

var (type, reference) = (context.Schema.Type, context.Schema.Format) switch
{
(_, _) when context.Schema.IsUnixTimestamp() => ("global::System.DateTimeOffset", false),

(_, _) when context.Schema.IsAnyOf() && context.IsComponent => ($"global::{context.Settings.Namespace}.{context.Id}", true),
(_, _) when context.Schema.IsOneOf() && context.IsComponent => ($"global::{context.Settings.Namespace}.{context.Id}", true),
(_, _) when context.Schema.IsAllOf() && context.IsComponent => ($"global::{context.Settings.Namespace}.{context.Id}", true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>net4.6.2;netstandard2.0;net6.0;net8.0</TargetFrameworks>
<NoWarn>$(NoWarn);CA1031;CA1307;CA1724;CA1056;CA1054;CA1865;CA1847;CA2227</NoWarn>
<NoWarn>$(NoWarn);CA1031;CA1307;CA1724;CA1056;CA1054;CA1865;CA1847;CA2227;CA1862</NoWarn>
</PropertyGroup>

<ItemGroup Label="Global Usings">
Expand Down
4 changes: 4 additions & 0 deletions src/libs/OpenApiGenerator/SdkGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.SelectAndReportExceptions((x, c) => Sources.Polyfills(x, c)
.AsFileWithName(), context, Id)
.AddSource(context);
settings
.SelectAndReportExceptions((x, c) => Sources.UnixTimestampJsonConverter(x, c)
.AsFileWithName(), context, Id)
.AddSource(context);

var data = context.AdditionalTextsProvider
.Where(static text => text.Path.EndsWith(".yaml", StringComparison.InvariantCultureIgnoreCase) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: JsonConverters.UnixTimestamp.g.cs
#nullable enable

namespace OpenApiGenerator.JsonConverters
{
/// <inheritdoc />
public class UnixTimestampJsonConverter : global::Newtonsoft.Json.JsonConverter<global::System.DateTimeOffset>
{
/// <inheritdoc />
public override global::System.DateTimeOffset ReadJson(
global::Newtonsoft.Json.JsonReader reader,
global::System.Type objectType,
global::System.DateTimeOffset existingValue,
bool hasExistingValue,
global::Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Integer)
{
switch (reader.Value)
{
case long unixTimestamp:
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
case int unixTimestampInt:
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
}
}

return default;
}

/// <inheritdoc />
public override void WriteJson(
global::Newtonsoft.Json.JsonWriter writer,
global::System.DateTimeOffset value,
global::Newtonsoft.Json.JsonSerializer serializer)
{
writer.WriteValue(value.ToUnixTimeSeconds());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: JsonConverters.UnixTimestamp.g.cs
#nullable enable

namespace OpenApiGenerator.JsonConverters
{
/// <inheritdoc />
public class UnixTimestampJsonConverter : global::System.Text.Json.Serialization.JsonConverter<global::System.DateTimeOffset>
{
/// <inheritdoc />
public override global::System.DateTimeOffset Read(
ref global::System.Text.Json.Utf8JsonReader reader,
global::System.Type typeToConvert,
global::System.Text.Json.JsonSerializerOptions options)
{
if (reader.TokenType == global::System.Text.Json.JsonTokenType.Number)
{
if (reader.TryGetInt64(out long unixTimestamp))
{
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
}
if (reader.TryGetInt32(out int unixTimestampInt))
{
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
}
}

return default;
}

/// <inheritdoc />
public override void Write(
global::System.Text.Json.Utf8JsonWriter writer,
global::System.DateTimeOffset value,
global::System.Text.Json.JsonSerializerOptions options)
{
long unixTimestamp = value.ToUnixTimeSeconds();
writer.WriteNumberValue(unixTimestamp);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: JsonConverters.UnixTimestamp.g.cs
#nullable enable

namespace OpenApiGenerator.JsonConverters
{
/// <inheritdoc />
public class UnixTimestampJsonConverter : global::Newtonsoft.Json.JsonConverter<global::System.DateTimeOffset>
{
/// <inheritdoc />
public override global::System.DateTimeOffset ReadJson(
global::Newtonsoft.Json.JsonReader reader,
global::System.Type objectType,
global::System.DateTimeOffset existingValue,
bool hasExistingValue,
global::Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == global::Newtonsoft.Json.JsonToken.Integer)
{
switch (reader.Value)
{
case long unixTimestamp:
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
case int unixTimestampInt:
return global::System.DateTimeOffset.FromUnixTimeSeconds(unixTimestampInt);
}
}

return default;
}

/// <inheritdoc />
public override void WriteJson(
global::Newtonsoft.Json.JsonWriter writer,
global::System.DateTimeOffset value,
global::Newtonsoft.Json.JsonSerializer serializer)
{
writer.WriteValue(value.ToUnixTimeSeconds());
}
}
}
Loading

0 comments on commit c0b6df1

Please sign in to comment.