Skip to content

Commit 6cf36f6

Browse files
committed
Handle binary formats for pre-3.1
1 parent 655c2c8 commit 6cf36f6

7 files changed

Lines changed: 104 additions & 24 deletions

File tree

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"sdk": {
3-
"version": "10.0.302"
3+
"version": "10.0.301"
44
}
55
}

src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -130,26 +130,7 @@ public IEnumerable<IOpenApiParameter> ConvertToFormDataParameters(IOpenApiWriter
130130
{
131131
foreach (var property in properties)
132132
{
133-
var paramSchema = property.Value.CreateShallowCopy();
134-
if ((paramSchema.Type & JsonSchemaType.String) == JsonSchemaType.String
135-
&& ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)
136-
|| "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)))
137-
{
138-
var updatedSchema = paramSchema switch
139-
{
140-
OpenApiSchema s => s, // we already have a copy
141-
// we have a copy of a reference but don't want to mutate the source schema
142-
// TODO might need recursive resolution of references here
143-
OpenApiSchemaReference r when r.Target is not null => (OpenApiSchema)r.Target.CreateShallowCopy(),
144-
OpenApiSchemaReference => throw new InvalidOperationException("Unresolved reference target"),
145-
_ => throw new InvalidOperationException("Unexpected schema type")
146-
};
147-
148-
updatedSchema.Type = "file".ToJsonSchemaType();
149-
updatedSchema.Format = null;
150-
paramSchema = updatedSchema;
151-
152-
}
133+
var paramSchema = property.Value;
153134
yield return new OpenApiFormDataParameter()
154135
{
155136
Description = paramSchema.Description,

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,13 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
589589
writer.WriteProperty(OpenApiConstants.Description, Description);
590590

591591
// format
592-
writer.WriteProperty(OpenApiConstants.Format, Format);
592+
var format = Format;
593+
if (version < OpenApiSpecVersion.OpenApi3_1)
594+
{
595+
format ??= GetKnownTypeAndFormatPreOpenApi31()?.Format;
596+
}
597+
598+
writer.WriteProperty(OpenApiConstants.Format, format);
593599

594600
// default
595601
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
@@ -844,7 +850,14 @@ private void SerializeAsV2(
844850
writer.WriteProperty(OpenApiConstants.Description, Description);
845851

846852
// format
847-
WriteFormatProperty(writer);
853+
if (Format is null && GetKnownTypeAndFormatPreOpenApi31() is { } typeAndFormat)
854+
{
855+
writer.WriteProperty(OpenApiConstants.Format, typeAndFormat.Format);
856+
}
857+
else
858+
{
859+
WriteFormatProperty(writer);
860+
}
848861

849862
// title
850863
writer.WriteProperty(OpenApiConstants.Title, Title);
@@ -1008,7 +1021,13 @@ private void SerializeAsV2(
10081021

10091022
private void SerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version)
10101023
{
1024+
// TODO: Handle "file" type for 2.0.
1025+
// Spec https://spec.openapis.org/oas/v2.0.html#data-types
10111026
var typeToUse = Type;
1027+
if (version < OpenApiSpecVersion.OpenApi3_1)
1028+
{
1029+
typeToUse ??= GetKnownTypeAndFormatPreOpenApi31()?.Type;
1030+
}
10121031

10131032
if (typeToUse is null)
10141033
{
@@ -1105,6 +1124,22 @@ private void SerializeNullable(IOpenApiWriter writer, OpenApiSpecVersion version
11051124
}
11061125
}
11071126

1127+
private (JsonSchemaType Type, string Format)? GetKnownTypeAndFormatPreOpenApi31()
1128+
{
1129+
// https://spec.openapis.org/oas/v3.2.0.html#migrating-binary-descriptions-from-oas-3-0
1130+
if (Type is not null && Type.Value.HasFlag(JsonSchemaType.String) && ContentEncoding == "base64")
1131+
{
1132+
return (Type.Value, "byte");
1133+
}
1134+
1135+
if (Type is null && !string.IsNullOrEmpty(ContentMediaType))
1136+
{
1137+
return (JsonSchemaType.String, "binary");
1138+
}
1139+
1140+
return null;
1141+
}
1142+
11081143
#if NET5_0_OR_GREATER
11091144
private static readonly Array jsonSchemaTypeValues = System.Enum.GetValues<JsonSchemaType>();
11101145
#else

src/Microsoft.OpenApi/Reader/V2/OpenApiParameterDeserializer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System.Text.Json.Nodes;
@@ -74,6 +74,16 @@ internal static partial class OpenApiV2Deserializer
7474
{
7575
var schema = GetOrCreateSchema(o);
7676
schema.Type = type.ToJsonSchemaType();
77+
// TODO: This should be represented using the 3.2 approach.
78+
// The object model must reflect the "latest" version of the spec.
79+
// Note that for parameters in 2.0, the "file" type is specified directly
80+
// on the parameter object. But for responses, the "file" type is an
81+
// extension of the Json Schema object, as in, it's not allowed by
82+
// Json Schema Draft 4, but is allowed as an OpenAPI 2.0 extension.
83+
// All that should be handled correctly.
84+
// The deserialization logic should try to map everything to the "3.2" way
85+
// of doing things.
86+
// And serialization should assume that the object model is in the "3.2" way of doing things.
7787
if ("file".Equals(type, StringComparison.OrdinalIgnoreCase))
7888
{
7989
schema.Format = "binary";

src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,18 @@ internal static partial class OpenApiV2Deserializer
277277
OpenApiConstants.PatternPropertiesExtension,
278278
(o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c)
279279
},
280+
{
281+
OpenApiConstants.ContentEncodingExtension,
282+
(o, n, _, _) => o.ContentEncoding = n.GetScalarValue()
283+
},
284+
{
285+
OpenApiConstants.ContentMediaTypeExtension,
286+
(o, n, _, _) => o.ContentMediaType = n.GetScalarValue()
287+
},
288+
{
289+
OpenApiConstants.ContentSchemaExtension,
290+
(o, n, doc, c) => o.ContentSchema = LoadSchema(n, doc, c)
291+
},
280292
};
281293

282294
private static readonly PatternFieldMap<OpenApiSchema> _openApiSchemaPatternFields = new PatternFieldMap<OpenApiSchema>
@@ -308,6 +320,26 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
308320
}
309321
}
310322

323+
// The object model represents the latest version of the spec.
324+
// https://spec.openapis.org/oas/v3.2.0.html#migrating-binary-descriptions-from-oas-3-0
325+
// When we deserialize from V2, we detect the "old way" of specifying binary descriptions, and
326+
// transform it in the object model to the latest thing.
327+
if (schema.Type.HasValue && schema.Type.Value.HasFlag(JsonSchemaType.String) &&
328+
schema.Format == "byte" &&
329+
schema.ContentEncoding is null)
330+
{
331+
schema.ContentEncoding = "base64";
332+
schema.Format = null;
333+
}
334+
335+
if (schema.Type.HasValue && schema.Type.Value == JsonSchemaType.String &&
336+
schema.Format == "binary")
337+
{
338+
schema.ContentMediaType ??= "application/octet-stream";
339+
schema.Format = null;
340+
schema.Type = null;
341+
}
342+
311343
return schema;
312344
}
313345
}

src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,26 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
420420
schema.Type = JsonSchemaType.Null;
421421
}
422422

423+
// The object model represents the latest version of the spec.
424+
// https://spec.openapis.org/oas/v3.2.0.html#migrating-binary-descriptions-from-oas-3-0
425+
// When we deserialize from V3, we detect the "old way" of specifying binary descriptions, and
426+
// transform it in the object model to the latest thing.
427+
if (schema.Type.HasValue && schema.Type.Value.HasFlag(JsonSchemaType.String) &&
428+
schema.Format == "byte" &&
429+
schema.ContentEncoding is null)
430+
{
431+
schema.ContentEncoding = "base64";
432+
schema.Format = null;
433+
}
434+
435+
if (schema.Type.HasValue && schema.Type.Value == JsonSchemaType.String &&
436+
schema.Format == "binary")
437+
{
438+
schema.ContentMediaType ??= "application/octet-stream";
439+
schema.Format = null;
440+
schema.Type = null;
441+
}
442+
423443
return schema;
424444
}
425445
}

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,8 @@ public async Task SerializeMissingPropertiesEmitsOaiExtensionsInV3()
15531553
{
15541554
var expected = JsonNode.Parse("""
15551555
{
1556+
"type": "string",
1557+
"format": "binary",
15561558
"x-jsonschema-$anchor": "root",
15571559
"x-jsonschema-contentEncoding": "base64",
15581560
"x-jsonschema-contentMediaType": "application/jwt",

0 commit comments

Comments
 (0)