RicoSuter / NSwag

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

System.InvalidCastException when running NSwag via MSBuild #3031

Open stevenvolckaert opened 3 years ago

stevenvolckaert commented 3 years ago

When I execute NSwag ("runtime": "NetCore31") via NSwag.MSBuild v13.7.0 on the following file, I get a System.InvalidCastException:

2>Executing file 'nswag.json' with variables 'Configuration=Debug'...
2>System.InvalidCastException: Unable to cast object of type 'NSwag.OpenApiResponse' to type 'NJsonSchema.JsonSchema'.
2>   at NJsonSchema.References.JsonReferenceBase`1.NJsonSchema.References.IJsonReferenceBase.set_Reference(IJsonReference value)
2>   at NJsonSchema.JsonSchemaReferenceUtilities.JsonReferenceUpdater.VisitJsonReferenceAsync(IJsonReference reference, String path, String typeNameHint)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj, String path, String typeNameHint, ISet`1 checkedObjects, Action`1 replacer)
2>   at NJsonSchema.Visitors.AsyncJsonReferenceVisitorBase.VisitAsync(Object obj)
2>   at NJsonSchema.JsonSchemaReferenceUtilities.JsonReferenceUpdater.VisitAsync(Object obj)
2>   at NJsonSchema.JsonSchemaReferenceUtilities.UpdateSchemaReferencesAsync(Object rootObject, JsonReferenceResolver referenceResolver, IContractResolver contractResolver)
2>   at NJsonSchema.Infrastructure.JsonSchemaSerialization.FromJsonAsync[T](String json, SchemaType schemaType, String documentPath, Func`2 referenceResolverFactory, IContractResolver contractResolver)
2>   at NSwag.OpenApiDocument.FromJsonAsync(String data, String documentPath, SchemaType expectedSchemaType, Func`2 referenceResolverFactory) in C:\projects\nswag\src\NSwag.Core\OpenApiDocument.cs:line 190
2>   at NSwag.OpenApiDocument.FromFileAsync(String filePath) in C:\projects\nswag\src\NSwag.Core\OpenApiDocument.cs:line 211
2>   at NSwag.Commands.OutputCommandBase.ReadSwaggerDocumentAsync(String input) in C:\projects\nswag\src\NSwag.Commands\Commands\OutputCommandBase.cs:line 53
2>   at NSwag.Commands.Generation.FromDocumentCommand.RunAsync() in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\FromDocumentCommand.cs:line 62
2>   at NSwag.Commands.Generation.FromDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\FromDocumentCommand.cs:line 53
2>   at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in C:\projects\nswag\src\NSwag.Commands\NSwagDocumentBase.cs:line 280
2>   at NSwag.Commands.NSwagDocument.ExecuteAsync() in C:\projects\nswag\src\NSwag.Commands\NSwagDocument.cs:line 81
2>   at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in C:\projects\nswag\src\NSwag.Commands\Commands\Document\ExecuteDocumentCommand.cs:line 86
2>   at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Document\ExecuteDocumentCommand.cs:line 32
2>   at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
2>   at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
2>   at NConsole.CommandLineProcessor.Process(String[] args, Object input)
2>   at NSwag.Commands.NSwagCommandProcessor.Process(String[] args) in C:\projects\nswag\src\NSwag.Commands\NSwagCommandProcessor.cs:line 56
2>C:\Users\steven.volckaert\Repos\kdc\document-signing-service\src\Connective.ESignatures.Clients\Connective.ESignatures.Clients.csproj(40,5): error MSB3073: The command "dotnet "C:\Users\steven.volckaert\.nuget\packages\nswag.msbuild\13.7.0\buildCrossTargeting\../tools/NetCore31/dotnet-nswag.dll" run nswag.json /variables:Configuration=Debug" exited with code -1.
2>Done building project "Connective.ESignatures.Clients.csproj" -- FAILED.

Any ideas what could be wrong?

The OpenAPI spec can be found in this gist: Connective.ESignatures.OpenApiSpec.v4.yaml

Many thanks for your help!

nswag.json

{
  "runtime": "NetCore31",
  "defaultVariables": null,
  "documentGenerator": {
    "fromDocument": {
      "url": "Connective.ESignatures.OpenApiSpec.v4.yaml",
      "output": null
    }
  },
  "codeGenerators": {
    "openApiToCSharpClient": {
      "clientBaseClass": "Kdc.HttpClientBase",
      "configurationClass": "Kdc.HttpServiceSettings",
      "generateClientClasses": true,
      "generateClientInterfaces": false,
      "clientBaseInterface": null,
      "injectHttpClient": true,
      "disposeHttpClient": false,
      "protectedMethods": [],
      "generateExceptionClasses": true,
      "exceptionClass": "HttpServiceException",
      "wrapDtoExceptions": true,
      "useHttpClientCreationMethod": false,
      "httpClientType": "System.Net.Http.HttpClient",
      "useHttpRequestMessageCreationMethod": true,
      "useBaseUrl": true,
      "generateBaseUrlProperty": false,
      "generateSyncMethods": false,
      "exposeJsonSerializerSettings": false,
      "clientClassAccessModifier": "public",
      "typeAccessModifier": "public",
      "generateContractsOutput": false,
      "contractsNamespace": null,
      "contractsOutputFilePath": null,
      "parameterDateTimeFormat": "s",
      "parameterDateFormat": "yyyy-MM-dd",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "useRequestAndResponseSerializationSettings": false,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "{controller}Client",
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "additionalNamespaceUsages": [],
      "additionalContractNamespaceUsages": [],
      "generateOptionalParameters": false,
      "generateJsonMethods": false,
      "enforceFlagEnums": false,
      "parameterArrayType": "System.Collections.Generic.IEnumerable",
      "parameterDictionaryType": "System.Collections.Generic.IDictionary",
      "responseArrayType": "System.Collections.Generic.ICollection",
      "responseDictionaryType": "System.Collections.Generic.IDictionary",
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "HttpServiceResponse",
      "namespace": "Connective.ESignatures",
      "requiredPropertiesMustBeDefined": true,
      "dateType": "System.DateTime",
      "jsonConverters": null,
      "anyType": "object",
      "dateTimeType": "System.DateTime",
      "timeType": "System.TimeSpan",
      "timeSpanType": "System.TimeSpan",
      "arrayType": "System.Collections.Generic.ICollection",
      "arrayInstanceType": "System.Collections.ObjectModel.Collection",
      "dictionaryType": "System.Collections.Generic.IDictionary",
      "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
      "arrayBaseType": "System.Collections.ObjectModel.Collection",
      "dictionaryBaseType": "System.Collections.Generic.Dictionary",
      "classStyle": "Poco",
      "generateDefaultValues": true,
      "generateDataAnnotations": true,
      "excludedTypeNames": [],
      "excludedParameterNames": [],
      "handleReferences": false,
      "generateImmutableArrayProperties": false,
      "generateImmutableDictionaryProperties": false,
      "jsonSerializerSettingsTransformationMethod": null,
      "inlineNamedArrays": false,
      "inlineNamedDictionaries": false,
      "inlineNamedTuples": true,
      "inlineNamedAny": false,
      "generateDtoTypes": true,
      "generateOptionalPropertiesAsNullable": false,
      "generateNullableReferenceTypes": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "Connective.ESignatures.Clients.cs",
      "newLineBehavior": "Auto"
    }
  }
}
stevenvolckaert commented 3 years ago

I could pinpoint the issue to the following:

  '/esig/webportalapi/v4/packages/{packageId}/stakeholders/{stakeholderId}/actors':
    post:
      tags:
        - Actors
      summary: Creates an actor for a stakeholder
      description: >
        Creates an actor for a stakeholder and adds it in the first process step
        that has an actor with the same type.  

        If no such step exists a new one is created.  

        **_Note_:** _all_ approvers have to come in the first step, all
        receivers in the last. With no other actor types in those groups.
      operationId: Actors_CreateForStakeholderWithId
      parameters:
      - $ref: '#/components/parameters/packageIdPathParam'
      - $ref: '#/components/parameters/stakeholderIdPathParam'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateActor'
      responses:
        '201':
          description: Actor was created
          headers:
            Location:
              schema:
                $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/SignerActor'
                  - $ref: '#/components/schemas/ApproverActor'
                  - $ref: '#/components/schemas/ReceiverActor'
        '400':
          $ref: '#/components/responses/ValidationFailed'
        '401':
          $ref: '#/components/responses/UnAuthorized'
        '404':
          description: Target package or stakeholder with id could not be found
#          content:
#            application/json:
#              schema:
#                oneOf:
#                  - $ref: '#/components/responses/NotFound.Package'
#                  - $ref: '#/components/responses/NotFound.Stakeholder'
        '409':
          description: Actor could not be added to the stakeholder
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
jeremyVignelles commented 3 years ago

So your API can reply with one of the two types? How is a strong-typed language like C# supposed to work with that? What would be the return type?

For these kind of jobs, I'd try the "wrap response" option in NSwag, it seems to allow for multiple types to be returned, but I never used it.

stevenvolckaert commented 3 years ago

Thanks for the suggestion. I've forwarded this error to the owners of this API (I didn't make it myself); it seems to be an error according to their support department.

RicoSuter commented 3 years ago

It seems that the schema references a response which is not a schema and then you get the cast exception... spec seems to be wrong.

stevenvolckaert commented 3 years ago

Thanks for the feedback @RicoSuter!