Skip to content
This repository was archived by the owner on Jun 16, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c6fe04a
the model for the input values of the examples
ArcturusZhang Aug 18, 2023
4238e1c
change the serializedname to be not-null
ArcturusZhang Aug 18, 2023
fab7fb8
protocol method should be good
ArcturusZhang Aug 18, 2023
9ef2711
implement the xml part
ArcturusZhang Aug 18, 2023
f359cd7
convenience method is almost done
ArcturusZhang Aug 18, 2023
8399caa
Merge remote-tracking branch 'origin/feature/v3' into new-refactor-sa…
ArcturusZhang Aug 18, 2023
2c849e4
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Aug 19, 2023
84f6346
fix the convenience sample and introduce the short version and all pa…
ArcturusZhang Aug 21, 2023
76f1848
fix the xml formatter issue
ArcturusZhang Aug 21, 2023
8c38768
fix
ArcturusZhang Aug 19, 2023
ccff33a
fix lro issue
ArcturusZhang Aug 21, 2023
48b66c7
Merge remote-tracking branch 'origin/feature/v3' into new-refactor-sa…
ArcturusZhang Aug 21, 2023
dcb4020
fix an issue when body is optional
ArcturusZhang Aug 21, 2023
691b904
fix another issue
ArcturusZhang Aug 22, 2023
fd461f8
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Aug 22, 2023
89b38a4
fix the endpoint issue
ArcturusZhang Aug 23, 2023
4a1ffa2
handles lro pageable as well
ArcturusZhang Aug 23, 2023
7ccdef4
fix more issues
ArcturusZhang Aug 23, 2023
429d15d
more regen
ArcturusZhang Aug 24, 2023
69ea321
fix another issue in client parameter promotion
ArcturusZhang Aug 24, 2023
1f01d86
met some issues on the serialization format - might need to reconside…
ArcturusZhang Aug 24, 2023
51ee632
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Aug 24, 2023
5e04864
Merge remote-tracking branch 'origin/feature/v3' into new-refactor-sa…
ArcturusZhang Sep 4, 2023
d2f0c88
Merge remote-tracking branch 'origin/feature/v3' into new-refactor-sa…
ArcturusZhang Sep 5, 2023
4cf1620
provided a more reliable way of writing framework types. type provide…
ArcturusZhang Sep 6, 2023
40e4f08
implemented the type provider part
ArcturusZhang Sep 6, 2023
17e96c4
a few refactor
ArcturusZhang Sep 6, 2023
727845f
regenerate everything
ArcturusZhang Sep 6, 2023
706c734
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Sep 6, 2023
66789dd
regenerate lowlevel projects
ArcturusZhang Sep 7, 2023
0197fce
change the implementation type of IList from array to List<T> and reg…
ArcturusZhang Sep 7, 2023
4a01516
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Sep 7, 2023
b38cf96
refactor around the xml writer
ArcturusZhang Sep 7, 2023
b39afd3
fix the issue in agrifood
ArcturusZhang Sep 7, 2023
546a827
solve the cast issue
ArcturusZhang Sep 7, 2023
d92e592
regenerate all projects
ArcturusZhang Sep 7, 2023
87d1355
resolve comments
ArcturusZhang Sep 12, 2023
8560c08
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Sep 12, 2023
902f3f9
regenerate
ArcturusZhang Sep 12, 2023
c47f3e3
use anonymous object syntax when we could, switch to dictionary when …
ArcturusZhang Sep 12, 2023
7ab2efc
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Sep 13, 2023
69df8b5
regenerate
ArcturusZhang Sep 13, 2023
989435f
Merge branch 'feature/v3' into new-refactor-sample-generator
ArcturusZhang Sep 14, 2023
8378c01
a few refactor and clean up
ArcturusZhang Sep 14, 2023
7c9b4af
regenerate
ArcturusZhang Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ static GeneratedCodeWorkspace()
private static Task<Project>? _cachedProject;

private Project _project;
private Dictionary<string, string> _docFiles { get; init; }
private Dictionary<string, XmlDocument> _xmlDocFiles { get; }

private GeneratedCodeWorkspace(Project generatedCodeProject)
{
_project = generatedCodeProject;
_docFiles = new Dictionary<string, string>();
_xmlDocFiles = new();
}

