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