using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Json.LitJson;
namespace ServiceClientGenerator
{
public static class GeneratorHelpers
{
public static string DetermineSigner(string signatureVersion, string serviceBasename)
{
switch (serviceBasename)
{
case "EventBridge":
// we should not continue to add new hardcoded service specific signers
// and instead implement a solution based on a signer selection specification
return "EventBridgeSigner";
}
switch (signatureVersion)
{
case "v2":
return "QueryStringSigner";
case "v3https":
return "AWS3Signer";
case "v4":
return "AWS4Signer";
case "s3":
return "Amazon.S3.Internal.S3Signer";
case "s3v4":
return "S3Signer";
case "bearer":
return "BearerTokenSigner";
case "":
return "NullSigner";
default:
throw new Exception("Unknown signer: " + signatureVersion);
}
}
public static readonly DateTime EPOCH_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static int ConvertToUnixEpochSeconds(DateTime dateTime)
{
TimeSpan ts = new TimeSpan(dateTime.ToUniversalTime().Ticks - EPOCH_START.Ticks);
return Convert.ToInt32(ts.TotalSeconds);
}
public static double ConvertToUnixEpochSecondsDouble(DateTime dateTime)
{
TimeSpan ts = new TimeSpan(dateTime.ToUniversalTime().Ticks - EPOCH_START.Ticks);
double seconds = Math.Round(ts.TotalMilliseconds, 0) / 1000.0;
return seconds;
}
// List members in EC2 are always considered flattened, so we drop the 'member' prefix
public static string DetermineAWSQueryListMemberPrefix(Member member)
{
if (member.model.IsEC2Protocol || member.Shape.IsFlattened)
return string.Empty;
if (member.Shape.IsList)
return "member";
if (member.Shape.IsMap)
return "entry";
throw new Exception("Unknown member type for list member prefix determination");
}
///
/// Inspects list member to determine if the original list shape in the model has been
/// substituted and if so, whether a member suffix should be used to extract the value
/// for use in the query. An example usage would be the replacement of IpRange (in EC2)
/// within an IpRangeList - we treat as a list of strings, yet need to get to the
/// IpRange.CidrIp member in the query marshalling. Note that we also have some EC2
/// operations where we don't want this submember extraction too even though the
/// same substitite is in use.
///
///
///
public static string DetermineAWSQueryListMemberSuffix(Operation operation, Member member)
{
if (member.Shape.ModelListShape == null)
return null;
string suffixMember = null;
var substituteShapeData = member.model.Customizations.GetSubstituteShapeData(member.ModelShape.ModelListShape.Name);
if (substituteShapeData != null && substituteShapeData[CustomizationsModel.EmitFromMemberKey] != null)
{
var useSuffix = true;
if (substituteShapeData[CustomizationsModel.ListMemberSuffixExclusionsKey] != null)
{
var exclusions = substituteShapeData[CustomizationsModel.ListMemberSuffixExclusionsKey];
foreach (JsonData excl in exclusions)
{
if (string.Equals(operation.Name, (string)excl, StringComparison.Ordinal))
{
useSuffix = false;
break;
}
}
}
if (useSuffix)
suffixMember = (string)substituteShapeData[CustomizationsModel.EmitFromMemberKey];
}
return suffixMember;
}
///
/// Determines if a property modifier for the member is suppressing automatic marshall
/// generation code for the field. If true, custom code in the pipeline will handle the
/// member.
///
///
///
///
public static bool UseCustomMarshall(Member member, Operation operation)
{
if (member.PropertyModifier != null && member.PropertyModifier.IsSetUseCustomMarshall)
return member.PropertyModifier.UseCustomMarshall;
if (member.PropertyInjector != null && member.PropertyInjector.IsSetUseCustomMarshall)
return member.PropertyInjector.UseCustomMarshall;
return false;
}
// The marshal name must always be up-cased (first letter only) when used with EC2's
// variant of AWSQuery. We can also apply operation-specific customizations for marshal
// names
public static string DetermineAWSQueryMarshallName(Member member, Operation operation)
{
var isEC2Protocol = member.model.IsEC2Protocol;
CustomizationsModel.OperationModifiers modifiers = null;
if (operation != null)
modifiers = operation.OperationModifiers;
var marshallName = new StringBuilder();
if (modifiers != null)
{
var marshallOverride = modifiers.GetMarshallNameOverrides(member.OwningShape.Name, member.BasePropertyName);
if (marshallOverride != null)
{
var marshallOverrideName = !isEC2Protocol
? marshallOverride.MarshallName
: (string.IsNullOrEmpty(marshallOverride.MarshallLocationName)
? marshallOverride.MarshallName
: marshallOverride.MarshallLocationName);
marshallName.Append(TransformMarshallLocationName(isEC2Protocol, marshallOverrideName));
}
}
// if the operation didn't override the marshal location, is there a property modifier doing so?
if (marshallName.Length == 0 && member.PropertyModifier != null)
{
var locationName = TransformMarshallLocationName(isEC2Protocol, member.PropertyModifier.LocationName);
marshallName.Append(locationName);
}
// if the marshal name still isn't set, fall back to the model
if (marshallName.Length == 0)
{
string modelMarshallName;
if (!string.IsNullOrEmpty(member.MarshallQueryName))
{
modelMarshallName = member.MarshallQueryName;
}
else
{
if (isEC2Protocol && !string.IsNullOrEmpty(member.MarshallLocationName))
{
modelMarshallName = member.MarshallLocationName;
}
else
{
modelMarshallName = member.MarshallName;
}
modelMarshallName = TransformMarshallLocationName(isEC2Protocol, modelMarshallName);
}
marshallName.Append(modelMarshallName);
}
// also check if we need to emit from a submember as a result of shape substitution
var substituteShapeData = member.model.Customizations.GetSubstituteShapeData(member.ModelShape.Name);
if (substituteShapeData != null && substituteShapeData[CustomizationsModel.EmitFromMemberKey] != null)
{
var valueMember = (string) substituteShapeData[CustomizationsModel.EmitFromMemberKey];
var subMember = member.ModelShape.Members.Single(m => m.PropertyName.Equals(valueMember, StringComparison.Ordinal));
if (subMember != null)
{
string subExpression;
if (!string.IsNullOrEmpty(subMember.MarshallQueryName))
{
subExpression = subMember.MarshallQueryName;
}
else
{
subExpression = string.IsNullOrEmpty(subMember.MarshallLocationName)
? subMember.MarshallName
: subMember.MarshallLocationName;
subExpression = TransformMarshallLocationName(isEC2Protocol, subExpression);
}
marshallName.AppendFormat(".{0}", subExpression);
}
}
return marshallName.ToString();
}
// The "locationName" must always be lower-cased (first letter only) when used with EC2's
// variant of AWSQuery. MarshallLocationName isn't consistently set.
public static string DetermineAWSQueryBaseUnmarshallName(Member member)
{
if (!member.model.IsEC2Protocol)
return member.MarshallName;
var baseExpression = string.IsNullOrEmpty(member.MarshallLocationName)
? member.MarshallName
: member.MarshallLocationName;
return TransformUnmarshallLocationName(true, baseExpression);
}
// returns the unmarshall expression for a member
public static string DetermineAWSQueryTestExpression(Member member)
{
var isEC2Protocol = member.model.IsEC2Protocol;
var testExpression = DetermineAWSQueryBaseUnmarshallName(member);
if (member.IsList)
{
if (!member.Shape.IsFlattened)
{
testExpression += "/";
if (member.Shape.ListMarshallName != null)
testExpression += member.Shape.ListMarshallName;
else
testExpression += "member";
// If the list element shape has a customization replacing it
// with another shape, extend the expression with any subexpression
// to the value member that the replaced shape has. This allows us to
// handle collections of EC2's AttributeValue shape which is replaced
// by a 'string' and we unmarshall the collection using the shape's 'value'
// member.
var listShape = member.Shape.ModelListShape;
var substituteShapeData = member.model.Customizations.GetSubstituteShapeData(listShape.Name);
if (substituteShapeData != null)
{
if (substituteShapeData[CustomizationsModel.EmitFromMemberKey] != null)
{
var valueMember = (string)substituteShapeData[CustomizationsModel.EmitFromMemberKey];
if (isEC2Protocol)
testExpression += "/" + TransformUnmarshallLocationName(true, valueMember);
else
testExpression += "/" + valueMember;
}
else
{
if (listShape.ValueMarshallName != null)
testExpression += "/" + listShape.ValueMarshallName;
}
}
}
else
{
testExpression = member.Shape.ListMarshallName;
}
}
else if (member.IsMap)
{
if (!member.Shape.IsFlattened)
testExpression += "/entry";
}
else
{
var substituteShapeData = member.model.Customizations.GetSubstituteShapeData(member.ModelShape.Name);
if (substituteShapeData != null && substituteShapeData[CustomizationsModel.EmitFromMemberKey] != null)
{
var valueMember = (string)substituteShapeData[CustomizationsModel.EmitFromMemberKey];
var subMember = member.ModelShape.Members.Single(m => m.PropertyName.Equals(valueMember, StringComparison.Ordinal));
var subExpression = string.IsNullOrEmpty(subMember.MarshallLocationName)
? subMember.MarshallName
: subMember.MarshallLocationName;
if (!string.IsNullOrEmpty(subExpression))
testExpression += "/" + subExpression;
}
}
return testExpression;
}
///
/// Returns true if the specified operation has been tagged as requiring the usual
/// 'Result' class suppressed (due to a void return type)
///
///
public static bool HasSuppressedResult(Operation operation)
{
return operation.model.Customizations.ResultGenerationSuppressions.Contains(operation.Name);
}
// common code to upper case, when EC2, the first char of the marshall location name
public static string TransformMarshallLocationName(bool isEC2Protocol, string locationName)
{
if (string.IsNullOrEmpty(locationName) || !isEC2Protocol)
return locationName;
return locationName.ToUpperFirstCharacter();
}
// common code to lower case, when EC2, the first char of the unmarshall location name
public static string TransformUnmarshallLocationName(bool isEC2Protocol, string locationName)
{
if (string.IsNullOrEmpty(locationName) || !isEC2Protocol)
return locationName;
return locationName.ToLowerFirstCharacter();
}
///
/// Sets the first character of the param to lower, if it's an acronym it lowers it all until the next word
///
/// The name of the parameter name for the constructor
/// The parameter as a camel cased name
public static string CamelCaseParam(string param, bool removeUnderscoresAndDashes = false)
{
param = param ?? "";
if (removeUnderscoresAndDashes)
{
// handle kebab-case and snake_case
param = string.Join("", param.Split('-').Select((s, i) => i == 0 ? s : s.ToUpperFirstCharacter()));
param = string.Join("", param.Split('_').Select((s, i) => i == 0 ? s : s.ToUpperFirstCharacter()));
}
if (param.Length < 2 || char.IsUpper(param.ToCharArray()[0]) && char.IsLower(param.ToCharArray()[1]))
{
if ((char.ToLower(param.ToCharArray()[0]) + param.Substring(1)).Equals("namespace"))
{
return "awsNamespace";
}
return param.Length < 2 ? param.ToLower() : char.ToLower(param.ToCharArray()[0]) + param.Substring(1);
}
// If it gets here it's an acronym
int secondWord = 0;
for (int i = 0; i < param.ToCharArray().Length - 1; i++)
{
if (char.IsUpper(param.ToCharArray()[i]) && char.IsLower(param.ToCharArray()[i + 1]))
{
secondWord = i;
break;
}
else if (char.IsUpper(param.ToCharArray()[i]) && char.IsUpper(param.ToCharArray()[i + 1]))
{
continue;
}
}
if (secondWord == 0)
{
if (param.ToLower().Equals("namespace"))
{
return "awsNamespace";
}
return param.ToLower();
}
var camelParam = new StringBuilder();
for (int i = 0; i < secondWord; i++)
{
camelParam.Append(char.ToLower(param.ToCharArray()[i]));
}
camelParam.Append(param.Substring(secondWord));
if (camelParam.ToString().Equals("namespace"))
{
return "awsNamespace";
}
return camelParam.ToString();
}
}
public static class StringExtensions
{
public static string ToPascalCase(this string s)
{
return GeneratorHelpers.CamelCaseParam(s, removeUnderscoresAndDashes: true).ToUpperFirstCharacter();
}
public static string ToCamelCase(this string s)
{
return GeneratorHelpers.CamelCaseParam(s);
}
public static string ToClassMemberCase(this string s)
{
return "_" + s.ToCamelCase();
}
///
/// Capitalizes the first character of a string, used to create proper naming for services, attributes, and operations
///
public static string ToUpperFirstCharacter(this string s)
{
var txt = s.Substring(0,1).ToUpperInvariant();
if (s.Length > 1)
txt += s.Substring(1);
return txt;
}
///
/// Changes first character of a string to lower case.
///
public static string ToLowerFirstCharacter(this string s)
{
var txt = s.Substring(0, 1).ToLowerInvariant();
if (s.Length > 1)
txt += s.Substring(1);
return txt;
}
public static string ToNativeType(this string type, bool useNullableTypes = false)
{
switch (type.ToLower())
{
case "string": return "string";
case "boolean": return "bool" + (useNullableTypes ? "?" : "");
default:
throw new Exception("Unsupported type");
}
}
public static string ToNativeValue(this JsonData value)
{
if (value.IsBoolean) return value.ToString().ToLower();
if (value.IsString) return $"\"{(string)value}\"";
if (value.IsInt) return $"{(int)value}";
throw new Exception("Unsupported type");
}
public static string SanitizeQuotes(this string s)
{
return s.Replace("\"", "\"\"");
}
public static string ToVariableName(this string s)
{
return s.Replace("-", "_");
}
}
}