Skip to content

Commit e26140b

Browse files
committed
- Allow composite expression with the following formats: - Strings with runtime expression embedded in { } - Plain strings without any runtime expression - Strings with {$xxxx} where xxxx doesn't constitute a valid runtime expression is NOT allowed. A set of braces with a dolalr sign denotes a runtime expression in a composite expression and should not be used in other random scenarios. - Adjust unit tests
1 parent e28c438 commit e26140b

18 files changed

+286
-88
lines changed

src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal static partial class OpenApiV3Deserializer
2020
private static readonly PatternFieldMap<OpenApiCallback> _callbackPatternFields =
2121
new PatternFieldMap<OpenApiCallback>
2222
{
23-
{s => s.StartsWith("$"), (o, p, n) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n))},
23+
{s => !s.StartsWith("x-"), (o, p, n) => o.AddPathItem(RuntimeExpression.Build(p), LoadPathItem(n))},
2424
{s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, LoadExtension(p,n))},
2525
};
2626

src/Microsoft.OpenApi/Expressions/CompositeExpression.cs

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

4-
using System;
54
using System.Collections.Generic;
65
using System.Linq;
7-
using System.Text;
86
using System.Text.RegularExpressions;
9-
using System.Threading.Tasks;
107

118
namespace Microsoft.OpenApi.Expressions
129
{
@@ -16,7 +13,8 @@ namespace Microsoft.OpenApi.Expressions
1613
public class CompositeExpression : RuntimeExpression
1714
{
1815
private readonly string template;
19-
private Regex expressionPattern = new Regex("{(?<exp>[^}]+)");
16+
private Regex expressionPattern = new Regex(@"{(?<exp>\$[^}]*)");
17+
2018
/// <summary>
2119
/// Expressions embedded into string literal
2220
/// </summary>

src/Microsoft.OpenApi/Expressions/MethodExpression.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,10 @@ public sealed class MethodExpression : RuntimeExpression
1818
/// </summary>
1919
public override string Expression { get; } = Method;
2020

21-
/// <summary>
22-
/// Gets the singleton.
23-
/// </summary>
24-
public static MethodExpression Instance = new MethodExpression();
25-
2621
/// <summary>
2722
/// Private constructor.
2823
/// </summary>
29-
private MethodExpression()
24+
public MethodExpression()
3025
{
3126
}
3227
}

src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,32 +34,27 @@ public static RuntimeExpression Build(string expression)
3434
throw Error.ArgumentNullOrWhiteSpace(nameof(expression));
3535
}
3636

37-
if (expression.Contains("{$"))
37+
if ( !expression.StartsWith( Prefix ) )
3838
{
39-
return new CompositeExpression(expression);
40-
}
41-
42-
if (!expression.StartsWith(Prefix))
43-
{
44-
throw new OpenApiException(string.Format(SRResource.RuntimeExpressionMustBeginWithDollar, expression));
39+
return new CompositeExpression( expression );
4540
}
4641

4742
// $url
4843
if (expression == UrlExpression.Url)
4944
{
50-
return UrlExpression.Instance;
45+
return new UrlExpression();
5146
}
5247

5348
// $method
5449
if (expression == MethodExpression.Method)
5550
{
56-
return MethodExpression.Instance;
51+
return new MethodExpression();
5752
}
5853

5954
// $statusCode
6055
if (expression == StatusCodeExpression.StatusCode)
6156
{
62-
return StatusCodeExpression.Instance;
57+
return new StatusCodeExpression();
6358
}
6459

6560
// $request.
@@ -78,7 +73,7 @@ public static RuntimeExpression Build(string expression)
7873
return new ResponseExpression(source);
7974
}
8075

81-
throw new OpenApiException(string.Format(SRResource.RuntimeExpressionHasInvalidFormat, expression));
76+
throw new OpenApiException( string.Format( SRResource.RuntimeExpressionHasInvalidFormat, expression ) );
8277
}
8378

8479
/// <summary>

src/Microsoft.OpenApi/Expressions/StatusCodeExpression.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,10 @@ public sealed class StatusCodeExpression : RuntimeExpression
1818
/// </summary>
1919
public override string Expression { get; } = StatusCode;
2020

