Skip to content

Commit c9d1bae

Browse files
authored
Merge pull request #258 from Microsoft/dm/read-fragment
Read fragments of OpenAPI Specifications
2 parents c94ef0f + 498f51d commit c9d1bae

File tree

16 files changed

+460
-52
lines changed

16 files changed

+460
-52
lines changed

src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ internal interface IOpenApiVersionService
2323
OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type);
2424

2525
/// <summary>
26-
/// Function that converts a MapNode into a Tag object in a version specific way
26+
/// Loads an OpenAPI Element from a document fragment
2727
/// </summary>
28-
Func<MapNode, OpenApiTag> TagLoader { get; }
28+
/// <typeparam name="T">Type of element to load</typeparam>
29+
/// <param name="node">document fragment node</param>
30+
/// <returns>Instance of OpenAPIElement</returns>
31+
T LoadElement<T>(ParseNode node) where T : IOpenApiElement;
2932

3033
/// <summary>
3134
/// Converts a generic RootNode instance into a strongly typed OpenApiDocument

src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<Company>Microsoft</Company>
1111
<Title>Microsoft.OpenApi.Readers</Title>
1212
<PackageId>Microsoft.OpenApi.Readers</PackageId>
13-
<Version>1.0.0</Version>
13+
<Version>1.1.0-preview.1</Version>
1414
<Description>OpenAPI.NET Readers for JSON and YAML documents</Description>
1515
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
1616
<PackageTags>OpenAPI .NET</PackageTags>

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using Microsoft.OpenApi.Exceptions;
88
using Microsoft.OpenApi.Extensions;
9+
using Microsoft.OpenApi.Interfaces;
910
using Microsoft.OpenApi.Models;
1011
using Microsoft.OpenApi.Readers.Interface;
1112
using Microsoft.OpenApi.Readers.Services;
@@ -102,6 +103,61 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic)
102103
return document;
103104
}
104105

106+
/// <summary>
107+
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
108+
/// </summary>
109+
/// <param name="input">Stream containing OpenAPI description to parse.</param>
110+
/// <param name="version">Version of the OpenAPI specification that the fragment conforms to.</param>
111+
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
112+
/// <returns>Instance of newly created OpenApiDocument</returns>
113+
public T ReadFragment<T>(Stream input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement
114+
{
115+
ParsingContext context;
116+
YamlDocument yamlDocument;
117+
diagnostic = new OpenApiDiagnostic();
118+
119+
// Parse the YAML/JSON
120+
try
121+
{
122+
yamlDocument = LoadYamlDocument(input);
123+
}
124+
catch (SyntaxErrorException ex)
125+
{
126+
diagnostic.Errors.Add(new OpenApiReaderError(ex));
127+
return default(T);
128+
}
129+
130+
context = new ParsingContext
131+
{
132+
ExtensionParsers = _settings.ExtensionParsers
133+
};
134+
135+
IOpenApiElement element = null;
136+
137+
try
138+
{
139+
// Parse the OpenAPI element
140+
element = context.ParseFragment<T>(yamlDocument, version, diagnostic);
141+
}
142+
catch (OpenApiException ex)
143+
{
144+
diagnostic.Errors.Add(new OpenApiError(ex));
145+
}
146+
147+
// Validate the element
148+
if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0)
149+
{
150+
var errors = element.Validate(_settings.RuleSet);
151+
foreach (var item in errors)
152+
{
153+
diagnostic.Errors.Add(item);
154+
}
155+
}
156+
157+
return (T)element;
158+
}
159+
160+
105161
/// <summary>
106162
/// Helper method to turn streams into YamlDocument
107163
/// </summary>

src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33

44
using System.IO;
5+
using Microsoft.OpenApi.Interfaces;
56
using Microsoft.OpenApi.Models;
67
using Microsoft.OpenApi.Readers.Interface;
78

@@ -20,7 +21,7 @@ public class OpenApiStringReader : IOpenApiReader<string, OpenApiDiagnostic>
2021
/// <param name="settings"></param>
2122
public OpenApiStringReader(OpenApiReaderSettings settings = null)
2223
{
23-
_settings = settings ?? new OpenApiReaderSettings();
24+
_settings = settings ?? new OpenApiReaderSettings();
2425
}
2526

