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