Skip to content

Commit

Permalink
- draft go snippets generation
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent Biret <[email protected]>
  • Loading branch information
baywet committed Nov 2, 2021
1 parent 6a03cb0 commit 830ac94
Show file tree
Hide file tree
Showing 7 changed files with 571 additions and 1 deletion.
210 changes: 210 additions & 0 deletions CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using CodeSnippetsReflection.OpenAPI.LanguageGenerators;
using Microsoft.OpenApi.Services;
using Xunit;

namespace CodeSnippetsReflection.OpenAPI.Test
{
public class GoGeneratorTests {
private const string ServiceRootUrl = "https://graph.microsoft.com/v1.0";
private static OpenApiUrlTreeNode _v1TreeNode;
private static async Task<OpenApiUrlTreeNode> GetV1TreeNode() {
if(_v1TreeNode == null) {
_v1TreeNode = await SnippetModelTests.GetTreeNode("https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml");
}
return _v1TreeNode;
}
private readonly GoGenerator _generator = new();
[Fact]
public async Task GeneratesTheCorrectFluentAPIPath() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains(".Me().Messages()", result);
}
[Fact]
public async Task GeneratesTheCorrectFluentAPIPathForIndexedCollections() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages/{{message-id}}");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("messageId := \"message-id\"", result);
Assert.Contains(".Me().MessagesById(&messageId)", result);
}
[Fact]
public async Task GeneratesTheSnippetHeader() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("graphClient := msgraphsdk.NewGraphServiceClient(requestAdapter)", result);
}
[Fact]
public async Task GeneratesTheGetMethodCall() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Get", result);
}
[Fact]
public async Task GeneratesThePostMethodCall() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/me/messages");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Post", result);
}
[Fact]
public async Task GeneratesThePatchMethodCall() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Patch, $"{ServiceRootUrl}/me/messages/{{message-id}}");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Patch", result);
}
[Fact]
public async Task GeneratesThePutMethodCall() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Put, $"{ServiceRootUrl}/applications/{{application-id}}/logo");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Put", result);
}
[Fact]
public async Task GeneratesTheDeleteMethodCall() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Delete, $"{ServiceRootUrl}/me/messages/{{message-id}}");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Delete", result);
Assert.DoesNotContain("result, err :=", result);
}
[Fact]
public async Task WritesTheRequestPayload() {
const string userJsonObject = "{\r\n \"accountEnabled\": true,\r\n " +
"\"displayName\": \"displayName-value\",\r\n " +
"\"mailNickname\": \"mailNickname-value\",\r\n " +
"\"userPrincipalName\": \"[email protected]\",\r\n " +
" \"passwordProfile\" : {\r\n \"forceChangePasswordNextSignIn\": true,\r\n \"password\": \"password-value\"\r\n }\r\n}";//nested passwordProfile Object

using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/users")
{
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json")
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("msgraphsdk.NewUser", result);
Assert.Contains("SetAccountEnabled(true)", result);
Assert.Contains("SetPasswordProfile = new PasswordProfile", result);
Assert.Contains("SetDisplayName(\"displayName-value\")", result);
}
[Fact]
public async Task WritesALongAndFindsAnAction() {
const string userJsonObject = "{\r\n \"chainId\": 10\r\n\r\n}";

using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/teams/{{team-id}}/sendActivityNotification")
{
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json")
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("10L", result);
Assert.DoesNotContain("microsoft.graph", result);
}
[Fact]
public async Task WritesADouble() {
const string userJsonObject = "{\r\n \"minimumAttendeePercentage\": 10\r\n\r\n}";

using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/me/findMeetingTimes")
{
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json")
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("10d", result);
}
[Fact]
public async Task GeneratesABinaryPayload() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Put, $"{ServiceRootUrl}/applications/{{application-id}}/logo") {
Content = new ByteArrayContent(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 })
};
requestPayload.Content.Headers.ContentType = new ("application/octect-stream");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("new MemoryStream", result);
}
[Fact]
public async Task GeneratesABase64UrlPayload() {
const string userJsonObject = "{\r\n \"contentBytes\": \"wiubviuwbegviwubiu\"\r\n\r\n}";
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/chats/{{chat-id}}/messages/{{chatMessage-id}}/hostedContents") {
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json")
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Encoding.ASCII.GetBytes", result);
}
[Fact]
public async Task GeneratesADateTimeOffsetPayload() {
const string userJsonObject = "{\r\n \"receivedDateTime\": \"2021-08-30T20:00:00:00Z\"\r\n\r\n}";
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/me/messages") {
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json")
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("DateTimeOffset.Parse", result);
}
[Fact]
public async Task GeneratesAnArrayPayloadInAdditionalData() {
const string userJsonObject = "{\r\n \"[email protected]\": [\r\n \"https://graph.microsoft.com/v1.0/directoryObjects/{id}\",\r\n \"https://graph.microsoft.com/v1.0/directoryObjects/{id}\",\r\n \"https://graph.microsoft.com/v1.0/directoryObjects/{id}\"\r\n ]\r\n}";
using var requestPayload = new HttpRequestMessage(HttpMethod.Patch, $"{ServiceRootUrl}/groups/{{group-id}}") {
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json")
};
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("new List", result);
Assert.Contains("AdditionalData", result);
Assert.Contains("members", result); // property name hasn't been changed
}
[Fact]
public async Task GeneratesSelectQueryParameters() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me?$select=displayName,id");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("displayName", result);
Assert.Contains("(q) =>", result);
}
[Fact]
public async Task GeneratesCountBooleanQueryParameters() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/users?$count=true&$select=displayName,id");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("displayName", result);
Assert.DoesNotContain("\"true\"", result);
Assert.Contains("true", result);
}
[Fact]
public async Task GeneratesSkipQueryParameters() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/users?$skip=10");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.DoesNotContain("\"10\"", result);
Assert.Contains("10", result);
}
[Fact]
public async Task GeneratesSelectExpandQueryParameters() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/groups?$expand=members($select=id,displayName)");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Expand", result);
Assert.Contains("members($select=id,displayName)", result);
Assert.DoesNotContain("Select", result);
}
[Fact]
public async Task GeneratesRequestHeaders() {
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/groups");
requestPayload.Headers.Add("ConsistencyLevel", "eventual");
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode());
var result = _generator.GenerateCodeSnippet(snippetModel);
Assert.Contains("Add(\"ConsistencyLevel\", \"eventual\");", result);
Assert.Contains("(h) =>", result);
}
//TODO test for DateTimeOffset
}
}
23 changes: 23 additions & 0 deletions CodeSnippetsReflection.OpenAPI/KiotaOpenApiOperationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// THIS CLASS IS COPIED FROM KIOTA TO GET THE SAME NAMING CONVENTIONS, WE SHOULD FIND A WAY TO MUTUALIZE THE CODE
using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.Models;