2627
/// <summary>
@@ -38,5 +39,21 @@ public OpenApiDocument Read(string input, out OpenApiDiagnostic diagnostic)
3839
return new OpenApiStreamReader(_settings).Read(memoryStream, out diagnostic);
3940
}
4041
}
42+
43+
/// <summary>
44+
/// Reads the string input and parses it into an Open API element.
45+
/// </summary>
46+
public T ReadFragment<T>(string input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement
47+
{
48+
using (var memoryStream = new MemoryStream())
49+
{
50+
var writer = new StreamWriter(memoryStream);
51+
writer.Write(input);
52+
writer.Flush();
53+
memoryStream.Position = 0;
54+
55+
return new OpenApiStreamReader(_settings).ReadFragment<T>(memoryStream, version, out diagnostic);
56+
}
57+
}
4158
}
4259
}

src/Microsoft.OpenApi.Readers/ParsingContext.cs

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public class ParsingContext
3232
/// <summary>
3333
/// Initiates the parsing process. Not thread safe and should only be called once on a parsing context
3434
/// </summary>
35-
/// <param name="yamlDocument"></param>
36-
/// <param name="diagnostic"></param>
35+
/// <param name="yamlDocument">Yaml document to parse.</param>
36+
/// <param name="diagnostic">Diagnostic object which will return diagnostic results of the operation.</param>
3737
/// <returns>An OpenApiDocument populated based on the passed yamlDocument </returns>
3838
internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diagnostic)
3939
{
@@ -47,13 +47,13 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diag
4747
{
4848
case string version when version == "2.0":
4949
VersionService = new OpenApiV2VersionService();
50-
doc = this.VersionService.LoadDocument(this.RootNode);
50+
doc = VersionService.LoadDocument(RootNode);
5151
diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi2_0;
5252
break;
5353

5454
case string version when version.StartsWith("3.0"):
55-
this.VersionService = new OpenApiV3VersionService();
56-
doc = this.VersionService.LoadDocument(this.RootNode);
55+
VersionService = new OpenApiV3VersionService();
56+
doc = VersionService.LoadDocument(RootNode);
5757
diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_0;
5858
break;
5959

@@ -64,6 +64,34 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diag
6464
return doc;
6565
}
6666

67+
/// <summary>
68+
/// Initiates the parsing process of a fragment. Not thread safe and should only be called once on a parsing context
69+
/// </summary>
70+
/// <param name="yamlDocument"></param>
71+
/// <param name="version">OpenAPI version of the fragment</param>
72+
/// <param name="diagnostic">Diagnostic object which will return diagnostic results of the operation.</param>
73+
/// <returns>An OpenApiDocument populated based on the passed yamlDocument </returns>
74+
internal T ParseFragment<T>(YamlDocument yamlDocument, OpenApiSpecVersion version, OpenApiDiagnostic diagnostic) where T: IOpenApiElement
75+
{
76+
var node = ParseNode.Create(this, diagnostic, yamlDocument.RootNode);
77+
78+
T element = default(T);
79+
80+
switch (version)
81+
{
82+
case OpenApiSpecVersion.OpenApi2_0:
83+
VersionService = new OpenApiV2VersionService();
84+
element = this.VersionService.LoadElement<T>(node);
85+
break;
86+
87+
case OpenApiSpecVersion.OpenApi3_0:
88+
this.VersionService = new OpenApiV3VersionService();
89+
element = this.VersionService.LoadElement<T>(node);
90+
break;
91+
}
92+
93+
return element;
94+
}
6795

6896
/// <summary>
6997
/// Gets the version of the Open API document.
@@ -82,20 +110,6 @@ private static string GetVersion(RootNode rootNode)
82110
return versionNode?.GetScalarValue();
83111
}
84112

85-
private void ComputeTags(List<OpenApiTag> tags, Func<MapNode, OpenApiTag> loadTag)
86-
{
87-
// Precompute the tags array so that each tag reference does not require a new deserialization.
88-
var tagListPointer = new JsonPointer("#/tags");
89-
90-
var tagListNode = RootNode.Find(tagListPointer);
91-
92-
if (tagListNode != null && tagListNode is ListNode)
93-
{
94-
var tagListNodeAsListNode = (ListNode)tagListNode;
95-
tags.AddRange(tagListNodeAsListNode.CreateList(loadTag));
96-
}
97-
}
98-
99113
/// <summary>
100114
/// Service providing all Version specific conversion functions
101115
/// </summary>
@@ -108,7 +122,6 @@ internal IOpenApiVersionService VersionService
108122
set
109123
{
110124
_versionService = value;
111-
ComputeTags(Tags, VersionService.TagLoader);
112125
}
113126
}
114127

src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs

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

4+
using System;
5+
using Microsoft.OpenApi.Any;
46
using Microsoft.OpenApi.Extensions;
57
using Microsoft.OpenApi.Models;
8+
using Microsoft.OpenApi.Readers.Exceptions;
69
using Microsoft.OpenApi.Readers.ParseNodes;
710

