Skip to content

Commit 202c684

Browse files
committed
Refactoring to simplify interactions around ParsingContext
1 parent bdba12d commit 202c684

File tree

6 files changed

+157
-103
lines changed

6 files changed

+157
-103
lines changed

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,62 +19,42 @@ namespace Microsoft.OpenApi.Readers
1919
/// </summary>
2020
public class OpenApiStreamReader : IOpenApiReader<Stream, OpenApiDiagnostic>
2121
{
22-
/// <summary>
23-
/// Gets the version of the Open API document.
24-
/// </summary>
25-
private static string GetVersion(RootNode rootNode)
26-
{
27-
var versionNode = rootNode.Find(new JsonPointer("/openapi"));
28-
29-
if (versionNode != null)
30-
{
31-
return versionNode.GetScalarValue();
32-
}
33-
34-
versionNode = rootNode.Find(new JsonPointer("/swagger"));
35-
36-
return versionNode?.GetScalarValue();
37-
}
22+
3823

3924
/// <summary>
4025
/// Reads the stream input and parses it into an Open API document.
4126
/// </summary>
4227
public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic)
4328
{
44-
RootNode rootNode;
45-
var context = new ParsingContext();
29+
ParsingContext context;
4630
diagnostic = new OpenApiDiagnostic();
4731

4832
try
4933
{
50-
using (var streamReader = new StreamReader(input))
51-
{
52-
var yamlStream = new YamlStream();
53-
yamlStream.Load(streamReader);
34+
YamlDocument yamlDocument = LoadYamlDocument(input);
35+
context = new ParsingContext();
36+
return context.Parse(yamlDocument, diagnostic);
5437

55-
var yamlDocument = yamlStream.Documents.First();
56-
rootNode = new RootNode(context, diagnostic, yamlDocument);
57-
}
5838
}
5939
catch (SyntaxErrorException ex)
6040
{
6141
diagnostic.Errors.Add(new OpenApiError(string.Empty, ex.Message));
6242

6343
return new OpenApiDocument();
6444
}
45+
46+
}
6547

66-
var inputVersion = GetVersion(rootNode);
67-
68-
switch (inputVersion)
48+
internal static YamlDocument LoadYamlDocument(Stream input)
49+
{
50+
YamlDocument yamlDocument;
51+
using (var streamReader = new StreamReader(input))
6952
{
70-
case "2.0":
71-
context.ReferenceService = new OpenApiV2ReferenceService(rootNode);
72-
return OpenApiV2Deserializer.LoadOpenApi(rootNode);
73-
74-
default:
75-
context.ReferenceService = new OpenApiV3ReferenceService(rootNode);
76-
return OpenApiV3Deserializer.LoadOpenApi(rootNode);
53+
var yamlStream = new YamlStream();
54+
yamlStream.Load(streamReader);
55+
yamlDocument = yamlStream.Documents.First();
7756
}
57+
return yamlDocument;
7858
}
7959
}
8060
}

src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public RootNode(
1818
YamlDocument yamlDocument) : base(context, diagnostic)
1919
{
2020
_yamlDocument = yamlDocument;
21+
context.RootNode = this;
2122
}
2223

2324
public ParseNode Find(JsonPointer referencePointer)

src/Microsoft.OpenApi.Readers/ParsingContext.cs

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

4+
using System;
45
using System.Collections.Generic;
56
using System.Linq;
67
using Microsoft.OpenApi.Interfaces;
78
using Microsoft.OpenApi.Models;
9+
using Microsoft.OpenApi.Readers.ParseNodes;
810
using Microsoft.OpenApi.Readers.ReferenceServices;
11+
using SharpYaml.Serialization;
912

