using Json.LitJson; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; namespace ServiceClientGenerator { /// /// Represents a code sample for an operation. /// public class Example : BaseModel { public const string IdKey = "id", TitleKey = "title", DescriptionKey = "description", InputKey = "input", OutputKey = "output", CommentsKey = "comments"; public Example(ServiceModel model, string operationName, JsonData data) : base(model, data) { this.OperationName = operationName; } /// /// The name of the operation this sample is for /// public string OperationName { get; set; } /// /// The operation metadata associated with this example. /// public Operation Operation { get { return this.model.FindOperation(OperationName); } } /// /// The example id taken from the model. /// /// /// This unique id is used for the region in the emitted code sample /// that will be parsed to include the code in the documentation. /// public string Id { get { return data.SafeGetString(IdKey); } } /// /// The title for the example. /// public string Title { get { return data.SafeGetString(TitleKey); } } /// /// Descriptive text for the example. /// public string Description { get { return data.SafeGetString(DescriptionKey); } } /// /// The sample data for the input parameters. /// public IDictionary InputParameters { get { var inputMap = data.SafeGet(InputKey); if (null == inputMap) return new Dictionary(); return inputMap.GetMap(); } } /// /// The sample data for the output parameters. /// public IDictionary OutputParameters { get { var outputMap = data.SafeGet(OutputKey); if (null == outputMap) return new Dictionary(); return outputMap.GetMap(); } } /// /// Comments for the sample input data. /// /// /// A map of property name to comment text. /// public IDictionary InputComments { get { return GetComments(InputKey); } } /// /// Comments for the sample response data. /// /// /// A map of property name to comment text. /// public IDictionary OutputComments { get { return GetComments(OutputKey); } } // Common to the InputComments and OutputComments properties private IDictionary GetComments(string key) { var comments = this.data[CommentsKey]; if (null != comments) { var map = comments.SafeGet(key); if (null != map) return map.GetStringMap(); } return new Dictionary(); } /// /// For the request, build the literals and/or instantiators to assign to each /// property in the request shape for which sample data was supplied in the example. /// /// A list of strings of the form 'PropertyName = value' with comments at the /// end, if present. public IList GetRequestAssignments(int currentIndent) { var result = new List(); if (!InputParameters.Any()) return result; var last = InputParameters.Last().Key; foreach (var param in InputParameters) { var member = Operation.RequestStructure.Members.GetMemberByName(param.Key); if (null == member) continue; var sb = new StringBuilder(); var cb = new CodeBuilder(sb, currentIndent); cb.Append(member.PropertyName).Append(" = "); GetSampleLiteral(member, param.Value, cb); if (param.Key != last) cb.Append(","); if (InputComments.ContainsKey(param.Key)) cb.Append(" // ").Append(InputComments[param.Key]); result.Add(sb.ToString()); } return result; } public IList GetResponseAssignments() { var result = new List(); foreach (var param in OutputParameters) { var member = Operation.ResponseStructure.Members.GetMemberByName(param.Key); if (null == member) continue; var shapeType = ShapeType(member.Shape); result.Add(string.Format("{0} {1} = response.{2};{3}", shapeType, member.ArgumentName, member.PropertyName, OutputComments.ContainsKey(param.Key) ? " // " + OutputComments[param.Key] : "")); } return result; } /// /// Given a member and sample data, build a literal/instantation for the /// member's type with the sample data. /// /// The member in the model /// Sample data to populate the literal with /// A CodeBuilder instance to write the code to. public void GetSampleLiteral(Member member, JsonData data, CodeBuilder cb) { GetSampleLiteral(member.Shape, data, cb); } /// /// Given a Shape and sample data, build a literal/instantiation for the /// Shape's type with the sample data. /// /// The Shape in the model /// Sample data to populate the literal with /// A CodeBuilder instance to write the code to. public void GetSampleLiteral(Shape shape, JsonData data, CodeBuilder cb) { if (shape.IsString && data.IsString) cb.AppendQuote(data.ToString()); else if (shape.IsBoolean) cb.Append(data.ToString().ToLower()); else if (shape.IsFloat || shape.IsInt || shape.IsDouble || shape.IsLong) cb.Append(data.ToString()); else if (shape.IsList && data.IsArray) { var itemType = shape.ListShape; cb.AppendFormat("new List<{0}> ", ShapeType(itemType)).OpenBlock(); for (int i = 0; i < data.Count; i++) { GetSampleLiteral(itemType, data[i], cb); if (i < (data.Count - 1)) cb.AppendLine(","); } cb.CloseBlock(); } else if (shape.IsMap && data.IsObject) { var keyType = shape.KeyShape; var valType = shape.ValueShape; cb.AppendFormat("new Dictionary<{0}, {1}> ", ShapeType(keyType), ShapeType(valType)); cb.OpenBlock(); foreach (var k in data.PropertyNames) { cb.Append("{ "); GetSampleLiteral(keyType, k, cb); cb.Append(", "); GetSampleLiteral(valType, data[k], cb); cb.Append(" }"); if (k != data.PropertyNames.Last()) cb.AppendLine(","); } cb.CloseBlock(); } else if (shape.IsStructure && data.IsObject) { cb.AppendFormat("new {0} ", ShapeType(shape)); if (data.PropertyNames.Count() > 1) cb.OpenBlock(); else cb.Append("{ "); foreach (var field in data.PropertyNames) { var property = shape.Members.GetMemberByName(field); if (null == property) continue; cb.AppendFormat("{0} = ", property.PropertyName); GetSampleLiteral(property, data[field], cb); if (field != data.PropertyNames.Last()) cb.AppendLine(","); } if (data.PropertyNames.Count() > 1) cb.CloseBlock(); else cb.Append(" }"); } else if (shape.IsMemoryStream && data.IsString) { cb.AppendFormat("new {0}({1})", ShapeType(shape), data.ToString()); } else if (shape.IsDateTime) { string exampleValue = null; if (data.IsString) { var textValue = data.ToString(); // Even though the date in the service example is in UTC format (i.e. ending with 'Z'), we need to tell TryParse about it. // If not, the parsed result will have DateTimeKind = Local, and the output can change depending on where the generator runs. if (DateTime.TryParse(textValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateTime parsedDateTime)) { exampleValue = string.Format("new DateTime({0}, DateTimeKind.Utc)", parsedDateTime.ToString("yyyy, M, d, h, m, s")); } } if (string.IsNullOrEmpty(exampleValue)) { exampleValue = "DateTime.UtcNow"; } cb.Append(exampleValue); } else { // default value cb.Append(""); } } /// /// Return the type name for a shape /// /// The shape to get the type name for /// private string ShapeType(Shape shape) { if (shape.IsBoolean) return "bool"; if (shape.IsInt) return "int"; if (shape.IsLong) return "long"; if (shape.IsFloat) return "float"; if (shape.IsDouble) return "double"; if (shape.IsDateTime) return "DateTime"; if (shape.IsMemoryStream) return "MemoryStream"; if (shape.IsMap) return string.Format("Dictionary<{0}, {1}>", ShapeType(shape.KeyShape), ShapeType(shape.ValueShape)); if (shape.IsList) return string.Format("List<{0}>", ShapeType(shape.ListShape)); if (shape.IsStructure) return shape.Name; if (shape.IsPrimitiveType) return shape.Type; throw new InvalidOperationException(string.Format("Unable to resolve type for shape {0}", shape.Name)); } } }