NSwag + NJsonSchema fails to process Azure's openapi.json "...because no document path is available." (with patch)
Describe the bug
Azure's openapi.json (permalink, and attached): - causes NSwagStudio to crash, and NJsonSchema to complain; in-part, due to external references.
Get the current NSwag
masterbranch HEAD (2a5ca6e199035833984aa270f059a93e3f513bfe) and build it locally (usingbuild.batworks for me).Get the current NJsonSchema
masterbranch HEAD (6d3580c03079107bacf8869e5e64df7feb138cd9) and build it locally (usingbuild.batworks for me).Clone the
azure-rest-api-specsrepo locally and checkout commit09fee50b88b6951671d9b82597759cd3f4b3776b- then locate$\specification\web\resource-manager\Microsoft.Web\AppService\stable\2025-05-01\openapi.jsonand load that into NSwagStudio and click the Generate button.- Ensure that you also have
"$\specification\common-types\resource-management\v5\types.jsonat that exact location too.
- Ensure that you also have
You'll get this error message popup:
System.NotSupportedException: Could not resolve the JSON path '../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter' because no document path is available. Runtime: Net80 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, 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 204 at NSwag.Commands.Generation.FromDocumentCommand.RunAsync() in /_/src/NSwag.Commands/Commands/Generation/FromDocumentCommand.cs:line 61 at NSwag.Commands.Generation.FromDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Generation/FromDocumentCommand.cs:line 52 at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in /_/src/NSwag.Commands/NSwagDocumentBase.cs:line 266 at NSwag.Commands.NSwagDocument.ExecuteAsync() in /_/src/NSwag.Commands/NSwagDocument.cs:line 63 at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 72 at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in /_/src/NSwag.Commands/Commands/Document/ExecuteDocumentCommand.cs:line 29 at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input) in /_/src/NSwag.Commands/NConsole/CommandLineProcessor.cs:line 184 at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input) in /_/src/NSwag.Commands/NConsole/CommandLineProcessor.cs:line 120 at NSwag.Commands.NSwagCommandProcessor.ProcessAsync(String[] args) in /_/src/NSwag.Commands/NSwagCommandProcessor.cs:line 61I was able to fix this by adding
documentPath(as either a new JSON property or as method parameter) to the following types/members, as they pass values from NSwagStudio, to the temp files on-disk, and into NJsonSchema:NSwagDocumentInputOutputCommandBase.GetInputSwaggerDocumentOutputCommandBase.ReadSwaggerDocumentAsyncFromDocumentCommand.DocumentPath,ReadSwaggerDocumentAsyncOpenApiDocumentGeneratorSettingsOpenApiDocument.FromJsonAsyncOpenApiYamlDocument.FromYamlAsync(for consistency's sake)WebApiOpenApiDocumentGenerator.CreateDocumentAsyncAspNetCoreOpenApiDocumentGenerator.CreateDocumentAsync(for consistency's sake)
BTW, I noticed that NSwag/NJsonSchema is (incorrectly?) sensitive to whether or not the
documentPathends with a slash;- i.e.: it works when the path is
C:\git\clones\azure-rest-api-specs\specification\web\resource-manager\Microsoft.Web\AppService\stable\2025-05-01\ - ...but not when it's missing the terminal slash: e.g.
C:\git\clones\azure-rest-api-specs\specification\web\resource-manager\Microsoft.Web\AppService\stable\2025-05-01 - ...this is because NJsonSchema thinks the
documentPathis mis-parented. - I recommend changing
documentPathfrom astring?to aDirectoryPath?(which also makes it almost self-documenting).
- i.e.: it works when the path is
After all of that, I then moved on to my next error:
System.InvalidOperationException HResult=0x80131509 Message=Could not find the JSON path of a referenced schema: NJsonSchema.JsonSchema,NJsonSchema.JsonSchema. Manually referenced schemas must be added to the 'Definitions' of a parent schema. Source=NJsonSchema StackTrace: at NJsonSchema.JsonPathUtilities.GetJsonPaths(Object rootObject, IEnumerable`1 searchedObjects, IContractResolver contractResolver) in C:\git\forks\NJsonSchema\src\NJsonSchema\JsonPathUtilities.cs:line 65- I wasn't able to properly fix this issue, because I wasn't able to fully understand what's going on (e.g. I'll admit I'm still not sure exactly what a "JsonPath" is in this context); the error-message mentions "
NJsonSchema.JsonSchema,NJsonSchema.JsonSchema" but I feel that's a bug too...- (While debugging,
JsonPathUtilities.GetJsonPaths'smappingscontained about 2700+ entries; only 2 of which hadp.Value == null). - My workaround was to simply comment-out the
throw new InvalidOperationException- which then allowedNSwagStudioto run to completion.
- (While debugging,
- I wasn't able to properly fix this issue, because I wasn't able to fully understand what's going on (e.g. I'll admit I'm still not sure exactly what a "JsonPath" is in this context); the error-message mentions "
- My fixes/workarounds are available in a branch in my clone repo if they're of interest to you or anyone else; Owing to time-constraints I had to introduce other breaking changes and faff-around in the build system and project files in order to build and run the rest of the project locally.
- My fixes are in an earlier commit in the same branch, here: https://github.com/daiplusplus/NSwag/commit/deef571b40c6fdc57585a7c5ac38d95a33e57a78 - I haven't submitted this as a PR (yet) because I don't know if this is the approach Rico would have preferred, and also because it's incomplete as far as the UX and user-story is concerned (e.g. I haven't added a new corresponding textbox field to NSwagStudio's UI yet).
Version of NSwag toolchain, computer and .NET runtime used
The current master HEAD: 2a5ca6e199035833984aa270f059a93e3f513bfe. Issue affects (at least) net462 and net8.0; I did not check with net9.0, net10.0 or netstandard2.0 builds.
To Reproduce
See above.
Expected behavior
No errors and NSwagStudio does not crash.
Additional context
Nothing I can think of right now.
Attachments
needs-document-path.json - This is a verbatim copy of the
..._config.jsonJSON file that NSwagStudio places in my%TEMP%when it runsnswag.exe run tempJsonPath. It also contains the OpenAPI spec as an embedded JSON-in-a-string-in-JSON.has-document-path.json - This is what NSwagStudio produces after I made my changes: it's the same as
needs-document-path.jsonabove, except withdocumentPathset to the original filessystem location ofopenapi.jsonon my computer so that the external JSON Schema references can be resolved.Azure-azure-rest-api-specs_Microsoft.Web-AppService-2025-05-01_openapi.json - This is a verbatim copy of Azure's AppServices OpenAPI spec.
common-types.zip - This is a verbatim copy of the
"$\specification\common-types\"directory which contains the referenced JSON types. It's included for posterity; I recommend simply getting agit checkout 09fee50b88ofazure-rest-api-specsfrom Azure's repo rather than using these as you'll have to ensure the relative-paths match.
Source: RicoSuter/NSwag