Skip to content

Commit 85697f1

Browse files
Copilotbaywet
andcommitted
Add itemEncoding and prefixEncoding fields with serialization/deserialization support
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 6952bfc commit 85697f1

File tree

6 files changed

+212
-0
lines changed

6 files changed

+212
-0
lines changed

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,16 @@ public static class OpenApiConstants
320320
/// </summary>
321321
public const string Encoding = "encoding";
322322

323+
/// <summary>
324+
/// Field: ItemEncoding
325+
/// </summary>
326+
public const string ItemEncoding = "itemEncoding";
327+
328+
/// <summary>
329+
/// Field: PrefixEncoding
330+
/// </summary>
331+
public const string PrefixEncoding = "prefixEncoding";
332+
323333
/// <summary>
324334
/// Field: RequestBodies
325335
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiMediaType.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ public class OpenApiMediaType : IOpenApiSerializable, IOpenApiExtensible
3838
/// </summary>
3939
public IDictionary<string, OpenApiEncoding>? Encoding { get; set; }
4040

41+
/// <summary>
42+
/// An encoding object for items in an array schema.
43+
/// Only applies when the schema is of type array.
44+
/// </summary>
45+
public OpenApiEncoding? ItemEncoding { get; set; }
46+
47+
/// <summary>
48+
/// An array of encoding objects for prefixItems in an array schema.
49+
/// Each element corresponds to a prefixItem in the schema.
50+
/// </summary>
51+
public IList<OpenApiEncoding>? PrefixEncoding { get; set; }
52+
4153
/// <summary>
4254
/// Serialize <see cref="OpenApiExternalDocs"/> to Open Api v3.0.
4355
/// </summary>
@@ -57,6 +69,8 @@ public OpenApiMediaType(OpenApiMediaType? mediaType)
5769
Example = mediaType?.Example != null ? JsonNodeCloneHelper.Clone(mediaType.Example) : null;
5870
Examples = mediaType?.Examples != null ? new Dictionary<string, IOpenApiExample>(mediaType.Examples) : null;
5971
Encoding = mediaType?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(mediaType.Encoding) : null;
72+
ItemEncoding = mediaType?.ItemEncoding != null ? new OpenApiEncoding(mediaType.ItemEncoding) : null;
73+
PrefixEncoding = mediaType?.PrefixEncoding != null ? new List<OpenApiEncoding>(mediaType.PrefixEncoding.Select(e => new OpenApiEncoding(e))) : null;
6074
Extensions = mediaType?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(mediaType.Extensions) : null;
6175
}
6276

@@ -108,6 +122,32 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
108122
// encoding
109123
writer.WriteOptionalMap(OpenApiConstants.Encoding, Encoding, callback);
110124

125+
// itemEncoding - serialize as native field in v3.2+, as extension in earlier versions
126+
if (ItemEncoding != null)
127+
{
128+
if (version >= OpenApiSpecVersion.OpenApi3_2)
129+
{
130+
writer.WriteOptionalObject(OpenApiConstants.ItemEncoding, ItemEncoding, callback);
131+
}
132+
else
133+
{
134+
writer.WriteOptionalObject(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.ItemEncoding, ItemEncoding, callback);
135+
}
136+
}
137+
138+
// prefixEncoding - serialize as native field in v3.2+, as extension in earlier versions
139+
if (PrefixEncoding != null)
140+
{
141+
if (version >= OpenApiSpecVersion.OpenApi3_2)
142+
{
143+
writer.WriteOptionalCollection(OpenApiConstants.PrefixEncoding, PrefixEncoding, callback);
144+
}
145+
else
146+
{
147+
writer.WriteOptionalCollection(OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.PrefixEncoding, PrefixEncoding, callback);
148+
}
149+
}
150+
111151
// extensions
112152
writer.WriteExtensions(Extensions, version);
113153

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ internal static partial class OpenApiV3Deserializer
3030
OpenApiConstants.Encoding,
3131
(o, n, t) => o.Encoding = n.CreateMap(LoadEncoding, t)
3232
},
33+
{
34+
OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.ItemEncoding,
35+
(o, n, t) => o.ItemEncoding = LoadEncoding(n, t)
36+
},
37+
{
38+
OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.PrefixEncoding,
39+
(o, n, t) => o.PrefixEncoding = n.CreateList(LoadEncoding, t)
40+
},
3341
};
3442

3543
private static readonly PatternFieldMap<OpenApiMediaType> _mediaTypePatternFields =

src/Microsoft.OpenApi/Reader/V31/OpenApiMediaTypeDeserializer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ internal static partial class OpenApiV31Deserializer
3535
o.Encoding = n.CreateMap(LoadEncoding, t);
3636
}
3737
},
38+
{
39+
OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.ItemEncoding, (o, n, t) =>
40+
{
41+
o.ItemEncoding = LoadEncoding(n, t);
42+
}
43+
},
44+
{
45+
OpenApiConstants.ExtensionFieldNamePrefix + "oai-" + OpenApiConstants.PrefixEncoding, (o, n, t) =>
46+
{
47+
o.PrefixEncoding = n.CreateList(LoadEncoding, t);
48+
}
49+
},
3850
};
3951

