generator/AWSPSGeneratorLib/ConfigModel/ConfigModel.cs (861 lines of code) (raw):

using System; using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; using System.IO; using System.Diagnostics; using System.Text; using System.Xml; using AWSPowerShellGenerator.Analysis; using System.ComponentModel; using AWSPowerShellGenerator.Generators; using System.Management.Automation; namespace AWSPowerShellGenerator.ServiceConfig { /// <summary> /// Collection of service ConfigModel objects /// </summary> public class ConfigModelCollection { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("VerbMappings")] public List<Map> VerbMappingsList = new List<Map>(); Dictionary<string, string> _verbMappings; /// <summary> /// Cross-service verb remaps /// </summary> [XmlIgnore] public Dictionary<string, string> VerbMappings { get { if (_verbMappings == null) _verbMappings = VerbMappingsList.ToDictionary(m => m.From, m => m.To); return _verbMappings; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("OperationNameMappings")] public List<Map> OperationNameMappingsList = new List<Map>(); Dictionary<string, string> _operationNameMappings; /// <summary> /// Cross-service operation name remaps /// </summary> [XmlIgnore] public Dictionary<string, string> OperationNameMappings { get { if (_operationNameMappings == null) _operationNameMappings = OperationNameMappingsList.ToDictionary(m => m.From, m => m.To); return _operationNameMappings; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("NounMappings")] public List<Map> NounMappingsList = new List<Map>(); Dictionary<string, string> _nounMappings; /// <summary> /// Cross-service noun remaps /// </summary> [XmlIgnore] public Dictionary<string, string> NounMappings { get { if (_nounMappings == null) _nounMappings = NounMappingsList.ToDictionary(m => m.From, m => m.To); return _nounMappings; } } /// <summary> /// Global list of member types that we should not attempt to flatten to 'typename_membername' during /// codegen. The global list should really be restricted to just types external to AWS services - /// non-flatting types at the service level can be declared in the service config files, and are fused /// with this collection automatically. /// </summary> [XmlArray("TypesNotToFlatten")] [XmlArrayItem("Type")] public List<string> TypesNotToFlatten { get; set; } = new List<string>(); /// <summary> /// List of cmdlet output properties that are considered metadata, this is used to fill in the /// list of metadata properties for an operation during its first automated configuration. /// We include common pagination properties here in order to have them show up for in the configuration /// and help identifying cmdlets with misconfigured pagination /// </summary> [XmlArray("MetadataProperties")] [XmlArrayItem("Property")] public List<string> MetadataPropertyNames { get; set; } /// <summary> /// List of cmdlet parameters that are considered metadata and moved to the end of the parameters list /// </summary> [XmlArray("MetadataParameters")] [XmlArrayItem("Parameter")] public List<string> MetadataParameterNames { get; set; } /// <summary> /// Collection of xml config files that the generator should operate on /// </summary> [XmlArray] [XmlArrayItem("Path")] public List<string> Configs { get; set; } = new List<string>(); /// <summary> /// Collection of deserialized service configuration models indexed by C2jFilename /// </summary> [XmlIgnore] public Dictionary<string, ConfigModel> ConfigModels { get; set; } = new Dictionary<string, ConfigModel>(); /// <summary> /// Global definition of types our cmdlets can accept as 'InputObject' /// from the pipeline and the type member to cmdlet parameter mapping(s) /// that should be performed if the type is detected /// </summary> [XmlArray("InputObjectMappingRules")] [XmlArrayItem("InputObjectMapping")] public List<InputObjectMapping> InputObjectMappingRulesList { get; set; } Dictionary<string, InputObjectMapping> _inputObjectMappingRules; [XmlIgnore] public Dictionary<string, InputObjectMapping> InputObjectMappingRules { get { if (_inputObjectMappingRules == null) _inputObjectMappingRules = InputObjectMappingRulesList.ToDictionary(a => a.MappingRefName, a => a); return _inputObjectMappingRules; } } [XmlArray("IncludeLibraries")] public List<Library> IncludeLibrariesList = new List<Library>(); [XmlArray("CommonModuleAliases")] [XmlArrayItem("AliasSet")] public List<AliasSet> CommonModuleAliasesList = new List<AliasSet>(); Dictionary<string, HashSet<string>> _commonModuleAliases; [XmlIgnore] public Dictionary<string, HashSet<string>> CommonModuleAliases { get { return _commonModuleAliases ?? (_commonModuleAliases = CommonModuleAliasesList.ToDictionary(a => a.Cmdlet, a => a.Aliases)); } } /// <summary> /// Loads the core config.xml file and the indicated service configuration files it contains. /// </summary> /// <param name="configurationsFolder"></param> /// <param name="verbose"></param> /// <returns></returns> public static ConfigModelCollection LoadAllConfigs(string configurationsFolder, bool verbose = false) { var manifestConfigFile = Path.GetFullPath(Path.Combine(configurationsFolder, "Configs.xml")); if (verbose) Console.WriteLine("...loading configuration manifest {0}", manifestConfigFile); var manifestConfig = DeserializeModelCollection(manifestConfigFile); var serviceConfigurationsFolder = Path.Combine(configurationsFolder, CmdletGenerator.ServiceConfigFoldername); var serviceConfigurations = Directory.GetFiles(serviceConfigurationsFolder, "*.xml", SearchOption.TopDirectoryOnly); foreach (var configFile in serviceConfigurations.OrderBy(x => x)) { if (verbose) Console.WriteLine("...loading service configuration {0}", configFile); try { var configModel = DeserializeModel(configFile); manifestConfig.ConfigModels.Add(configModel.C2jFilename, configModel); } catch (Exception e) { throw new Exception("Failed to deserialize config model " + configFile, e); } } return manifestConfig; } private static ConfigModelCollection DeserializeModelCollection(string fileName) { try { var serializer = new XmlSerializer(typeof(ConfigModelCollection)); using (var fs = new FileStream(fileName, FileMode.Open)) { using (var reader = new StreamReader(fs)) { return (ConfigModelCollection)serializer.Deserialize(reader); } } } catch (Exception e) { throw new InvalidDataException("Unable to retrieve content for file " + fileName, e); } } private static ConfigModel DeserializeModel(string fileName) { try { var serializer = new XmlSerializer(typeof(ConfigModel)); using (var fs = new FileStream(fileName, FileMode.Open)) { using (var reader = new StreamReader(fs)) { return (ConfigModel)serializer.Deserialize(reader); } } } catch (Exception e) { throw new InvalidDataException("Unable to retrieve content for file " + fileName, e); } } } /// <summary> /// Defines a single input object member to cmdlet parameter mapping rule /// </summary> public class MappingRule { /// <summary> /// The name of the member in the cast input object the cmdlet should extract /// </summary> [XmlAttribute] public string MemberName { get; set; } /// <summary> /// The cmdlet parameter to which the extracted member should be applied /// </summary> [XmlAttribute] public string ParamName { get; set; } /// <summary> /// InputObject parameter help documentation for this rule /// </summary> [XmlAttribute] public string HelpDescription { get; set; } } /// <summary> /// Defines type conversion mapping rules for a given type /// </summary> public class InputObjectMapping { /// <summary> /// Name that can be used to reference this type mapping from other config files /// </summary> [XmlAttribute("RefName")] public string MappingRefName { get; set; } /// <summary> /// Returns true if this entry is a reference to a global map entry via /// MappingRefName /// </summary> [XmlIgnore] public bool IsGlobalReference { get { return string.IsNullOrEmpty(this.TypeName); } } /// <summary> /// The CLR type that these map rules apply to /// </summary> [XmlAttribute("Type")] public string TypeName { get; set; } [XmlArray("MappingRules")] [XmlArrayItem("MappingRule")] public List<MappingRule> MappingRules { get; set; } } /// <summary> /// Defines the configuration for a given service model that is passed to the generator /// </summary> public class ConfigModel { #region Configuration Properties /// <summary> /// Manually increment this number in the xml configuration file when /// making heavy changes to the format or content of the configuration. /// It will prevent automatically applying overrides if they present /// an older version. /// </summary> public int FileVersion = 0; /// <summary> /// <para> /// If specified, allows us to skip reflecting over the service client /// to generate cmdlets but still process other data in the config /// (eg legacy aliases). /// </para> /// <remarks> /// We currently use this for CloudSearchDomain which has non- /// standard client constructors and all of its operations /// excluded from codegen. Due to originally having plural cmdlet /// names, we want to take advantage of alias processing but none /// of the rest of the generation process. /// </remarks> /// </summary> public bool SkipCmdletGeneration; /// <summary> /// Base name of the corresponding C2j file containing the api model. We /// only use this in transition to the new C2j-based PowerShell generator /// framework. /// </summary> public string C2jFilename; /// <summary> /// Assembly name the AWSSDK or AWSPowerShell prefix is not included /// </summary> public string AssemblyName = string.Empty; /// <summary> /// Service-specific 'tag' to be prefixed onto each noun and alias /// </summary> public string ServiceNounPrefix = string.Empty; /// <summary> /// The marketing name of the service. /// </summary> public string ServiceName = string.Empty; /// <summary> /// Typename of the interface implemented by the service client /// </summary> public string ServiceClientInterface = string.Empty; /// <summary> /// Typename of the service client type to be instantiated /// </summary> public string ServiceClient = string.Empty; public string ServiceClientConfig { get { var clientIndex = ServiceClient.LastIndexOf("Client"); if (clientIndex < 0) throw new InvalidOperationException("Cannot determine config name for client " + ServiceClient); return (ServiceClient.Substring(0, clientIndex) + "Config"); } } /// <summary> /// The root namespace of the service in the SDK, eg Amazon.EC2 /// </summary> public string ServiceNamespace => "Amazon." + AssemblyName; /// <summary> /// The Guid assigned to the service module. /// </summary> public string ServiceModuleGuid = Guid.NewGuid().ToString(); /// <summary> /// Default region to use for the cmdlets if Region isn't passed in. /// </summary> public string DefaultRegion; /// <summary> /// For S3 only, switch instructs generator to treat response object as result object. /// </summary> [XmlIgnore] public bool IsS3 { get { return string.Equals(ServiceClientInterface, "IAmazonS3", StringComparison.Ordinal); } } /// <summary> /// Optional name of a parameter across one or more cmdlets that we should apply /// PipelineParameter to, unless the service operation being generated has an /// override. /// </summary> public string PipelineParameter = string.Empty; public AutoIteration AutoIterate = null; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("VerbMappings")] public List<Map> VerbMappingsList = new List<Map>(); Dictionary<string, string> _verbMappings; /// <summary> /// Service-specific overrides of verb remaps /// </summary> [XmlIgnore] public Dictionary<string, string> VerbMappings { get { return _verbMappings ?? (_verbMappings = VerbMappingsList.ToDictionary(m => m.From, m => m.To)); } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("NounMappings")] public List<Map> NounMappingsList = new List<Map>(); Dictionary<string, string> _nounMappings; /// <summary> /// Service-specific noun remaps /// </summary> [XmlIgnore] public Dictionary<string, string> NounMappings { get { return _nounMappings ?? (_nounMappings = NounMappingsList.ToDictionary(m => m.From, m => m.To)); } } /// <summary> /// Collection of service-global parameter customizations. /// </summary> [XmlArray("Params")] [XmlArrayItem("Param")] public List<Param> CustomParametersList = new List<Param>(); private Dictionary<string, Param> _customParameters; [XmlIgnore] public IDictionary<string, Param> CustomParameters { get { if (_customParameters == null) { _customParameters = CustomParametersList.ToDictionary(p => p.Name, p => p); } return _customParameters; } } public Param FindCustomParameterData(string parameterName) { if (CustomParameters.ContainsKey(parameterName)) return CustomParameters[parameterName]; return null; } public bool ShouldExcludeParameter(string parameterName) { return ShouldExcludeParameter(parameterName, CustomParameters); } internal static bool ShouldExcludeParameter(string parameterName, IDictionary<string, Param> customizedParameters) { if (customizedParameters == null) return false; // we use a convention of a trailing _ on the parameter name to // allow us to exclude a group of parameters by common prefix foreach (var p in customizedParameters) { var param = p.Value; if (param.Exclude) { if (param.Name.EndsWith("_", StringComparison.Ordinal) && parameterName.StartsWith(param.Name, StringComparison.Ordinal)) return true; if (param.Name.Equals(parameterName, StringComparison.Ordinal)) return true; } } return false; } /// <summary> /// Additional aliases defined in the config, beyond the automatic aliases we generate. /// These go into awsaliases.ps1. /// Key is the cmdlet name; value is the alias (including parameters) /// </summary> [XmlArray("CustomAliases")] [XmlArrayItem("AliasSet")] public List<AliasSet> CustomAliasesList = new List<AliasSet>(); Dictionary<string, HashSet<string>> _customAliases; [XmlIgnore] public Dictionary<string, HashSet<string>> CustomAliases { get { return _customAliases ?? (_customAliases = CustomAliasesList.ToDictionary(a => a.Cmdlet, a => a.Aliases)); } } /// <summary> /// Legacy alias entries for custom, hand-maintained cmdlets for a service. /// These entries are used to ensure the aliases get emitted into the /// AWSPowerShellLegacyAliases.psm1 file and the overall module manifest. Hand- /// maintained cmdlets that need a legacy alias should also have a LegacyAlias /// attribute entry added to their AWSCmdletAttribute - this ensures that an table /// of contents entry is generated for the alias in the web doc generator. /// NOTE: Only use this collection in config files to provide aliases for custom /// cmdlets. For generatable service operations, use the LegacyAlias attribute /// on the respective ServiceOperation element. /// Key is the current cmdlet name; value is the backwards-compatible alias /// (there should only ever be one alias) /// </summary> [XmlArray("LegacyAliases")] [XmlArrayItem("AliasSet")] public List<AliasSet> LegacyAliasesList = new List<AliasSet>(); Dictionary<string, HashSet<string>> _legacyAliases; [XmlIgnore] public Dictionary<string, HashSet<string>> LegacyAliases { get { return _legacyAliases ?? (_legacyAliases = LegacyAliasesList.ToDictionary(a => a.Cmdlet, a => a.Aliases)); } } /// <summary> /// List of common service-specific output properties to be considered as metadata, this /// is used to fill in the list of metadata properties for an operation during its first /// automated configuration /// </summary> [XmlArray("MetadataProperties")] [XmlArrayItem("Property")] public List<string> MetadataPropertyNames { get; set; } public const string ParamEmitterComplexKeyFormat = "{0}#{1}"; // type#propname [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("ParamEmitters")] [XmlArrayItem("ParamEmitter")] public List<ParamEmitter> ParamEmittersList = new List<ParamEmitter>(); Dictionary<string, string> _typeSpecificParamEmitters; /// <summary> /// Returns the service-specific custom parameter emitters that are tied to a /// specific type. These parameters are only injected on cmdlets that have a /// parameter matching the specified type. /// </summary> [XmlIgnore] public Dictionary<string, string> TypeSpecificParamEmitters { get { if (_typeSpecificParamEmitters == null) { _typeSpecificParamEmitters = new Dictionary<string, string>(); foreach (var p in ParamEmittersList) { if (p.IsGlobalInjectionEmitter) continue; var key = !string.IsNullOrEmpty(p.ParamName) ? string.Format(ParamEmitterComplexKeyFormat, p.ParamType, p.ParamName) : p.ParamType; _typeSpecificParamEmitters.Add(key, p.EmitterType); } } return _typeSpecificParamEmitters; } } List<ParamEmitter> _globalInjectionParamEmitters; /// <summary> /// Returns the collection of param emitters that are configured globally /// for a service. These parameters are injected into every cmdlet unless /// the cmdlet is configured in the exclusion list for an emitter. /// </summary> [XmlIgnore] public List<ParamEmitter> GlobalInjectionParamEmitters { get { if (_globalInjectionParamEmitters == null) { _globalInjectionParamEmitters = new List<ParamEmitter>(); foreach (var p in ParamEmittersList) { if (p.IsGlobalInjectionEmitter) _globalInjectionParamEmitters.Add(p); } } return _globalInjectionParamEmitters; } } private List<ServiceOperation> _serviceOperationsList = new List<ServiceOperation>(); [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlArray("ServiceOperations")] [XmlArrayItem("ServiceOperation")] public List<ServiceOperation> ServiceOperationsList { get { return _serviceOperationsList; } set { _serviceOperationsList = value; _serviceOperations = null; } } Dictionary<string, ServiceOperation> _serviceOperations; [XmlIgnore] public Dictionary<string, ServiceOperation> ServiceOperations { get { if (_serviceOperations == null) _serviceOperations = ServiceOperationsList.ToDictionary(a => a.MethodName + "Async", a => a); return _serviceOperations; } } public string GetServiceCmdletClassName(bool usingAnonymousAuth) { if (usingAnonymousAuth) return string.Concat("Anonymous", ServiceClient); return ServiceClient; } private bool? _requiresAnonymousServiceCmdletClass = null; [XmlIgnore] public bool RequiresAnonymousServiceCmdletClass { get { if (_requiresAnonymousServiceCmdletClass == null) { _requiresAnonymousServiceCmdletClass = false; foreach (var so in ServiceOperationsList) { if (so.RequiresAnonymousAuthentication) { _requiresAnonymousServiceCmdletClass = true; break; } } } return _requiresAnonymousServiceCmdletClass.Value; } } /// <summary> /// Service-specific collection of type names that will not be flattened /// during parameter generation. This collection is fused with the global /// collection automatically during codegen. /// </summary> [XmlArray("TypesNotToFlatten")] [XmlArrayItem("Type")] public List<string> TypesNotToFlatten { get; set; } = new List<string>(); #endregion #region Generated Output Properties [XmlIgnore] public ArgumentCompleterDetails ArgumentCompleters { get; } = new ArgumentCompleterDetails(); [XmlIgnore] public Dictionary<string, AdvancedCmdletInfo> AdvancedCmdlets { get; } = new Dictionary<string, AdvancedCmdletInfo>(StringComparer.CurrentCultureIgnoreCase); [XmlIgnore] public IEnumerable<string> SDKDependencies { get; set; } [XmlIgnore] public readonly List<AnalysisError> AnalysisErrors = new List<AnalysisError>(); [XmlIgnore] public System.Reflection.Assembly Assembly; #endregion public ConfigModel() { } public void Serialize(string filePath) { Console.WriteLine("Updating configuration file for service {0}, file {1}", ServiceName, filePath); try { var serializer = new XmlSerializer(typeof(ConfigModel)); var writerSettings = new XmlWriterSettings { Encoding = new UTF8Encoding(false), Indent = true, IndentChars = " " }; using (var writer = XmlWriter.Create(filePath, writerSettings)) { serializer.Serialize(writer, this); } } catch (Exception e) { throw new InvalidDataException("Unable to serialize updated model to " + filePath, e); } } } /// <summary> /// Information about handwritten cmdlets. These are filled in by AdvancedCmdletScanner /// </summary> public class AdvancedCmdletInfo { public List<string> OperationNames = new List<string>(); } /// <summary> /// Encapsulates all the generation info for a given service operation /// </summary> public class ServiceOperation { [XmlAttribute] public string MethodName; /// <summary> /// The property of the operation's output class to be returned from the cmdlet /// (unless the user specifies differently using the -Select parameter). /// A value of null means that the operation doens't return any non-metadata /// property,.the cmdlet will return void to allow choosing a proper OutputProperty /// value at a later date without breaking backward compatibility (in case return /// properties are added to the operation). /// A value of "" means that the whole service response should be returned. /// </summary> [XmlAttribute] public string OutputProperty; /// <summary> /// Set to the name of a member of the response class that contains the true output /// of the call (happens when the SDK wraps the output into a nested member, instead /// of hosting it in the response class itself - it does this with some SWF response /// types). The generator will use the indicated member when it does Output and /// auto-pagination inspection instead of using the outer response class. The /// semantics for 'Output' will therefore apply to the wrapping member, not the response /// class. /// </summary> [XmlAttribute] public string OutputWrapper; [XmlAttribute("Verb")] public string RequestedVerb = string.Empty; [XmlAttribute("Noun")] public string RequestedNoun = string.Empty; [XmlArray("Params")] [XmlArrayItem("Param")] public List<Param> CustomParametersList = new List<Param>(); private Dictionary<string, Param> _customParameters; [XmlIgnore] public IDictionary<string, Param> CustomParameters { get { if (_customParameters == null) { _customParameters = CustomParametersList?.ToDictionary(param => param.Name, param => param) ?? new Dictionary<string, Param>(); } return _customParameters; } } public bool ShouldExcludeParameter(string parameterName) { return ConfigModel.ShouldExcludeParameter(parameterName, CustomParameters); } public Param FindCustomParameterData(string parameterName) { if (CustomParameters.ContainsKey(parameterName)) return CustomParameters[parameterName]; return null; } /// <summary> /// If set, the operation is excluded from generation. /// </summary> [XmlAttribute] [DefaultValue(false)] public bool Exclude; /// <summary> /// Set true to suppresses generation of the SupportsShouldProcess /// attribution and code pattern foir cmdlets that don't change /// system state but whose verb is not on the 'ignore' list. /// </summary> [XmlAttribute] [DefaultValue(false)] public bool IgnoreSupportsShouldProcess; /// <summary> /// If the cmdlet verb is one that needs SupportsShouldProcess /// attributing and the request class has more than one property, /// indicates the name of the cmdlet property that we will prompt /// for confirmation on. /// </summary> [XmlAttribute] public string ShouldProcessTarget; /// <summary> /// If the resources the cmdlet is going to operate on can't be /// identified to the point of being able to include them in a /// confirmation prompt, set this true to indicate that the /// generator should not error on the failure to have an explicit /// target be set in the config. /// </summary> [XmlAttribute] [DefaultValue(false)] public bool AnonymousShouldProcessTarget; /// <summary> /// If the cmdlet verb is one that needs SupportsShouldProcess /// attributing, optionally contains a more readable display-format /// noun for the type of object we are prompting for confirmation on /// (eg 'Customer Gateway'). If not specified, the cmdlet noun is used /// in any confirmation messages we generate. /// </summary> [XmlAttribute] public string ShouldProcessMsgNoun; /// <summary> /// The type of anonymous authentication permitted for a given operation. /// Most operations require user authentication, some allow it to be optional /// (via an injected SwitchParameter that the user can specify) and others /// always operate anonymously. /// </summary> public enum AnonymousAuthenticationMode { Never, Optional, Always } /// <summary> /// If set 'Always;, the operation is unauthenticated and will be generated to use a /// service cmdlet base class that is configured to use AnonymousCredentials (eventually /// we could detect this from attribution on the operation in the SDK or C2j model). /// </summary> [XmlAttribute] [DefaultValue(AnonymousAuthenticationMode.Never)] public AnonymousAuthenticationMode AnonymousAuthentication = AnonymousAuthenticationMode.Never; [XmlIgnore] public bool RequiresAnonymousAuthentication { get { return AnonymousAuthentication == AnonymousAuthenticationMode.Always; } } /// <summary> /// Set of parameter names, ;-delimited, that should have Position data emitted /// in order of definition (starting at 0). PS recommendation is no more than 5 /// per cmdlet. This list will be with prefixed with the cross-operation set /// of PositionalParameters. /// </summary> [XmlAttribute] public string PositionalParameters; [XmlIgnore] public string[] PositionalParametersList { get { return PositionalParameters?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0]; } set { PositionalParameters = value == null ? null : string.Join(";", value); } } /// <summary> /// Name of the single parameter (single per PS convention) that should be /// tagged as accepting pipeline input by value if the service global /// PipelineParameter setting does not apply. /// </summary> /// <remarks> /// This differs from pipeline input by matching property name, of which /// more than one param can be tagged. /// </remarks> [XmlAttribute] public string PipelineParameter = string.Empty; /// <summary> /// If set true, the cmdlet can be generated without triggering an /// error due to a missing PipelineParameter attribute. /// </summary> [XmlAttribute] public bool NoPipelineParameter; /// <summary> /// Custom pass-thru expression override to use instead of automatically /// emitted the parameter marked for pipeline input to the output, for /// operations that have an output type of 'void'. /// </summary> public PassThruOverride PassThru; /// <summary> /// Overrides the service level iteration settings for an operation, for /// services that use inconsistent markers etc across their apis /// </summary> public AutoIteration AutoIterate; /// <summary> /// This should not be set to true for new operations. /// When set to true, auto-iteration defined in the config will be used. /// otherwise .NET SDK's Paginator attributes will be used. /// </summary> [XmlAttribute] [DefaultValue(false)] public bool LegacyV4Pagination = false; public enum LegacyPaginationType { Default, DisablePagination, UseEmitLimit } /// <summary> /// For AWSPowerShell and AWSPowerShell.NetCore, emit legacy pagination code using EmitLimit /// </summary> [XmlAttribute] [DefaultValue(LegacyPaginationType.Default)] public LegacyPaginationType LegacyPagination; [XmlAttribute] public string LegacyPaginationCountParameter; /// <summary> /// <para> /// If specified, the legacy cmdlet name for which a Set-Alias entry will be added to /// the AWSPowerShellLegacyAliases.psm1 file during generation. /// </para> /// <para> /// This mechanism allows us to rename cmdlets going forward without introducing a /// breaking change (the psm1 file is auto-loaded when our module loads). The value /// of the attribute is the old name of the cmdlet - this will be used as the -Name /// value to the Set-Alias cmdlet. The -Value for Set-Alias will be the current name /// of the cmdlet. /// </para> /// </summary> [XmlAttribute] public string LegacyAlias; /// <summary> /// Operation-specific collection of type names that will not be flattened /// during parameter generation. This collection is fused with the service and global /// collections automatically during codegen. /// </summary> [XmlArray("TypesNotToFlatten")] [XmlArrayItem("Type")] public List<string> TypesNotToFlatten { get; set; } = new List<string>(); /// <summary> /// If set, this message is used for the Obsolete attribute. /// </summary> [XmlAttribute] public string ReplacementObsoleteMessage; /// <summary> /// Must be one of the values of System.Management.Automation.ConfirmImpact or null /// </summary> [XmlAttribute] public string ConfirmImpact; /// <summary> /// Default parameter set for the cmdlet. /// </summary> [XmlAttribute] public string DefaultParameterSet; #region Data constructed during generation /// <summary> /// The analyzer instance and its results for this op /// </summary> [XmlIgnore] public OperationAnalyzer Analyzer; /// <summary> /// The verb we decided, or were directed, to use for the cmdlet /// </summary> [XmlIgnore] public string SelectedVerb; /// <summary> /// The noun we decided, or were directed, to use for the cmdlet /// </summary> [XmlIgnore] public string SelectedNoun; /// <summary> /// The noun from the split-apart service method name; for use as /// the noun in confirmation messages if the cmdlet needs to implement /// the ShouldSupportProcess pattern /// </summary> [XmlIgnore] public string OriginalNoun; /// <summary> /// Set true once we've encountered the operation config and matched it /// with a method on the service client. If any operations are still false /// when we conclude the service generation, we emit a build fail since it /// indicates we could be building against an out-of-date sdk. /// </summary> [XmlIgnore] public bool Processed; /// <summary> /// Set when the generator detects a method that is not configured already. /// The generator will take a best-guess attempt to set up a service operation /// entry that can then be adjusted by hand if needed. /// </summary> [XmlIgnore] public bool IsAutoConfiguring; /// <summary> /// Set when auto-configuring if we detect an SDK 'List' verb. We'll /// auto-remap to 'Get' and then append 'List' to the noun. /// </summary> [XmlIgnore] public bool IsRemappedListOperation; [XmlIgnore] public readonly List<AnalysisError> AnalysisErrors = new List<AnalysisError>(); #endregion } /// <summary> /// Represents a configuration for a parameter emitted as part of a service operation. /// Parameters can be renamed, renamed with an alias, have just a set of aliases applied, /// be configured to be named 'as is' (ie no shortening or singularization) or have a default /// value available if the user skips the parameter. /// </summary> public class Param { /// <summary> /// Tells us where the customization originated. /// </summary> public enum CustomizationOrigin { FromConfig = 0, DuringGeneration } [XmlIgnore] public CustomizationOrigin Origin; /// <summary> /// The analyzed (and fully flattened) name of the parameter /// </summary> [XmlAttribute] public string Name; /// <summary> /// If specified, contains the name that will be emitted for the parameter (ie /// this is the final user-visible name). If not set, the parameter will not be /// renamed. /// </summary> [XmlAttribute] public string NewName; /// <summary> /// If set false, the parameter will not be automatically renamed (by shortening /// and/or singularization); to change the parameter name in these cases /// a NewName attribute must be used otherwise the original analyzed name will /// be emitted. /// </summary> [XmlAttribute] [DefaultValue(true)] public bool AutoRename = true; /// <summary> /// If set true, the parameter will be exluded from generation and will not be /// end-user visible. /// </summary> [XmlAttribute] [DefaultValue(false)] public bool Exclude = false; /// <summary> /// If set false, an alias of the original name will not be applied to the /// parameter (need a flag because an empty list doesn't uniquely define this /// case). /// </summary> [XmlAttribute] [DefaultValue(true)] public bool AutoApplyAlias = true; /// <summary> /// Sets a default value to be used if the user does not specify the parameter /// to the command. Supports string or scalar (int, float, double) parameters /// only. /// During generation don't use this value, use SimplePropertyInfo.DefaultValue /// instead /// </summary> [XmlAttribute] public string DefaultValue; /// <summary> /// If specified, contains a set of one or more aliases, ;-delimited, to apply /// for the parameter that have been read from the configuration file. Access this /// collection via the Aliases property, do not modify this string. /// </summary> [XmlAttribute(AttributeName = "Alias")] public string AliasesList; /// <summary> /// The processed set of aliases for use when emitting code. This collection can /// be updated as we inspect the parameters for a cmdlet. /// </summary> [XmlIgnore] public HashSet<string> Aliases { get { return AliasesList == null ? new HashSet<string>() : new HashSet<string>(AliasesList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } set { AliasesList = value == null ? null : string.Join(";", value); } } /// <summary> /// If specified, contains a set of one or more parameters, ;-delimited, that cannot /// be used together with the current one. /// </summary> [XmlAttribute(AttributeName = "ExclusiveParameters")] public string ExclusiveParametersList; /// <summary> /// The processed set of parameters that are not allowed when the current one is /// specified. This is used when emitting code. /// </summary> [XmlIgnore] public HashSet<string> ExclusiveParameters { get { return ExclusiveParametersList == null ? new HashSet<string>() : new HashSet<string>(ExclusiveParametersList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } set { ExclusiveParametersList = value == null ? null : string.Join(";", value); } } public enum AutoConversion { None = 0, ToBase64 = 1 } /// <summary> /// If set, the cmdlet automatically converts from the source type (string or /// byte array) to a base64 representation required by the service. /// </summary> [XmlAttribute] [DefaultValue(AutoConversion.None)] public AutoConversion AutoConvert = AutoConversion.None; /// <summary> /// If set, this message is used for the Obsolete attribute. /// </summary> [XmlAttribute] public string ReplacementObsoleteMessage; /// <summary> /// The ParameterSetName to be used. This is for backward compatibility only. /// </summary> [XmlAttribute] public string ParameterSetName; /// <summary> /// The parameter is mandatory. This is for backward compatibility only. /// </summary> [XmlAttribute] [DefaultValue(false)] public bool Mandatory; /// <summary> /// Replacement documentation for this parameter. This is for backward compatibility only. /// </summary> [XmlAttribute] [DefaultValue(null)] public string ParameterReplacementDocumentation; } public class AliasSet { [XmlAttribute] public string Cmdlet = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [XmlText] public string AliasesField = string.Empty; [XmlIgnore] public HashSet<string> Aliases { get { return new HashSet<string>(AliasesField.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } } } public class AutoIteration { // set of aliases applied to paging tokens cross-services, to smooth // out service naming differences. We keep the parameter name to help // users who either know the service api or want to reference // documentation. The names are chosen to likely never interfere // with service names. private const string NextAlias = "NextToken"; private const string EmitLimitAlias = "MaxItems"; /// <summary> /// The field in the request class that indicates where the service /// should start returning results from /// </summary> [XmlAttribute] public string Start; /// <summary> /// The field in the response/result class that indicates where the /// next 'page' of results starts from /// </summary> [XmlAttribute] public string Next; /// <summary> /// For services that allow the user to set a max number of records /// to return; this can be more or less than the service's page size /// (if the service has one) /// </summary> [XmlAttribute] public string EmitLimit; /// <summary> /// The service's max records per call value; not all services have one /// </summary> [XmlAttribute] [DefaultValue(-1)] public int ServicePageSize = -1; /// <summary> /// The service requires EmitLimit to be set. The configuration of this /// value was set in order to maintain the existing behavior of the module. /// We have no idea what is the correct value for each service unless we /// test each operation! /// </summary> [XmlAttribute] [DefaultValue(false)] public bool PageSizeIsRequired; /// <summary> /// Returns the autoiteration settings, if any, for an operation by combining the service level /// settings and any operation-level overrides. For operation level overrides, not all settings /// need to be specified. For operations that have less fields than the service level, specify /// an empty string for the non-present fields otherwise the service level setting will be /// assumed. /// </summary> /// <param name="parentSettings">The service-level autoiteration settings, if any</param> /// <param name="childSettings">Service-operation overrides, if any</param> /// <returns>The combined iteration settings with child settings overriding parent settings where set</returns> public static AutoIteration Combine(AutoIteration parentSettings, AutoIteration childSettings) { var chosenSettings = childSettings ?? parentSettings; if (chosenSettings == null) return null; return new AutoIteration { Start = chosenSettings.Start, Next = chosenSettings.Next, ServicePageSize = chosenSettings.ServicePageSize, EmitLimit = chosenSettings.EmitLimit, PageSizeIsRequired = chosenSettings.PageSizeIsRequired }; } /// <summary> /// Allows the generator to filter out properties related to iteration; note that the /// 'is truncated' property is allowed through, since it doesn't relate directly /// to paging/page sizes /// </summary> /// <param name="propertyName"></param> /// <returns></returns> public bool IsIterationParameter(string propertyName) { return propertyName == Start || propertyName == EmitLimit; } /// <summary> /// Returns the cross-service alias for the specified iteration parameter (start and max params /// only, 'itrnext' is an internal field) provided the parameter name isn't already the alias. /// </summary> /// <param name="propertyName"></param> /// <returns></returns> public string GetIterationParameterAlias(string propertyName) { if (propertyName == Start && propertyName != NextAlias) return NextAlias; if (propertyName == EmitLimit && propertyName != EmitLimitAlias) return EmitLimitAlias; return null; } } public class Mapping<K, V> { public K From = default(K); public V To = default(V); public Mapping() { } public Mapping(K from, V to) { From = from; To = to; } } public class Map { [XmlAttribute] public string From = string.Empty; [XmlAttribute] public string To = string.Empty; public Map() { } public Map(string from, string to) { From = from; To = to; } } public class Library { [XmlAttribute] public string Name = string.Empty; [XmlAttribute] public bool AddAsReference = false; public Library() { } public Library(string name, bool addAsReference) { Name = name; AddAsReference = addAsReference; } } public class ParamEmitter { [XmlAttribute] public string ParamType = string.Empty; [XmlAttribute] public string ParamName = string.Empty; [XmlAttribute] public string EmitterType = string.Empty; [XmlAttribute] public string Exclude = string.Empty; /// <summary> /// Globally injected emitters are used to inject arbitrary parameters that don't map to /// model shape types. S3's UseAcceleratedEndpoint and UseDualstackEndpoint are /// examples of these; both are bools (so the type cannot be used as a key in the /// emitter dictionary) and both get added to multiple cmdlets. /// </summary> [XmlIgnore] public bool IsGlobalInjectionEmitter { get { return string.IsNullOrEmpty(ParamName) && string.IsNullOrEmpty(ParamType); } } public ParamEmitter() { } public ParamEmitter(string paramType, string emitterType) : this(paramType, string.Empty, emitterType) { } public ParamEmitter(string paramType, string paramName, string emitterType) { ParamType = paramType; ParamName = paramName; EmitterType = emitterType; } } /// <summary> /// Contains the custom code expression and documentation for the -PassThru parameter. /// <para> /// If not specified (the default) for a service operation that has an output type of /// 'void', the value passed to the parameter declared as the PipelineParameter will /// be emitted (so long as the user sets the -PassThru switch). /// </para> /// <para> /// If this customization is specified, the assignment to the CmdletOutput's PipelineOutput /// property in the cmdlet executor is done from the code expression supplied as /// Expression. This can be a reference to a request object field (e.g. 'request.Tags') or /// a call to a custom method built as part of an extension class to the cmdlet. /// </para> /// </summary> public class PassThruOverride { /// <summary> /// The code expression that yields the output from the cmdlet. This can /// be a reference to a member of the SDK request object, or a member of the /// cmdletContext instance or a call to a method implemented in an extension /// class for the cmdlet. /// </summary> /// <example>context.Tags</example> /// <example>GetTagOutputFromHere(context.Tags)</example> public string Expression { get; set; } /// <summary> /// The type of the object that is output (for collections, this should /// be the collected object type). This is used to augment the OutputType /// attribute on the cmdlet. /// </summary> public string Type { get; set; } /// <summary> /// 'One liner' documentation describing what is output. The generator will /// automatically append a 'By default, this cmdlet does not generate any output.' /// suffix to follow other cmdlet standards. /// </summary> public string Documentation { get; set; } } /// <summary> /// Tracks usage of SDK ConstantClass-derived types in parameters so that we /// can generate argument completers for a service. /// </summary> public class ArgumentCompleterDetails { /// <summary> /// Tracks the members of each ConstantClass-derived type /// </summary> private Dictionary<string, IEnumerable<string>> _constantClassMembers = new Dictionary<string, IEnumerable<string>>(); /// <summary> /// Tracks the cmdlet references, by parameter name, for a given ConstantClass-derived type /// </summary> private Dictionary<string, ConstantClassReferences> _constantClassReferences = new Dictionary<string, ConstantClassReferences>(); /// <summary> /// Returns the ordered collection of ConstantClass-derived type names that were /// found to be referenced during cmdlet generation or inspection of hand-maintained /// cmdlets. /// </summary> public IEnumerable<string> ReferencedClasses { get { var l = new List<string>(_constantClassReferences.Keys); l.Sort(); // return ordered so codegen'd file gets consistent layout return l; } } /// <summary> /// Returns the collection of parameter and cmdlet references for a ConstantClass-derived /// type. /// </summary> /// <param name="constantClassTypename"></param> /// <returns></returns> public ConstantClassReferences GetReferencesFor(string constantClassTypename) { ConstantClassReferences refs; if (_constantClassReferences.TryGetValue(constantClassTypename, out refs) && refs != null) return refs; throw new ArgumentException(string.Format("ConstantClass-derived type {0} has not been encountered during generation", constantClassTypename)); } /// <summary> /// Returns the collection of members for a ConstantClass-derived type. /// </summary> /// <param name="constantClassTypename"></param> /// <returns></returns> public IEnumerable<string> GetConstantClassMembers(string constantClassTypename) { IEnumerable<string> members; if (_constantClassMembers.TryGetValue(constantClassTypename, out members) && members != null) return members; throw new ArgumentException(string.Format("ConstantClass-derived type {0} has not been encountered during generation", constantClassTypename)); } /// <summary> /// Indicates if the members of the ConstantClass-derived type have already been registered. /// </summary> /// <param name="constantClassTypeName"></param> /// <returns></returns> public bool IsConstantClassRegistered(string constantClassTypeName) { return _constantClassMembers.ContainsKey(constantClassTypeName); } /// <summary> /// Registers the members of a ConstantClass-derived type so that we can generate /// an argument completer for the type. /// </summary> /// <param name="constantClassTypename"></param> /// <param name="setMembers"></param> public void AddConstantClass(string constantClassTypename, IEnumerable<string> setMembers) { if (_constantClassMembers.ContainsKey(constantClassTypename)) return; _constantClassMembers.Add(constantClassTypename, setMembers); } /// <summary> /// Initializes or adds a new cmdlet parameter reference to a ConstantClass-derived /// SDK 'enum' type. /// </summary> /// <param name="constantClassTypename"></param> /// <param name="parameterName"></param> /// <param name="cmdletName"></param> public void AddConstantClassReference(string constantClassTypename, string parameterName, string cmdletName) { if (_constantClassReferences.ContainsKey(constantClassTypename)) { var reference = _constantClassReferences[constantClassTypename]; reference.AddReference(parameterName, cmdletName); } else { var reference = new ConstantClassReferences(); reference.AddReference(parameterName, cmdletName); _constantClassReferences.Add(constantClassTypename, reference); } } } /// <summary> /// Tracks the cmdlets that reference an SDK ConstantClass-derived 'enum' type /// via the same-named parameter. Cmdlets that reference the same 'enum' type /// using a different parameter name will trigger a new ConstantClassReference /// instance. /// </summary> public class ConstantClassReferences { private SortedDictionary<string, HashSet<string>> _references = new SortedDictionary<string, HashSet<string>>(); /// <summary> /// The set of parameter names that have been used to reference the /// ConstantClass-derived type. Ideally across a service their should /// be a consistent (and therefore single) name used, but we can't /// guarantee this. /// </summary> public IEnumerable<string> ParameterNames { get { var l = new List<string>(_references.Keys); l.Sort(); return l; } } /// <summary> /// The names of the cmdlets that reference the SDK ConstantClass-derived /// type via a parameter of name parameterName. The names are returned in /// sorted order so we generate consistently ordered file contents. /// </summary> public IEnumerable<string> GetCmdletReferences(string parameterName) { if (!_references.ContainsKey(parameterName)) throw new ArgumentException("No reference for specified parameter name"); var refs = _references[parameterName]; var ret = new List<string>(refs); ret.Sort(); return ret; } /// <summary> /// Adds a reference from a cmdlet via the named parameter. /// </summary> /// <param name="parameterName"></param> /// <param name="cmdletName"></param> public void AddReference(string parameterName, string cmdletName) { if (_references.ContainsKey(parameterName)) { var r = _references[parameterName]; r.Add(cmdletName); } else { var r = new HashSet<string>(); r.Add(cmdletName); _references.Add(parameterName, r); } } } }