/// <summary>
Expand All @@ -86,10 +86,10 @@ public void AddGeneratedFile(string name, string text)
/// Add generated doc file.
/// </summary>
/// <param name="name">Name of the doc file, including the relative path to the "Generated" folder.</param>
/// <param name="text">Content of the doc file.</param>
public void AddGeneratedDocFile(string name, string text)
/// <param name="xmlDocument">Content of the doc file.</param>
public void AddGeneratedDocFile(string name, XmlDocument xmlDocument)
{
_docFiles.Add(name, text);
_xmlDocFiles.Add(name, xmlDocument);
}

public async IAsyncEnumerable<(string Name, string Text)> GetGeneratedFilesAsync()
Expand All @@ -110,6 +110,8 @@ public void AddGeneratedDocFile(string name, string text)
documents.Add(Task.Run(() => ProcessDocument(compilation, document, suppressedTypeNames)));
}

var generatedDocs = new Dictionary<string, SyntaxTree>();

foreach (var task in documents)
{
var processed = await task;
Expand All @@ -118,11 +120,15 @@ public void AddGeneratedDocFile(string name, string text)
processed = await Formatter.FormatAsync(processed);
var text = await processed.GetSyntaxTreeAsync();
yield return (processed.Name, text!.ToString());
generatedDocs.Add(processed.Name, text);
}

