Skip to content

Commit 3516127

Browse files
authored
Merge pull request #449 from microsoft/dm/inlinewriter
Implement the ability to support inlining references when writing out OpenAPI descriptions
2 parents 8ea512c + 9e42a5f commit 3516127

20 files changed

+460
-40
lines changed

src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using Microsoft.OpenApi.Any;
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using Microsoft.OpenApi.Any;
25
using Microsoft.OpenApi.Interfaces;
36
using Microsoft.OpenApi.Readers.ParseNodes;
47
using Microsoft.OpenApi.Validations;

src/Microsoft.OpenApi.Workbench/MainModel.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using System;
55
using System.ComponentModel;
66
using System.Diagnostics;
7+
using System.Globalization;
78
using System.IO;
89
using System.Text;
910
using Microsoft.OpenApi.Extensions;
1011
using Microsoft.OpenApi.Models;
1112
using Microsoft.OpenApi.Readers;
1213
using Microsoft.OpenApi.Services;
1314
using Microsoft.OpenApi.Validations;
15+
using Microsoft.OpenApi.Writers;
1416

1517
namespace Microsoft.OpenApi.Workbench
1618
{
@@ -31,6 +33,11 @@ public class MainModel : INotifyPropertyChanged
3133

3234
private string _renderTime;
3335

36+
/// <summary>
37+
/// Default format.
38+
/// </summary>
39+
private bool _Inline = false;
40+
3441
/// <summary>
3542
/// Default format.
3643
/// </summary>
@@ -112,6 +119,16 @@ public OpenApiFormat Format
112119
}
113120
}
114121