4052
private static readonly PatternFieldMap<OpenApiMediaType> _mediaTypePatternFields =

src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ internal static partial class OpenApiV32Deserializer
3535
o.Encoding = n.CreateMap(LoadEncoding, t);
3636
}
3737
},
38+
{
39+
OpenApiConstants.ItemEncoding, (o, n, t) =>
40+
{
41+
o.ItemEncoding = LoadEncoding(n, t);
42+
}
43+
},
44+
{
45+
OpenApiConstants.PrefixEncoding, (o, n, t) =>
46+
{
47+
o.PrefixEncoding = n.CreateList(LoadEncoding, t);
48+
}
49+
},
3850
};
3951

4052
private static readonly PatternFieldMap<OpenApiMediaType> _mediaTypePatternFields =

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ public class OpenApiMediaTypeTests
7373
}
7474
};
7575

76+
public static OpenApiMediaType MediaTypeWithItemEncoding = new()
77+
{
78+
ItemEncoding = OpenApiEncodingTests.AdvanceEncoding
79+
};
80+
81+
public static OpenApiMediaType MediaTypeWithPrefixEncoding = new()
82+
{
83+
PrefixEncoding = new List<OpenApiEncoding>
84+
{
85+
OpenApiEncodingTests.AdvanceEncoding,
86+
new OpenApiEncoding
87+
{
88+
ContentType = "application/json",
89+
Style = ParameterStyle.Simple
90+
}
91+
}
92+
};
93+
7694
public static OpenApiMediaType MediaTypeWithObjectExamples = new()
7795
{
7896
Examples = new Dictionary<string, IOpenApiExample>
@@ -443,5 +461,117 @@ public void MediaTypeCopyConstructorWorks()
443461
Assert.Empty(clone.Extensions);
444462
Assert.Null(MediaTypeWithObjectExamples.Example);
445463
}
464+
465+
[Fact]
466+
public async Task SerializeMediaTypeWithItemEncodingAsV32JsonWorks()
467+
{
468+
// Arrange
469+
var expected =
470+
"""
471+
{
472+
"itemEncoding": {
473+
"contentType": "image/png, image/jpeg",
474+
"style": "simple",
475+
"explode": true,
476+
"allowReserved": true
477+
}
478+
}
479+
""";
480+
481+
// Act
482+
var actual = await MediaTypeWithItemEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
483+
484+
// Assert
485+
actual = actual.MakeLineBreaksEnvironmentNeutral();
486+
expected = expected.MakeLineBreaksEnvironmentNeutral();
487+
Assert.Equal(expected, actual);
488+
}
489+
490+
[Fact]
491+
public async Task SerializeMediaTypeWithItemEncodingAsV31JsonWorks()
492+
{
493+
// Arrange
494+
var expected =
495+
"""
496+
{
497+
"x-oai-itemEncoding": {
498+
"contentType": "image/png, image/jpeg",
499+
"style": "simple",
500+
"explode": true,
501+
"allowReserved": true
502+
}
503+
}
504+
""";
505+
506+
// Act
507+
var actual = await MediaTypeWithItemEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
508+
509+
// Assert
510+
actual = actual.MakeLineBreaksEnvironmentNeutral();
511+
expected = expected.MakeLineBreaksEnvironmentNeutral();
512+
Assert.Equal(expected, actual);
513+
}
514+
515+
[Fact]
516+
public async Task SerializeMediaTypeWithPrefixEncodingAsV32JsonWorks()
517+
{
518+
// Arrange
519+
var expected =
520+
"""
521+
{
522+
"prefixEncoding": [
523+
{
524+
"contentType": "image/png, image/jpeg",
525+
"style": "simple",
526+
"explode": true,
527+
"allowReserved": true
528+
},
529+
{
530+
"contentType": "application/json",
531+
"style": "simple"
532+
}
533+
]
534+
}
535+
""";
536+
537+
// Act
538+
var actual = await MediaTypeWithPrefixEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
539+
540+
// Assert
541+
actual = actual.MakeLineBreaksEnvironmentNeutral();
542+
expected = expected.MakeLineBreaksEnvironmentNeutral();
543+
Assert.Equal(expected, actual);
544+
}
545+
546+
[Fact]
547+
public async Task SerializeMediaTypeWithPrefixEncodingAsV31JsonWorks()
548+
{
549+
// Arrange
550+
var expected =
551+
"""
552+
{
553+
"x-oai-prefixEncoding": [
554+
{
555+
"contentType": "image/png, image/jpeg",
556+
"style": "simple",
557+
"explode": true,
558+
"allowReserved": true
559+
},
560+
{
561+
"contentType": "application/json",
562+
"style": "simple"
563+
}
564+
]
565+
}
566+
""";
567+
568+
// Act
569+
var actual = await MediaTypeWithPrefixEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
570+
571+
// Assert
572+
actual = actual.MakeLineBreaksEnvironmentNeutral();
573+
expected = expected.MakeLineBreaksEnvironmentNeutral();
574+
Assert.Equal(expected, actual);
575+
}
446576
}
447577
}

0 commit comments

Comments
 (0)