Skip to content

Commit 0ed8e2b

Browse files
authored
Merge pull request #161 from Microsoft/dm/context-refactor
Refactoring to simplify interactions around ParsingContext
2 parents 9a0a57e + db2e5c2 commit 0ed8e2b

File tree

11 files changed

+215
-212
lines changed

11 files changed

+215
-212
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4+
using System;
45
using Microsoft.OpenApi.Interfaces;
56
using Microsoft.OpenApi.Models;
7+
using Microsoft.OpenApi.Readers.ParseNodes;
68

7-
namespace Microsoft.OpenApi.Readers.ReferenceServices
9+
namespace Microsoft.OpenApi.Readers.Interface
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">Instance of ParsingContext to use for retrieving references.</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,17 @@ 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+
/// <summary>
39+
/// Function that converts a MapNode into a Tag object in a version specific way
40+
/// </summary>
41+
Func<MapNode, OpenApiTag> TagLoader { get; }
42+
43+
/// <summary>
44+
/// Converts a generic RootNode instance into a strongly typed OpenApiDocument
45+
/// </summary>
46+
/// <param name="rootNode">RootNode containing the information to be converted into an OpenAPI Document</param>
47+
/// <returns>Instance of OpenApiDocument populated with data from rootNode</returns>
48+
OpenApiDocument LoadDocument(RootNode rootNode);
3449
}
3550
}

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
using System.Linq;
66
using Microsoft.OpenApi.Models;
77
using Microsoft.OpenApi.Readers.Interface;
8-
using Microsoft.OpenApi.Readers.ParseNodes;
9-
using Microsoft.OpenApi.Readers.ReferenceServices;
10-
using Microsoft.OpenApi.Readers.V2;
11-
using Microsoft.OpenApi.Readers.V3;
128
using SharpYaml;
139
using SharpYaml.Serialization;
1410

@@ -19,69 +15,48 @@ namespace Microsoft.OpenApi.Readers
1915
/// </summary>
2016
public class OpenApiStreamReader : IOpenApiReader<Stream, OpenApiDiagnostic>
2117
{
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-
}
3818

3919
/// <summary>
4020
/// Reads the stream input and parses it into an Open API document.
4121
/// </summary>
22+
/// <param name="input">Stream containing OpenAPI description to parse.</param>
23+
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
24+
/// <returns>Instance of newly created OpenApiDocument</returns>
4225
public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic)
4326
{
44-
RootNode rootNode;
45-
var context = new ParsingContext();
27+
ParsingContext context;
28+
YamlDocument yamlDocument;
4629
diagnostic = new OpenApiDiagnostic();
4730

4831
try
4932
{
50-
using (var streamReader = new StreamReader(input))
51-
{
52-
var yamlStream = new YamlStream();
53-
yamlStream.Load(streamReader);
54-
55-
var yamlDocument = yamlStream.Documents.First();
56-
rootNode = new RootNode(context, diagnostic, yamlDocument);
57-
}
33+
yamlDocument = LoadYamlDocument(input);
5834
}
5935
catch (SyntaxErrorException ex)
6036
{
6137
diagnostic.Errors.Add(new OpenApiError(string.Empty, ex.Message));
62-
6338
return new OpenApiDocument();
6439
}
6540

66-
var inputVersion = GetVersion(rootNode);
41+
context = new ParsingContext();
42+
return context.Parse(yamlDocument, diagnostic);
43+
}
6744

68-
if ( inputVersion == "2.0")
69-
{
70-
context.ReferenceService = new OpenApiV2ReferenceService(rootNode);
71-
return OpenApiV2Deserializer.LoadOpenApi(rootNode);
72-
}
73-
else if (inputVersion.StartsWith("3.0."))
74-
{
75-
context.ReferenceService = new OpenApiV3ReferenceService(rootNode);
76-
return OpenApiV3Deserializer.LoadOpenApi(rootNode);
77-
}
78-
else
45+
/// <summary>
46+
/// Helper method to turn streams into YamlDocument
47+
/// </summary>
48+
/// <param name="input">Stream containing YAML formatted text</param>
49+
/// <returns>Instance of a YamlDocument</returns>
50+
internal static YamlDocument LoadYamlDocument(Stream input)
51+
{
52+
YamlDocument yamlDocument;
53+
using (var streamReader = new StreamReader(input))
7954
{
80-
// If version number is not recognizable,
81-
// our best effort will try to deserialize the document to V3.
82-
context.ReferenceService = new OpenApiV3ReferenceService(rootNode);
83-
return OpenApiV3Deserializer.LoadOpenApi(rootNode);
55+
var yamlStream = new YamlStream();
56+
yamlStream.Load(streamReader);
57+
yamlDocument = yamlStream.Documents.First();
8458
}
59+
return yamlDocument;
8560
}
8661
}
8762
}

