Skip to content

Commit 8bda200

Browse files
committed
Merge branch 'po/PowerShellSlicing' into po/PowerShellSlicingExtra
2 parents f4dee06 + 4eff4c4 commit 8bda200

File tree

7 files changed

+198
-46
lines changed

7 files changed

+198
-46
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.OpenApi.Any;
2+
using Microsoft.OpenApi.Interfaces;
3+
using System.Collections.Generic;
4+
5+
namespace Microsoft.OpenApi.Hidi.Extensions
6+
{
7+
internal static class OpenApiExtensibleExtensions
8+
{
9+
/// <summary>
10+
/// Gets an extension value from the extensions dictionary.
11+
/// </summary>
12+
/// <param name="extensions">A dictionary of <see cref="IOpenApiExtension"/>.</param>
13+
/// <param name="extensionKey">The key corresponding to the <see cref="IOpenApiExtension"/>.</param>
14+
/// <returns>A <see cref="string"/> value matching the provided extensionKey. Return null when extensionKey is not found. </returns>
15+
public static string GetExtension(this IDictionary<string, IOpenApiExtension> extensions, string extensionKey)
16+
{
17+
string extensionValue = null;
18+
if (extensions.TryGetValue(extensionKey, out var value) && value != null)
19+
{
20+
extensionValue = (value as OpenApiString)?.Value;
21+
}
22+
return extensionValue;
23+
}
24+
}
25+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Microsoft.OpenApi.Hidi.Extensions
6+
{
7+
/// <summary>
8+
/// Extension class for <see cref="string"/>.
9+
/// </summary>
10+
internal static class StringExtensions
11+
{
12+
/// <summary>
13+
/// Checks if the specified searchValue is equal to the target string based on the specified <see cref="StringComparison"/>.
14+
/// </summary>
15+
/// <param name="target">The target string to commpare to.</param>
16+
/// <param name="searchValue">The search string to seek.</param>
17+
/// <param name="comparison">The <see cref="StringComparison"/> to use. This defaults to <see cref="StringComparison.OrdinalIgnoreCase"/>.</param>
18+
/// <returns>true if the searchValue parameter occurs within this string; otherwise, false.</returns>
19+
public static bool IsEquals(this string target, string searchValue, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
20+
{
21+
if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(searchValue))
22+
{
23+
return false;
24+
}
25+
return target.Equals(searchValue, comparison);
26+
}
27+
28+
/// <summary>
29+
/// Splits the target string in substrings based on the specified char separator.
30+
/// </summary>
31+
/// <param name="target">The target string to split by char. </param>
32+
/// <param name="separator">The char separator.</param>
33+
/// <returns>An <see cref="IList{String}"/> containing substrings.</returns>
34+
public static IList<string> SplitByChar(this string target, char separator)
35+
{
36+
if (string.IsNullOrWhiteSpace(target))
37+
{
38+
return new List<string>();
39+
}
40+
return target.Split(new char[] { separator }, StringSplitOptions.RemoveEmptyEntries).ToList();
41+
}
42+
}
43+
}

src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs

Lines changed: 97 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text;
45
using System.Text.RegularExpressions;
56
using Humanizer;
67
using Humanizer.Inflections;
8+
using Microsoft.OpenApi.Hidi.Extensions;
79
using Microsoft.OpenApi.Models;
810
using Microsoft.OpenApi.Services;
911

@@ -29,7 +31,7 @@ static PowerShellFormatter()
2931
Vocabularies.Default.AddSingular("(statistics)$", "$1");
3032
}
3133

32-
//TODO: FHL for PS
34+
//TODO: FHL taks for PS
3335
// Fixes (Order matters):
3436
// 1. Singularize operationId operationIdSegments.
3537
// 2. Add '_' to verb in an operationId.
@@ -50,9 +52,9 @@ public override void Visit(OpenApiSchema schema)
5052

