Skip to content

External ref to a schema fails if root property of referred schema is a property of JsonSchema #3341

@jimfrederic

Description

@jimfrederic

This may be an odd file structure, but I believe it is legal. The main yaml references a schema in a different file. If the top level element in that file is any property of JsonSchema, (e.g., title, maximum, parent, etc), then NSwag fails. I used Title in this example, and basically, instead of adding a schema "Title" to JsonSchema.ExtensionData, it tries to set JsonSchema.Title.

This may actually be an issue with NJsonSchema, but this is how it manifests in NSwag.

Version info

$ nswag version
NSwag NPM CLI
NSwag command line tool for .NET Core NetCore21, toolchain v13.10.7.0 (NJsonSchema v10.3.9.0 (Newtonsoft.Json v11.0.0.0))
Visit http://NSwag.org for more information.
NSwag bin directory: C:\Users\JFREDERIC\AppData\Roaming\npm\node_modules\nswag\bin\binaries\NetCore21

NSwag version: 13.10.7.0
NJsonSchema version: 10.3.9.0 (Newtonsoft.Json v11.0.0.0)

Contents of test_main.yaml

openapi: '3.0.3'
info:
  title: Testcase API
  version: '1.0'
paths:
  /endpoint:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: 'test_ext.yaml#/Title'
      responses:
        '200':
          description: Success

Contents of test_ext.yaml

Title:
  type: object

Contents of nswag.json

{
  "runtime": "NetCore21",
  "defaultVariables": null,
  "documentGenerator": {
    "fromDocument": {
      "json": "./test_main.yaml",
      "output": null
    }
  }
}

What happens when I nswag run

NSwag NPM CLI
NSwag command line tool for .NET Core NetCore21, toolchain v13.10.7.0 (NJsonSchema v10.3.9.0 (Newtonsoft.Json v11.0.0.0))
Visit http://NSwag.org for more information.
NSwag bin directory: C:\Users\JFREDERIC\AppData\Roaming\npm\node_modules\nswag\bin\binaries\NetCore21