src/Microsoft.OpenApi.Readers/ParsingContext.cs

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,112 @@
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;
8-
using Microsoft.OpenApi.Readers.ReferenceServices;
9+
using Microsoft.OpenApi.Readers.Interface;
10+
using Microsoft.OpenApi.Readers.ParseNodes;
11+
using Microsoft.OpenApi.Readers.V2;
12+
using Microsoft.OpenApi.Readers.V3;
13+
using SharpYaml.Serialization;
914

1015
namespace Microsoft.OpenApi.Readers
1116
{
1217
/// <summary>
13-
/// Parsing context.
18+
/// The Parsing Context holds temporary state needed whilst parsing an OpenAPI Document
1419
/// </summary>
1520
public class ParsingContext
1621
{
1722
private readonly Stack<string> _currentLocation = new Stack<string>();
23+
private readonly Dictionary<string, IOpenApiReferenceable> _referenceStore = new Dictionary<string, IOpenApiReferenceable>();
24+
private readonly Dictionary<string, object> _tempStorage = new Dictionary<string, object>();
25+
private IOpenApiVersionService _versionService;
26+
internal RootNode RootNode { get; set; }
27+
internal List<OpenApiTag> Tags { get; private set; } = new List<OpenApiTag>();
1828

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

22-
private readonly Dictionary<string, object> _tempStorage = new Dictionary<string, object>();
30+
/// <summary>
31+
/// Initiates the parsing process. Not thread safe and should only be called once on a parsing context
32+
/// </summary>
33+
/// <param name="yamlDocument"></param>
34+
/// <param name="diagnostic"></param>
35+
/// <returns>An OpenApiDocument populated based on the passed yamlDocument </returns>
36+
internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diagnostic)
37+
{
38+
RootNode = new RootNode(this, diagnostic, yamlDocument);
39+
40+
var inputVersion = GetVersion(RootNode);
41+
42+
OpenApiDocument doc;
43+
44+
if (inputVersion == "2.0")
45+
{
46+
VersionService = new OpenApiV2VersionService();
47+
doc = this.VersionService.LoadDocument(this.RootNode);
48+
}
49+
else if (inputVersion.StartsWith("3.0."))
50+
{
51+
this.VersionService = new OpenApiV3VersionService();
52+
doc = this.VersionService.LoadDocument(this.RootNode);
53+
}
54+
else
55+
{
56+
// If version number is not recognizable,
57+
// our best effort will try to deserialize the document to V3.
58+
this.VersionService = new OpenApiV3VersionService();
59+
doc = this.VersionService.LoadDocument(this.RootNode);
60+
}
61+
return doc;
62+
}
2363

2464
/// <summary>
25-
/// Reference service.
65+
/// Gets the version of the Open API document.
2666
/// </summary>
27-
internal IOpenApiReferenceService ReferenceService { get; set; }
67+
private static string GetVersion(RootNode rootNode)
68+
{
69+
var versionNode = rootNode.Find(new JsonPointer("/openapi"));
70+
71+
if (versionNode != null)
72+
{
73+
return versionNode.GetScalarValue();
74+
}
75+
76+
versionNode = rootNode.Find(new JsonPointer("/swagger"));
77+
78+
return versionNode?.GetScalarValue();
79+
}
80+
81+
private void ComputeTags(List<OpenApiTag> tags, Func<MapNode,OpenApiTag> loadTag )
82+
{
83+
// Precompute the tags array so that each tag reference does not require a new deserialization.
84+
var tagListPointer = new JsonPointer("#/tags");
85+
86+
var tagListNode = RootNode.Find(tagListPointer);
87+
88+
if (tagListNode != null && tagListNode is ListNode)
89+
{
90+
var tagListNodeAsListNode = (ListNode)tagListNode;
91+
tags.AddRange(tagListNodeAsListNode.CreateList(loadTag));
92+
}
93+
}
94+
95+
/// <summary>
96+
/// Service providing all Version specific conversion functions
97+
/// </summary>
98+
internal IOpenApiVersionService VersionService
99+
{
100+
get
101+
{
102+
return _versionService;
103+
}
104+
set
105+
{
106+
_versionService = value;
107+
ComputeTags(Tags, VersionService.TagLoader);
108+
}
109+
}
28110

29111
/// <summary>
30112
/// End the current object.
@@ -58,9 +140,9 @@ public IOpenApiReferenceable GetReferencedObject(
58140
return referencedObject;
59141
}
60142

61-
var reference = ReferenceService.ConvertToOpenApiReference(referenceString, referenceType);
143+
var reference = VersionService.ConvertToOpenApiReference(referenceString, referenceType);
62144

63-
var isReferencedObjectFound = ReferenceService.TryLoadReference(reference, out referencedObject);
145+
var isReferencedObjectFound = VersionService.TryLoadReference(this, reference, out referencedObject);
64146

65147
if (isReferencedObjectFound)
66148
{

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

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,30 @@
22
// Licensed under the MIT license.
33

44
using System;
5-
using System.Collections.Generic;
65
using Microsoft.OpenApi.Exceptions;
76
using Microsoft.OpenApi.Interfaces;
87
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.Readers.Interface;
99
using Microsoft.OpenApi.Readers.ParseNodes;
1010
using Microsoft.OpenApi.Readers.Properties;
11-
using Microsoft.OpenApi.Readers.V2;
12-
using Microsoft.OpenApi.Readers.V3;
1311

14-
namespace Microsoft.OpenApi.Readers.ReferenceServices
12+
13+
namespace Microsoft.OpenApi.Readers.V2
1514
{
1615
/// <summary>
17-
/// The reference service for the Open API V2.0.
16+
/// The version specific implementations for OpenAPI V2.0.
1817
/// </summary>
19-
internal class OpenApiV2ReferenceService : IOpenApiReferenceService
18+
internal class OpenApiV2VersionService : IOpenApiVersionService
2019
{
21-
private readonly RootNode _rootNode;
22-
23-
private readonly List<OpenApiTag> _tags = new List<OpenApiTag>();
24-
2520
/// <summary>
26-
/// Initializes a new instance of the <see cref="OpenApiV2ReferenceService"/> class.
21+
/// Return a function that converts a MapNode into a V2 OpenApiTag
2722
/// </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");
35-
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-
}
23+
public Func<MapNode, OpenApiTag> TagLoader => OpenApiV2Deserializer.LoadTag;
4424

4525
/// <summary>
4626
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
4727
/// </summary>
48-
public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceable referencedObject)
28+
public bool TryLoadReference(ParsingContext context, OpenApiReference reference, out IOpenApiReferenceable referencedObject)
4929
{
5030
referencedObject = null;
5131

@@ -68,7 +48,7 @@ public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceab
6848
// Special case for Tag
6949
if (reference.Type == ReferenceType.Tag)
7050
{
71-
foreach (var tag in _tags)
51+
foreach (var tag in context.Tags)
7252
{
7353
if (tag.Name == reference.Id)
7454
{
@@ -84,7 +64,7 @@ public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceab
8464
var jsonPointer =
8565
new JsonPointer("#/" + GetReferenceTypeV2Name(reference.Type.Value) + "/" + reference.Id);
8666

87-
var node = _rootNode.Find(jsonPointer);
67+
var node = context.RootNode.Find(jsonPointer);
8868

8969
switch (reference.Type)
9070
{
@@ -116,7 +96,7 @@ public bool TryLoadReference(OpenApiReference reference, out IOpenApiReferenceab
11696
return true;
11797
}
11898

119-
private OpenApiReference ParseLocalReference(string localReference)
99+
private static OpenApiReference ParseLocalReference(string localReference)
120100
{
121101
if (string.IsNullOrWhiteSpace(localReference))
122102
{
@@ -244,5 +224,10 @@ public OpenApiReference ConvertToOpenApiReference(string reference, ReferenceTyp
244224

245225
throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, reference));
246226
}
227+
228+
public OpenApiDocument LoadDocument(RootNode rootNode)
229+
{
230+
return OpenApiV2Deserializer.LoadOpenApi(rootNode);
231+
}
247232
}
248233
}

0 commit comments

Comments
 (0)