5153
public override void Visit(OpenApiPathItem pathItem)
5254
{
53-
if (pathItem.Operations.ContainsKey(OperationType.Put))
55+
if (pathItem.Operations.TryGetValue(OperationType.Put, out var value))
5456
{
55-
var operationId = pathItem.Operations[OperationType.Put].OperationId;
57+
var operationId = value.OperationId;
5658
pathItem.Operations[OperationType.Put].OperationId = ResolvePutOperationId(operationId);
5759
}
5860

@@ -61,55 +63,38 @@ public override void Visit(OpenApiPathItem pathItem)
6163

6264
public override void Visit(OpenApiOperation operation)
6365
{
64-
if (operation.OperationId == null)
66+
if (string.IsNullOrWhiteSpace(operation.OperationId))
6567
throw new ArgumentNullException(nameof(operation.OperationId), $"OperationId is required {PathString}");
6668

6769
var operationId = operation.OperationId;
70+
var operationTypeExtension = operation.Extensions.GetExtension("x-ms-docs-operation-type");
71+
if (operationTypeExtension.IsEquals("function"))
72+
operation.Parameters = ResolveFunctionParameters(operation.Parameters);
6873

74+
// Order matters. Resolve operationId.
6975
operationId = RemoveHashSuffix(operationId);
76+
if (operationTypeExtension.IsEquals("action") || operationTypeExtension.IsEquals("function"))
77+
operationId = RemoveKeyTypeSegment(operationId, operation.Parameters);
78+
operationId = SingularizeAndDeduplicateOperationId(operationId.SplitByChar('.'));
7079
operationId = ResolveODataCastOperationId(operationId);
7180
operationId = ResolveByRefOperationId(operationId);
72-
73-
74-
var operationIdSegments = operationId.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).ToList();
75-
operationId = SingularizeAndDeduplicateOperationId(operationIdSegments);
81+
// Verb segment resolution should always be last. user.get -> user_Get
82+
operationId = ResolveVerbSegmentInOpertationId(operationId);
7683

7784
operation.OperationId = operationId;
7885
base.Visit(operation);
7986
}
8087

81-
private void AddAddtionalPropertiesToSchema(OpenApiSchema schema)
88+
private static string ResolveVerbSegmentInOpertationId(string operationId)
8289
{
83-
if (schema != null && !_schemaLoop.Contains(schema) && "object".Equals(schema?.Type, StringComparison.OrdinalIgnoreCase))
84-
{
85-
schema.AdditionalProperties = new OpenApiSchema() { Type = "object" };
86-
87-
/* Because 'additionalProperties' are now being walked,
88-
* we need a way to keep track of visited schemas to avoid
89-
* endlessly creating and walking them in an infinite recursion.
90-
*/
91-
_schemaLoop.Push(schema.AdditionalProperties);
92-
}
93-
}
94-
95-
private static void ResolveOneOfSchema(OpenApiSchema schema)
96-
{
97-
if (schema.OneOf?.Any() ?? false)
98-
{
99-
var newSchema = schema.OneOf.FirstOrDefault();
100-
schema.OneOf = null;
101-
FlattenSchema(schema, newSchema);
102-
}
103-
}
104-
105-
private static void ResolveAnyOfSchema(OpenApiSchema schema)
106-
{
107-
if (schema.AnyOf?.Any() ?? false)
108-
{
109-
var newSchema = schema.AnyOf.FirstOrDefault();
110-
schema.AnyOf = null;
111-
FlattenSchema(schema, newSchema);
112-
}
90+
var charPos = operationId.LastIndexOf('.', operationId.Length - 1);
91+
if (operationId.Contains('_') || charPos < 0)
92+
return operationId;
93+
// TODO: Optimize this call.
94+
var newOperationId = new StringBuilder(operationId);
95+
newOperationId[charPos] = '_';
96+
operationId = newOperationId.ToString();
97+
return operationId;
11398
}
11499

