Skip to content

Commit 61b44fc

Browse files
authored
Merge pull request #465 from microsoft/dm/fixv2inline
Fixed inlining of v2
2 parents 86c41f8 + 596b359 commit 61b44fc

File tree

3 files changed

+218
-43
lines changed

3 files changed

+218
-43
lines changed

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Runtime.CompilerServices;
78
using Microsoft.OpenApi.Any;
89
using Microsoft.OpenApi.Exceptions;
910
using Microsoft.OpenApi.Interfaces;
11+
using Microsoft.OpenApi.Services;
1012
using Microsoft.OpenApi.Writers;
1113

1214
namespace Microsoft.OpenApi.Models
@@ -131,12 +133,17 @@ public void SerializeAsV2(IOpenApiWriter writer)
131133
if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences)
132134
{
133135
var loops = writer.GetSettings().LoopDetector.Loops;
134-
writer.WriteStartObject();
136+
135137
if (loops.TryGetValue(typeof(OpenApiSchema), out List<object> schemas))
136138
{
137139
var openApiSchemas = schemas.Cast<OpenApiSchema>().Distinct().ToList()
138140
.ToDictionary<OpenApiSchema, string>(k => k.Reference.Id);
139141

142+
foreach (var schema in openApiSchemas.Values.ToList())
143+
{
144+
FindSchemaReferences.ResolveSchemas(Components, openApiSchemas);
145+
}
146+
140147
writer.WriteOptionalMap(
141148
OpenApiConstants.Definitions,
142149
openApiSchemas,
@@ -145,8 +152,6 @@ public void SerializeAsV2(IOpenApiWriter writer)
145152
component.SerializeAsV2WithoutReference(w);
146153
});
147154
}
148-
writer.WriteEndObject();
149-
return;
150155
}
151156
else
152157
{
@@ -390,4 +395,47 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
390395
}
391396
}
392397
}
398+
399+
internal class FindSchemaReferences : OpenApiVisitorBase
400+
{
401+
private Dictionary<string, OpenApiSchema> Schemas;
402+
403+
public static void ResolveSchemas(OpenApiComponents components, Dictionary<string, OpenApiSchema> schemas )
404+
{
405+
var visitor = new FindSchemaReferences();
406+
visitor.Schemas = schemas;
407+
var walker = new OpenApiWalker(visitor);
408+
walker.Walk(components);
409+
}
410+
411+
public override void Visit(IOpenApiReferenceable referenceable)
412+
{
413+
switch (referenceable)
414+
{
415+
case OpenApiSchema schema:
416+
if (!Schemas.ContainsKey(schema.Reference.Id))
417+
{
418+
Schemas.Add(schema.Reference.Id, schema);
419+
}
420+
break;
421+
422+
default:
423+
break;
424+
}
425+
base.Visit(referenceable);
426+
}
427+
428+
public override void Visit(OpenApiSchema schema)
429+
{
430+
// This is needed to handle schemas used in Responses in components
431+
if (schema.Reference != null)
432+
{
433+
if (!Schemas.ContainsKey(schema.Reference.Id))
434+
{
435+
Schemas.Add(schema.Reference.Id, schema);
436+
}
437+
}
438+
base.Visit(schema);
439+
}
440+
}
393441
}

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ internal void SerializeAsV2(
455455
if (!settings.LoopDetector.PushLoop<OpenApiSchema>(this))
456456
{
457457
settings.LoopDetector.SaveLoop(this);
458-
Reference.SerializeAsV3(writer);
458+
Reference.SerializeAsV2(writer);
459459
return;
460460
}
461461
}

test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs

Lines changed: 166 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,77 @@ public void WriteDateTimeAsJsonShouldMatchExpected(DateTimeOffset dateTimeOffset
351351
[Fact]
352352

353353
public void WriteInlineSchema()
354+
{
355+
// Arrange
356+
var doc = CreateDocWithSimpleSchemaToInline();
357+
358+
var expected =
359+
@"openapi: 3.0.1
360+
info:
361+
title: Demo
362+
version: 1.0.0
363+
paths:
364+
/:
365+
get:
366+
responses:
367+
'200':
368+
description: OK
369+
content:
370+
application/json:
371+
schema:
372+
type: object
373+
components: { }";
374+
375+
var outputString = new StringWriter(CultureInfo.InvariantCulture);
376+
var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences});
377+
378+
// Act
379+
doc.SerializeAsV3(writer);
380+
var actual = outputString.GetStringBuilder().ToString();
381+
382+
// Assert
383+
actual = actual.MakeLineBreaksEnvironmentNeutral();
384+
expected = expected.MakeLineBreaksEnvironmentNeutral();
385+
Assert.Equal(expected, actual);
386+
}
387+
388+
389+
390+
[Fact]
391+
public void WriteInlineSchemaV2()
392+
{
393+
var doc = CreateDocWithSimpleSchemaToInline();
394+
395+
var expected =
396+
@"swagger: '2.0'
397+
info:
398+
title: Demo
399+
version: 1.0.0
400+
paths:
401+
/:
402+
get:
403+
produces:
404+
- application/json
405+
responses:
406+
'200':
407+
description: OK
408+
schema:
409+
type: object";
410+
411+
var outputString = new StringWriter(CultureInfo.InvariantCulture);
412+
var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences });
413+
414+
// Act
415+
doc.SerializeAsV2(writer);
416+
var actual = outputString.GetStringBuilder().ToString();
417+
418+
// Assert
419+
actual = actual.MakeLineBreaksEnvironmentNeutral();
420+
expected = expected.MakeLineBreaksEnvironmentNeutral();
421+
Assert.Equal(expected, actual);
422+
}
423+
424+
private static OpenApiDocument CreateDocWithSimpleSchemaToInline()
354425
{
355426
// Arrange
356427
var thingSchema = new OpenApiSchema()
@@ -397,6 +468,15 @@ public void WriteInlineSchema()
397468
["thing"] = thingSchema}
398469
}
399470
};
471+
return doc;
472+
}
473+
474+
[Fact]
475+
476+
public void WriteInlineRecursiveSchema()
477+
{
478+
// Arrange
479+
var doc = CreateDocWithRecursiveSchemaReference();
400480

401481
var expected =
402482
@"openapi: 3.0.1
@@ -413,10 +493,29 @@ public void WriteInlineSchema()
413493
application/json:
414494
schema:
415495
type: object
416-
components: { }";
496+
properties:
497+
children:
498+
$ref: '#/components/schemas/thing'
499+
related:
500+
type: integer
501+
components:
502+
schemas:
503+
thing:
504+
type: object
505+
properties:
506+
children:
507+
type: object
508+
properties:
509+
children:
510+
$ref: '#/components/schemas/thing'
511+
related:
512+
type: integer
513+
related:
514+
type: integer";
515+
// Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles.
417516

418517
var outputString = new StringWriter(CultureInfo.InvariantCulture);
419-
var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences});
518+
var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences });
420519