namespace CodeSnippetsReflection.OpenAPI {
public static class OpenApiOperationExtensions {
private static readonly HashSet<string> successCodes = new() {"200", "201", "202"}; //204 excluded as it won't have a schema
public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation)
{
// Return Schema that represents all the possible success responses!
// For the moment assume 200s and application/json
var schemas = operation.Responses.Where(r => successCodes.Contains(r.Key))
.SelectMany(re => re.Value.Content)
.Where(c => c.Key == "application/json")
.Select(co => co.Value.Schema)
.Where(s => s is not null);

return schemas.FirstOrDefault();
}
}

}
13 changes: 13 additions & 0 deletions CodeSnippetsReflection.OpenAPI/KiotaOpenApiReferenceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// THIS CLASS IS COPIED FROM KIOTA TO GET THE SAME NAMING CONVENTIONS, WE SHOULD FIND A WAY TO MUTUALIZE THE CODE
using CodeSnippetsReflection.StringExtensions;
using Microsoft.OpenApi.Models;

namespace CodeSnippetsReflection.OpenAPI {
public static class OpenApiReferenceExtensions {
public static string GetClassName(this OpenApiReference reference) {
var referenceId = reference?.Id;
return referenceId?[((referenceId?.LastIndexOf('.') ?? 0) + 1)..]
?.ToFirstCharacterUpperCase();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// ------------------------------------------------------------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using CodeSnippetsReflection.StringExtensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Services;

// THIS CLASS IS COPIED FROM KIOTA TO GET THE SAME NAMING CONVENTIONS, WE SHOULD FIND A WAY TO MUTUALIZE THE CODE
namespace CodeSnippetsReflection.OpenAPI {

public static class KiotaOpenApiUrlTreeNodeExtensions {
private static readonly Regex PathParametersRegex = new(@"(?:\w+)?=?'?\{(?<paramName>\w+)\}'?,?", RegexOptions.Compiled);
private static readonly char requestParametersChar = '{';
private static readonly char requestParametersEndChar = '}';
private static readonly char requestParametersSectionChar = '(';
private static readonly char requestParametersSectionEndChar = ')';
private static readonly MatchEvaluator requestParametersMatchEvaluator = (match) => {
return "With" + match.Groups["paramName"].Value.ToFirstCharacterUpperCase();
};
private static readonly Regex idClassNameCleanup = new(@"Id\d?$", RegexOptions.Compiled);
///<summary>
/// Returns the class name for the node with more or less precision depending on the provided arguments
///</summary>
public static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) {
var rawClassName = (operation?.GetResponseSchema()?.Reference?.GetClassName() ??
CleanupParametersFromPath(currentNode.Segment)?.ReplaceValueIdentifier())
.TrimEnd(requestParametersEndChar)
.TrimStart(requestParametersChar)
.TrimStart('$') //$ref from OData
.Split('-')
.First();
if((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup.IsMatch(rawClassName))
rawClassName = idClassNameCleanup.Replace(rawClassName, string.Empty);
return prefix + rawClassName?.Split('.', StringSplitOptions.RemoveEmptyEntries)?.LastOrDefault() + suffix;
}
public static bool DoesNodeBelongToItemSubnamespace(this OpenApiUrlTreeNode currentNode) => currentNode.IsPathSegmentWithSingleSimpleParameter();
public static bool IsPathSegmentWithSingleSimpleParameter(this OpenApiUrlTreeNode currentNode) =>
currentNode?.Segment.IsPathSegmentWithSingleSimpleParameter() ?? false;
private static bool IsPathSegmentWithSingleSimpleParameter(this string currentSegment)
{
return (currentSegment?.StartsWith(requestParametersChar) ?? false) &&
currentSegment.EndsWith(requestParametersEndChar) &&
currentSegment.Count(x => x == requestParametersChar) == 1;
}
private static string CleanupParametersFromPath(string pathSegment) {
if((pathSegment?.Contains(requestParametersChar) ?? false) ||
(pathSegment?.Contains(requestParametersSectionChar) ?? false))
return PathParametersRegex.Replace(pathSegment, requestParametersMatchEvaluator)
.TrimEnd(requestParametersSectionEndChar)
.Replace(requestParametersSectionChar.ToString(), string.Empty);
return pathSegment;
}
}
}
Loading

0 comments on commit 830ac94

Please sign in to comment.