21-
/// <summary>
22-
/// Gets the singleton.
23-
/// </summary>
24-
public static StatusCodeExpression Instance = new StatusCodeExpression();
25-
2621
/// <summary>
2722
/// Private constructor.
2823
/// </summary>
29-
private StatusCodeExpression()
24+
public StatusCodeExpression()
3025
{
3126
}
3227
}

src/Microsoft.OpenApi/Expressions/UrlExpression.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,10 @@ public sealed class UrlExpression : RuntimeExpression
1818
/// </summary>
1919
public override string Expression { get; } = Url;
2020

21-
/// <summary>
22-
/// Gets the singleton.
23-
/// </summary>
24-
public static UrlExpression Instance = new UrlExpression();
25-
2621
/// <summary>
2722
/// Private constructor.
2823
/// </summary>
29-
private UrlExpression()
24+
public UrlExpression()
3025
{
3126
}
3227
}

test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
<SignAssembly>true</SignAssembly>
1313
<AssemblyOriginatorKeyFile>..\..\src\Microsoft.OpenApi.snk</AssemblyOriginatorKeyFile>
1414
</PropertyGroup>
15-
<ItemGroup>
16-
<None Remove="V3Tests\Samples\OpenApiDocument\securedApi.yaml" />
17-
<None Remove="V3Tests\Samples\OpenApiOperation\securedOperation.yaml" />
18-
</ItemGroup>
1915
<ItemGroup>
2016
<EmbeddedResource Include="OpenApiReaderTests\Samples\unsupported.v1.yaml">
2117
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
@@ -74,6 +70,9 @@
7470
<EmbeddedResource Include="V2Tests\Samples\OpenApiSecurityScheme\oauth2PasswordSecurityScheme.yaml">
7571
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
7672
</EmbeddedResource>
73+
<EmbeddedResource Include="V3Tests\Samples\OpenApiCallback\multipleCallbacksWithReference.yaml">
74+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
75+
</EmbeddedResource>
7776
<EmbeddedResource Include="V3Tests\Samples\OpenApiCallback\basicCallback.yaml">
7877
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
7978
</EmbeddedResource>
@@ -161,9 +160,9 @@
161160
</PackageReference>
162161
<PackageReference Include="SharpYaml" Version="1.6.1">
163162
</PackageReference>
164-
<PackageReference Include="xunit" Version="2.3.0">
163+
<PackageReference Include="xunit" Version="2.3.1">
165164
</PackageReference>
166-
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0">
165+
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1">
167166
</PackageReference>
168167
</ItemGroup>
169168

@@ -205,7 +204,7 @@
205204
<EmbeddedResource Include="V2Tests\Samples\OpenApiSecurityScheme\oauth2ImplicitSecurityScheme.yaml">
206205
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
207206
</EmbeddedResource>
208-
<EmbeddedResource Include="V3Tests\Samples\OpenApiCallback\advancedCallbackWithReference.yaml">
207+
<EmbeddedResource Include="V3Tests\Samples\OpenApiCallback\callbackWithReference.yaml">
209208
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
210209
</EmbeddedResource>
211210
<EmbeddedResource Include="V3Tests\Samples\OpenApiInfo\advancedInfo.yaml">

test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public void ParseBodyParameterShouldSucceed()
3333
// Assert
3434
// Body parameter is currently not translated via LoadParameter.
3535
// This design may be revisited and this unit test may likely change.
36-
parameter.ShouldBeEquivalentTo(null);
36+
parameter.Should().BeNull();
3737
}
3838

3939
[Fact]
@@ -114,7 +114,7 @@ public void ParseFormDataParameterShouldSucceed()
114114
// Assert
115115
// Form data parameter is currently not translated via LoadParameter.
116116
// This design may be revisited and this unit test may likely change.
117-
parameter.ShouldBeEquivalentTo(null);
117+
parameter.Should().BeNull();
118118
}
119119

120120
[Fact]

test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ public void ParseBasicCallbackShouldSucceed()
7575
}
7676