421520
// Act
422521
doc.SerializeAsV3(writer);
@@ -428,27 +527,44 @@ public void WriteInlineSchema()
428527
Assert.Equal(expected, actual);
429528
}
430529

431-
432-
[Fact]
433-
434-
public void WriteInlineRecursiveSchema()
530+
private static OpenApiDocument CreateDocWithRecursiveSchemaReference()
435531
{
436-
// Arrange
437-
var thingSchema = new OpenApiSchema() {
532+
var thingSchema = new OpenApiSchema()
533+
{
438534
Type = "object",
439535
UnresolvedReference = false,
440-
Reference = new OpenApiReference {
441-
Id = "thing",
536+
Reference = new OpenApiReference
537+
{
538+
Id = "thing",
442539
Type = ReferenceType.Schema
443540
}
444541
};
445542
thingSchema.Properties["children"] = thingSchema;
446-
447-
var doc = new OpenApiDocument() {
448-
Info = new OpenApiInfo() { Title = "Demo",
449-
Version = "1.0.0" },
450-
Paths = new OpenApiPaths() {
451-
["/"] = new OpenApiPathItem {
543+
544+
var relatedSchema = new OpenApiSchema()
545+
{
546+
Type = "integer",
547+
UnresolvedReference = false,
548+
Reference = new OpenApiReference
549+
{
550+
Id = "related",
551+
Type = ReferenceType.Schema
552+
}
553+
};
554+
555+
thingSchema.Properties["related"] = relatedSchema;
556+
557+
var doc = new OpenApiDocument()
558+
{
559+
Info = new OpenApiInfo()
560+
{
561+
Title = "Demo",
562+
Version = "1.0.0"
563+
},
564+
Paths = new OpenApiPaths()
565+
{
566+
["/"] = new OpenApiPathItem
567+
{
452568
Operations = {
453569
[OperationType.Get] = new OpenApiOperation() {
454570
Responses = {
@@ -462,50 +578,61 @@ public void WriteInlineRecursiveSchema()
462578
}
463579
}
464580
}
465-
}
581+
}
466582
}
467583
},
468-
Components = new OpenApiComponents {
584+
Components = new OpenApiComponents
585+
{
469586
Schemas = {
470-
["thing"] = thingSchema}
587+
["thing"] = thingSchema}
471588
}
472589
};
473-
590+
return doc;
591+
}
592+
593+
[Fact]
594+
public void WriteInlineRecursiveSchemav2()
595+
{
596+
// Arrange
597+
var doc = CreateDocWithRecursiveSchemaReference();
598+
474599
var expected =
475-
@"openapi: 3.0.1
600+
@"swagger: '2.0'
476601
info:
477602
title: Demo
478603
version: 1.0.0
479604
paths:
480605
/:
481606
get:
607+
produces:
608+
- application/json
482609
responses:
483610
'200':
484611
description: OK
485-
content:
486-
application/json:
487-
schema:
488-
type: object
489-
properties:
490-
children:
491-
$ref: '#/components/schemas/thing'
492-
components:
493-
schemas:
494-
thing:
495-
type: object
496-
properties:
497-
children:
498-
type: object
499-
properties:
500-
children:
501-
$ref: '#/components/schemas/thing'";
612+
schema:
613+
type: object
614+
properties:
615+
children:
616+
$ref: '#/definitions/thing'
617+
related:
618+
type: integer
619+
definitions:
620+
thing:
621+
type: object
622+
properties:
623+
children:
624+
$ref: '#/definitions/thing'
625+
related:
626+
$ref: '#/definitions/related'
627+
related:
628+
type: integer";
502629
// Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles.
503630

504631
var outputString = new StringWriter(CultureInfo.InvariantCulture);
505632
var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences });
506633

507634
// Act
508-
doc.SerializeAsV3(writer);
635+
doc.SerializeAsV2(writer);
509636
var actual = outputString.GetStringBuilder().ToString();
510637

511638
// Assert

0 commit comments

Comments
 (0)