Skip to content
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
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
{
writer.WriteStartObject();

if (version == OpenApiSpecVersion.OpenApi3_1)
if (version >= OpenApiSpecVersion.OpenApi3_1)
{
WriteJsonSchemaKeywords(writer);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Reader.V32;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V32Tests;

public class OpenApiCallbackReferenceDeserializerTests
{
[Fact]
public void ShouldDeserializeCallbackReferenceAnnotations()
{
var json =
"""
{
"$ref": "#/components/callbacks/MyCallback"
}
""";

var hostDocument = new OpenApiDocument();
hostDocument.AddComponent("MyCallback", new OpenApiCallback
{
// Optionally add a PathItem or similar here if needed
});
var jsonNode = JsonNode.Parse(json);
var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode);

var result = OpenApiV32Deserializer.LoadCallback(parseNode, hostDocument);

Assert.NotNull(result);
var resultReference = Assert.IsType<OpenApiCallbackReference>(result);

Assert.Equal("MyCallback", resultReference.Reference.Id);
Assert.NotNull(resultReference.Target);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using Microsoft.OpenApi.Reader;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V32Tests
{
public class OpenApiComponentsTests
{
[Theory]
[InlineData("./FirstLevel/SecondLevel/ThridLevel/File.json#/components/schemas/ExternalRelativePathModel", "ExternalRelativePathModel", "./FirstLevel/SecondLevel/ThridLevel/File.json")]
[InlineData("File.json#/components/schemas/ExternalSimpleRelativePathModel", "ExternalSimpleRelativePathModel", "File.json")]
[InlineData("A:\\Dir\\File.json#/components/schemas/ExternalAbsWindowsPathModel", "ExternalAbsWindowsPathModel", "A:\\Dir\\File.json")]
[InlineData("/Dir/File.json#/components/schemas/ExternalAbsUnixPathModel", "ExternalAbsUnixPathModel", "/Dir/File.json")]
[InlineData("https://host.lan:1234/path/to/file/resource.json#/components/schemas/ExternalHttpsModel", "ExternalHttpsModel", "https://host.lan:1234/path/to/file/resource.json")]
[InlineData("File.json", "File.json", null)]
public void ParseExternalSchemaReferenceShouldSucceed(string reference, string referenceId, string externalResource)
{
var input = $@"{{
""schemas"": {{
""Model"": {{
""$ref"": ""{reference.Replace("\\", "\\\\")}""
}}
}}
}}
";
var openApiDocument = new OpenApiDocument();

// Act
var components = OpenApiModelFactory.Parse<OpenApiComponents>(input, OpenApiSpecVersion.OpenApi3_2, openApiDocument, out _, "json");

// Assert
var schema = components.Schemas["Model"] as OpenApiSchemaReference;
var expected = new OpenApiSchemaReference(referenceId, openApiDocument, externalResource);
Assert.Equivalent(expected, schema);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.OpenApi.Readers.Tests.V32Tests
{
public class OpenApiDocumentSerializationTests
{
private const string SampleFolderPath = "V32Tests/Samples/OpenApiDocument/";

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
public async Task Serialize_DoesNotMutateDom(OpenApiSpecVersion version)
{
// Arrange
var filePath = Path.Combine(SampleFolderPath, "docWith32properties.json");
var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings);

// Act: Serialize using System.Text.Json
var options = new JsonSerializerOptions
{
Converters =
{
new HttpMethodOperationDictionaryConverter()
},
};
var originalSerialized = JsonSerializer.Serialize(doc, options);
Assert.NotNull(originalSerialized); // sanity check

// Serialize using native OpenAPI writer
var jsonWriter = new StringWriter();
var openApiWriter = new OpenApiJsonWriter(jsonWriter);
switch (version)
{
case OpenApiSpecVersion.OpenApi3_2:
doc.SerializeAsV32(openApiWriter);
break;
case OpenApiSpecVersion.OpenApi3_1:
doc.SerializeAsV31(openApiWriter);
break;
case OpenApiSpecVersion.OpenApi3_0:
doc.SerializeAsV3(openApiWriter);
break;
default:
doc.SerializeAsV2(openApiWriter);
break;
}

// Serialize again with STJ after native writer serialization
var finalSerialized = JsonSerializer.Serialize(doc, options);
Assert.NotNull(finalSerialized); // sanity check

// Assert: Ensure no mutation occurred in the DOM after native serialization
Assert.True(JsonNode.DeepEquals(originalSerialized, finalSerialized), "OpenAPI DOM was mutated by the native serializer.");
}
}

public class HttpMethodOperationDictionaryConverter : JsonConverter<Dictionary<HttpMethod, OpenApiOperation>>
{
public override Dictionary<HttpMethod, OpenApiOperation> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}

public override void Write(Utf8JsonWriter writer, Dictionary<HttpMethod, OpenApiOperation> value, JsonSerializerOptions options)
{
writer.WriteStartObject();

foreach (var kvp in value)
{
writer.WritePropertyName(kvp.Key.Method.ToLowerInvariant());
JsonSerializer.Serialize(writer, kvp.Value, options);
}

writer.WriteEndObject();
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
openapi: '3.2.0'
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
info:
title: Sample OpenAPI 3.2 API
description: A sample API demonstrating OpenAPI 3.2 features
license:
name: Apache 2.0
identifier: Apache-2.0
version: 2.0.0
summary: Sample OpenAPI 3.2 API with the latest features
servers:
- url: https://api.example.com/v2
description: Main production server
paths:
/pets:
get:
tags:
- pets
summary: List all pets
operationId: listPets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
schema:
exclusiveMaximum: 100
exclusiveMinimum: 1
type: integer
responses:
'200':
description: A paged array of pets
content:
application/json:
schema:
$ref: https://example.com/schemas/pet.json
/sample:
get:
summary: Sample endpoint
responses:
'200':
description: Sample response
content:
application/json:
schema:
$id: https://example.com/schemas/person.schema.yaml
$schema: https://json-schema.org/draft/2020-12/schema
$comment: A schema defining a pet object with optional references to dynamic components.
$vocabulary:
https://json-schema.org/draft/2020-12/vocab/core: true
https://json-schema.org/draft/2020-12/vocab/applicator: true
https://json-schema.org/draft/2020-12/vocab/validation: true
https://json-schema.org/draft/2020-12/vocab/meta-data: false
https://json-schema.org/draft/2020-12/vocab/format-annotation: false
$dynamicAnchor: addressDef
title: Pet
required:
- name
type: object
properties:
name:
$comment: The pet's full name
type: string
address:
$comment: Reference to an address definition which can change dynamically
$dynamicRef: '#addressDef'
description: Schema for a pet object
components:
schemas:
Pet:
$id: https://example.com/schemas/pet.json
$comment: This schema represents a pet in the system.
$defs:
ExtraInfo:
type: string
required:
- id
- weight
type: object
properties:
id:
type: string
format: uuid
weight:
exclusiveMinimum: 0
type: number
description: Weight of the pet in kilograms
attributes:
patternProperties:
'^attr_[A-Za-z]+$':
type: string
type:
- 'null'
- object
description: Dynamic attributes for the pet
securitySchemes:
api_key:
type: apiKey
name: api_key
in: header
security:
- api_key: [ ]
tags:
- name: pets
webhooks:
newPetAlert:
post:
summary: Notify about a new pet being added
requestBody:
content:
application/json:
schema:
type: string
required: true
responses:
'200':
description: Webhook processed successfully
Loading
Loading