Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Parameter Grouping to AutoRest for Azure.CSharp, Azure.NodeJS #381

Merged
merged 1 commit into from
Oct 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion AutoRest/AutoRest.Core/AutoRest.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="ClientModel\CollectionFormat.cs" />
<Compile Include="ClientModel\EnumValue.cs" />
<Compile Include="ClientModel\EnumType.cs" />
<Compile Include="ClientModel\ParameterMapping.cs" />
<Compile Include="ClientModel\PrimaryType.cs" />
<Compile Include="ClientModel\ParameterLocation.cs" />
<Compile Include="ClientModel\Constraint.cs" />
Expand Down Expand Up @@ -87,4 +88,4 @@
</CodeAnalysisDictionary>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
31 changes: 31 additions & 0 deletions AutoRest/AutoRest.Core/ClientModel/Method.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public Method()
Parameters = new List<Parameter>();
RequestHeaders = new Dictionary<string, string>();
Responses = new Dictionary<HttpStatusCode, IType>();
InputParameterMappings = new List<ParameterMapping>();
}

/// <summary>
Expand Down Expand Up @@ -58,6 +59,34 @@ public Method()
/// </summary>
public List<Parameter> Parameters { get; private set; }

/// <summary>
/// Gets or sets the logical parameter.
/// </summary>
public IEnumerable<Parameter> LogicalParameters
{
get
{
return Parameters.Where(gp => gp.Location != ParameterLocation.None)
.Union(InputParameterMappings.Select(m => m.OutputParameter));
}
}

/// <summary>
/// Gets or sets the body parameter.
/// </summary>
public Parameter Body
{
get
{
return LogicalParameters.FirstOrDefault(p => p.Location == ParameterLocation.Body);
}
}

/// <summary>
/// Gets the list of input Parameter Mappings
/// </summary>
public List<ParameterMapping> InputParameterMappings { get; private set; }

/// <summary>
/// Gets or sets request headers.
/// </summary>
Expand Down Expand Up @@ -131,8 +160,10 @@ public object Clone()
newMethod.Parameters = new List<Parameter>();
newMethod.RequestHeaders = new Dictionary<string, string>();
newMethod.Responses = new Dictionary<HttpStatusCode, IType>();
newMethod.InputParameterMappings = new List<ParameterMapping>();
this.Extensions.ForEach(e => newMethod.Extensions[e.Key] = e.Value);
this.Parameters.ForEach(p => newMethod.Parameters.Add((Parameter)p.Clone()));
this.InputParameterMappings.ForEach(m => newMethod.InputParameterMappings.Add((ParameterMapping)m.Clone()));
this.RequestHeaders.ForEach(r => newMethod.RequestHeaders[r.Key] = r.Value);
this.Responses.ForEach(r => newMethod.Responses[r.Key] = r.Value);
return newMethod;
Expand Down
3 changes: 1 addition & 2 deletions AutoRest/AutoRest.Core/ClientModel/Parameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ public Parameter()
/// Gets or sets the documentation.
/// </summary>
public string Documentation { get; set; }



/// <summary>
/// Gets vendor extensions dictionary.
/// </summary>
Expand Down
65 changes: 65 additions & 0 deletions AutoRest/AutoRest.Core/ClientModel/ParameterMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Rest.Generator.ClientModel
{
using System;
using System.Globalization;

/// <summary>
/// Defines a parameter mapping.
/// </summary>
public class ParameterMapping : ICloneable
{
/// <summary>
/// Gets or sets the input parameter.
/// </summary>
public Parameter InputParameter { get; set; }

/// <summary>
/// Gets or sets the input parameter dot separated property path.
/// </summary>
public string InputParameterProperty { get; set; }

/// <summary>
/// Gets or sets the output parameter.
/// </summary>
public Parameter OutputParameter { get; set; }

/// <summary>
/// Gets or sets the output parameter dot separated property path.
/// </summary>
public string OutputParameterProperty { get; set; }

/// <summary>
/// Returns a string representation of the ParameterMapping object.
/// </summary>
/// <returns>
/// A string representation of the ParameterMapping object.
/// </returns>
public override string ToString()
{
string outputPath = OutputParameter.Name;
if (OutputParameterProperty != null)
{
outputPath += "." + OutputParameterProperty;
}
string inputPath = InputParameter.Name;
if (InputParameterProperty != null)
{
inputPath += "." + InputParameterProperty;
}
return string.Format(CultureInfo.InvariantCulture, "{0} = {1}", outputPath, inputPath);
}

/// <summary>
/// Performs a deep clone of a parameter mapping.
/// </summary>
/// <returns>A deep clone of current object.</returns>
public object Clone()
{
ParameterMapping paramMapping = (ParameterMapping)this.MemberwiseClone();
return paramMapping;
}
}
}
22 changes: 22 additions & 0 deletions AutoRest/AutoRest.Core/CodeNamer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,28 @@ public virtual void NormalizeClientModel(ServiceClient client)
parameter.Name = GetParameterName(parameter.Name);
parameter.Type = NormalizeType(parameter.Type);
}