foreach (var doc in _docFiles)
foreach (var (docName, doc) in _xmlDocFiles)
{
yield return (doc.Key, doc.Value);
var xmlDocument = doc.XmlDocDocument;
var testDocument = generatedDocs[doc.TestFileName];
var content = await XmlFormatter.FormatAsync(xmlDocument, testDocument);
yield return (docName, content);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/AutoRest.CSharp/Common/AutoRest/Plugins/XmlDocument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Xml.Linq;

namespace AutoRest.CSharp.AutoRest.Plugins
{
public record XmlDocument(string TestFileName, XDocument XmlDocDocument);
}
125 changes: 125 additions & 0 deletions src/AutoRest.CSharp/Common/AutoRest/Plugins/XmlFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using AutoRest.CSharp.Output.Models;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace AutoRest.CSharp.AutoRest.Plugins
{
internal class XmlFormatter
{
internal static async Task<string> FormatAsync(XDocument document, SyntaxTree syntaxTree)
{
var methods = await GetMethods(syntaxTree);
// first we need to get the members
var members = document.Element("doc")!
.Element("members")!
.Elements("member")!;

foreach (var member in members)
{
// get the example element
var exampleElement = member.Element("example");
if (exampleElement == null)
continue;

foreach (var codeElement in exampleElement.Elements("code"))
{
var testMethodName = codeElement.Value;
// find the magic comment and replace it with real code
if (methods.TryGetValue(testMethodName, out var methodDeclaration))
{
var lines = GetLines(methodDeclaration.Body!);
var content = FormatContent(lines);
// this will give you
// <[[CDATA
// var our = code;
// ]]>
codeElement.ReplaceAll(new XCData(content));
}
}
}

var writer = new XmlStringWriter();
XmlWriterSettings settings = new XmlWriterSettings { OmitXmlDeclaration = false, Indent = true };
using (XmlWriter xw = XmlWriter.Create(writer, settings))
{
document.Save(xw);
}

return writer.ToString();
}

private static IEnumerable<string> GetLines(BlockSyntax methodBlock)
{
if (!methodBlock.Statements.Any())
return Array.Empty<string>();

// here we have to get the string of all statements and then split by new lines
// this is because in the StatementSyntax, the NewLines (\n or \r\n) could appear at the end of the statement or at the beginning of the statement
// therefore to keep it simple, we just combine all the text together and then split by the new lines to trim the extra spaces in front of every line
var builder = new StringBuilder();
foreach (var statement in methodBlock.Statements)
{
builder.Append(statement.ToFullString());
}

return builder.ToString().Split(Environment.NewLine);
}

/// <summary>
/// This method trims the leading spaces of the lines, and it also adds proper amount of spaces to the content of spaces because of the bug of Roslyn: https://github.com/dotnet/roslyn/issues/8269
/// </summary>
/// <param name="lines"></param>
/// <returns></returns>
private static string FormatContent(IEnumerable<string> lines)
{
if (!lines.Any())
return string.Empty;

var builder = new StringBuilder();

var first = lines.First();
var trimed = first.TrimStart();
var spaces = first.Length - trimed.Length;
//int level = 0;
foreach (var line in lines)
{
if (line.Length >= spaces)
builder.AppendLine().Append(line.Substring(spaces));
else
builder.AppendLine().Append(line.TrimStart());
}

return builder.ToString();
}

private static async Task<Dictionary<string, MethodDeclarationSyntax>> GetMethods(SyntaxTree syntaxTree)
{
var result = new Dictionary<string, MethodDeclarationSyntax>();
var root = await syntaxTree.GetRootAsync();

foreach (var method in root.DescendantNodes().OfType<MethodDeclarationSyntax>())
{
result.Add(method.Identifier.Text, method);
}

return result;
}

private class XmlStringWriter : StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
}
}
2 changes: 2 additions & 0 deletions src/AutoRest.CSharp/Common/Generation/Types/TypeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ internal static bool IsIEnumerableType(CSharpType type)

internal static bool IsIEnumerableOfT(CSharpType type) => type.IsFrameworkType && type.FrameworkType == typeof(IEnumerable<>);

internal static bool IsOperationOfT(CSharpType type) => type.IsFrameworkType && type.FrameworkType == typeof(Operation<>);

internal static bool IsIAsyncEnumerableOfT(CSharpType type) => type.IsFrameworkType && type.FrameworkType == typeof(IAsyncEnumerable<>);

internal static bool IsAsyncPageable(CSharpType type) => type.IsFrameworkType && type.FrameworkType == typeof(AsyncPageable<>);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AutoRest.CSharp.Common.Input.Examples
{
internal class ExampleMockValueBuilder
{
public const string MockExampleKey = "mock-example";

private const string EndpointMockValue = "<https://my-service.azure.com>";

private readonly static ConcurrentDictionary<InputType, InputExampleValue> _cache = new();

public static InputClientExample BuildClientExample(InputClient client)
{
var clientParameterExamples = new List<InputParameterExample>();
foreach (var parameter in client.Parameters)
{
var parameterExample = BuildParameterExample(parameter);
clientParameterExamples.Add(parameterExample);
}

return new(clientParameterExamples);
}

public static InputOperationExample BuildOperationExample(InputOperation operation)
{
var parameterExamples = new List<InputParameterExample>();
foreach (var parameter in operation.Parameters)
{
var parameterExample = BuildParameterExample(parameter);
parameterExamples.Add(parameterExample);
}

return new(parameterExamples);
}

private static InputParameterExample BuildParameterExample(InputParameter parameter)
{
// if the parameter is constant, we just put the constant into the example value instead of mocking a new one
if (parameter.Kind == InputOperationParameterKind.Constant)
{
InputExampleValue value;
if (parameter.DefaultValue != null)
{
// when it is constant, it could have DefaultValue
value = InputExampleValue.Value(parameter.Type, parameter.DefaultValue.Value);
}
else if (parameter.Type is InputUnionType unionType && unionType.UnionItemTypes.First() is InputLiteralType literalType)
{
// or it could be a union of literal types
value = InputExampleValue.Value(parameter.Type, literalType.Value);
}
else
{
// fallback to null
value = InputExampleValue.Null(parameter.Type);
}
return new(parameter, value);
}

// if the parameter is endpoint
if (parameter.IsEndpoint)
{
var value = InputExampleValue.Value(parameter.Type, EndpointMockValue);
return new(parameter, value);
}

if (parameter.DefaultValue != null)
{
var value = InputExampleValue.Value(parameter.Type, parameter.DefaultValue.Value);
return new(parameter, value);
}

var exampleValue = BuildExampleValue(parameter.Type, parameter.Name, new HashSet<InputModelType>());
return new(parameter, exampleValue);
}

private static InputExampleValue BuildExampleValue(InputType type, string? hint, HashSet<InputModelType> visitedModels) => type switch
{
InputListType listType => BuildListExampleValue(listType, hint, visitedModels),
InputDictionaryType dictionaryType => BuildDictionaryExampleValue(dictionaryType, hint, visitedModels),
InputEnumType enumType => BuildEnumExampleValue(enumType),
InputPrimitiveType primitiveType => BuildPrimitiveExampleValue(primitiveType, hint),
InputLiteralType literalType => InputExampleValue.Value(literalType, literalType.Value),
InputModelType modelType => BuildModelExampleValue(modelType, visitedModels),
InputUnionType unionType => BuildExampleValue(unionType.UnionItemTypes.First(), hint, visitedModels),
_ => InputExampleValue.Object(type, new Dictionary<string, InputExampleValue>())
};

private static InputExampleValue BuildListExampleValue(InputListType listType, string? hint, HashSet<InputModelType> visitedModels)
{
var exampleElementValue = BuildExampleValue(listType.ElementType, hint, visitedModels);

return InputExampleValue.List(listType, new[] { exampleElementValue });
}

private static InputExampleValue BuildDictionaryExampleValue(InputDictionaryType dictionaryType, string? hint, HashSet<InputModelType> visitedModels)
{
var exampleValue = BuildExampleValue(dictionaryType.ValueType, hint, visitedModels);

return InputExampleValue.Object(dictionaryType, new Dictionary<string, InputExampleValue>
{
["key"] = exampleValue
});
}

private static InputExampleValue BuildEnumExampleValue(InputEnumType enumType)
{
var enumValue = enumType.AllowedValues.First();
return InputExampleValue.Value(enumType, enumValue.Value);
}

private static InputExampleValue BuildPrimitiveExampleValue(InputPrimitiveType primitiveType, string? hint) => primitiveType.Kind switch
{
InputTypeKind.Stream => InputExampleValue.Value(primitiveType, "<filePath>"),
InputTypeKind.Boolean => InputExampleValue.Value(primitiveType, true),
InputTypeKind.Date => InputExampleValue.Value(primitiveType, "2022-05-10"),
InputTypeKind.DateTime => InputExampleValue.Value(primitiveType, "2022-05-10T14:57:31.2311892-04:00"),
InputTypeKind.DateTimeISO8601 => InputExampleValue.Value(primitiveType, "2022-05-10T18:57:31.2311892Z"),
InputTypeKind.DateTimeRFC1123 => InputExampleValue.Value(primitiveType, "Tue, 10 May 2022 18:57:31 GMT"),
InputTypeKind.DateTimeRFC3339 => InputExampleValue.Value(primitiveType, "2022-05-10T18:57:31.2311892Z"),
InputTypeKind.DateTimeRFC7231 => InputExampleValue.Value(primitiveType, "Tue, 10 May 2022 18:57:31 GMT"),
InputTypeKind.DateTimeUnix => InputExampleValue.Value(primitiveType, "1652209051"),
InputTypeKind.Float32 => InputExampleValue.Value(primitiveType, 123.45f),
InputTypeKind.Float64 => InputExampleValue.Value(primitiveType, 123.45d),
InputTypeKind.Float128 => InputExampleValue.Value(primitiveType, 123.45m),
InputTypeKind.Guid => InputExampleValue.Value(primitiveType, "73f411fe-4f43-4b4b-9cbd-6828d8f4cf9a"),
InputTypeKind.Int32 => InputExampleValue.Value(primitiveType, 1234),
InputTypeKind.Int64 => InputExampleValue.Value(primitiveType, 1234L),
InputTypeKind.String => string.IsNullOrWhiteSpace(hint) ? InputExampleValue.Value(primitiveType, "<String>") : InputExampleValue.Value(primitiveType, $"<{hint}>"),
InputTypeKind.DurationISO8601 => InputExampleValue.Value(primitiveType, "PT1H23M45S"),
InputTypeKind.DurationConstant => InputExampleValue.Value(primitiveType, "01:23:45"),
InputTypeKind.Time => InputExampleValue.Value(primitiveType, "01:23:45"),
InputTypeKind.Uri => InputExampleValue.Value(primitiveType, "http://localhost:3000"),
InputTypeKind.DurationSeconds => InputExampleValue.Value(primitiveType, 10),
InputTypeKind.DurationSecondsFloat => InputExampleValue.Value(primitiveType, 10f),
_ => InputExampleValue.Object(primitiveType, new Dictionary<string, InputExampleValue>())
};

private static InputExampleValue BuildModelExampleValue(InputModelType model, HashSet<InputModelType> visitedModels)
{
if (_cache.TryGetValue(model, out var value))
return value;

var dict = new Dictionary<string, InputExampleValue>();
var result = InputExampleValue.Object(model, dict);
visitedModels.Add(model);
_cache.TryAdd(model, result);
// iterate all the properties
foreach (var modelOrBase in model.GetSelfAndBaseModels())
{
foreach (var property in modelOrBase.Properties)
{
if (visitedModels.Contains(property.Type) || property.IsReadOnly)
continue;

var exampleValue = BuildExampleValue(property.Type, property.SerializedName, visitedModels);

dict.Add(property.SerializedName, exampleValue);
}
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;

namespace AutoRest.CSharp.Common.Input.Examples
{
internal record InputClientExample(IReadOnlyList<InputParameterExample> ClientParameters);
}
Loading