RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.79k stars 1.29k forks source link

openapi discriminator/mapping values can be bare schema names #4475

Open jamesmanning opened 1 year ago

jamesmanning commented 1 year ago

versions: NSwagStudio 13.19.0.0 NSwag 13.19.0.0 NJsonSchema 10.9.0.0 (Newtonsoft.Json v13.0.0.0)

NOTE: changing the mapping values so they're explicit schema refs (changing "Foo" to "#/components/schemas/Foo") is a viable workaround, so this isn't a blocker, just requires modifying the original openapi definition to make the mapping values explicit schema refs.

Was trying to use nswag to generate a client from this source:

https://github.com/ccouzens/vcloud-rest-openapi/blob/main/36.3.json

Specifically via this url to the actual file: https://raw.githubusercontent.com/ccouzens/vcloud-rest-openapi/main/36.3.json

nswag file contents:

{
  "runtime": "Net60",
  "defaultVariables": null,
  "documentGenerator": {
    "fromDocument": {
      "json": "",
      "url": "https://raw.githubusercontent.com/ccouzens/vcloud-rest-openapi/main/36.3.json",
      "output": null,
      "newLineBehavior": "Auto"
    }
  },
  "codeGenerators": {}
}

It fails because (AFAICT) it takes the discriminator / mapping values and assumes they're schema references (I think, I may be misinterpreting the situation), so it tries to resolve the string value as a json path but there's no such external file.

System.InvalidOperationException: Could not resolve the JSON path 'ovf_AnnotationSection_Type' with the full JSON path 'https://raw.githubusercontent.com/ccouzens/vcloud-rest-openapi/main/ovf_AnnotationSection_Type'.
 ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).

Runtime: Net60
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at CallSite.Target(Closure , CallSite , Object )
   at NJsonSchema.Infrastructure.DynamicApis.HttpGetAsync(String url, CancellationToken cancellationToken)
   at NJsonSchema.JsonSchema.FromUrlAsync(String url, Func`2 referenceResolverFactory, CancellationToken cancellationToken)
   at NJsonSchema.JsonReferenceResolver.ResolveUrlReferenceAsync(String url, CancellationToken cancellationToken)
   at NJsonSchema.JsonReferenceResolver.ResolveUrlReferenceWithAlreadyResolvedCheckAsync(String fullJsonPath, String jsonPath, Type targetType, IContractResolver contractResolver, Boolean append, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at NJsonSchema.JsonReferenceResolver.ResolveUrlReferenceWithAlreadyResolvedCheckAsync(String fullJsonPath, String jsonPath, Type targetType, IContractResolver contractResolver, Boolean append, CancellationToken cancellationToken)
   at NJsonSchema.JsonReferenceResolver.ResolveReferenceAsync(Object rootObject, String jsonPath, Type targetType, IContractResolver contractResolver, Boolean append, CancellationToken cancellationToken)
   at NJsonSchema.JsonReferenceResolver.ResolveReferenceAsync(Object rootObject, String jsonPath, Type targetType, IContractResolver contractResolver, CancellationToken cancellationToken)
   at NJsonSchema.JsonSchemaReferenceUtilities.JsonReferenceUpdater.VisitJsonReferenceAsync(IJsonReference reference, String path, String typeNameHint, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer, CancellationToken cancellationToken)
   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, CancellationToken cancellationToken)
   at NJsonSchema.JsonSchemaReferenceUtilities.JsonReferenceUpdater.VisitAsync(Object obj, CancellationToken cancellationToken)
   at NJsonSchema.JsonSchemaReferenceUtilities.UpdateSchemaReferencesAsync(Object rootObject, JsonReferenceResolver referenceResolver, IContractResolver contractResolver, CancellationToken cancellationToken)
   at NJsonSchema.Infrastructure.JsonSchemaSerialization.FromJsonWithLoaderAsync[T](Func`1 loader, SchemaType schemaType, String documentPath, Func`2 referenceResolverFactory, IContractResolver contractResolver, CancellationToken cancellationToken)
   at NSwag.OpenApiDocument.FromJsonAsync(String data, String documentPath, SchemaType expectedSchemaType, Func`2 referenceResolverFactory, CancellationToken cancellationToken) in /_/src/NSwag.Core/OpenApiDocument.cs:line 203
   at NSwag.OpenApiDocument.FromUrlAsync(String url, CancellationToken cancellationToken) in /_/src/NSwag.Core/OpenApiDocument.cs:line 235
   at NSwag.Commands.Generation.FromDocumentCommand.RunAsync() in /_/src/NSwag.Commands/Commands/Generation/FromDocumentCommand.cs:line 62
   at NSwag.Commands.Generation.FromDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Generation/FromDocumentCommand.cs:line 53
   at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in /_/src/NSwag.Commands/NSwagDocumentBase.cs:line 275
   at NSwag.Commands.NSwagDocument.ExecuteAsync() in /_/src/NSwag.Commands/NSwagDocument.cs:line 81
   at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 85
   at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 32
   at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
   at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
   at NSwag.Commands.NSwagCommandProcessor.ProcessAsync(String[] args) in /_/src/NSwag.Commands/NSwagCommandProcessor.cs:line 61