Executing file 'nswag.json' with variables ''...
System.InvalidOperationException: Could not resolve the JSON path 'test_ext.yaml#/Title' within the file path '.\test_ext.yaml'. ---> Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: {. Path 'Title', line 1, position 11.
   at Newtonsoft.Json.JsonTextReader.ReadStringValue(ReadType readType)
   at Newtonsoft.Json.JsonTextReader.ReadAsString()
   at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Populate(JsonReader reader, Object target)
   at NJsonSchema.ExtensionDataDeserializationConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at NJsonSchema.Infrastructure.JsonSchemaSerialization.FromJson[T](String json, IContractResolver contractResolver)
   at NJsonSchema.Infrastructure.JsonSchemaSerialization.FromJsonAsync[T](String json, SchemaType schemaType, String documentPath, Func`2 referenceResolverFactory, IContractResolver contractResolver, CancellationToken cancellationToken)
   at NJsonSchema.JsonSchema.FromJsonAsync(String data, String documentPath, Func`2 referenceResolverFactory, CancellationToken cancellationToken)
   at NJsonSchema.Yaml.JsonSchemaYaml.FromYamlAsync(String data, String documentPath, Func`2 referenceResolverFactory, CancellationToken cancellationToken)
   at NJsonSchema.Yaml.JsonSchemaYaml.FromFileAsync(String filePath, Func`2 referenceResolverFactory, CancellationToken cancellationToken)
   at NJsonSchema.Yaml.JsonAndYamlReferenceResolver.ResolveFileReferenceAsync(String filePath, CancellationToken cancellationToken)
   at NJsonSchema.JsonReferenceResolver.ResolveFileReferenceWithAlreadyResolvedCheckAsync(String filePath, Type targetType, IContractResolver contractResolver, String jsonPath, Boolean append, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at NJsonSchema.JsonReferenceResolver.ResolveFileReferenceWithAlreadyResolvedCheckAsync(String filePath, Type targetType, IContractResolver contractResolver, String jsonPath, 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.FromJsonAsync[T](String json, 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 C:\projects\nswag\src\NSwag.Core\OpenApiDocument.cs:line 197
   at NSwag.OpenApiYamlDocument.FromYamlAsync(String data, String documentPath, SchemaType expectedSchemaType, Func`2 referenceResolverFactory, CancellationToken cancellationToken) in C:\projects\nswag\src\NSwag.Core.Yaml\OpenApiYamlDocument.cs:line 76
   at NSwag.OpenApiYamlDocument.FromFileAsync(String filePath, CancellationToken cancellationToken) in C:\projects\nswag\src\NSwag.Core.Yaml\OpenApiYamlDocument.cs:line 98
   at NSwag.Commands.OutputCommandBase.ReadSwaggerDocumentAsync(String input) in C:\projects\nswag\src\NSwag.Commands\Commands\OutputCommandBase.cs:line 49
   at NSwag.Commands.Generation.FromDocumentCommand.RunAsync() in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\FromDocumentCommand.cs:line 62
   at NSwag.Commands.Generation.FromDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Generation\FromDocumentCommand.cs:line 53
   at NSwag.Commands.NSwagDocumentBase.GenerateSwaggerDocumentAsync() in C:\projects\nswag\src\NSwag.Commands\NSwagDocumentBase.cs:line 280
   at NSwag.Commands.NSwagDocument.ExecuteAsync() in C:\projects\nswag\src\NSwag.Commands\NSwagDocument.cs:line 81
   at NSwag.Commands.Document.ExecuteDocumentCommand.ExecuteDocumentAsync(IConsoleHost host, String filePath) in C:\projects\nswag\src\NSwag.Commands\Commands\Document\ExecuteDocumentCommand.cs:line 85
   at NSwag.Commands.Document.ExecuteDocumentCommand.RunAsync(CommandLineProcessor processor, IConsoleHost host) in C:\projects\nswag\src\NSwag.Commands\Commands\Document\ExecuteDocumentCommand.cs:line 39
   at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input)
   at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input)
   at NConsole.CommandLineProcessor.Process(String[] args, Object input)
   at NSwag.Commands.NSwagCommandProcessor.Process(String[] args) in C:\projects\nswag\src\NSwag.Commands\NSwagCommandProcessor.cs:line 56child_process.js:655
    throw err;
    ^

Error: Command failed: dotnet "C:\Users\JFREDERIC\AppData\Roaming\npm\node_modules\nswag\bin/binaries/NetCore21/dotnet-nswag.dll" run
    at checkExecSyncError (child_process.js:616:11)
    at Object.execSync (child_process.js:652:15)
    at C:\Users\JFREDERIC\AppData\Roaming\npm\node_modules\nswag\bin\nswag.js:67:11
    at ChildProcess.exithandler (child_process.js:299:7)
    at ChildProcess.emit (events.js:315:20)
    at maybeClose (internal/child_process.js:1048:16)
    at Socket.<anonymous> (internal/child_process.js:439:11)
    at Socket.emit (events.js:315:20)
    at Pipe.<anonymous> (net.js:673:12) {
  status: 4294967295,
  signal: null,
  output: [ null, null, null ],
  pid: 45272,
  stdout: null,
  stderr: null
}

This problem is easy enough to work around by modifying the OpenAPI document, when you control those files. One fix to the example OpenApi doc files used here is simply to nest the references one level deeper. E.g.:

Contents of test_main.yaml, "fixed"

openapi: '3.0.3'
info:
  title: Testcase API
  version: '1.0'
paths:
  /endpoint:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: 'test_ext.yaml#/nestDeeperAsWorkaround/Title'
      responses:
        '200':
          description: Success

Contents of test_ext.yaml, "fixed"

nestDeeperAsWorkaround:
  Title:
    type: object

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions