Skip to content

Commit

Permalink
Refactor parameter grouping and enable better comments
Browse files Browse the repository at this point in the history
  - Parameter grouping code is clearer.
  - Models for parameter grouping now more clearly state what
    operations they are used on.
  • Loading branch information
matthchr committed May 13, 2016
1 parent f69f5ce commit ac754e6
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static void NormalizeAzureClientModel(ServiceClient serviceClient, Settin
ParseODataExtension(serviceClient);
FlattenModels(serviceClient);
FlattenMethodParameters(serviceClient, settings);
AddParameterGroups(serviceClient);
ParameterGroupExtensionHelper.AddParameterGroups(serviceClient);
AddLongRunningOperations(serviceClient);
AddAzureProperties(serviceClient);
SetDefaultResponses(serviceClient);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Compile Include="ClientModelHelpers.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="ParameterGroupExtensionHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
Expand Down
137 changes: 1 addition & 136 deletions AutoRest/Generators/Extensions/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static void NormalizeClientModel(ServiceClient serviceClient, Settings se
{
FlattenModels(serviceClient);
FlattenMethodParameters(serviceClient, settings);
AddParameterGroups(serviceClient);
ParameterGroupExtensionHelper.AddParameterGroups(serviceClient);
ProcessParameterizedHost(serviceClient, settings);
}

Expand Down Expand Up @@ -333,141 +333,6 @@ public static void RemoveUnreferencedTypes(ServiceClient serviceClient, HashSet<
}
}

/// <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");
}

HashSet<CompositeType> generatedParameterGroups = new HashSet<CompositeType>();

foreach (Method method in serviceClient.Methods)
{
//Copy out flattening transformations as they should be the last
List<ParameterTransformation> flatteningTransformations = method.InputParameterTransformation.ToList();
method.InputParameterTransformation.Clear();

//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))
{
JContainer extensionObject = parameter.Extensions[ParameterGroupExtension] as JContainer;
if (extensionObject != null)
{
string specifiedGroupName = extensionObject.Value<string>("name");
string parameterGroupName;
if (specifiedGroupName == null)
{
string postfix = extensionObject.Value<string>("postfix") ?? "Parameters";
parameterGroupName = method.Group + "-" + method.Name + "-" + postfix;
}
else
{
parameterGroupName = specifiedGroupName;
}

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
};
// Copy over extensions
foreach (var key in parameter.Extensions.Keys)
{
groupProperty.Extensions[key] = parameter.Extensions[key];
}

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

foreach (string parameterGroupName in parameterGroups.Keys)
{
CompositeType parameterGroupType =
generatedParameterGroups.FirstOrDefault(item => item.Name == parameterGroupName);
if (parameterGroupType == null)
{
parameterGroupType = new CompositeType
{
Name = parameterGroupName,
Documentation = "Additional parameters for one or more operations"
};
generatedParameterGroups.Add(parameterGroupType);

//Add to the service client
serviceClient.ModelTypes.Add(parameterGroupType);
}
foreach (Property property in parameterGroups[parameterGroupName].Keys)
{
Property matchingProperty = parameterGroupType.Properties.FirstOrDefault(
item => item.Name == property.Name &&
item.IsReadOnly == property.IsReadOnly &&
item.DefaultValue == property.DefaultValue &&
item.SerializedName == property.SerializedName);
if (matchingProperty == null)
{
parameterGroupType.Properties.Add(property);
}
}

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 = ClientModel.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];

var parameterTransformation = new ParameterTransformation
{
OutputParameter = p
};
parameterTransformation.ParameterMappings.Add(new ParameterMapping
{
InputParameter = parameterGroup,
InputParameterProperty = property.GetClientName()
});
method.InputParameterTransformation.Add(parameterTransformation);
method.Parameters.Remove(p);
}
}

// Copy back flattening transformations if any
flatteningTransformations.ForEach(t => method.InputParameterTransformation.Add(t));
}
}

/// <summary>
/// Flattens the request payload if the number of properties of the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
namespace Microsoft.Rest.Generator
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClientModel;
using Newtonsoft.Json.Linq;

public static class ParameterGroupExtensionHelper
{
private class ParameterGroup
{
public string Name { get; }

public Dictionary<Property, Parameter> ParameterMapping { get; }

public ParameterGroup(string name, Dictionary<Property, Parameter> parameterMapping)
{
this.Name = name;
this.ParameterMapping = parameterMapping;
}
}

private static Property CreateParameterGroupProperty(Parameter 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
};

// Copy over extensions
foreach (var key in parameter.Extensions.Keys)
{
groupProperty.Extensions[key] = parameter.Extensions[key];
}

return groupProperty;
}

private static ParameterGroup BuildParameterGroup(string parameterGroupName, Method method)
{
Dictionary<Property, Parameter> parameterMapping = method.Parameters.Where(
p => GetParameterGroupName(method.Group, method.Name, p) == parameterGroupName).ToDictionary(
CreateParameterGroupProperty,
p => p);

return new ParameterGroup(parameterGroupName, parameterMapping);
}

