using Json.LitJson; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ServiceClientGenerator { public class OperationPaginatorConfig { private readonly JsonData data; private readonly Operation operation; /// /// The name of the operation /// public string Name { get; } private IList _resultKeys; /// /// Result keys are the values in the response which /// can be enumerated on /// public IList ResultKeys { get { if (this._resultKeys == null) { this._resultKeys = CreatePaginatorConfigOptionList(data[ServiceModel.ResultKeyKey], false, 'o'); } return this._resultKeys; } } private IList _inputTokens; /// /// The token in the request specifying where to start /// receiving data /// public IList InputTokens { get { if (this._inputTokens == null) { this._inputTokens = InitializeInputTokens(); } return this._inputTokens; } } private IList _outputTokens; /// /// The token in the response to use in the subsequent request /// to specify where to start receiving data /// public IList OutputTokens { get { if (this._outputTokens == null) { this._outputTokens = InitializeOutputTokens(); } return this._outputTokens; } } private OperationPaginatorConfigOption _moreResults; /// /// Whether or not the response is truncated /// public OperationPaginatorConfigOption MoreResults { get { if (this._moreResults == null) { this._moreResults = GetOperationPaginatorConfigOption(data[ServiceModel.MoreResultsKey], false); } return this._moreResults; } } private OperationPaginatorConfigOption _limitKey; /// /// The key specifying the number of results per /// request /// public OperationPaginatorConfigOption LimitKey { get { if (this._limitKey == null) { this._limitKey = GetOperationPaginatorConfigOption(data[ServiceModel.LimitKeyKey]); } return this._limitKey; } } /// /// Return an OperationPaginatorConfigOption object for the paginator option. /// This contains the Member associated with the option. Also, it adjusts the /// name if it is a jmespath expression. /// /// The JsonData node provided in the paginator config /// Check the request object for a member matching the node /// Check the response object for a member matching the node /// Check the query object for a member matching the node /// internal OperationPaginatorConfigOption GetOperationPaginatorConfigOption(JsonData node, bool checkRequest = true, bool checkResponse = true) { Member configOption = null; var foundOptionInResponse = false; if (node == null) { return null; } if (IsJmesPath(node, out var jmesPathChar)) { return HandleJmesPath(node, (char) jmesPathChar, operation); } if (checkRequest) { configOption = CheckRequestForNode(m => m.ModeledName.Equals(node.ToString())); } if (checkResponse && configOption == null) { configOption = CheckResponseForNode(m => m.ModeledName.Equals(node.ToString()) || m.MarshallName.Equals(node.ToString())); // foundOptionInResponse is used for wrapped result members which need to be // handled differently for the SWF service. foundOptionInResponse = configOption != null; } if (configOption == null) { return null; } if (this.operation.UseWrappingResult && foundOptionInResponse) { return new OperationPaginatorConfigOption(false, this.operation.OperationModifiers.WrappedResultMember, configOption); } return new OperationPaginatorConfigOption(false, configOption); } /// /// Check all parts of request for node to find the corresponding /// member /// /// Function to search all members for node /// internal Member CheckRequestForNode(Func checkFunction) { Member configOption = null; if (operation.RequestHasBodyMembers) { configOption = operation.RequestBodyMembers.SingleOrDefault(checkFunction); } if (operation.RequestHasUriMembers && configOption == null) { configOption = operation.RequestUriMembers.SingleOrDefault(checkFunction); } if (operation.RequestHasQueryStringMembers && configOption == null) { configOption = operation.RequestQueryStringMembers.SingleOrDefault(checkFunction); } if (operation.RequestHasHeaderMembers && configOption == null) { configOption = operation.RequestHeaderMembers.SingleOrDefault(checkFunction); } return configOption; } /// /// Check all parts of response for node to find the corresponding /// member /// /// Function to search all members for node /// internal Member CheckResponseForNode(Func checkFunction) { Member configOption = null; if (operation.ResponseHasBodyMembers && configOption == null) { configOption = operation.ResponseBodyMembers.SingleOrDefault(checkFunction); } if (operation.ResponseHasHeaderMembers && configOption == null) { configOption = operation.ResponseHeaderMembers.SingleOrDefault(checkFunction); } return configOption; } /// /// Check if a paginator option is a jmespath expression. /// Currently support only '.' /// /// The JsonData node provided in the paginator config /// The character which is contained in the jmespath expression /// (currently only supports '.') /// internal static bool IsJmesPath(JsonData node, out char? jmesPathChar) { var stringNode = node.ToString(); string jPattern = "^[a-zA-Z0-9]+\\.[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)*$"; if (System.Text.RegularExpressions.Regex.IsMatch(stringNode, jPattern)) { jmesPathChar = '.'; return true; } jmesPathChar = null; return false; } /// /// Handle a jmespath expression for a paginator option. Currently /// only supporting '.' /// /// The JsonData node provided in the paginator config /// The character which is contained in the jmespath expression /// (currently only supports '.') /// internal static OperationPaginatorConfigOption HandleJmesPath(JsonData node, char jmesPathChar, Operation operation) { if (jmesPathChar == '.') { var nestedMembers = node.ToString().Split('.'); var currentShape = operation.ResponseStructure; Member currentMember = null; var codePath = new StringBuilder(); for(int i = 0; i < nestedMembers.Length; i++) { currentMember = currentShape.Members.FirstOrDefault(x => string.Equals(x.ModeledName, nestedMembers[i])); if (currentMember == null) { return null; } if(codePath.Length > 0) { codePath.Append('.'); } codePath.Append(currentMember.PropertyName); currentShape = currentMember.Shape; if (currentShape == null) { return null; } } return new OperationPaginatorConfigOption(true, currentMember, $"{codePath}"); } return null; } /// /// Only initialize the input token and the output token /// because they are mandatory. If they are not configured /// properly, set operation.UnsupportedPaginatorConfig to true /// internal void InitializeOperationPaginatorConfig() { this._inputTokens = InitializeInputTokens(); this._outputTokens = InitializeOutputTokens(); if (this.InputTokens.Count != this.OutputTokens.Count) { this.operation.UnsupportedPaginatorConfig = true; } } /// /// Return a list of all input tokens as OperationPaginatorConfigOptions /// /// internal IList InitializeInputTokens() { return CreatePaginatorConfigOptionList(data[ServiceModel.InputTokenKey], true, 'i'); } /// /// Return a list of all output tokens as OperationPaginatorConfigOptions /// /// internal IList InitializeOutputTokens() { return CreatePaginatorConfigOptionList(data[ServiceModel.OutputTokenKey], true, 'o'); } /// /// Create a list of OperationPaginatorConfigOptions based on the data provided /// /// The JsonData node provided in the paginator config /// Should the operation's paginator config be valid if there is an /// issue finding a matching member /// Whether this is input or output token or neither /// internal IList CreatePaginatorConfigOptionList(JsonData node, bool setUnsupported = true, char mode = 'n') { var checkRequest = mode == 'i' || mode == 'n'; var checkResponse = mode == 'o' || mode == 'n'; IList optionList = new List(); if (node == null) { if (setUnsupported) { this.operation.UnsupportedPaginatorConfig = true; } return optionList; } else if (node.IsArray) { for (var i = 0; i < node.Count; i++) { var option = GetOperationPaginatorConfigOption(node[i], checkRequest, checkResponse); if (option != null) { optionList.Add(option); } else if (setUnsupported) { this.operation.UnsupportedPaginatorConfig = true; } } } else { var option = GetOperationPaginatorConfigOption(node, checkRequest, checkResponse); if (option != null) { optionList.Add(option); } else if (setUnsupported) { this.operation.UnsupportedPaginatorConfig = true; } } return optionList; } /// /// Create a new OperationPaginatorConfig /// /// /// /// public OperationPaginatorConfig(Operation operation, string name, JsonData data) { this.operation = operation; Name = name; this.data = data; this.operation.UnsupportedPaginatorConfig = false; InitializeOperationPaginatorConfig(); } } }