1013
namespace Microsoft.OpenApi.Readers
1114
{
1215
/// <summary>
13-
/// Parsing context.
16+
/// The Parsing Context holds temporary state needed whilst parsing an OpenAPI Document
1417
/// </summary>
1518
public class ParsingContext
1619
{
1720
private readonly Stack<string> _currentLocation = new Stack<string>();
21+
private readonly Dictionary<string, IOpenApiReferenceable> _referenceStore = new Dictionary<string, IOpenApiReferenceable>();
22+
private readonly Dictionary<string, object> _tempStorage = new Dictionary<string, object>();
23+
private readonly List<OpenApiTag> _tags = new List<OpenApiTag>();
24+
private IOpenApiVersionService _versionService;
25+
private RootNode _rootNode;
1826

19-
private readonly Dictionary<string, IOpenApiReferenceable> _referenceStore =
20-
new Dictionary<string, IOpenApiReferenceable>();
2127

22-
private readonly Dictionary<string, object> _tempStorage = new Dictionary<string, object>();
28+
/// <summary>
29+
/// Initiates the parsing process. Not thread safe and should only be called once on a parsing context
30+
/// </summary>
31+
/// <param name="yamlDocument"></param>
32+
/// <param name="diagnostic"></param>
33+
/// <returns>An OpenApiDocument populated based on the passed yamlDocument </returns>
34+
internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diagnostic)
35+
{
36+
_rootNode = new RootNode(this, diagnostic, yamlDocument);
37+
38+
var inputVersion = GetVersion(_rootNode);
39+
40+
OpenApiDocument doc;
41+
switch (inputVersion)
42+
{
43+
case "2.0":
44+
this.ReferenceService = new OpenApiV2VersionService();
45+
doc = this.ReferenceService.LoadOpenApi(_rootNode);
46+
break;
47+
48+
default:
49+
this.ReferenceService = new OpenApiV3VersionService();
50+
doc = this.ReferenceService.LoadOpenApi(_rootNode);
51+
break;
52+
}
53+
54+
return doc;
55+
}
56+
57+
internal RootNode RootNode { get
58+
{
59+
return _rootNode;
60+
}
61+
set
62+
{
63+
{
64+
_rootNode = value;
65+
66+
}
67+
}
68+
}
69+
70+
internal List<OpenApiTag> Tags
71+
{
72+
get
73+
{
74+
return _tags;
75+
}
76+
}
2377

2478
/// <summary>
25-
/// Reference service.
79+
/// Gets the version of the Open API document.
2680
/// </summary>
27-
internal IOpenApiReferenceService ReferenceService { get; set; }
81+
private static string GetVersion(RootNode rootNode)
82+
{
83+
var versionNode = rootNode.Find(new JsonPointer("/openapi"));
84+
85+
if (versionNode != null)
86+
{
87+
return versionNode.GetScalarValue();
88+
}
89+
90+
versionNode = rootNode.Find(new JsonPointer("/swagger"));
91+
92+
return versionNode?.GetScalarValue();
93+
}
94+
95+
private void ComputeTags(List<OpenApiTag> tags, Func<MapNode,OpenApiTag> loadTag )
96+
{
97+
// Precompute the tags array so that each tag reference does not require a new deserialization.
98+
var tagListPointer = new JsonPointer("#/tags");
99+
100+
var tagListNode = _rootNode.Find(tagListPointer);
101+
102+
if (tagListNode != null && tagListNode is ListNode)
103+
{
104+
var tagListNodeAsListNode = (ListNode)tagListNode;
105+
tags.AddRange(tagListNodeAsListNode.CreateList(loadTag));
106+
}
107+
}
108+
109+
110+
28111

29112
/// <summary>
30-
/// End the current object.
113+
/// Reference service.
31114
/// </summary>
32-
public void EndObject()
115+
internal IOpenApiVersionService ReferenceService { get {
116+
return _versionService;
117+
}
118+
set {
119+
_versionService = value;
120+
ComputeTags(_tags, ReferenceService.TagLoader);
121+
}
122+
}
123+
124+
/// <summary>
125+
/// End the current object.
126+
/// </summary>
127+
public void EndObject()
33128
{
34129
_currentLocation.Pop();
35130
}
@@ -60,7 +155,7 @@ public IOpenApiReferenceable GetReferencedObject(
60155

61156
var reference = ReferenceService.ConvertToOpenApiReference(referenceString, referenceType);
62157

63-
var isReferencedObjectFound = ReferenceService.TryLoadReference(reference, out referencedObject);
158+
var isReferencedObjectFound = ReferenceService.TryLoadReference(this, reference, out referencedObject);
64159

65160
if (isReferencedObjectFound)
66161
{

src/Microsoft.OpenApi.Readers/ReferenceServices/IOpenApiReferenceService.cs renamed to src/Microsoft.OpenApi.Readers/ReferenceServices/IOpenApiVersionService.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33

44
using Microsoft.OpenApi.Interfaces;
55
using Microsoft.OpenApi.Models;
6+
using Microsoft.OpenApi.Readers.ParseNodes;
7+
using System;
68

79
namespace Microsoft.OpenApi.Readers.ReferenceServices
810
{
911
/// <summary>
10-
/// Interface for Open API Reference parse.
12+
/// Interface to a version specific parsing implementations.
1113
/// </summary>
12-
public interface IOpenApiReferenceService
14+
internal interface IOpenApiVersionService
1315
{
1416
/// <summary>
1517
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
1618
/// </summary>
19+
/// <param name="context"></param>
1720
/// <param name="reference">The <see cref="OpenApiReference"/> object.</param>
1821
/// <param name="referencedObject">The object that is being referenced.</param>
1922
/// <returns>
@@ -22,7 +25,7 @@ public interface IOpenApiReferenceService
2225
/// a new tag will be returned in the outer parameter and the return value will be false.
2326
/// If reference is null, no object will be returned and the return value will be false.
2427
/// </returns>
25-
bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceable referencedObject);
28+
bool TryLoadReference(ParsingContext context, OpenApiReference reference, out IOpenApiReferenceable referencedObject);
2629

2730
/// <summary>
2831
/// Parse the string to a <see cref="OpenApiReference"/> object.
@@ -31,5 +34,13 @@ public interface IOpenApiReferenceService
3134
/// <param name="type">The type of the reference.</param>
3235
/// <returns>The <see cref="OpenApiReference"/> object or null.</returns>
3336
OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type);
37+
38+
39+
/// <summary>
40+
/// Function that converts MapNodes into Tag objects in a version specific way
41+
/// </summary>
42+
Func<MapNode, OpenApiTag> TagLoader { get; }
43+
44+
OpenApiDocument LoadOpenApi(RootNode rootNode);
3445
}
3546
}

src/Microsoft.OpenApi.Readers/ReferenceServices/OpenApiV2ReferenceService.cs renamed to src/Microsoft.OpenApi.Readers/ReferenceServices/OpenApiV2VersionService.cs

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,22 @@
99
using Microsoft.OpenApi.Readers.ParseNodes;
1010
using Microsoft.OpenApi.Readers.Properties;
1111
using Microsoft.OpenApi.Readers.V2;
12-
using Microsoft.OpenApi.Readers.V3;
12+
1313

1414
namespace Microsoft.OpenApi.Readers.ReferenceServices
1515
{
1616
/// <summary>
17-
/// The reference service for the Open API V2.0.
17+
/// The version specific implementations for OpenAPI V2.0.
1818
/// </summary>
19-
internal class OpenApiV2ReferenceService : IOpenApiReferenceService
19+
internal class OpenApiV2VersionService : IOpenApiVersionService
2020
{
21-
private readonly RootNode _rootNode;
22-
23-
private readonly List<OpenApiTag> _tags = new List<OpenApiTag>();
24-
25-
/// <summary>
26-
/// Initializes a new instance of the <see cref="OpenApiV2ReferenceService"/> class.
27-
/// </summary>
28-
/// <param name="rootNode">The root node.</param>
29-
public OpenApiV2ReferenceService(RootNode rootNode)
30-
{
31-
_rootNode = rootNode ?? throw new ArgumentNullException(nameof(rootNode));
32-
33-
// Precompute the tags array so that each tag reference does not require a new deserialization.
34-
var tagListPointer = new JsonPointer("#/tags");
21+
public Func<MapNode, OpenApiTag> TagLoader { get { return OpenApiV2Deserializer.LoadTag; } }
3522

36-
var tagListNode = _rootNode.Find(tagListPointer);
37-
38-
if (tagListNode != null && tagListNode is ListNode)
39-
{
40-
var tagListNodeAsListNode = (ListNode)tagListNode;
41-
_tags.AddRange(tagListNodeAsListNode.CreateList(OpenApiV3Deserializer.LoadTag));
42-
}
43-
}
4423

4524
/// <summary>
4625
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
4726
/// </summary>
48-
public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceable referencedObject)
27+
public bool TryLoadReference(ParsingContext context, OpenApiReference reference, out IOpenApiReferenceable referencedObject)
4928
{
5029
referencedObject = null;
5130

@@ -68,7 +47,7 @@ public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceab
6847
// Special case for Tag
6948
if (reference.Type == ReferenceType.Tag)
7049
{
71-
foreach (var tag in _tags)
50+
foreach (var tag in context.Tags)
7251
{
7352
if (tag.Name == reference.Id)
7453
{
@@ -84,7 +63,7 @@ public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceab
8463
var jsonPointer =
8564
new JsonPointer("#/" + GetReferenceTypeV2Name(reference.Type.Value) + "/" + reference.Id);
8665

87-
var node = _rootNode.Find(jsonPointer);
66+
var node = context.RootNode.Find(jsonPointer);
8867

8968
switch (reference.Type)
9069
{
@@ -115,7 +94,7 @@ public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceab
11594
return true;
11695
}
11796

118-
private OpenApiReference ParseLocalReference(string localReference)
97+
private static OpenApiReference ParseLocalReference(string localReference)
11998
{
12099
if (string.IsNullOrWhiteSpace(localReference))
121100
{
@@ -243,5 +222,10 @@ public OpenApiReference ConvertToOpenApiReference(string reference, ReferenceTyp
243222

244223
throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, reference));
245224
}
225+
226+
public OpenApiDocument LoadOpenApi(RootNode rootNode)
227+
{
228+
return OpenApiV2Deserializer.LoadOpenApi(rootNode);
229+
}
246230
}
247231
}

0 commit comments

Comments
 (0)