Trying to use the csharp-netcore generator from openapi-generator seems to generate a client successfully.

AFAICT the issue is the spec allowing the mapping values to be either schema names or references

https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#fixed-fields-21

where mapping is defined as An object to hold mappings between payload values and schema **names or references**.

The example there includes a case where the mapping value is just the name of the schema

components:
  schemas:
    Pet:
      type: object
      required:
      - pet_type
      properties:
        pet_type:
          type: string
      discriminator:
        propertyName: pet_type
        mapping:
          cachorro: **Dog**
    [...]
    Dog:
      allOf:
      - $ref: '#/components/schemas/Pet'
      - type: object
        # all other properties specific to a `Dog`
        properties:
          bark:
            type: string

In this particular case, the mapping values aren't referencing external files, just names of existing schemas within the same document - specifically, this is the first problematic mapping entry that causes the error:

  "ovf_Section_Type": {
    "title": "ovf_Section_Type",
    "discriminator": {
      "propertyName": "_type",
      "mapping": {
        "AnnotationSectionType": "ovf_AnnotationSection_Type",

And the "ovf_AnnotationSection_Type" value it's referring to isn't a file, but an existing schema:

  "components": {
    [...]
    "schemas": {
      [...]
      "ovf_AnnotationSection_Type": {
        "title": "ovf_AnnotationSection_Type",
        "description": "User defined annotation",
        "allOf": [
          {
            "$ref": "#/components/schemas/ovf_Section_Type"
          },
          {
            "type": "object",
            "properties": {
              "annotation": {
                "$ref": "#/components/schemas/ovf_Msg_Type"
              }
            },
            "additionalProperties": false
          }
        ]
      },

I'm not sure if the issue here is nswag or njsonschema, so I'm happy to move the issue if this is the wrong repository for it.

Thank you!

ccouzens commented 1 year ago

https://github.com/ccouzens/vcloud-rest-openapi/blob/main/36.3.json

Specifically via this url to the actual file: https://raw.githubusercontent.com/ccouzens/vcloud-rest-openapi/main/36.3.json

We might merge a workaround for this issue. In which case the links useful to this bug report will become:

https://github.com/ccouzens/vcloud-rest-openapi/blob/96d1afc005ab90cddf1d16d5c7073b679f714cd8/36.3.json and https://raw.githubusercontent.com/ccouzens/vcloud-rest-openapi/96d1afc005ab90cddf1d16d5c7073b679f714cd8/36.3.json

jamesmanning commented 1 year ago

Very good point @ccouzens , I should have specified a commit-based version instead of the main branch version!