Skip to content

Commit c0cb10c

Browse files
committed
Handle binary formats for pre-3.1
1 parent 91c5d41 commit c0cb10c

7 files changed

Lines changed: 107 additions & 25 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: 40 additions & 3 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,15 @@ private void SerializeAsV2(
10081021

10091022
private void SerializeTypePropertyForVersion2(IOpenApiWriter writer)
10101023
{
1011-
if (Type is not { } type || type == JsonSchemaType.Null)
1024+
// TODO: Handle "file" type for 2.0.
1025+
// Spec https://spec.openapis.org/oas/v2.0.html#data-types
1026+
var typeToUse = Type;
1027+
if (version < OpenApiSpecVersion.OpenApi3_1)
1028+
{
1029+
typeToUse ??= GetKnownTypeAndFormatPreOpenApi31()?.Type;
1030+
}
1031+
1032+
if (typeToUse is not { } type || type == JsonSchemaType.Null)
10121033
{
10131034
return;
10141035
}
@@ -1157,6 +1178,22 @@ private void SerializeNullable(IOpenApiWriter writer, OpenApiSpecVersion version
11571178
}
11581179
}
11591180

1181+
private (JsonSchemaType Type, string Format)? GetKnownTypeAndFormatPreOpenApi31()
1182+
{
1183+
// https://spec.openapis.org/oas/v3.2.0.html#migrating-binary-descriptions-from-oas-3-0
1184+
if (Type is not null && Type.Value.HasFlag(JsonSchemaType.String) && ContentEncoding == "base64")
1185+
{
1186+
return (Type.Value, "byte");
1187+
}
1188+
1189+
if (Type is null && !string.IsNullOrEmpty(ContentMediaType))
1190+
{
1191+
return (JsonSchemaType.String, "binary");
1192+
}
1193+
1194+
return null;
1195+
}
1196+
11601197
#if NET5_0_OR_GREATER
11611198
private static readonly Array jsonSchemaTypeValues = System.Enum.GetValues<JsonSchemaType>();
11621199
#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
@@ -438,6 +438,26 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
438438
}
439439
}
440440

441+
// The object model represents the latest version of the spec.
442+
// https://spec.openapis.org/oas/v3.2.0.html#migrating-binary-descriptions-from-oas-3-0
443+
// When we deserialize from V3, we detect the "old way" of specifying binary descriptions, and
444+
// transform it in the object model to the latest thing.
445+
if (schema.Type.HasValue && schema.Type.Value.HasFlag(JsonSchemaType.String) &&
446+
schema.Format == "byte" &&
447+
schema.ContentEncoding is null)
448+
{
449+
schema.ContentEncoding = "base64";
450+
schema.Format = null;
451+
}
452+
453+
if (schema.Type.HasValue && schema.Type.Value == JsonSchemaType.String &&
454+
schema.Format == "binary")
455+
{
456+
schema.ContentMediaType ??= "application/octet-stream";
457+
schema.Format = null;
458+
schema.Type = null;
459+
}
460+
441461
return schema;
442462
}
443463

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

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

0 commit comments

Comments
 (0)