foreach (var parameterMapping in method.InputParameterMappings)
{
parameterMapping.InputParameter.Name = GetParameterName(parameterMapping.InputParameter.Name);
parameterMapping.InputParameter.Type = NormalizeType(parameterMapping.InputParameter.Type);
parameterMapping.OutputParameter.Name = GetParameterName(parameterMapping.OutputParameter.Name);
parameterMapping.OutputParameter.Type = NormalizeType(parameterMapping.OutputParameter.Type);

if (parameterMapping.InputParameterProperty != null)
{
parameterMapping.InputParameterProperty = string.Join(".",
parameterMapping.InputParameterProperty.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => GetPropertyName(p)));
}

if (parameterMapping.OutputParameterProperty != null)
{
parameterMapping.OutputParameterProperty = string.Join(".",
parameterMapping.OutputParameterProperty.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Select(p => GetPropertyName(p)));
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions AutoRest/AutoRest.Core/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists",
Scope = "member", Target = "Microsoft.Rest.Generator.ClientModel.Method.#Parameters")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Rfc", Scope = "member", Target = "Microsoft.Rest.Generator.ClientModel.PrimaryType.#DateTimeRfc1123")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Microsoft.Rest.Generator.ClientModel.Method.#InputParameterMappings")]
101 changes: 101 additions & 0 deletions AutoRest/Generators/Azure.Common/Azure.Common/AzureCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ public abstract class AzureCodeGenerator : CodeGenerator
//TODO: Ideally this would be the same extension as the ClientRequestIdExtension and have it specified on the response headers,
//TODO: But the response headers aren't currently used at all so we put an extension on the operation for now
public const string RequestIdExtension = "x-ms-request-id";
public const string ParameterGroupExtension = "x-ms-parameter-grouping";
public const string ApiVersion = "api-version";
public const string AcceptLanguage = "accept-language";

private const string ResourceType = "Resource";
private const string SubResourceType = "SubResource";
private const string ResourceProperties = "Properties";
Expand Down Expand Up @@ -64,6 +66,7 @@ public override void NormalizeClientModel(ServiceClient serviceClient)
AddLongRunningOperations(serviceClient);
AddAzureProperties(serviceClient);
SetDefaultResponses(serviceClient);
AddParameterGroups(serviceClient);
}

/// <summary>
Expand Down Expand Up @@ -204,6 +207,104 @@ public static void AddLongRunningOperations(ServiceClient serviceClient)
}
}

/// <summary>
/// Adds the parameter groups to operation parameters.
/// </summary>
/// <param name="serviceClient"></param>
public static void AddParameterGroups(ServiceClient serviceClient)
{
if (serviceClient == null)
{
throw new ArgumentNullException("serviceClient");
}

foreach (Method method in serviceClient.Methods)
{
//This group name is normalized by each languages code generator later, so it need not happen here.
Dictionary<string, Dictionary<Property, Parameter>> parameterGroups = new Dictionary<string, Dictionary<Property, Parameter>>();

foreach (Parameter parameter in method.Parameters)
{
if (parameter.Extensions.ContainsKey(ParameterGroupExtension))
{
Newtonsoft.Json.Linq.JContainer extensionObject = parameter.Extensions[ParameterGroupExtension] as Newtonsoft.Json.Linq.JContainer;
if (extensionObject != null)
{
string parameterGroupName = method.Group + "-" + method.Name + "-" + "Parameters";
parameterGroupName = extensionObject.Value<string>("name") ?? parameterGroupName;

if (!parameterGroups.ContainsKey(parameterGroupName))
{
parameterGroups.Add(parameterGroupName, new Dictionary<Property, Parameter>());
}

Property groupProperty = new Property()
{
IsReadOnly = false, //Since these properties are used as parameters they are never read only
Name = parameter.Name,
IsRequired = parameter.IsRequired,
DefaultValue = parameter.DefaultValue,
//Constraints = parameter.Constraints, Omit these since we don't want to perform parameter validation
Documentation = parameter.Documentation,
Type = parameter.Type,
SerializedName = null //Parameter is never serialized directly
};

parameterGroups[parameterGroupName].Add(groupProperty, parameter);
}
}
}

foreach (string parameterGroupName in parameterGroups.Keys)
{
//Define the new parameter group type (it's always a composite type)
CompositeType parameterGroupType = new CompositeType()
{
Name = parameterGroupName,
Documentation = "Additional parameters for the " + method.Name + " operation."
};

//Populate the parameter group type with properties.
foreach (Property property in parameterGroups[parameterGroupName].Keys)
{
parameterGroupType.Properties.Add(property);
}

//Add to the service client
serviceClient.ModelTypes.Add(parameterGroupType);

bool isGroupParameterRequired = parameterGroupType.Properties.Any(p => p.IsRequired);

//Create the new parameter object based on the parameter group type
Parameter parameterGroup = new Parameter()
{
Name = parameterGroupName,
IsRequired = isGroupParameterRequired,
Location = ParameterLocation.None,
SerializedName = string.Empty,
Type = parameterGroupType,
Documentation = "Additional parameters for the operation"
};

method.Parameters.Add(parameterGroup);

//Link the grouped parameters to their parent, and remove them from the method parameters
foreach (Property property in parameterGroups[parameterGroupName].Keys)
{
Parameter p = parameterGroups[parameterGroupName][property];

method.InputParameterMappings.Add(new ParameterMapping
{
InputParameter = parameterGroup,
OutputParameter = p,
InputParameterProperty = property.Name
});
method.Parameters.Remove(p);
}
}
}
}

