using Json.LitJson;
using ServiceClientGenerator.Endpoints;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServiceClientGenerator
{
///
/// An object that represents the operations for a service api
///
public class Operation : BaseModel
{
///
/// The name of the operation as found in the json model
///
readonly string name;
private OperationPaginatorConfig _operationPaginatorConfig;
///
/// Constructs on operation from a service model, operation name, and the json model
///
/// The model of the service the operation belongs to
/// The name of the operation
/// The json data from the model file
public Operation(ServiceModel model, string name, JsonData data)
: base(model, data)
{
this.model = model;
this.name = name;
this.data = data;
}
///
/// Constructs on operation from a service model, operation name, the json model, and the json paginators
///
/// The model of the service the operation belongs to
/// The name of the operation
/// The json data from the model file
/// The json data from the paginators file
public Operation(ServiceModel model, string name, JsonData data, JsonData paginatorData)
: this(model, name, data)
{
if (paginatorData[ServiceModel.OutputTokenKey] != null && paginatorData[ServiceModel.InputTokenKey] != null)
{
_operationPaginatorConfig = new OperationPaginatorConfig(this, name, paginatorData);
}
}
///
/// The name of the operation determined by checking if there are any customizations for a change of the name
/// then using the provided name if there isn't a custom name.
///
public string Name
{
get
{
var modifiers = this.model.Customizations.GetOperationModifiers(ShapeName);
if (modifiers != null && !string.IsNullOrEmpty(modifiers.Name))
return modifiers.Name;
return ShapeName;
}
}
///
/// Returns the raw shape name without customization
///
public string ShapeName
{
get
{
return this.name;
}
}
public override string Documentation
{
get
{
var modifiers = this.model.Customizations.GetOperationModifiers(this.name);
if (modifiers != null && !string.IsNullOrEmpty(modifiers.Documentation))
return modifiers.Documentation;
return base.Documentation;
}
}
///
/// Determines if the operation is Deprecated.
///
public bool IsDeprecated
{
get
{
if (data[ServiceModel.DeprecatedKey] != null && data[ServiceModel.DeprecatedKey].IsBoolean)
return (bool)data[ServiceModel.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.GetOperationModifiers(this.name)?.DeprecatedMessage ??
data[ServiceModel.DeprecatedMessageKey].CastToString();
if (message == null)
throw new Exception(string.Format("The 'message' property of the 'deprecated' trait is missing for operation {0}.\nFor example: \"OperationName\":{{\"name\":\"OperationName\", ... \"deprecated\":true, \"deprecatedMessage\":\"This operation is deprecated\"}}", this.name));
return message;
}
}
///
/// Determines if a checksum needs to be sent in the Content-MD5 header.
/// Checks both the older "httpChecksumRequired" property as well as the newer
/// "httpChecksum.requestChecksumRequired" property
///
public bool HttpChecksumRequired
{
get
{
if (data[ServiceModel.HttpChecksumRequiredKey] != null && data[ServiceModel.HttpChecksumRequiredKey].IsBoolean)
{
if ((bool)data[ServiceModel.HttpChecksumRequiredKey])
{
return true;
}
}
if (ChecksumConfiguration != null && ChecksumConfiguration.RequestChecksumRequired)
{
return true;
}
return false;
}
}
///
/// Request and response flexible checksum configuration, read from the "httpChecksum" object
///
public ChecksumConfiguration ChecksumConfiguration
{
get
{
if (data[ServiceModel.HttpChecksumKey] != null)
return new ChecksumConfiguration(data[ServiceModel.HttpChecksumKey]);
return null;
}
}
///
/// Determines whether this operation's marshaller needs to call the checksum
/// handling in Core. This means it either requires a MD5 checksum and/or supports
/// flexible checksums.
///
public bool RequiresChecksumDuringMarshalling
{
get
{
if (HttpChecksumRequired)
{
return true;
}
if (!string.IsNullOrEmpty(ChecksumConfiguration?.RequestAlgorithmMember))
{
return true;
}
return false;
}
}
///
/// Determines if the operation is customized to be only internally accessible.
///
public bool IsInternal
{
get
{
var modifiers = this.model.Customizations.GetOperationModifiers(this.name);
if (modifiers != null)
return modifiers.IsInternal;
return false;
}
}
///
/// Determines if the operation is to be excluded from generation
///
public bool IsExcluded
{
get
{
var modifiers = this.model.Customizations.GetOperationModifiers(this.name);
if (modifiers != null)
return modifiers.IsExcluded;
return false;
}
}
// set if we need to 'demote' the response output members into a shape hosted
// within a result class that doesn't exist in the service model. As an example
// EC2's DescribeImageAttribute outputs a response with multiple members. We take
// those and put them into an ImageAttribute shape that is referenced by the
// DescribeImageAttributeResult class (which doesn't exist in EC2's model).
public bool UseWrappingResult
{
get
{
var modifiers = this.model.Customizations.GetOperationModifiers(this.name);
if (modifiers != null)
return modifiers.UseWrappingResult;
return false;
}
}
///
/// Allows the operation to return an empty result when the operation is modeled to return body result structure.
///
public bool AllowEmptyResult
{
get
{
var modifiers = this.model.Customizations.GetOperationModifiers(this.name);
if (modifiers != null)
return modifiers.AllowEmptyResult;
return false;
}
}
public bool WrapsResultShape(string shapeName)
{
var modifiers = this.model.Customizations.GetOperationModifiers(this.name);
if (modifiers != null && modifiers.UseWrappingResult)
{
var wrappedShape = modifiers.WrappedResultShape;
return (!string.IsNullOrEmpty(wrappedShape) &&
wrappedShape.Equals(shapeName, StringComparison.Ordinal));
}
return false;
}
///
/// If the operation uses a payload member, return that member so that it can be generated for the operation as well.
/// Payloads are place holders in the json models. They are represented as another shape and contain the actual necessary attributes.
///
public Member RequestPayloadMember
{
get
{
if (this.RequestStructure != null)
{
var payload = this.RequestStructure.PayloadMemberName;
if (!string.IsNullOrWhiteSpace(payload))
{
return this.RequestStructure.Members.Single(m => m.MarshallName.Equals(payload, StringComparison.InvariantCultureIgnoreCase));
}
}
return null;
}
}
///
/// The response payload member, null if one does not exist.
///
public Member ResponsePayloadMember
{
get
{
if (this.ResponseStructure != null)
{
var payload = this.ResponseStructure.PayloadMemberName;
if (!string.IsNullOrWhiteSpace(payload))
{
return this.ResponseStructure.Members.Single(m => m.MarshallName.Equals(payload, StringComparison.InvariantCultureIgnoreCase));
}
}
return null;
}
}
///
/// Gets the namespace of the payload for an XML object
///
public string XmlNamespace
{
get
{
if (this.RequestPayloadMember != null)
{
return RequestPayloadMember.XmlNamespace;
}
return this.Input.XmlNamespace;
}
}
///
/// A list of the members that are in the request header, empty if none
///
public IList RequestHeaderMembers
{
get
{
return this.RequestStructure == null ?
new List() :
this.RequestStructure.Members.Where(
m => m.MarshallLocation == MarshallLocation.Header || m.MarshallLocation == MarshallLocation.Headers).ToList();
}
}
///
/// A list of members that are in the response header, empty if none
///
public IList ResponseHeaderMembers
{
get
{
return this.ResponseStructure == null ?
new List() :
this.ResponseStructure.Members.Where(
m => m.MarshallLocation == MarshallLocation.Header || m.MarshallLocation == MarshallLocation.Headers).ToList();
}
}
///
/// A member that is defined by the status code of the response, null if none
///
public Member ResponseStatusCodeMember
{
get
{
return this.ResponseStructure == null ?
null :
this.ResponseStructure.Members.SingleOrDefault(
m => m.MarshallLocation == MarshallLocation.StatusCode);
}
}
///
/// A member in the response that is sent as a stream, null if none
///
public Member ResponseStreamingMember
{
get
{
return this.ResponseStructure == null ?
null :
this.ResponseStructure.Members.SingleOrDefault(m => m.IsStreaming);
}
}
///
/// Members that are part of the URI for the request, empty if none
///
public IList RequestUriMembers
{
get
{
return this.RequestStructure == null ?
new List() :
this.RequestStructure.Members.Where(
m => m.MarshallLocation == MarshallLocation.Uri).ToList();
}
}
///
/// List of members that are part of the QueryString, empty if none
///
public IList RequestQueryStringMembers
{
get
{
return this.RequestStructure == null ?
new List() :
this.RequestStructure.Members.Where(
m => m.MarshallLocation == MarshallLocation.QueryString).ToList();
}
}
///
/// A member in the request that is sent as a stream, null if none
///
public Member RequestStreamingMember
{
get
{
return this.RequestStructure == null ?
null :
this.RequestStructure.Members.SingleOrDefault(m => m.IsStreaming);
}
}
///
/// Members who are part of the request's body
///
public IList RequestBodyMembers
{
get
{
if (this.RequestStructure == null)
return new List();
var payloadName = this.RequestStructure.PayloadMemberName;
return this.RequestStructure.Members.Where(
m =>
m.MarshallLocation == MarshallLocation.Body &&
!string.Equals(m.MarshallName, payloadName, StringComparison.Ordinal)).ToList();
}
}
///
/// Members who are part of the response's body
///
public IList ResponseBodyMembers
{
get
{
if (this.ResponseStructure == null)
return new List();
var payloadName = this.ResponseStructure.PayloadMemberName;
return this.ResponseStructure.Members.Where(
m =>
m.MarshallLocation == MarshallLocation.Body &&
!string.Equals(m.MarshallName, payloadName, StringComparison.Ordinal)).ToList();
}
}
///
/// List of members that are decorated with a hostLabel value equal to true
///
public IEnumerable RequestHostPrefixMembers
{
get
{
return this.RequestStructure == null ?
new List() :
this.RequestStructure.Members.Where(m => m.IsHostLabel);
}
}
public bool IsEventStreamOutput => ResponseStructure?.Members?.Any(
member => member.Shape?.IsEventStream ?? false)
?? false;
///
/// Determines if the request structure will have members in the header
///
public bool RequestHasHeaderMembers
{
get
{
return (this.RequestHeaderMembers.Count > 0);
}
}
///
/// Determines if the request structure will have members in the body
///
public bool RequestHasBodyMembers
{
get
{
// Has any members which are marshalled as part of the request body
return (this.RequestBodyMembers.Count > 0);
}
}
///
/// Determines if the request structure has uri members
///
public bool RequestHasUriMembers
{
get
{
return (this.RequestUriMembers.Count > 0);
}
}
///
/// Determines if the request structure has query string members
///
public bool RequestHasQueryStringMembers
{
get
{
// Has any members which are marshalled as part of the request body
return this.RequestStructure != null &&
this.RequestStructure.Members.Any(m => m.MarshallLocation == MarshallLocation.QueryString);
}
}
///
/// Determines if the response structure will have members in the body
///
public bool ResponseHasBodyMembers
{
get
{
// Has any members which are marshalled as part of the response body
return (this.ResponseBodyMembers.Count > 0);
}
}
///
/// Determines if the response structure will have members in the header
///
public bool ResponseHasHeaderMembers
{
get
{
return (this.ResponseHeaderMembers.Count > 0);
}
}
///
/// Use query string if there are body members or a streamed member
///
public bool UseQueryString
{
get
{
return this.RequestStructure != null &&
this.RequestStructure.Members.Any(m => m.MarshallLocation == MarshallLocation.QueryString);
}
}
///
/// The input of the operation, a shape for a request structure
///
public OperationInput Input
{
get
{
JsonData inputNode = this.data[ServiceModel.InputKey];
if (inputNode == null)
return null;
return new OperationInput(this.model, inputNode);
}
}
public OperationAuthType? AuthType
{
get
{
return OperationAuthTypeParser.Parse(this.data[ServiceModel.AuthTypeKey]);
}
}
///
/// The method of the operation (i.e. POST, GET, ...)
///
public string HttpMethod
{
get
{
JsonData httpNode = this.data[ServiceModel.HttpKey];
if (httpNode == null)
return string.Empty;
JsonData methodNode = httpNode[ServiceModel.MethodKey];
if (methodNode == null)
return string.Empty;
return methodNode.ToString();
}
}
///
/// The endpoint hostPrefix of the operation
///
public string EndpointHostPrefix
{
get
{
JsonData endpointNode = this.data[ServiceModel.EndpointKey];
if (endpointNode == null)
return string.Empty;
return endpointNode[ServiceModel.HostPrefixKey]?.ToString() ?? string.Empty;
}
}
///
/// The endpointoperation flag marking if this is the operation to use for endpoint discovery.
///
public bool IsEndpointOperation
{
get
{
return (bool)(this.data[ServiceModel.EndpointOperationKey] ?? false);
}
}
///
/// The endpointdiscovery flag specifying if this operation is to use endpoint discovery.
///
public bool EndpointDiscoveryEnabled
{
get
{
return this.data[ServiceModel.EndpointDiscoveryKey] != null ? true : false;
}
}
///
/// The endpointdiscovery required flag specifying if this operation is required use endpoint discovery.
///
public bool IsEndpointDiscoveryRequired
{
get
{
JsonData endpointDiscovery = this.data[ServiceModel.EndpointDiscoveryKey];
if (endpointDiscovery == null)
return false;
JsonData required = endpointDiscovery[ServiceModel.RequiredKey];
if (required == null)
return false;
if (required.IsBoolean)
return (bool)(required ?? false);
return Convert.ToBoolean((string)required);
}
}
///
/// Members that are marked with the "endpointdiscoveryid" = true trait
///
public IList RequestEndpointDiscoveryIdMembers
{
get
{
if (this.RequestStructure == null)
return new List();
return this.RequestStructure.Members.Where(m => m.IsEndpointDiscoveryId && m.GetPrimitiveType() == "String").ToList();
}
}
///
/// Determines if the structure has any members that are marked with the "endpointdiscoveryid" = true trait
///
public bool RequestHasEndpointDiscoveryIdMembers
{
get
{
return (this.RequestEndpointDiscoveryIdMembers.Count > 0);
}
}
///
/// Determines if there is an Operation member for the operation with the request structure marked with "endpointoperation" = true.
///
public bool RequestHasOperationEndpointOperationMember
{
get
{
if (this.RequestStructure == null)
return false;
return this.RequestStructure.Members.FirstOrDefault(m => m.PropertyName == "Operation") != null;
}
}
///
/// Determines if there is an Identifiers member for the operation with the request structure marked with "endpointoperation" = true.
///
public bool RequestHasIdentifiersEndpointOperationMember
{
get
{
if (this.RequestStructure == null)
return false;
return this.RequestStructure.Members.FirstOrDefault(m => m.PropertyName == "Operation") != null;
}
}
///
/// Returns the Request URI without any query parameters.
///
public string RequestUri
{
get
{
return this.RequestUriRaw.Split(new char[] { '?' }, 2)[0];
}
}
///
/// Returns the static query parameters which are specified in the RequestUri itself.
/// Example : Cloud Search Domain's Search operation (/2013-01-01/search?format=sdk&pretty=true).
///
public Dictionary StaticQueryParameters
{
get
{
var queryParams = new Dictionary();
var segments = this.RequestUriRaw.Split(new char[] { '?' }, 2);
if (segments.Count() > 1)
{
var staticQueryParams = segments[1];
foreach (string s in staticQueryParams.Split('&', ';'))
{
string[] nameValuePair = s.Split(new char[] { '=' }, 2);
if (nameValuePair.Length == 2 && nameValuePair[1].Length > 0)
{
queryParams.Add(nameValuePair[0], nameValuePair[1]);
}
else
{
queryParams.Add(nameValuePair[0], null);
}
}
}
return queryParams;
}
}
private string RequestUriRaw
{
get
{
JsonData httpNode = this.data[ServiceModel.HttpKey];
if (httpNode == null)
return string.Empty;
JsonData requestUri = httpNode[ServiceModel.RequestUriKey];
if (requestUri == null)
return string.Empty;
return requestUri.ToString();
}
}
///
/// The parent xml node for the response data coming back from the service.
///
public string ResultWrapper
{
get
{
JsonData output = this.data[ServiceModel.OutputKey];
if (output == null)
return string.Empty;
JsonData wrapper = output[ServiceModel.ResultWrapperKey];
if (wrapper == null)
return string.Empty;
return wrapper.ToString();
}
}
///
/// The list of errors that the service returns which will be turned into exceptions.
///
public IList Exceptions
{
get
{
var hashSet = new HashSet();
var errors = this.data[ServiceModel.ErrorsKey];
if (errors != null && errors.IsArray)
{
foreach (JsonData error in errors)
{
var extendsNode = error[ServiceModel.ShapeKey];
if (extendsNode == null)
continue;
var structure = this.model.FindShape(extendsNode.ToString());
hashSet.Add((ExceptionShape)structure);
}
}
return hashSet.OrderBy(x => x.Name).ToList();
}
}
///
/// The structure of the request for the operation as a shape, used to generate the request object
///
public Shape RequestStructure
{
get
{
var inputNode = this.data[ServiceModel.InputKey];
if (inputNode == null)
return null;
var structure = this.model.FindShape(inputNode[ServiceModel.ShapeKey].ToString());
return structure;
}
}
///
/// The shape of the response for the operation as a shape, used to generate the response object
///
public Shape ResponseStructure
{
get
{
var outputNode = this.data[ServiceModel.OutputKey];
if (outputNode == null)
return null;
var structure = this.model.FindShape(outputNode[ServiceModel.ShapeKey].ToString());
return structure;
}
}
///
/// For Set to true when the service model specifies a shape that should be wrapped in a response.
/// ElastiCache CreateCacheCluster is an example of this.
///
public bool IsResponseWrapped
{
get
{
var outputNode = this.data[ServiceModel.OutputKey];
if (outputNode == null)
return false;
var wrappedNode = outputNode[ServiceModel.WrapperKey];
if (wrappedNode == null)
return false;
return bool.Parse(wrappedNode.ToString());
}
}
public IList Examples
{
get
{
var list = new List();
var data = this.model.Customizations.GetExamples(this.name);
foreach(JsonData example in data)
{
list.Add(new Example(this.model, this.name, example));
}
return list;
}
}
public bool HasExamples
{
get
{
return this.model.Customizations.GetExamples(this.Name).Count > 0;
}
}
///
/// Determines if a given member should be treated as a greedy path, meaning
/// that the resource path contains {MEMBER_NAME+} instead of simply {MEMBER_NAME}.
///
///
///
public string GetUriResourcePathTarget(Member member,out bool isGreedy)
{
var greedyPathResourcePathIdentifier = "{" + member.MarshallLocationName + "+}";
var simplePathResourcePathIdentifier = "{" + member.MarshallLocationName + "}";
isGreedy = this.RequestUri.IndexOf(greedyPathResourcePathIdentifier, StringComparison.Ordinal) >= 0;
var isSimple = this.RequestUri.IndexOf(simplePathResourcePathIdentifier, StringComparison.Ordinal) >= 0;
if (isGreedy && isSimple)
throw new Exception(string.Format("Unexpected behavior/model, member {1} of operation {0} is both a simple and a greedy parameter", this.Name, member.PropertyName));
else if (!isGreedy && !isSimple)
throw new Exception(string.Format("Unexpected behavior/model, member {1} of operation {0} is neither a simple nor a greedy parameter", this.Name, member.PropertyName));
else if (isGreedy)
return greedyPathResourcePathIdentifier;
else
return simplePathResourcePathIdentifier;
}
///
/// Represents the operation as a string
///
/// The name of the operation, customized if specified
public override string ToString()
{
return Name;
}
///
/// Returns any operation modifiers that have been set for this operation.
///
public CustomizationsModel.OperationModifiers OperationModifiers
{
get { return this.model.Customizations.GetOperationModifiers(this.Name); }
}
public string RestAPIDocUrl
{
get {
string serviceId = this.model.ServiceUid;
if (!string.IsNullOrEmpty(serviceId))
{
return string.Format(@"http://docs.aws.amazon.com/goto/WebAPI/{0}/{1}", serviceId, ShapeName);
}
else
{
return null;
}
}
}
///
/// Paginators for an operation
///
public OperationPaginatorConfig Paginators { get { return this._operationPaginatorConfig; } }
///
/// Whether or not this operation has properly configured
/// paginators based on provided json configuration
///
public bool UnsupportedPaginatorConfig { get; set; }
private static ConcurrentDictionary _checkedService = new ConcurrentDictionary();
///
/// Gets list of static context parameters, used on operation to drive endpoint resolution
///
public List StaticContextParameters
{
get
{
var result = new List();
var parameters = data.SafeGet("staticContextParams");
if (parameters != null)
{
foreach (var param in parameters.GetMap())
{
result.Add(new StaticContextParameter
{
name = param.Key,
value = param.Value["value"]
});
}
}
return result;
}
}
}
}