115100
private static string ResolvePutOperationId(string operationId)
@@ -158,6 +143,79 @@ private static string RemoveHashSuffix(string operationId)
158143
return s_hashSuffixRegex.Match(operationId).Value;
159144
}
160145

146+
private static string RemoveKeyTypeSegment(string operationId, IList<OpenApiParameter> parameters)
147+
{
148+
var segments = operationId.SplitByChar('.');
149+
foreach (var parameter in parameters)
150+
{
151+
var keyTypeExtension = parameter.Extensions.GetExtension("x-ms-docs-key-type");
152+
if (keyTypeExtension != null)
153+
{
154+
if (operationId.Contains(keyTypeExtension))
155+
{
156+
segments.Remove(keyTypeExtension);
157+
}
158+
}
159+
}
160+
return string.Join(".", segments);
161+
}
162+
163+
private static IList<OpenApiParameter> ResolveFunctionParameters(IList<OpenApiParameter> parameters)
164+
{
165+
foreach (var parameter in parameters)
166+
{
167+
if (parameter.Content?.Any() ?? false)
168+
{
169+
// Replace content with a schema object of type array
170+
// for structured or collection-valued function parameters
171+
parameter.Content = null;
172+
parameter.Schema = new OpenApiSchema
173+
{
174+
Type = "array",
175+
Items = new OpenApiSchema
176+
{
177+
Type = "string"
178+
}
179+
};
180+
}
181+
}
182+
return parameters;
183+
}
184+
185+
private void AddAddtionalPropertiesToSchema(OpenApiSchema schema)
186+
{
187+
if (schema != null && !_schemaLoop.Contains(schema) && "object".Equals(schema?.Type, StringComparison.OrdinalIgnoreCase))
188+
{
189+
schema.AdditionalProperties = new OpenApiSchema() { Type = "object" };
190+
191+
/* Because 'additionalProperties' are now being walked,
192+
* we need a way to keep track of visited schemas to avoid
193+
* endlessly creating and walking them in an infinite recursion.
194+
*/
195+
_schemaLoop.Push(schema.AdditionalProperties);
196+
}
197+
}
198+
199+
private static void ResolveOneOfSchema(OpenApiSchema schema)
200+
{
201+
if (schema.OneOf?.Any() ?? false)
202+
{
203+
var newSchema = schema.OneOf.FirstOrDefault();
204+
schema.OneOf = null;
205+
FlattenSchema(schema, newSchema);
206+
}
207+
}
208+
209+
private static void ResolveAnyOfSchema(OpenApiSchema schema)
210+
{
211+
if (schema.AnyOf?.Any() ?? false)
212+
{
213+
var newSchema = schema.AnyOf.FirstOrDefault();
214+
schema.AnyOf = null;
215+
FlattenSchema(schema, newSchema);
216+
}
217+
}
218+
161219
private static void FlattenSchema(OpenApiSchema schema, OpenApiSchema newSchema)
162220
{
163221
if (newSchema != null)

src/Microsoft.OpenApi.Hidi/OpenApiService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
6868
OpenApiDocument document = await GetOpenApi(options.OpenApi, options.Csdl, options.CsdlFilter, options.SettingsConfig, options.InlineExternal, logger, cancellationToken, options.MetadataVersion);
6969
if (options.FilterOptions != null)
7070
document = await FilterOpenApiDocument(options.FilterOptions.FilterByOperationIds, options.FilterOptions.FilterByTags, options.FilterOptions.FilterByCollection, document, logger, cancellationToken);
71-
// TODO: Handle PS formating
71+
7272
var languageFormat = options.SettingsConfig.GetSection("LanguageFormat").Value;
73-
if (!string.IsNullOrWhiteSpace(languageFormat) && languageFormat.Equals("PowerShell", StringComparison.InvariantCultureIgnoreCase))
73+
if (Extensions.StringExtensions.IsEquals(languageFormat, "PowerShell"))
7474
{
7575
// PowerShell Walker.
7676
var powerShellFormatter = new PowerShellFormatter();

src/Microsoft.OpenApi.Hidi/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ internal static RootCommand CreateRootCommand()
3232

3333
var transformCommand = new Command("transform");
3434
transformCommand.AddOptions(commandOptions.GetAllCommandOptions());
35-
3635
transformCommand.Handler = new TransformCommandHandler(commandOptions);
3736

3837
var showCommand = new Command("show");

src/Microsoft.OpenApi/Services/CopyReferences.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ public override void Visit(IOpenApiReferenceable referenceable)
5252
}
5353
break;
5454

55+
case OpenApiRequestBody requestBody:
56+
EnsureComponentsExists();
57+
EnsureResponsesExists();
58+
EnsurRequestBodiesExists();
59+
if (!Components.RequestBodies.ContainsKey(requestBody.Reference.Id))
60+
{
61+
Components.RequestBodies.Add(requestBody.Reference.Id, requestBody);
62+
}
63+
break;
64+
5565
default:
5666
break;
5767
}
@@ -108,5 +118,13 @@ private void EnsureResponsesExists()
108118
_target.Components.Responses = new Dictionary<string, OpenApiResponse>();
109119
}
110120
}
121+
122+
private void EnsurRequestBodiesExists()
123+
{
124+
if (_target.Components.RequestBodies == null)
125+
{
126+
_target.Components.RequestBodies = new Dictionary<string, OpenApiRequestBody>();
127+
}
128+
}
111129
}
112130
}