/// <summary>
/// Creates azure specific properties.
/// </summary>
Expand Down
76 changes: 76 additions & 0 deletions AutoRest/Generators/CSharp/Azure.CSharp.Tests/AcceptanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using Fixtures.Azure.AcceptanceTestsResourceFlattening;
using Fixtures.Azure.AcceptanceTestsResourceFlattening.Models;
using Fixtures.Azure.AcceptanceTestsSubscriptionIdApiVersion;
using Fixtures.Azure.AcceptanceTestsAzureParameterGrouping;
using Fixtures.Azure.AcceptanceTestsAzureParameterGrouping.Models;
using Microsoft.Rest.Azure;
using Microsoft.Rest.Generator.CSharp.Azure.Tests.Properties;
using Microsoft.Rest.Generator.CSharp.Tests;
Expand Down Expand Up @@ -701,5 +703,79 @@ public void DurationTests()
client.Duration.PutPositiveDuration(new TimeSpan(123, 22, 14, 12, 11));
}
}

[Fact]
public void ParameterGroupingTests()
{
const int bodyParameter = 1234;
const string headerParameter = "header";
const int queryParameter = 21;
const string pathParameter = "path";

using (var client = new AutoRestParameterGroupingTestService(
Fixture.Uri,
new TokenCredentials(Guid.NewGuid().ToString())))
{
//Valid required parameters
ParameterGroupingPostRequiredParameters requiredParameters = new ParameterGroupingPostRequiredParameters(bodyParameter, pathParameter)
{
CustomHeader = headerParameter,
Query = queryParameter
};

client.ParameterGrouping.PostRequired(requiredParameters);

//Required parameters but null optional parameters
requiredParameters = new ParameterGroupingPostRequiredParameters(bodyParameter, pathParameter);

client.ParameterGrouping.PostRequired(requiredParameters);

//Required parameters object is not null, but a required property of the object is
requiredParameters = new ParameterGroupingPostRequiredParameters(null, pathParameter);

Assert.Throws<ValidationException>(() => client.ParameterGrouping.PostRequired(requiredParameters));

//null required parameters
Assert.Throws<ValidationException>(() => client.ParameterGrouping.PostRequired(null));

//Valid optional parameters
ParameterGroupingPostOptionalParameters optionalParameters = new ParameterGroupingPostOptionalParameters()
{
CustomHeader = headerParameter,
Query = queryParameter
};

client.ParameterGrouping.PostOptional(optionalParameters);

//null optional paramters
client.ParameterGrouping.PostOptional(null);

//Multiple grouped parameters
FirstParameterGroup firstGroup = new FirstParameterGroup
{
HeaderOne = headerParameter,
QueryOne = queryParameter
};
SecondParameterGroup secondGroup = new SecondParameterGroup
{
HeaderTwo = "header2",
QueryTwo = 42
};

client.ParameterGrouping.PostMultipleParameterGroups(firstGroup, secondGroup);

//Multiple grouped parameters -- some optional parameters omitted
firstGroup = new FirstParameterGroup
{
HeaderOne = headerParameter
};
secondGroup = new SecondParameterGroup
{
QueryTwo = 42
};

client.ParameterGrouping.PostMultipleParameterGroups(firstGroup, secondGroup);
}
}
}
}
Loading