using Json.LitJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServiceClientGenerator
{
///
/// Shapes are used to model structures and member types. If they are a structure the shape
/// defines what members it has and what shape those members are. It also defines which of those
/// members are required. If it is not a structure then it is used to specify the type of the member and its properties.
///
public class Shape : BaseModel
{
public const string ValueKey = "value";
public const string KeyKey = "key";
public const string MemberKey = "member";
public const string MembersKey = "members";
public const string PayloadKey = "payload";
public const string ExceptionKey = "exception";
public const string RetryableKey = "retryable";
public const string ThrottlingKey = "throttling";
public const string RequiresLengthKey = "requiresLength";
public const string StreamingKey = "streaming";
public const string TypeKey = "type";
public const string FlattenedKey = "flattened";
public const string RequiredKey = "required";
public const string MinKey = "min";
public const string MaxKey = "max";
public const string SensitiveKey = "sensitive";
public const string PatternKey = "pattern";
public const string ErrorKey = "error";
public const string ErrorCodeKey = "code";
public const string EventStreamKey = "eventstream";
public const string DeprecatedKey = "deprecated";
public const string DeprecatedMessageKey = "deprecatedMessage";
public const string TimestampFormatKey = "timestampFormat";
public const string DocumentKey = "document";
public const string EventKey = "event";
public static readonly HashSet NullableTypes = new HashSet {
"bool",
"boolean",
"decimal",
"double",
"DateTime",
"float",
"timestamp",
"int",
"integer",
"long",
};
public static readonly Dictionary PrimitiveTypeNames = new Dictionary
{
{ "blob", "MemoryStream" },
{ "boolean", "Bool" },
{ "decimal", "Decimal" },
{ "double", "Double" },
{ "float", "Float" },
{ "integer", "Int" },
{ "long", "Long" },
{ "string", "String" },
{ "timestamp", "DateTime"},
};
private readonly string _name;
public static Shape CreateShape(ServiceModel model, string name, JsonData data)
{
var exception = data[ExceptionKey];
if (exception != null && exception.IsBoolean)
{
return new ExceptionShape(model, name, data);
}
return new Shape(model, name, data);
}
///
/// Creates a shape with a reference to the model it's a part of, its name, and the json data of the shape pulled from the model.
/// Shapes are used to model structures and member types. If they are a structure the shape
/// defines what members it has and what shape those members are. It also defines which of those
/// members are required. If it is not a structure then it is used to specify the type of the member and its properties.
///
/// The model that contains the shape
/// The name of the shape
/// The json object of the shape, pulled form the model json
protected Shape(ServiceModel model, string name, JsonData data)
: base(model, data)
{
this._name = name.ToUpperFirstCharacter();
var nameOverride = this.model.Customizations.GetOverrideShapeName(this._name);
if (nameOverride != null)
this._name = nameOverride;
}
///
/// The name of the shape found in the model json
///
public virtual string Name
{
get { return this._name; }
}
///
/// Checks if an object is a Shape and has the same name as this shape
///
/// The object to compare to
/// If the object is a shape and has the same name of this shape
public override bool Equals(object obj)
{
if (!(obj is Shape))
return false;
return string.Equals(this.Name, ((Shape)obj).Name);
}
///
/// The hashcode of the shape is the hashcode of the name
///
/// The hashcode of the shape retrieved from the name
public override int GetHashCode()
{
return this.Name.GetHashCode();
}
///
/// String representation of the shape found by the name
///
/// The name of the shape as a string
public override string ToString()
{
return this.Name;
}
///
/// If the structure is a map, returns the Value shape. Otherwise returns null.
///
public Shape ValueShape
{
get
{
var valueNode = this.data[ValueKey];
if (valueNode == null)
return null;
var extendsNode = valueNode[ServiceModel.ShapeKey];
if (extendsNode == null)
return null;
return this.model.FindShape(extendsNode.ToString());
}
}
///
/// The marshall name used for the value part of a dictionary.
///
public string ValueMarshallName
{
get
{
var keyNode = this.data[ValueKey];
if (keyNode == null || keyNode[ServiceModel.LocationNameKey] == null)
return "value";
return keyNode[ServiceModel.LocationNameKey].ToString();
}
}
///
/// If the structure is a map, returns the Key shape. Otherwise returns null.
///
public Shape KeyShape
{
get
{
var valueNode = this.data[KeyKey];
if (valueNode == null)
return null;
var extendsNode = valueNode[ServiceModel.ShapeKey];
if (extendsNode == null)
return null;
return this.model.FindShape(extendsNode.ToString());
}
}
///
/// The marshall name used for the key part of a dictionary.
///
public string KeyMarshallName
{
get
{
var keyNode = this.data[KeyKey];
if (keyNode == null || keyNode[ServiceModel.LocationNameKey] == null)
return "key";
return keyNode[ServiceModel.LocationNameKey].ToString();
}
}
///
/// If the structure is a list it returns the shape contained into the list otherwise null.
/// This returns the list shape recognizing any shape substitution in effect via customizations.
///
public Shape ListShape
{
get
{
var shapeName = LookupListShapeName(true);
return !string.IsNullOrEmpty(shapeName) ? this.model.FindShape(shapeName) : null;
}
}
///
/// If the structure is a list it returns the shape contained into the list otherwise null.
/// This returns the original list shape from the model, ignoring any shape substitution in
/// effect via customizations.
///
public Shape ModelListShape
{
get
{
var shapeName = LookupListShapeName(false);
return !string.IsNullOrEmpty(shapeName) ? this.model.FindShape(shapeName) : null;
}
}
///
/// Returns the name of a shape declared to be used by a list type, optionally allowing
/// for substitution.
///
///
///
private string LookupListShapeName(bool allowSubstitution)
{
var valueNode = this.data[MemberKey];
if (valueNode == null)
return null;
var extendsNode = valueNode[ServiceModel.ShapeKey];
if (extendsNode == null)
return null;
var shapeName = extendsNode.ToString();
if (allowSubstitution)
{
var substituteShape = this.model.Customizations.GetSubstituteShapeName(shapeName);
if (substituteShape != null)
shapeName = substituteShape;
}
return shapeName;
}
///
/// The marshall name for the list elements
///
public string ListMarshallName
{
get
{
var keyNode = this.data[MemberKey];
if (keyNode == null || keyNode[ServiceModel.LocationNameKey] == null)
return null;
return keyNode[ServiceModel.LocationNameKey].ToString();
}
}
///
/// The name of the member that should be the payload of a request or is the payload of a response.
///
public string PayloadMemberName
{
get
{
var payloadNode = this.data[PayloadKey];
if (payloadNode == null)
return null;
return payloadNode.ToString();
}
}
///
/// Members of the shape, defined by another shape
///
public virtual IList Members
{
get
{
IList map = new List();
JsonData members = this.data[MembersKey];
if (members != null)
{
foreach (KeyValuePair kvp in members)
{
// filter out excluded members and perform any property
// renames at this stage to make downstream generation
// simpler
if (this.model.Customizations.IsExcludedProperty(kvp.Key, this.Name))
continue;
var propertyModifier = this.model.Customizations.GetPropertyModifier(this.Name, kvp.Key);
string propertyName;
if (propertyModifier != null && propertyModifier.EmitName != null)
propertyName = propertyModifier.EmitName;
else
propertyName = kvp.Key;
map.Add(new Member(this.model, this, propertyName, kvp.Key, kvp.Value, propertyModifier));
}
}
var shapeModifier = this.model.Customizations.GetShapeModifier(this.Name);
if (shapeModifier != null)
{
var injectedProperties = shapeModifier.InjectedPropertyNames;
foreach (var p in injectedProperties)
{
map.Add(new Member(this.model, this, p, p, shapeModifier.InjectedPropertyData(p)));
}
}
if (this.model.Customizations.RetainOriginalMemberOrdering)
return map;
else
return map.OrderBy(x => x.PropertyName).ToList();
}
}
///
/// Finds all structure MarshallNames under the current shape that contain no members
///
public IList FindMarshallNamesWithoutMembers()
{
List emptyMembers = new List();
HashSet processedMembers = new HashSet();
Queue shapeQueue = new Queue();
shapeQueue.Enqueue(this);
processedMembers.Add(this.MarshallName);
while (shapeQueue.Count > 0)
{
var currentShape = shapeQueue.Dequeue();
foreach (var child in currentShape.Members)
{
if (child.IsStructure && !processedMembers.Contains(child.MarshallName))
{
processedMembers.Add(child.MarshallName);
if (child.Shape.Members.Count != 0)
shapeQueue.Enqueue(child.Shape);
else
emptyMembers.Add(child.MarshallName);
}
}
}
return emptyMembers;
}
///
/// Find the member that is marked as payload
///
public Member PayloadMember
{
get
{
return Members.SingleOrDefault(m => string.Equals(m.ModeledName, PayloadMemberName
, StringComparison.InvariantCultureIgnoreCase));
}
}
public bool IsEventStream
{
get
{
var isEventStream = data[EventStreamKey];
if (isEventStream != null && isEventStream.IsBoolean)
{
return (bool) isEventStream;
}
return false;
}
}
///
/// If this shape is a primitive type this returns true so that the request can show if the member has been set or not
///
public bool IsNullable
{
get
{
return NullableTypes.Contains(this.Type);
}
}
///
/// Determines if the shape's type is a string
///
public bool IsString
{
get { return this.Type == "string"; }
}
///
/// Determines if the shape's type is a timestamp
///
public bool IsDateTime
{
get { return this.Type == "timestamp"; }
}
///
/// Determines if the shape's type is a integer
///
public bool IsInt
{
get { return this.Type == "integer"; }
}
///
/// Determines if the shape's type is a long
///
public bool IsLong
{
get { return this.Type == "long"; }
}
///
/// Determines if the shape's type is a float
///
public bool IsFloat
{
get { return this.Type == "float"; }
}
///
/// Determines if the shape's type is a double
///
public bool IsDouble
{
get { return this.Type == "double"; }
}
///
/// Determines if the shape's type is a boolean
///
public bool IsBoolean
{
get { return this.Type == "boolean"; }
}
///
/// Determines if the shape's type is a map
///
public bool IsMap
{
get
{
return this.Type == "map";
}
}
///
/// Determines if the shape's type is a list
///
public bool IsList
{
get
{
return this.Type == "list";
}
}
///
/// Determines if the shape's type is a string and enum is set in the json
///
public bool IsEnum
{
get
{
return this.Type == "string" && this.data["enum"] != null;
}
}
///
/// Determines if the shape's type is a structure
///
public bool IsStructure
{
get
{
return this.Type == "structure";
}
}
///
/// Determines if the shape's type is a blob
///
public bool IsMemoryStream
{
get
{
return this.Type == "blob";
}
}
///
/// Determines if the shape's json has a requiresLength attribute
///
public bool RequiresLength
{
get
{
return (bool)(this.data[RequiresLengthKey] ?? false);
}
}
///
/// Determines if the shape has the Document trait.
///
/// From the Spec:
/// 1. Structures marked with the document trait MUST NOT contain members.
/// 2. Document Types can not be used as an input, output, or error shape of an operation.
/// 3. IDocument types cannot function as unions, errors, event streams, or events. A Structure that has a Document trait can not also have
/// exception, fault, union, event, or eventstream traits.
///
/// NOTE: Restrictions are NOT enforced at this property. This will always return true if the document trait is present, even if the shape is not in a valid configuration.
///
public bool IsDocument
{
get
{
var documentNode = this.data[DocumentKey];
if (documentNode == null)
return false;
return bool.Parse(documentNode.ToString());
}
}
///
/// Determines if the shape's json has a streaming attribute
///
public bool IsStreaming
{
get
{
var streamingNode = this.data[StreamingKey];
if (streamingNode == null)
return false;
return bool.Parse(streamingNode.ToString());
}
}
public bool Sensitive
{
get
{
var sensitiveNode = data[SensitiveKey];
if (sensitiveNode == null)
return false;
return bool.Parse(sensitiveNode.ToString());
}
}
public long? Min
{
get
{
var value = data[MinKey];
if(value != null)
{
long min;
if (!long.TryParse(value.ToString(), out min))
{
Console.WriteLine("Generator does not support non-integer values for Min.");
return null;
}
return min;
}
return null;
}
}
public long? Max
{
get
{
var value = data[MaxKey];
if (value != null)
{
long max;
if (!long.TryParse(value.ToString(), out max))
{
Console.WriteLine("Generator does not support non-integer values for Max.");
return null;
}
return max;
}
return null;
}
}
public string Pattern
{
get
{
var value = data[PatternKey];
if (value == null) return null;
return value.ToString();
}
}
///
/// Determines the type of the shape from the type attribute
///
public string Type
{
get
{
var typeNode = this.data[TypeKey];
if (typeNode == null)
throw new Exception("Type is missing for shape " + this.Name);
return typeNode.ToString();
}
}
public bool HasErrorCode
{
get
{
var errorNode = this.data[ErrorKey];
if (errorNode != null)
return errorNode[ErrorCodeKey] != null;
return false;
}
}
public string ErrorCode
{
get
{
if (!HasErrorCode)
return null;
var errorNode = this.data[ErrorKey];
return errorNode[ErrorCodeKey].ToString();
}
}
///
/// Determines if the shape json has a flattened attribute
///
public bool IsFlattened
{
get
{
var flattened = data[FlattenedKey];
if (flattened == null || !flattened.IsBoolean) return false;
return (bool)flattened;
}
}
///
/// The name of the marshaller. The locationName if it's set, the shapes name otherwise.
///
public string MarshallName
{
get
{
return LocationName ?? this._name;
}
}
///
/// Looks if there is a locationName for the shape, null otherwise
///
public string LocationName
{
get
{
// if list, lookup member/metadata/xmlName
// otherwise, lookup metadata/xmlName
var source = data;
if (IsList)
{
source = data[MemberKey];
if (source == null) return null;
}
var locationName = source[ServiceModel.LocationNameKey];
if (locationName == null) return null;
return locationName.ToString();
}
}
public bool IsPrimitiveType
{
get
{
return PrimitiveTypeNames.ContainsKey(this.Type);
}
}
public string GetPrimitiveType()
{
string type;
if (!PrimitiveTypeNames.TryGetValue(this.Type, out type))
{
throw new Exception("Shape is not a primitive type: " + this.Type);
}
return type;
}
///
/// The member names listed in the shape's json required attribute
///
public IEnumerable RequiredMembers
{
get
{
var req = new List();
var required = data[RequiredKey];
if (required == null || !required.IsArray) return req;
req.AddRange(from object item in required select item.ToString());
req.Sort();
return req;
}
}
///
/// Determines if the shape is Deprecated.
///
public bool IsDeprecated
{
get
{
if (data[DeprecatedKey] != null && data[DeprecatedKey].IsBoolean)
return (bool)data[DeprecatedKey];
return false;
}
}
///
/// Returns the deprecation message specified in the model or in the customization file.
///
public string DeprecationMessage
{
get
{
string message = this.model.Customizations.GetShapeModifier(this._name)?.DeprecationMessage ??
data[DeprecatedMessageKey].CastToString();
if (message == null)
throw new Exception(string.Format("The 'message' property of the 'deprecated' trait is missing for shape {0}.\nFor example: \"ShapeName\":{{ ... \"members\":{{ ... }}, \"deprecated\":true, \"deprecatedMessage\":\"This type is deprecated\"}}", this._name));
return message;
}
}
///
/// If the shape is a request or response type, strips off the suffix
/// to yield the parent operation. Null is returned if the shape is a
/// regular model shape.
///
public string RelatedOperationName
{
get
{
const string RequestSuffix = "Request";
const string ResponseSuffix = "Response";
const string ResultSuffix = "Result";
var len = Name.Length;
if (Name.EndsWith(RequestSuffix, StringComparison.Ordinal))
return Name.Substring(0, len - RequestSuffix.Length);
if (Name.EndsWith(ResponseSuffix, StringComparison.Ordinal))
return Name.Substring(0, len - ResponseSuffix.Length);
if (Name.EndsWith(ResultSuffix, StringComparison.Ordinal))
return Name.Substring(0, len - ResultSuffix.Length);
return null;
}
}
///
/// Returns the marshaller method to use in the generated marshaller code for a
/// shape of primitive type. This is used while marshalling lists and maps.
///
public string PrimitiveMarshaller(MarshallLocation marshallLocation)
{
if (this.IsDateTime)
{
var timestampFormat = GetTimestampFormat(marshallLocation);
return "StringUtils.FromDateTimeTo" + timestampFormat;
}
else
{
return "StringUtils.From" + this.GetPrimitiveType();
}
}
///
/// Timestamp format for the shape.
///
public TimestampFormat GetTimestampFormat(MarshallLocation marshallLocation)
{
var timestampFormat = data.GetTimestampFormat();
if (timestampFormat == TimestampFormat.None)
{
timestampFormat = Member.GetDefaultTimestampFormat(marshallLocation, this.model.Type);
}
return timestampFormat;
}
public bool IsFieldRequired(string fieldName)
{
var requiredList = data[RequiredKey];
if (requiredList != null && requiredList.IsArray)
{
foreach (var name in requiredList)
{
if (string.Equals(name.ToString(), fieldName))
return true;
}
}
return false;
}
///
/// Returns true if the structure contains the event trait,
/// not to be confused with the EventStream structure shape
///
public bool IsEvent
{
get
{
return this.data.PropertyNames.Contains(EventKey);
}
}
}
}