src/Microsoft.OpenApi/Services/OpenApiFilterService.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static class OpenApiFilterService
6969
{
7070
var apiVersion = source.Info.Version;
7171

72-
var sources = new Dictionary<string, OpenApiDocument> {{ apiVersion, source}};
72+
var sources = new Dictionary<string, OpenApiDocument> { { apiVersion, source } };
7373
var rootNode = CreateOpenApiUrlTreeNode(sources);
7474

7575
// Iterate through urls dictionary and fetch operations for each url
@@ -135,7 +135,7 @@ public static OpenApiDocument CreateFilteredDocument(OpenApiDocument source, Fun
135135
Extensions = source.Info.Extensions
136136
},
137137

138-
Components = new OpenApiComponents {SecuritySchemes = source.Components.SecuritySchemes},
138+
Components = new OpenApiComponents { SecuritySchemes = source.Components.SecuritySchemes },
139139
SecurityRequirements = source.SecurityRequirements,
140140
Servers = source.Servers
141141
};
@@ -199,7 +199,7 @@ public static OpenApiUrlTreeNode CreateOpenApiUrlTreeNode(Dictionary<string, Ope
199199
}
200200
return rootNode;
201201
}
202-
202+
203203
private static IDictionary<OperationType, OpenApiOperation> GetOpenApiOperations(OpenApiUrlTreeNode rootNode, string relativeUrl, string label)
204204
{
205205
if (relativeUrl.Equals("/", StringComparison.Ordinal) && rootNode.HasOperations(label))
@@ -328,6 +328,15 @@ private static bool AddReferences(OpenApiComponents newComponents, OpenApiCompon
328328
target.Responses.Add(item);
329329
}
330330
}
331+
332+
foreach (var item in newComponents.RequestBodies)
333+
{
334+
if (!target.RequestBodies.ContainsKey(item.Key))
335+
{
336+
moreStuff = true;
337+
target.RequestBodies.Add(item);
338+
}
339+
}
331340
return moreStuff;
332341
}
333342

@@ -342,7 +351,7 @@ private static string ExtractPath(string url, IList<OpenApiServer> serverList)
342351
continue;
343352
}
344353

345-
var urlComponents = url.Split(new[]{ serverUrl }, StringSplitOptions.None);
354+
var urlComponents = url.Split(new[] { serverUrl }, StringSplitOptions.None);
346355
queryPath = urlComponents[1];
347356
}
348357

0 commit comments

Comments
 (0)