7777
[Fact]
78-
public void ParseAdvancedCallbackWithReferenceShouldSucceed()
78+
public void ParseCallbackWithReferenceShouldSucceed()
7979
{
80-
using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedCallbackWithReference.yaml")))
80+
using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml")))
8181
{
8282
// Act
8383
var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic);
@@ -131,5 +131,142 @@ public void ParseAdvancedCallbackWithReferenceShouldSucceed()
131131
});
132132
}
133133
}
134+
135+
[Fact]
136+
public void ParseMultipleCallbacksWithReferenceShouldSucceed()
137+
{
138+
using ( var stream = Resources.GetStream( Path.Combine( SampleFolderPath, "multipleCallbacksWithReference.yaml" ) ) )
139+
{
140+
// Act
141+
var openApiDoc = new OpenApiStreamReader().Read( stream, out var diagnostic );
142+
143+
// Assert
144+
var path = openApiDoc.Paths.First().Value;
145+
var subscribeOperation = path.Operations[OperationType.Post];
146+
147+
diagnostic.ShouldBeEquivalentTo(
148+
new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 } );
149+
150+
var callback1 = subscribeOperation.Callbacks["simpleHook"];
151+
152+
callback1.ShouldBeEquivalentTo(
153+
new OpenApiCallback
154+
{
155+
PathItems =
156+
{
157+
[RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem {
158+
Operations = {
159+
[OperationType.Post] = new OpenApiOperation()
160+
{
161+
RequestBody = new OpenApiRequestBody
162+
{
163+
Content =
164+
{
165+
["application/json"] = new OpenApiMediaType
166+
{
167+
Schema = new OpenApiSchema()
168+
{
169+
Type = "object"
170+
}
171+
}
172+
}
173+
},
174+
Responses = {
175+
["200"]= new OpenApiResponse
176+
{
177+
Description = "Success"
178+
}
179+
}
180+
}
181+
}
182+
}
183+
},
184+
Reference = new OpenApiReference
185+
{
186+
Type = ReferenceType.Callback,
187+
Id = "simpleHook",
188+
}
189+
} );
190+
191+
var callback2 = subscribeOperation.Callbacks["callback2"];
192+
callback2.ShouldBeEquivalentTo(
193+
new OpenApiCallback
194+
{
195+
PathItems =
196+
{
197+
[RuntimeExpression.Build("/simplePath")]= new OpenApiPathItem {
198+
Operations = {
199+
[OperationType.Post] = new OpenApiOperation()
200+
{
201+
RequestBody = new OpenApiRequestBody
202+
{
203+
Description = "Callback 2",
204+
Content =
205+
{
206+
["application/json"] = new OpenApiMediaType
207+
{
208+
Schema = new OpenApiSchema()
209+
{
210+
Type = "string"
211+
}
212+
}
213+
}
214+
},
215+
Responses = {
216+
["400"]= new OpenApiResponse
217+
{
218+
Description = "Callback Response"
219+
}
220+
}
221+
}
222+
},
223+
}
224+
}
225+
} );
226+
227+
var callback3 = subscribeOperation.Callbacks["callback3"];
228+
callback3.ShouldBeEquivalentTo(
229+
new OpenApiCallback
230+
{
231+
PathItems =
232+
{
233+
[RuntimeExpression.Build(@"http://example.com?transactionId={$request.body#/id}&email={$request.body#/email}")] = new OpenApiPathItem {
234+
Operations = {
235+
[OperationType.Post] = new OpenApiOperation()
236+
{
237+
RequestBody = new OpenApiRequestBody
238+
{
239+
Content =
240+
{
241+
["application/xml"] = new OpenApiMediaType
242+
{
243+
Schema = new OpenApiSchema()
244+
{
245+
Type = "object"
246+
}
247+
}
248+
}
249+
},
250+
Responses = {
251+
["200"]= new OpenApiResponse
252+
{
253+
Description = "Success"
254+
},
255+
["401"]= new OpenApiResponse
256+
{
257+
Description = "Unauthorized"
258+
},
259+
["404"]= new OpenApiResponse
260+
{
261+
Description = "Not Found"
262+
}
263+
}
264+
}
265+
}
266+
}
267+
}
268+
} );
269+
}
270+
}
134271
}
135272
}

0 commit comments

Comments
 (0)