using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Json.LitJson;
using System.Globalization;
using ServiceClientGenerator.Endpoints;
namespace ServiceClientGenerator
{
///
/// The service model represents the information of a service found through the manifest, model, and customization file.
/// It is used to get any information necessary to generate the clients, operations, and structures of a service.
///
public class ServiceModel
{
// metadata properties
public const string MetadataKey = "metadata";
public const string ApiVersionKey = "apiVersion";
public const string EndpointPrefixKey = "endpointPrefix";
public const string JsonVersionKey = "jsonVersion";
public const string ProtocolKey = "protocol";
public const string ProtocolSettingsKey = "protocolSettings";
public const string ProtocolSettingsH2Key = "h2";
public const string ServiceFullNameKey = "serviceFullName";
public const string SignatureVersionKey = "signatureVersion";
public const string TargetPrefixKey = "targetPrefix";
public const string UidKey = "uid";
public const string ServiceAbbreviationKey = "serviceAbbreviation";
public const string SigningNameKey = "signingName";
public const string ServiceIdKey = "serviceId";
public const string AWSQueryCompatibleKey = "awsQueryCompatible";
// operations
public const string OperationsKey = "operations";
public const string HttpKey = "http";
public const string MethodKey = "method";
public const string RequestUriKey = "requestUri";
public const string InputKey = "input";
public const string OutputKey = "output";
public const string ErrorsKey = "errors";
public const string ResultWrapperKey = "resultWrapper";
public const string AuthTypeKey = "authtype";
public const string EndpointKey = "endpoint";
public const string HostPrefixKey = "hostPrefix";
public const string EndpointOperationKey = "endpointoperation";
public const string EndpointDiscoveryKey = "endpointdiscovery";
public const string RequiredKey = "required";
public const string HttpChecksumRequiredKey = "httpChecksumRequired";
public const string HttpChecksumKey = "httpChecksum";
public const string RequestChecksumRequiredKey = "requestChecksumRequired";
public const string RequestAlgorithmMemberKey = "requestAlgorithmMember";
public const string RequestValidationModeMemberKey = "requestValidationModeMember";
public const string ResponseAlgorithmsKey = "responseAlgorithms";
// shapes
public const string ShapesKey = "shapes";
public const string ShapeKey = "shape";
public const string IdempotencyTokenKey = "idempotencyToken";
public const string DeprecatedKey = "deprecated";
public const string DeprecatedMessageKey = "deprecatedMessage";
public const string WrapperKey = "wrapper";
public const string LocationNameKey = "locationName";
public const string QueryNameKey = "queryName";
public const string XmlNamespaceUriKey = "uri";
public const string XmlNamespaceKey = "xmlNamespace";
public const string EndpointDiscoveryIdKey = "endpointdiscoveryid";
// pagination
public const string PaginationKey = "pagination";
public const string ResultKeyKey = "result_key";
public const string InputTokenKey = "input_token";
public const string OutputTokenKey = "output_token";
public const string LimitKeyKey = "limit_key";
public const string MoreResultsKey = "more_results";
// documentation
public const string DocumentationKey = "documentation";
// client context params
public const string ClientContextParams = "clientContextParams";
///
/// This model contains information about customizations needed during the generation process
///
readonly CustomizationsModel _customizationModel;
///
/// The information found as a property at the top of the model structure which contains information on how to generate the service
///
JsonData _metadata;
///
/// This is the entire model document for the service processed as a json object
///
internal JsonData DocumentRoot;
///
/// This is the paginators for the service processed as a json object
///
internal JsonData PaginatorsRoot;
///
/// Used for unit testing, creates a service model from a TextReader so that the generation can be checked
///
/// The reader to get the model information from
/// The reader to get any customizations for the service from
public ServiceModel(TextReader serviceModelReader, TextReader customizationReader)
{
InitializeServiceModel(serviceModelReader);
this._customizationModel = new CustomizationsModel(customizationReader);
}
///
/// Used by the generator to create a model for the service being generated
///
/// Path to the file containing the model of the service
/// Path to the customizations file for the service
public ServiceModel(string serviceModelPath, string customizationModelPath)
{
using (var reader = new StreamReader(serviceModelPath))
InitializeServiceModel(reader);
this._customizationModel = new CustomizationsModel(customizationModelPath);
}
///
/// Used by the generator to create a model for the service being generated
///
/// Path to the file containing the model of the service
/// Path to the customizations file for the service
/// /// Path to the customizations file for the service
public ServiceModel(string serviceModelPath, string customizationModelPath, string servicePaginatorsPath)
{
using (var reader = new StreamReader(serviceModelPath))
{
InitializeServiceModel(reader);
}
if (File.Exists(servicePaginatorsPath))
{
using (var reader = new StreamReader(servicePaginatorsPath))
InitializePaginators(reader);
}
this._customizationModel = new CustomizationsModel(customizationModelPath);
}
///
/// Sets the base json info no matter how the model was constructed
///
/// The reader to pull the model json from
private void InitializeServiceModel(TextReader reader)
{
this.DocumentRoot = JsonMapper.ToObject(reader);
this._metadata = this.DocumentRoot[MetadataKey];
}
///
/// Sets the base json info no matter how the model was constructed
///
/// The reader to pull the model json from
private void InitializePaginators(TextReader reader)
{
this.PaginatorsRoot = JsonMapper.ToObject(reader);
}
///
/// Indicates that this service was converted from query to json 1.0
/// and may be sending AWSQuery compatible error code data in header.
///
public bool IsAwsQueryCompatible
{
get
{
return this._metadata.PropertyNames.Contains(AWSQueryCompatibleKey);
}
}
///
/// Whether or not this service has paginators available
/// for any operation
///
public bool HasPaginators { get; private set; } = false;
///
/// The customization model for the service
///
public CustomizationsModel Customizations
{
get { return this._customizationModel; }
}
public ServiceModel ParentModel { get; set; }
///
/// Provides the targetPrefix if it is set in the metadata.
/// Target prefix is defined so that the target can be set in the header of the request
///
public string TargetPrefix
{
get { return this._metadata[TargetPrefixKey] == null ? "" : this._metadata[TargetPrefixKey].ToString(); }
}
///
/// The json version as defined in the metadata, used to determine the content type header
///
public string JsonVersion
{
get
{
var version = this._metadata[JsonVersionKey] == null ? "" : this._metadata[JsonVersionKey].ToString();
if (version == "1")
version = "1.0";
return version;
}
}
///
/// Unique Service ID. For models without this entry, this may return null;
///
public string ServiceUid
{
get
{
return this._metadata[UidKey] == null ? null : this._metadata[UidKey].ToString();
}
}
///
/// Used to determine the signer version to use, important in generating signatures for the client
///
public string SignatureVersion
{
get
{
var version = this._metadata[SignatureVersionKey] == null ? "" : this._metadata[SignatureVersionKey].ToString();
return version;
}
}
///
/// Determines what service type the model uses, found in the metadata
///
public ServiceType Type
{
get
{
var serviceType = Protocol;
if (serviceType.Equals("rest-xml", StringComparison.InvariantCulture))
{
serviceType = "rest_xml";
}
else if (serviceType.Equals("rest-json", StringComparison.InvariantCulture))
{
serviceType = "rest_json";
}
else if (serviceType.Equals("ec2", StringComparison.InvariantCulture))
{
serviceType = "query";
}
ServiceType value;
if (!Enum.TryParse(serviceType, true, out value))
{
throw new Exception("Unknown service type: " + serviceType);
}
return value;
}
}
public string Protocol
{
get { return this._metadata[ProtocolKey] == null ? "" : this._metadata[ProtocolKey].ToString(); }
}
public JsonData ProtocolSettings => _metadata[ProtocolSettingsKey];
public H2SupportDegree H2Support => Enum.TryParse(ProtocolSettings?[ProtocolSettingsH2Key]?.ToString(), true, out var specifiedH2Degree)
? specifiedH2Degree
: H2SupportDegree.None;
public bool IsEC2Protocol
{
get { return Protocol.Equals("ec2", StringComparison.OrdinalIgnoreCase); }
}
///
/// The information about the service found as the value for the documentation property of the json model
///
public string Documentation
{
get
{
var docNode = this.DocumentRoot[DocumentationKey];
if (docNode == null)
return string.Empty;
return docNode.ToString();
}
}
///
/// Returns the abbreviation name of a service
///
public string ServiceAbbreviation
{
get { return Utils.JsonDataToString(this._metadata[ServiceAbbreviationKey]); }
}
///
/// Returns the public name of a service
///
public string ServiceFullName
{
get { return Utils.JsonDataToString(this._metadata[ServiceFullNameKey]); }
}
///
/// Returns the endpoint prefix of a service
///
public string EndpointPrefix
{
get { return Utils.JsonDataToString(this._metadata[EndpointPrefixKey]); }
}
///
/// Returns the signing name of a service
///
public string SigningName
{
get { return Utils.JsonDataToString(this._metadata[SigningNameKey]); }
}
///
/// Returns the Service Id of a service
///
public string ServiceId
{
get
{
return Utils.JsonDataToString(this._metadata[ServiceIdKey]);
}
}
///
/// Gets the operation information based on the name of the operation
///
/// The name of the operation that information is needed for
/// An Operation object that contains details about the operation requested
public Operation FindOperation(string name)
{
return this.Operations.FirstOrDefault(x => x.Name == name);
}
///
/// Gets the operation marked endpointoperation if one exists
///
/// An Operation object that contains details about the operation requested
public Operation FindEndpointOperation()
{
return this.Operations.FirstOrDefault(x => x.IsEndpointOperation);
}
///
/// The operations available through the service api found in the model.
/// These operation objects contain the necessary information to construct the required operation structures for the API
///
public List Operations
{
get
{
var list = new List();
foreach (KeyValuePair kvp in DocumentRoot[OperationsKey])
{
Operation operation;
if (this.PaginatorsRoot != null && this.PaginatorsRoot[PaginationKey][kvp.Key] != null)
{
operation = new Operation(this, kvp.Key,
kvp.Value, this.PaginatorsRoot[PaginationKey][kvp.Key]);
if (operation.Paginators != null && !operation.UnsupportedPaginatorConfig)
{
this.HasPaginators = true;
}
}
else
{
operation = new Operation(this, kvp.Key, kvp.Value);
}
if (operation.IsExcluded)
{
ExcludedOperations.Add(operation.Name);
}
// Event Streams are not supported (yet)
else if (H2Support == H2SupportDegree.EventStream && operation.IsEventStreamOutput)
{
ExcludedOperations.Add(operation.Name);
}
else
{
list.Add(operation);
}
}
return list.OrderBy(x => x.Name).ToList();
}
}
public IDictionary OperationsNameMapping
{
get
{
var mapping = new Dictionary();
foreach (KeyValuePair kvp in DocumentRoot[OperationsKey])
{
var operation = new Operation(this, kvp.Key, kvp.Value);
if (operation.ShapeName != operation.Name)
{
mapping.Add(operation.Name, operation.ShapeName);
}
}
return mapping;
}
}
public IEnumerable Exceptions
{
get
{
var exceptions = new List();
foreach (var operation in this.Operations)
{
foreach (var exception in operation.Exceptions)
{
if (exceptions.All(e => !e.Name.Equals(exception.Name)))
exceptions.Add(exception);
}
}
return exceptions;
}
}
readonly HashSet _excludedOperations = new HashSet();
public HashSet ExcludedOperations
{
get { return _excludedOperations; }
}
///
/// Returns all the shapes that are of type structure.
///
public IEnumerable Structures
{
get
{
var list = new List();
foreach (KeyValuePair kvp in DocumentRoot[ShapesKey])
{
var type = kvp.Value["type"];
if (type != null && type.ToString() == "structure")
list.Add(Shape.CreateShape(this, kvp.Key, kvp.Value));
}
return list.OrderBy(x => x.Name).ToList();
}
}
///
/// Returns all the shapes.
///
public IEnumerable Shapes
{
get
{
var list = new List();
foreach (KeyValuePair kvp in DocumentRoot[ShapesKey])
{
list.Add(Shape.CreateShape(this, kvp.Key, kvp.Value));
}
return list.OrderBy(x => x.Name).ToList();
}
}
///
/// Returns list of enums defined in the service model.
///
/// Includes enums from current service model, which are also
/// defined in the parent model.
///
public IEnumerable Enumerations(bool includeParentEnums)
{
var list = new List();
foreach (KeyValuePair kvp in DocumentRoot[ShapesKey])
{
var type = kvp.Value["type"];
if (type != null && type.ToString() == "string" && kvp.Value["enum"] != null)
list.Add(new Enumeration(this, kvp.Key, kvp.Value));
}
list = list.OrderBy(x => x.Name).ToList();
if (includeParentEnums || this.ParentModel == null)
{
return list;
}
else
{
// Remove enums already defined in the parent model
return list.Where(e => ParentModel.Enumerations(true).All(en => !e.ModelName.Equals(en.ModelName)));
}
}
///
/// Search the model for shape.
///
/// The name of the shape to search for
/// A shape object with information about the shape that was searched for
public Shape FindShape(string name)
{
var shapes = this.DocumentRoot[ShapesKey];
var shape = shapes[name];
return Shape.CreateShape(this, name, shape);
}
///
/// Gets list of client context parameters,
/// used to extend client config with new properties and drive endpoint resolution
///
public List ClientContextParameters
{
get
{
var result = new List();
var parameters = DocumentRoot.SafeGet(ClientContextParams);
if (parameters == null)
{
return result;
}
foreach(var param in parameters.GetMap())
{
// Skip S3/S3 Control-specific parameters as we already
// have custom definitions for them in client config.
if ((ServiceId == "S3" || ServiceId == "S3 Control") && (
param.Key == "UseArnRegion" ||
param.Key == "DisableMultiRegionAccessPoints" ||
param.Key == "Accelerate" ||
param.Key == "ForcePathStyle"))
{
continue;
}
result.Add(new ClientContextParameter
{
name = param.Key,
documentation = param.Value.SafeGetString("documentation"),
type = param.Value.SafeGetString("type")
});
}
return result;
}
}
///
/// A value retrieved from the json model that is included in service requests
///
public string APIVersion
{
get { return this.DocumentRoot[MetadataKey][ApiVersionKey].ToString(); }
}
///
/// The service model represented as a string
///
/// Format: targetPrefix - APIVersion
public override string ToString()
{
return string.Format("{0} - {1}", TargetPrefix, APIVersion);
}
}
}