Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenAPI example rendered incorrectly for strings that happen to be DateTime(Offset) strings #60630

Closed
1 task done
martincostello opened this issue Feb 26, 2025 · 2 comments
Closed
1 task done
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc External This is an issue in a component not contained in this repository. It is open for tracking purposes. feature-openapi

Comments

@martincostello
Copy link
Member

martincostello commented Feb 26, 2025

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

It's possible this is a Microsoft.OpenApi issue, but I figured I'd start here.

Upgrading an application with an OpenAPI document from ASP.NET Core 9 to ASP.NET Core 10 preview 1, the examples in the returned JSON (or YAML) document are being rendered with string properties that happen to parse as a DateTimeOffset being rendered as such, rather than the original string representation.

It seems that something somewhere sees the string will parse as a DateTimeOffset, converts it, and then subsequently it is formatted as such, changing the string's value.

Image

As far as I can tell, my OpenAPI extensions are rendering the examples as strings correctly when assigned to the Example property on the relevant OpenAPI primitive.

Image

The relevant snippet from the OpenAPI schema is:

"TimeResponse": {
  "type": "object",
  "properties": {
    "timestamp": {
      "type": "string",
      "description": "The timestamp for the response for which the times are generated.",
      "format": "date-time"
    },
    "rfc1123": {
      "type": "string",
      "description": "The current UTC date and time in RFC1123 format."
    },
    "unix": {
      "type": "integer",
      "description": "The number of seconds since the UNIX epoch.",
      "format": "int64"
     },
     "universalSortable": {
       "type": "string",
       "description": "The current UTC date and time in universal sortable format."
      },
    "universalFull": {
      "type": "string",
      "description": "The current UTC date and time in universal full format."
    }
  },
  "description": "Represents the response from the /time API resource.",
  "example": {
    "timestamp": "2016-06-03T18:44:14.0000000+00:00",
    "rfc1123": "2016-06-03T18:44:14.0000000+00:00",
    "unix": 1464979454,
    "universalSortable": "2016-06-03T18:44:14.0000000+00:00",
    "universalFull": "2016-06-03T18:44:14.0000000+01:00"
 }
}

rfc1123, universalSortable and universalFull are all being rendered the same as timestamp when they should be rendered in different string formats.

Expected Behavior

String properties that are not explicitly typed as the format: date-time are not coerced to a different type.

Steps To Reproduce

  1. Clone martincostello/api@a5135f4
  2. Run build.ps1 from the root of the repository.

Exceptions (if any)

The Json_Schema_Is_Correct and Yaml_Schema_Is_Correct tests fail.

.NET Version

10.0.100-preview.1.25120.13

Anything else?

No.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Feb 26, 2025
@martincostello martincostello added feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Feb 26, 2025
martincostello added a commit to martincostello/api that referenced this issue Feb 26, 2025
Skip tests failing due to dotnet/aspnetcore#60630.
@martincostello
Copy link
Member Author

Have just discovered a similar effect with an internal API that uses DateOnly. The expected schema and example is:

"DotNetRuntime": {
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The name of the .NET product."
    },
    "channel": {
      "type": "string",
      "description": "The channel of the .NET runtime."
    },
    "isSupported": {
      "type": "boolean",
      "description": "Whether the .NET runtime is supported."
    },
    "endOfLife": {
      "type": [
        null,
        "string"
      ],
      "description": "The end-of-life date of the runtime, if known.",
      "format": "date"
    },
    "latestRuntime": {
      "type": "string",
      "description": "The latest runtime version of the .NET channel version."
    },
    "latestSdk": {
      "type": "string",
      "description": "The latest SDK version of the .NET channel version."
    }
  },
  "description": "Represents information about a .NET runtime.",
  "example": {
    "name": ".NET",
    "channel": "8.0",
    "isSupported": true,
-    "endOfLife": "2026-11-10",
+    "endOfLife": "2026-11-10T00:00:00.0000000+00:00",
     "latestRuntime": "8.0.1",
    "latestSdk": "8.0.101"
  }
}

This is the C# model:

using System.Text.Json.Serialization;
using MartinCostello.OpenApi;

namespace InternalApp;

/// <summary>
/// Represents information about a .NET runtime.
/// </summary>
[OpenApiExample<DotNetRuntime>]
public sealed class DotNetRuntime : IExampleProvider<DotNetRuntime>
{
    /// <summary>
    /// Gets or sets the name of the .NET product.
    /// </summary>
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the channel of the .NET runtime.
    /// </summary>
    [JsonPropertyName("channel")]
    public string Channel { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets a value indicating whether the .NET runtime is supported.
    /// </summary>
    [JsonPropertyName("isSupported")]
    public bool IsSupported { get; set; }

    /// <summary>
    /// Gets or sets the end-of-life date of the runtime, if known.
    /// </summary>
    [JsonPropertyName("endOfLife")]
    public DateOnly? EndOfLife { get; set; }

    /// <summary>
    /// Gets or sets the latest runtime version of the .NET channel version.
    /// </summary>
    [JsonPropertyName("latestRuntime")]
    public string LatestRuntime { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the latest SDK version of the .NET channel version.
    /// </summary>
    [JsonPropertyName("latestSdk")]
    public string LatestSdk { get; set; } = string.Empty;

    /// <inheritdoc/>
    public static DotNetRuntime GenerateExample()
    {
        return new()
        {
            Name = ".NET",
            Channel = "8.0",
            IsSupported = true,
            EndOfLife = new DateOnly(2026, 11, 10),
            LatestRuntime = "8.0.1",
            LatestSdk = "8.0.101",
        };
    }
}

@captainsafia
Copy link
Member

@martincostello Thanks for filing this issue! This is a dupe of microsoft/OpenAPI.NET#2137 on the underlying OpenAPI library. We'll pick up the fix when we consume the next OpenAPI update.

@captainsafia captainsafia added the External This is an issue in a component not contained in this repository. It is open for tracking purposes. label Feb 26, 2025
martincostello added a commit to martincostello/api that referenced this issue Mar 3, 2025
Skip tests failing due to dotnet/aspnetcore#60630.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc External This is an issue in a component not contained in this repository. It is open for tracking purposes. feature-openapi
Projects
None yet
Development

No branches or pull requests

2 participants