Skip to content

fix: sub-schema references are invalid #2401

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

Merged
merged 1 commit into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
/// <summary>
/// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference.
/// </summary>
public OpenApiDocument? HostDocument { get => hostDocument; init => hostDocument = value; }

Check warning on line 58 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Make this an auto-implemented property and remove its backing field. (https://rules.sonarsource.com/csharp/RSPEC-2292)

Check warning on line 58 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Make this an auto-implemented property and remove its backing field. (https://rules.sonarsource.com/csharp/RSPEC-2292)

Check warning on line 58 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Make this an auto-implemented property and remove its backing field. (https://rules.sonarsource.com/csharp/RSPEC-2292)

Check warning on line 58 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Make this an auto-implemented property and remove its backing field. (https://rules.sonarsource.com/csharp/RSPEC-2292)

private string? _referenceV3;
/// <summary>
Expand Down Expand Up @@ -84,8 +84,14 @@
{
return Id;
}
if (!string.IsNullOrEmpty(Id) && Id is not null && Id.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
!string.IsNullOrEmpty(Id) && Id is not null && Id.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(Id) && Id is not null &&

Check warning on line 87 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Change this condition so that it does not always evaluate to 'True'. (https://rules.sonarsource.com/csharp/RSPEC-2589)

Check warning on line 87 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Change this condition so that it does not always evaluate to 'True'. (https://rules.sonarsource.com/csharp/RSPEC-2589)

Check warning on line 87 in src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs

View workflow job for this annotation

GitHub Actions / Build

Change this condition so that it does not always evaluate to 'True'. (https://rules.sonarsource.com/csharp/RSPEC-2589)
(Id.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
Id.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER
Id.Contains("#/components", StringComparison.OrdinalIgnoreCase)))
#else
Id.Contains("#/components")))
#endif
{
return Id;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"type": "object",
"properties": {
"name": {
"type": [
"string",
"null"
],
"format": null,
"x-schema-id": null
},
"parent": {
"type": [
"object",
"null"
],
"properties": {
"name": {
"type": [
"string",
"null"
],
"format": null,
"x-schema-id": null
},
"parent": {
"$ref": "#/properties/parent",
"x-schema-id": "Category"
},
"tags": {
"type": [
"array",
"null"
],
"items": {
"type": "object",
"properties": {
"name": {
"type": [
"string",
"null"
],
"format": null,
"x-schema-id": null
}
},
"required": [
"name"
],
"x-schema-id": "Tag"
}
}
},
"required": [
"name"
],
"x-schema-id": "Category"
},
"tags": {
"$ref": "#/properties/parent/properties/tags"
}
},
"required": [
"name"
],
"x-schema-id": "Category"
}
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public async Task ShouldResolveRelativeSubReferenceUsingParsingContext()
parsingContext.StartObject("schemas");
parsingContext.StartObject("Foo");
var document = new OpenApiDocument();

// Act
var fooComponentSchema = parsingContext.ParseFragment<OpenApiSchema>(schemaJsonNode, OpenApiSpecVersion.OpenApi3_1, document);
document.AddComponent("Foo", fooComponentSchema);
Expand Down Expand Up @@ -412,5 +412,28 @@ public async Task ShouldResolveRecursiveRelativeSubReference()
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, fooSchemaTagsProperty.Type);
Assert.Equal(JsonSchemaType.Object, fooSchemaTagsProperty.Items.Type);
}
[Fact]
public async Task ShouldResolveReferencesInSchemasFromSystemTextJson()
{
var filePath = Path.Combine(SampleFolderPath, "STJSchema.json");
using var fs = File.OpenRead(filePath);
var jsonNode = await JsonNode.ParseAsync(fs);

var parsingContext = new ParsingContext(new OpenApiDiagnostic());
var document = new OpenApiDocument();
var schema = parsingContext.ParseFragment<OpenApiSchema>(jsonNode, OpenApiSpecVersion.OpenApi3_1, document);
Assert.NotNull(schema);

document.AddComponent("Foo", schema);
var tagsProperty = Assert.IsType<OpenApiSchemaReference>(schema.Properties["tags"]);
// this is the reference that is generated by STJ schema generator which does not have OAI in context.
Assert.Equal("#/properties/parent/properties/tags", tagsProperty.Reference.ReferenceV3);
// this is the reference that needs to be used in the document for components resolution.
var absoluteReferenceId = $"#/components/schemas/Foo{tagsProperty.Reference.ReferenceV3.Replace("#", string.Empty)}";
schema.Properties["tags"] = new OpenApiSchemaReference(absoluteReferenceId, document);
var updatedTagsProperty = Assert.IsType<OpenApiSchemaReference>(schema.Properties["tags"]);
Assert.Equal(absoluteReferenceId, updatedTagsProperty.Reference.ReferenceV3);
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, updatedTagsProperty.Type);
}
}
}
Loading