122+
public bool Inline
123+
{
124+
get => _Inline;
125+
set
126+
{
127+
_Inline = value;
128+
OnPropertyChanged(nameof(Inline));
129+
}
130+
}
131+
115132
public OpenApiSpecVersion Version
116133
{
117134
get => _version;
@@ -232,11 +249,15 @@ internal void ParseDocument()
232249
private string WriteContents(OpenApiDocument document)
233250
{
234251
var outputStream = new MemoryStream();
252+
235253
document.Serialize(
236254
outputStream,
237255
Version,
238-
Format);
239-
256+
Format,
257+
new OpenApiWriterSettings() {
258+
ReferenceInline = this.Inline == true ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences
259+
});
260+
240261
outputStream.Position = 0;
241262

242263
return new StreamReader(outputStream).ReadToEnd();

src/Microsoft.OpenApi.Workbench/MainWindow.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<RadioButton GroupName="Format" Content="V3.0.1" Padding="5" Height="24" VerticalAlignment="Top" IsChecked="{Binding IsV3_0}" />
4343
<RadioButton GroupName="Format" Content="V2.0" Padding="5" Height="24" VerticalAlignment="Top" IsChecked="{Binding IsV2_0}" />
4444
</StackPanel>
45+
<CheckBox Name="Inline" Content="Inline" IsChecked="{Binding Inline}" />
4546
</StackPanel>
4647
</StackPanel>
4748
<TextBox x:Name="txtErrors" Margin="10,10,10,10" TextWrapping="Wrap" Text="{Binding Errors}" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" DockPanel.Dock="Top" MaxHeight="100" ScrollViewer.VerticalScrollBarVisibility="Auto" IsReadOnlyCaretVisible="True" IsManipulationEnabled="True" />
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Microsoft.OpenApi.Writers;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace Microsoft.OpenApi
9+
{
10+
internal static class OpenAPIWriterExtensions
11+
{
12+
/// <summary>
13+
/// Temporary extension method until we add Settings property to IOpenApiWriter in next major version
14+
/// </summary>
15+
/// <param name="openApiWriter"></param>
16+
/// <returns></returns>
17+
internal static OpenApiWriterSettings GetSettings(this IOpenApiWriter openApiWriter)
18+
{
19+
if (openApiWriter is OpenApiWriterBase)
20+
{
21+
return ((OpenApiWriterBase)openApiWriter).Settings;
22+
}
23+
return new OpenApiWriterSettings();
24+
}
25+
}
26+
}

src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public static void Serialize<T>(
5454
this T element,
5555
Stream stream,
5656
OpenApiSpecVersion specVersion,
57-
OpenApiFormat format)
57+
OpenApiFormat format,
58+
OpenApiWriterSettings settings = null)
5859
where T : IOpenApiSerializable
5960
{
6061
if (stream == null)
@@ -67,10 +68,10 @@ public static void Serialize<T>(
6768
switch (format)
6869
{
6970
case OpenApiFormat.Json:
70-
writer = new OpenApiJsonWriter(streamWriter);
71+
writer = new OpenApiJsonWriter(streamWriter,settings);
7172
break;
7273
case OpenApiFormat.Yaml:
73-
writer = new OpenApiYamlWriter(streamWriter);
74+
writer = new OpenApiYamlWriter(streamWriter, settings);
7475
break;
7576
default:
7677
throw new OpenApiException(string.Format(SRResource.OpenApiFormatNotSupported, format));
@@ -86,6 +87,7 @@ public static void Serialize<T>(
8687
/// <param name="element">The Open API element.</param>
8788
/// <param name="writer">The output writer.</param>
8889
/// <param name="specVersion">Version of the specification the output should conform to</param>
90+
8991
public static void Serialize<T>(this T element, IOpenApiWriter writer, OpenApiSpecVersion specVersion)
9092
where T : IOpenApiSerializable
9193
{
@@ -116,6 +118,7 @@ public static void Serialize<T>(this T element, IOpenApiWriter writer, OpenApiSp
116118
writer.Flush();
117119
}
118120

121+
119122
/// <summary>
120123
/// Serializes the <see cref="IOpenApiSerializable"/> to the Open API document as a string in JSON format.
121124
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiCallback.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
7070
throw Error.ArgumentNull(nameof(writer));
7171
}
7272

73-
if (Reference != null)
73+
if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
7474
{
7575
Reference.SerializeAsV3(writer);
7676
return;

src/Microsoft.OpenApi/Models/OpenApiComponents.cs

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

4+
using System;
45
using System.Collections.Generic;
6+
using System.Linq;
57
using Microsoft.OpenApi.Any;
68
using Microsoft.OpenApi.Interfaces;
79
using Microsoft.OpenApi.Writers;
@@ -76,6 +78,28 @@ public void SerializeAsV3(IOpenApiWriter writer)
7678
throw Error.ArgumentNull(nameof(writer));
7779
}
7880

81+
// If references have been inlined we don't need the to render the components section
82+
// however if they have cycles, then we will need a component rendered
83+
if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences)
84+
{
85+
var loops = writer.GetSettings().LoopDetector.Loops;
86+
writer.WriteStartObject();
87+
if (loops.TryGetValue(typeof(OpenApiSchema), out List<object> schemas))
88+
{
89+
var openApiSchemas = schemas.Cast<OpenApiSchema>().Distinct().ToList()
90+
.ToDictionary<OpenApiSchema, string>(k => k.Reference.Id);
91+
92+
writer.WriteOptionalMap(
93+
OpenApiConstants.Schemas,
94+
Schemas,
95+
(w, key, component) => {
96+
component.SerializeAsV3WithoutReference(w);
97+
});
98+
}
99+
writer.WriteEndObject();
100+
return;
101+
}
102+
79103
writer.WriteStartObject();
80104

81105
// Serialize each referenceable object as full object without reference if the reference in the object points to itself.

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -126,27 +126,50 @@ public void SerializeAsV2(IOpenApiWriter writer)
126126
// paths
127127
writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, (w, p) => p.SerializeAsV2(w));
128128

129-
// Serialize each referenceable object as full object without reference if the reference in the object points to itself.
130-
// If the reference exists but points to other objects, the object is serialized to just that reference.
131-
132-
// definitions
133-
writer.WriteOptionalMap(
134-
OpenApiConstants.Definitions,
135-
Components?.Schemas,
136-
(w, key, component) =>
129+
// If references have been inlined we don't need the to render the components section
130+
// however if they have cycles, then we will need a component rendered
131+
if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences)
132+
{
133+
var loops = writer.GetSettings().LoopDetector.Loops;
134+
writer.WriteStartObject();
135+
if (loops.TryGetValue(typeof(OpenApiSchema), out List<object> schemas))
137136
{
138-
if (component.Reference != null &&
139-
component.Reference.Type == ReferenceType.Schema &&
140-
component.Reference.Id == key)
141-
{
142-
component.SerializeAsV2WithoutReference(w);
143-
}
144-
else
137+
var openApiSchemas = schemas.Cast<OpenApiSchema>().Distinct().ToList()
138+
.ToDictionary<OpenApiSchema, string>(k => k.Reference.Id);
139+
140+
writer.WriteOptionalMap(
141+
OpenApiConstants.Definitions,
142+
openApiSchemas,
143+
(w, key, component) =>
144+
{
145+
component.SerializeAsV2WithoutReference(w);
146+
});
147+
}
148+
writer.WriteEndObject();
149+
return;
150+
}
151+
else
152+
{
153+
// Serialize each referenceable object as full object without reference if the reference in the object points to itself.
154+
// If the reference exists but points to other objects, the object is serialized to just that reference.
155+
// definitions
156+
writer.WriteOptionalMap(
157+
OpenApiConstants.Definitions,
158+
Components?.Schemas,
159+
(w, key, component) =>
145160
{
146-
component.SerializeAsV2(w);
147-
}
148-
});
149-
161+
if (component.Reference != null &&
162+
component.Reference.Type == ReferenceType.Schema &&
163+
component.Reference.Id == key)
164+
{
165+
component.SerializeAsV2WithoutReference(w);
166+
}
167+
else
168+
{
169+
component.SerializeAsV2(w);
170+
}
171+
});
172+
}
150173
// parameters
151174
writer.WriteOptionalMap(
152175
OpenApiConstants.Parameters,

src/Microsoft.OpenApi/Models/OpenApiExample.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
6464
throw Error.ArgumentNull(nameof(writer));
6565
}
6666

67-
if (Reference != null)
67+
if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
6868
{
6969
Reference.SerializeAsV3(writer);
7070
return;

src/Microsoft.OpenApi/Models/OpenApiHeader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
9696
throw Error.ArgumentNull(nameof(writer));
9797
}
9898

99-
if (Reference != null)
99+
if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
100100
{
101101
Reference.SerializeAsV3(writer);
102102
return;
@@ -161,7 +161,7 @@ public void SerializeAsV2(IOpenApiWriter writer)
161161
throw Error.ArgumentNull(nameof(writer));
162162
}
163163

164-
if (Reference != null)
164+
if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
165165
{
166166
Reference.SerializeAsV2(writer);
167167
return;

0 commit comments

Comments
 (0)