811
namespace Microsoft.OpenApi.Readers.V2
@@ -22,41 +25,107 @@ internal static partial class OpenApiV2Deserializer
2225
}
2326
},
2427
{
25-
"required", (o, n) =>
28+
"type", (o, n) =>
2629
{
27-
o.Required = bool.Parse(n.GetScalarValue());
30+
GetOrCreateSchema(o).Type = n.GetScalarValue();
2831
}
2932
},
3033
{
31-
"deprecated", (o, n) =>
34+
"format", (o, n) =>
3235
{
33-
o.Deprecated = bool.Parse(n.GetScalarValue());
36+
GetOrCreateSchema(o).Format = n.GetScalarValue();
3437
}
3538
},
3639
{
37-
"allowReserved", (o, n) =>
40+
"items", (o, n) =>
3841
{
39-
o.AllowReserved = bool.Parse(n.GetScalarValue());
42+
GetOrCreateSchema(o).Items = LoadSchema(n);
4043
}
4144
},
4245
{
43-
"style", (o, n) =>
46+
"collectionFormat", (o, n) =>
4447
{
45-
o.Style = n.GetScalarValue().GetEnumFromDisplayName<ParameterStyle>();
48+
LoadStyle(o, n.GetScalarValue());
4649
}
4750
},
4851
{
49-
"type", (o, n) =>
52+
"default", (o, n) =>
5053
{
51-
GetOrCreateSchema(o).Type = n.GetScalarValue();
54+
GetOrCreateSchema(o).Default = n.CreateAny();
55+
}
56+
},
57+
{
58+
"maximum", (o, n) =>
59+
{
60+
GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue());
5261
}
5362
},
5463
{
55-
"format", (o, n) =>
64+
"exclusiveMaximum", (o, n) =>
5665
{
57-
GetOrCreateSchema(o).Format = n.GetScalarValue();
66+
GetOrCreateSchema(o).ExclusiveMaximum = bool.Parse(n.GetScalarValue());
67+
}
68+
},
69+
{
70+
"minimum", (o, n) =>
71+
{
72+
GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue());
73+
}
74+
},
75+
{
76+
"exclusiveMinimum", (o, n) =>
77+
{
78+
GetOrCreateSchema(o).ExclusiveMinimum = bool.Parse(n.GetScalarValue());
79+
}
80+
},
81+
{
82+
"maxLength", (o, n) =>
83+
{
84+
GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue());
85+
}
86+
},
87+
{
88+
"minLength", (o, n) =>
89+
{
90+
GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue());
91+
}
92+
},
93+
{
94+
"pattern", (o, n) =>
95+
{
96+
GetOrCreateSchema(o).Pattern = n.GetScalarValue();
97+
}
98+
},
99+
{
100+
"maxItems", (o, n) =>
101+
{
102+
GetOrCreateSchema(o).MaxItems = int.Parse(n.GetScalarValue());
103+
}
104+
},
105+
{
106+
"minItems", (o, n) =>
107+
{
108+
GetOrCreateSchema(o).MinItems = int.Parse(n.GetScalarValue());
109+
}
110+
},
111+
{
112+
"uniqueItems", (o, n) =>
113+
{
114+
GetOrCreateSchema(o).UniqueItems = bool.Parse(n.GetScalarValue());
58115
}
59116
},
117+
{
118+
"multipleOf", (o, n) =>
119+
{
120+
GetOrCreateSchema(o).MultipleOf = decimal.Parse(n.GetScalarValue());
121+
}
122+
},
123+
{
124+
"enum", (o, n) =>
125+
{
126+
GetOrCreateSchema(o).Enum = n.CreateListOfAny();
127+
}
128+
}
60129
};
61130

62131
private static readonly PatternFieldMap<OpenApiHeader> _headerPatternFields = new PatternFieldMap<OpenApiHeader>
@@ -82,5 +151,25 @@ public static OpenApiHeader LoadHeader(ParseNode node)
82151

83152
return header;
84153
}
154+
155+
private static void LoadStyle(OpenApiHeader header, string style)
156+
{
157+
switch (style)
158+
{
159+
case "csv":
160+
header.Style = ParameterStyle.Simple;
161+
return;
162+
case "ssv":
163+
header.Style = ParameterStyle.SpaceDelimited;
164+
return;
165+
case "pipes":
166+
header.Style = ParameterStyle.PipeDelimited;
167+
return;
168+
case "tsv":
169+
throw new NotSupportedException();
170+
default:
171+
throw new OpenApiReaderException("Unrecognized header style: " + style);
172+
}
173+
}
85174
}
86175
}

0 commit comments

Comments
 (0)