private static string GetParameterGroupName(string methodGroupName, string methodName, Parameter parameter)
{
if (parameter.Extensions.ContainsKey(Extensions.ParameterGroupExtension))
{
JContainer extensionObject = parameter.Extensions[Extensions.ParameterGroupExtension] as JContainer;
if (extensionObject != null)
{
string specifiedGroupName = extensionObject.Value<string>("name");
string parameterGroupName;
if (specifiedGroupName == null)
{
string postfix = extensionObject.Value<string>("postfix") ?? "Parameters";
parameterGroupName = methodGroupName + "-" + methodName + "-" + postfix;
}
else
{
parameterGroupName = specifiedGroupName;
}
return parameterGroupName;
}
}

return null;
}

private static IEnumerable<string> ExtractParameterGroupNames(Method method)
{
return method.Parameters.Select(p => GetParameterGroupName(method.Group, method.Name, p)).Where(name => !string.IsNullOrEmpty(name)).Distinct();
}

private static IEnumerable<ParameterGroup> ExtractParameterGroups(Method method)
{
IEnumerable<string> parameterGroupNames = ExtractParameterGroupNames(method);

return parameterGroupNames.Select(parameterGroupName => BuildParameterGroup(parameterGroupName, method));
}

private static IEnumerable<Method> GetMethodsUsingParameterGroup(IEnumerable<Method> methods, ParameterGroup parameterGroup)
{
return methods.Where(m => ExtractParameterGroupNames(m).Contains(parameterGroup.Name));
}

private static string GenerateParameterGroupModelText(IEnumerable<Method> methodsWhichUseGroup)
{
Func<string, string, string> createOperationDisplayString = (group, name) =>
{
return string.IsNullOrEmpty(group) ? name : string.Format(CultureInfo.InvariantCulture, "{0}_{1}", group, name);
};

List<Method> methodList = methodsWhichUseGroup.ToList();
if (methodList.Count == 1)
{
Method method = methodList.Single();
return string.Format(CultureInfo.InvariantCulture, "Additional parameters for the {0} operation.",
createOperationDisplayString(method.Group, method.Name));
}
else if (methodList.Count <= 4)
{
string operationsString = string.Join(", ", methodList.Select(
m => string.Format(CultureInfo.InvariantCulture, createOperationDisplayString(m.Group, m.Name))));

return string.Format(CultureInfo.InvariantCulture, "Additional parameters for a set of operations, such as: {0}.", operationsString);
}
else
{
return "Additional parameters for a set of operations.";
}
}

/// <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");
}

HashSet<CompositeType> generatedParameterGroups = new HashSet<CompositeType>();

foreach (Method method in serviceClient.Methods)
{
//Copy out flattening transformations as they should be the last
List<ParameterTransformation> flatteningTransformations = method.InputParameterTransformation.ToList();
method.InputParameterTransformation.Clear();

//This group name is normalized by each languages code generator later, so it need not happen here.
IEnumerable<ParameterGroup> parameterGroups = ExtractParameterGroups(method);

List<Parameter> parametersToAddToMethod = new List<Parameter>();
List<Parameter> parametersToRemoveFromMethod = new List<Parameter>();

foreach (ParameterGroup parameterGroup in parameterGroups)
{
CompositeType parameterGroupType =
generatedParameterGroups.FirstOrDefault(item => item.Name == parameterGroup.Name);

if (parameterGroupType == null)
{
IEnumerable<Method> methodsWhichUseGroup = GetMethodsUsingParameterGroup(serviceClient.Methods, parameterGroup);

parameterGroupType = new CompositeType
{
Name = parameterGroup.Name,
Documentation = GenerateParameterGroupModelText(methodsWhichUseGroup)
};
generatedParameterGroups.Add(parameterGroupType);

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

foreach (Property property in parameterGroup.ParameterMapping.Keys)
{
Property matchingProperty = parameterGroupType.Properties.FirstOrDefault(
item => item.Name == property.Name &&
item.IsReadOnly == property.IsReadOnly &&
item.DefaultValue == property.DefaultValue &&
item.SerializedName == property.SerializedName);
if (matchingProperty == null)
{
parameterGroupType.Properties.Add(property);
}
}

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

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

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

var parameterTransformation = new ParameterTransformation
{
OutputParameter = p
};

parameterTransformation.ParameterMappings.Add(new ParameterMapping
{
InputParameter = newParameter,
InputParameterProperty = property.GetClientName()
});
method.InputParameterTransformation.Add(parameterTransformation);
parametersToRemoveFromMethod.Add(p);
}
}

method.Parameters.RemoveAll(p => parametersToRemoveFromMethod.Contains(p));
method.Parameters.AddRange(parametersToAddToMethod);

// Copy back flattening transformations if any
flatteningTransformations.ForEach(t => method.InputParameterTransformation.Add(t));
}
}
}
}

0 comments on commit ac754e6

Please sign in to comment.