using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ServiceClientGenerator.Generators;
using ServiceClientGenerator.Generators.Examples;
using ServiceClientGenerator.Generators.Marshallers;
using ServiceClientGenerator.Generators.NuGet;
using ServiceClientGenerator.Generators.SourceFiles;
using ServiceClientGenerator.Generators.TestFiles;
using StructureGenerator = ServiceClientGenerator.Generators.SourceFiles.StructureGenerator;
using Json.LitJson;
using System.Collections.Concurrent;
using ServiceClientGenerator.Generators.Endpoints;
using ServiceClientGenerator.Endpoints.Partitions;
using EventStreamExceptionGenerator = ServiceClientGenerator.Generators.SourceFiles.Exceptions.EventStreamExceptions;
namespace ServiceClientGenerator
{
public class GeneratorDriver
{
///
/// Generation manifest for the entire SDK.
///
public GenerationManifest GenerationManifest { get; private set; }
///
/// The configuration for the service, as read from the service models
/// manifest file.
///
public ServiceConfiguration Configuration { get; private set; }
///
/// The configurations for each supported platform that we can compile the service
/// against.
///
public IEnumerable ProjectFileConfigurations { get; private set; }
///
/// Runtime options for the generation process, as supplied at the command line.
///
public GeneratorOptions Options { get; private set; }
///
/// The folder under which all of the source files for the service
/// will exist.
///
public string ServiceFilesRoot { get; private set; }
///
/// The folder under which the code analysis project for the service will exist.
///
public string CodeAnalysisRoot { get; private set; }
///
/// The folder under which all of the test files exist.
///
public string TestFilesRoot { get; private set; }
///
/// The folder under which the generated source files for a service will
/// be placed. Typically (ServiceFilesRoot)/Generated. All content in this
/// folder hierarchy will be deleted prior to generation if the 'Clean'
/// generation option is set.
///
public string GeneratedFilesRoot { get; private set; }
///
/// The folder where all the sample files are located
///
public string SampleFilesRoot { get; private set; }
///
/// The folder under which all of the unit files for the service
/// will exist.
///
public string ServiceUnitTestFilesRoot { get; private set; }
private readonly HashSet _structuresToProcess = new HashSet();
private readonly HashSet _processedStructures = new HashSet();
private readonly HashSet _processedUnmarshallers = new HashSet();
private readonly HashSet _processedMarshallers = new HashSet();
private const string Bcl35SubFolder = "_bcl35";
private const string Bcl45SubFolder = "_bcl45";
private const string NetStandardSubFolder = "_netstandard";
private string PaginatorsSubFolder = string.Format("Model{0}_bcl45+netstandard", Path.AltDirectorySeparatorChar);
private string GeneratedTestsSubFolder = string.Format("UnitTests{0}Generated", Path.AltDirectorySeparatorChar);
private string CustomizationTestsSubFolder = string.Format("UnitTests{0}Generated{0}Customizations", Path.AltDirectorySeparatorChar);
private string PaginatorTestsSubFolder = string.Format("UnitTests{0}Generated{0}_bcl45+netstandard{0}Paginators", Path.AltDirectorySeparatorChar);
public const string SourceSubFoldername = "src";
public const string TestsSubFoldername = "test";
public const string CodeAnalysisFoldername = "code-analysis";
public const string ServicesSubFoldername = "Services";
public const string ServicesAnalysisSubFolderName = "ServiceAnalysis";
public const string CoreSubFoldername = "Core";
public const string GeneratedCodeFoldername = "Generated";
public const string CommonTestSubFoldername = "Common";
public const string UnitTestsSubFoldername = "UnitTests";
public const string IntegrationTestsSubFolderName = "IntegrationTests";
// Records any new project files we produce as part of generation. If this collection is
// not empty when we've processed all source, we must update the solution files to add
// the new projects.
private static readonly Dictionary NewlyCreatedProjectFiles
= new Dictionary();
public HashSet FilesWrittenToGeneratorFolder { get; private set; }
private static ConcurrentBag codeGeneratedServiceNames = new ConcurrentBag();
public GeneratorDriver(ServiceConfiguration config, GenerationManifest generationManifest, GeneratorOptions options)
{
FilesWrittenToGeneratorFolder = new HashSet();
GenerationManifest = generationManifest;
Configuration = config;
ProjectFileConfigurations = GenerationManifest.ProjectFileConfigurations;
Options = options;
ServiceFilesRoot = Utils.PathCombineAlt(Options.SdkRootFolder, SourceSubFoldername, ServicesSubFoldername, Configuration.ServiceFolderName);
ServiceUnitTestFilesRoot = Utils.PathCombineAlt(Options.SdkRootFolder, TestsSubFoldername, ServicesSubFoldername, Configuration.ServiceFolderName);
if (config.IsTestService)
{
ServiceFilesRoot = ServiceUnitTestFilesRoot;
}
GeneratedFilesRoot = Utils.PathCombineAlt(ServiceFilesRoot, GeneratedCodeFoldername);
CodeAnalysisRoot = Utils.PathCombineAlt(Options.SdkRootFolder, CodeAnalysisFoldername, ServicesAnalysisSubFolderName, Configuration.ServiceFolderName);
TestFilesRoot = Utils.PathCombineAlt(Options.SdkRootFolder, TestsSubFoldername);
codeGeneratedServiceNames.Add(Configuration.ServiceFolderName);
}
public void Execute()
{
if (Configuration.ServiceModel.H2Support == H2SupportDegree.Required)
{
Console.WriteLine("This service requires HTTP2 for all operations. The AWS SDK for .NET does not yet support this functionality. Not generating service.");
return;
}
ValidateServiceModel();
this.FilesWrittenToGeneratorFolder.Clear();
if (Options.Clean && !Configuration.IsChildConfig)
{
Console.WriteLine(@"-clean option set, deleting previously-generated code under .\Generated subfolders");
Directory.Delete(GeneratedFilesRoot, true);
Directory.CreateDirectory(GeneratedFilesRoot);
}
// .NET Framework 3.5 version
ExecuteGenerator(new ServiceClients(), "Amazon" + Configuration.ClassName + "Client.cs", Bcl35SubFolder);
ExecuteGenerator(new ServiceInterface(), "IAmazon" + Configuration.ClassName + ".cs", Bcl35SubFolder);
// .NET Framework 4.5 version
ExecuteGenerator(new ServiceClients45(), "Amazon" + Configuration.ClassName + "Client.cs", Bcl45SubFolder);
ExecuteGenerator(new ServiceInterface45(), "IAmazon" + Configuration.ClassName + ".cs", Bcl45SubFolder);
// .NET Standard version
ExecuteGenerator(new ServiceClientsNetStandard(), "Amazon" + Configuration.ClassName + "Client.cs", NetStandardSubFolder);
ExecuteGenerator(new ServiceInterfaceNetStandard(), "IAmazon" + Configuration.ClassName + ".cs", NetStandardSubFolder);
if (string.IsNullOrEmpty(Options.SelfServiceModel))
{
// Do not generate AssemblyInfo.cs and nuspec file for child model.
// Use the one generated for the parent model.
if (!this.Configuration.IsChildConfig && !Configuration.IsTestService)
{
GenerateNuspec();
GenerateCodeAnalysisProject();
}
}
if (!this.Configuration.IsChildConfig)
{
ExecuteGeneratorAssemblyInfo();
}
// Client config object
ExecuteGenerator(new Generators.SourceFiles.DefaultConfiguration(), "Amazon" + Configuration.ClassName + "DefaultConfiguration.cs");
ExecuteGenerator(new ServiceConfig(), "Amazon" + Configuration.ClassName + "Config.cs");
ExecuteGenerator(new ServiceMetadata(), "Amazon" + Configuration.ClassName + "Metadata.cs", "Internal");
if (Configuration.EndpointsRuleSet != null)
{
ExecuteGenerator(new EndpointParameters(), "Amazon" + Configuration.ClassName + "EndpointParameters.cs");
ExecuteGenerator(new EndpointProvider(), "Amazon" + Configuration.ClassName + "EndpointProvider.cs", "Internal");
ExecuteGenerator(new EndpointResolver(), "Amazon" + Configuration.ClassName + "EndpointResolver.cs", "Internal");
}
if (Configuration.EndpointTests != null)
{
ExecuteTestGenerator(new EndpointProviderTests(), Configuration.ClassName + "EndpointProviderTests.cs", "Endpoints");
}
if (Configuration.Namespace == "Amazon.S3")
{
ExecuteProjectFileGenerators();
return;
}
// The top level request that all operation requests are children of
ExecuteGenerator(new BaseRequest(), "Amazon" + Configuration.ClassName + "Request.cs", "Model");
var enumFileName = this.Configuration.IsChildConfig ?
string.Format("ServiceEnumerations.{0}.cs", Configuration.ClassName) : "ServiceEnumerations.cs";
// Any enumerations for the service
this.ExecuteGenerator(new ServiceEnumerations(), enumFileName);
#if !BCL35
// Any paginators for the service
if (Configuration.ServiceModel.HasPaginators)
{
foreach (var operation in Configuration.ServiceModel.Operations)
{
GeneratePaginator(operation);
}
ExecuteGenerator(new ServicePaginatorFactoryInterface(), $"I{Configuration.ServiceNameRoot}PaginatorFactory.cs", PaginatorsSubFolder);
ExecuteGenerator(new ServicePaginatorFactory(), $"{Configuration.ServiceNameRoot}PaginatorFactory.cs", PaginatorsSubFolder);
// Paginator tests only need to be generated against a single service,
// so generate for the Test service
if (Configuration.IsTestService)
GeneratePaginatorTests();
}
#endif
// Do not generate base exception if this is a child model.
// We use the base exceptions generated for the parent model.
if (!this.Configuration.IsChildConfig)
{
this.ExecuteGenerator(new BaseServiceException(), "Amazon" + this.Configuration.ClassName + "Exception.cs");
}
// Generates the Request, Response, Marshaller, Unmarshaller, and Exception objects for a given client operation
foreach (var operation in Configuration.ServiceModel.Operations)
{
GenerateRequest(operation);
GenerateResponse(operation);
GenerateRequestMarshaller(operation);
GenerateResponseUnmarshaller(operation);
GenerateEndpointDiscoveryMarshaller(operation);
GenerateExceptions(operation);
}
if (Configuration.ServiceModel.Customizations.GenerateCustomUnmarshaller)
{
GenerateCustomUnmarshallers(Configuration.ServiceModel.Customizations.CustomUnmarshaller);
}
// Generate any missed structures that are not defined or referenced by a request, response, marshaller, unmarshaller, or exception of an operation
GenerateStructures();
var fileName = string.Format("{0}MarshallingTests.cs", Configuration.ClassName);
// Generate tests based on the type of request it is
if (Configuration.ServiceModel.Type == ServiceType.Json)
ExecuteTestGenerator(new JsonMarshallingTests(), fileName, "Marshalling");
else if (Configuration.ServiceModel.Type == ServiceType.Query)
{
if (Configuration.ServiceModel.IsEC2Protocol)
ExecuteTestGenerator(new AWSQueryEC2MarshallingTests(), fileName, "Marshalling");
else
ExecuteTestGenerator(new AWSQueryMarshallingTests(), fileName, "Marshalling");
}
else if (Configuration.ServiceModel.Type == ServiceType.Rest_Xml || Configuration.ServiceModel.Type == ServiceType.Rest_Json)
ExecuteTestGenerator(new RestMarshallingTests(), fileName, "Marshalling");
//Generate endpoint discovery tests for classes that have an endpoint operation
if(Configuration.ServiceModel.FindEndpointOperation() != null)
{
fileName = string.Format("{0}EndpointDiscoveryMarshallingTests.cs", Configuration.ClassName);
ExecuteTestGenerator(new EndpointDiscoveryMarshallingTests(), fileName, "Marshalling");
}
// Test that simple customizations were generated correctly
GenerateCustomizationTests();
ExecuteProjectFileGenerators();
if (this.Configuration.ServiceModel.Customizations.HasExamples)
{
var servicename = Configuration.Namespace.Split('.').Last();
ExecuteExampleGenerator(new ExampleCode(), servicename + ".GeneratedSamples.cs", servicename);
ExecuteExampleGenerator(new ExampleMetadata(), servicename + ".GeneratedSamples.extra.xml");
}
}
///
/// Generates the request class for the operation.
///
/// The operation object which contains info about what the request needs to contain for the operation
void GenerateRequest(Operation operation)
{
var requestGenerator = new StructureGenerator
{
ClassName = operation.Name + "Request",
BaseClass = string.Format("Amazon{0}Request", Configuration.ClassName),
StructureType = StructureType.Request,
Operation = operation
};
if (operation.RequestStructure != null)
{
requestGenerator.Structure = this.Configuration.ServiceModel.FindShape(operation.RequestStructure.Name);
}
this.ExecuteGenerator(requestGenerator, requestGenerator.ClassName + ".cs", "Model");
if (operation.RequestStructure != null)
this.DetermineStructuresToProcess(operation.RequestStructure, false);
}
private void ValidateServiceModel()
{
// Check to see if any of the GET operations have invalid request body members.
foreach (var operation in Configuration.ServiceModel.Operations)
{
// These methods have already been released. The are broken and we need to work with the service teams to get them fixed.
if (string.Equals(operation.HttpMethod, "GET", StringComparison.InvariantCultureIgnoreCase) && operation.RequestHasBodyMembers)
{
if ((string.Equals("QuickSight", Configuration.ServiceId) && string.Equals("ListIAMPolicyAssignments", operation.Name)) ||
(string.Equals("OpenSearch", Configuration.ServiceId) && string.Equals("DescribeDomainAutoTunes", operation.Name)) ||
(string.Equals("ivs", Configuration.ServiceId) && string.Equals("ListTagsForResource", operation.Name)) ||
(string.Equals("EFS", Configuration.ServiceId) && string.Equals("DescribeAccountPreferences", operation.Name)) ||
(string.Equals("SESv2", Configuration.ServiceId) && string.Equals("ListContacts", operation.Name)) ||
(string.Equals("SESv2", Configuration.ServiceId) && string.Equals("ListImportJobs", operation.Name)) ||
(string.Equals("Elasticsearch Service", Configuration.ServiceId) && string.Equals("DescribeDomainAutoTunes", operation.Name))
)
{
continue;
}
throw new InvalidOperationException($"Failed to generate for service with Id {Configuration.ServiceId} because it contains a GET operation ({operation.Name}) with request body members.");
}
}
}
private void DetermineStructuresToProcess(Shape containingShape, bool includeContainingShape)
{
if (containingShape.IsStructure)
{
if (containingShape.IsDocument)
return;
if (this._structuresToProcess.Contains(containingShape))
return;
else if (includeContainingShape)
this._structuresToProcess.Add(containingShape);
foreach (var member in containingShape.Members)
{
if (member.IsStructure)
{
DetermineStructuresToProcess(member.Shape, true);
}
else if (member.IsList)
{
DetermineStructuresToProcess(member.Shape.ListShape, true);
}
else if (member.IsMap)
{
DetermineStructuresToProcess(member.Shape.ValueShape, true);
}
}
}
else if (containingShape.IsList)
{
DetermineStructuresToProcess(containingShape.ListShape, true);
}
else if (containingShape.IsMap)
{
DetermineStructuresToProcess(containingShape.ValueShape, true);
}
}
///
/// Generate the response class for the operation.
///
/// The operation object which contains info about what the response needs to contain for the operation
private void GenerateResponse(Operation operation)
{
var suppressResultGeneration = false;
if (operation.UseWrappingResult)
{
// response members will be demoted to a structure contained by a
// result container class (that is not in the service model)
var className = operation.Name + "Response";
string propertyName = null;
var propertyModifier
= this.Configuration.ServiceModel.Customizations.GetPropertyModifier(className, operation.ResponseStructure.Name);
if (propertyModifier != null && propertyModifier.EmitName != null)
propertyName = propertyModifier.EmitName;
else
propertyName = operation.ResponseStructure.Name;
var resultGenerator = new WrappingResultGenerator
{
Operation = operation,
ClassName = className,
BaseClass = "AmazonWebServiceResponse",
Structure = this.Configuration.ServiceModel.FindShape(operation.ResponseStructure.Name),
PropertyName = propertyName
};
// emit the wrapping result but do not mark the shape as processed as a consequence
this.ExecuteGenerator(resultGenerator, resultGenerator.ClassName + ".cs", "Model");
DetermineStructuresToProcess(resultGenerator.Structure, true);
}
else
{
// if the operation has a suppressed result modification, use the structure generator to emit
// an empty xxxxResponse class, derived from AmazonWebServiceResponse
suppressResultGeneration =
operation.ResponseStructure == null ||
this.Configuration.ServiceModel.Customizations.ResultGenerationSuppressions.Contains(operation.Name);
if (suppressResultGeneration)
{
var responseGenerator = new StructureGenerator
{
ClassName = operation.Name + "Response",
BaseClass = "AmazonWebServiceResponse",
Operation = operation,
StructureType = StructureType.Response
};
this.ExecuteGenerator(responseGenerator, responseGenerator.ClassName + ".cs", "Model");
}
else
{
var resultGenerator = new StructureGenerator
{
ClassName = operation.Name + "Response",
BaseClass = "AmazonWebServiceResponse",
IsWrapped = operation.IsResponseWrapped,
Operation = operation,
StructureType = StructureType.Response
};
if (operation.ResponseStructure != null)
{
if (operation.IsResponseWrapped)
{
// If IsResponseWrapped is true, the operation.ResponseStructure will point to a
// the shape with same name as ResponseWrapper.
var resultShape = this.Configuration.ServiceModel.FindShape(operation.ResponseStructure.Name);
var innerShape = resultShape.Members[0].Shape;
resultGenerator.Structure = innerShape;
}
else
{
resultGenerator.Structure =
this.Configuration.ServiceModel.FindShape(operation.ResponseStructure.Name);
}
}
this.ExecuteGenerator(resultGenerator, resultGenerator.ClassName + ".cs", "Model");
}
if (operation.ResponseStructure != null)
{
this.DetermineStructuresToProcess(operation.ResponseStructure, false);
}
}
//if (!suppressResultGeneration)
//{
// if (operation.ResponseStructure != null)
// {
// // Mark the shape as processed if it's being referred only as operation's
// // output shape and not being referred directly by any other shape or via an
// // operation modifier generating an artifical structure not in the service model.
// if (!IsShapeReferred(operation.ResponseStructure.Name, this.Configuration.ServiceModel)
// && !operation.WrapsResultShape(operation.ResponseStructure.Name))
// this._processedStructures.Add(operation.ResponseStructure.Name);
// }
// // This generates the legacy response class which just extends from the result class.
// var responseGenerator = new LegacyResponseClass()
// {
// OperationName = operation.Name
// };
// this.ExecuteGenerator(responseGenerator, operation.Name + "Response.cs", "Model");
//}
}
///
/// Checks if the shape is referred directly by another shape
///
/// Name of the shape to look for references of
/// The ServiceModel containing information about the shapes of the service
/// If the shape is a member of a structure or is a list or map
private static bool IsShapeReferred(string shapeName, ServiceModel serviceModel)
{
foreach (var shape in serviceModel.Shapes)
{
if (shape.IsStructure)
{
foreach (var member in shape.Members)
{
if (member.Shape.Name == shapeName)
{
return true;
}
}
}
else if (shape.IsList && shape.ListShape.Name == shapeName)
{
return true;
}
else if (shape.IsMap &&
(shape.ValueShape.Name == shapeName || shape.KeyShape.Name == shapeName))
{
return true;
}
}
return false;
}
///
/// Generates the request marshaller.
///
/// The operation to generate request marshallers for
void GenerateRequestMarshaller(Operation operation)
{
bool hasRequest = operation.RequestStructure != null;
bool normalizeMarshallers;
BaseRequestMarshaller generator;
GetRequestMarshaller(out generator, out normalizeMarshallers);
generator.Operation = operation;
this.ExecuteGenerator(generator, operation.Name + "RequestMarshaller.cs", "Model.Internal.MarshallTransformations");
if (hasRequest)
this._processedMarshallers.Add(operation.RequestStructure.Name);
if (normalizeMarshallers && hasRequest)
{
var lookup = new NestedStructureLookup();
lookup.SearchForNestedStructures(operation.RequestStructure);
foreach (var nestedStructure in lookup.NestedStructures)
{
// Skip structure marshallers that have already been generated for the parent model
if (IsShapePresentInParentModel(this.Configuration, nestedStructure.Name))
continue;
// Documents don't use a custom marshaller, always use DocumentMarshaller
if (nestedStructure.IsDocument)
continue;
if (!this._processedMarshallers.Contains(nestedStructure.Name))
{
var structureGenerator = GetStructureMarshaller();
structureGenerator.Structure = nestedStructure;
this.ExecuteGenerator(structureGenerator, nestedStructure.Name + "Marshaller.cs", "Model.Internal.MarshallTransformations");
//this.processedUnmarshallers.Add(nestedStructure.Name);
}
}
}
}
void GeneratePaginator(Operation operation)
{
if (operation.Paginators != null && !operation.UnsupportedPaginatorConfig)
{
// Generate operation paginator
BasePaginator paginatorGenerator = new BasePaginator();
paginatorGenerator.Operation = operation;
this.ExecuteGenerator(paginatorGenerator, $"{operation.Name}Paginator.cs", PaginatorsSubFolder);
// Generate operation paginator interface
BasePaginatorInterface paginatorInterfaceGenerator = new BasePaginatorInterface();
paginatorInterfaceGenerator.Operation = operation;
this.ExecuteGenerator(paginatorInterfaceGenerator, $"I{operation.Name}Paginator.cs", PaginatorsSubFolder);
}
}
///
/// Generates the response unmarshaller along with any dependent structure unmarshallers that are called by this response unmarshaller.
///
/// The operation to generate the unmarshaller for
void GenerateResponseUnmarshaller(Operation operation)
{
{
var baseException = string.Format("Amazon{0}Exception",
this.Configuration.IsChildConfig ?
this.Configuration.ParentConfig.ClassName : this.Configuration.ClassName);
var generator = GetResponseUnmarshaller();
generator.Operation = operation;
generator.IsWrapped = operation.IsResponseWrapped;
generator.HasSuppressedResult = this.Configuration.ServiceModel.Customizations.ResultGenerationSuppressions.Contains(operation.Name);
generator.BaseException = baseException;
var modifier = operation.model.Customizations.GetOperationModifiers(operation.Name);
if (modifier != null)
{
// can use wrapped member to effect a rename, even though we don't push response
// members down into a wrapped class
generator.WrappedResultMember = modifier.WrappedResultMember;
generator.IsWrapped = modifier.UseWrappingResult;
}
this.ExecuteGenerator(generator, operation.Name + "ResponseUnmarshaller.cs", "Model.Internal.MarshallTransformations");
// Add to the list of processed unmarshallers so we don't attempt to generate a generic structure unmarshaller.
if (operation.ResponseStructure != null)
{
// Mark the shape as processed if it's being referred only as operation's
// output shape and not being referred directly by any other shape or via an
// operation modifier generating an artifical structure not in the service model.
if (!IsShapeReferred(operation.ResponseStructure.Name, this.Configuration.ServiceModel)
&& !operation.WrapsResultShape(operation.ResponseStructure.Name))
this._processedUnmarshallers.Add(operation.ResponseStructure.Name);
}
}
if (operation.ResponseStructure != null)
{
var lookup = new NestedStructureLookup();
lookup.SearchForNestedStructures(operation.ResponseStructure);
//Do not generate an unmarshaller for the response's payload if it is of type EventStream
//This is because we attach the payload to the generic response and unmarshall it from there.
if (operation.IsEventStreamOutput)
{
if (operation.ResponsePayloadMember.ModelShape.IsEventStream)
{
//If the file was already generated incorrectly delete it
var unmarshallerName = operation.ResponsePayloadMember.ModelShape.Name + "Unmarshaller.cs";
var unmarshallerPath = Utils.PathCombineAlt(GeneratedFilesRoot, "Model","Internal", "MarshallTransformations", unmarshallerName);
if (File.Exists(unmarshallerPath))
{
File.Delete(unmarshallerPath);
}
return;
}
}
foreach (var nestedStructure in lookup.NestedStructures)
{
// Skip structure unmarshallers that have already been generated for the parent model
if (IsShapePresentInParentModel(this.Configuration, nestedStructure.Name))
continue;
if (this.Configuration.ServiceModel.Customizations.IsSubstitutedShape(nestedStructure.Name))
continue;
// Document structure don't need a custom marshaller, they use
// the 'simple' DocumentMarshaller in AWSSDK.
if (nestedStructure.IsDocument)
continue;
// Skip already processed unmarshallers. This handles the case of structures being returned in mulitiple requests.
if (!this._processedUnmarshallers.Contains(nestedStructure.Name))
{
var generator = GetStructureUnmarshaller();
generator.Structure = nestedStructure;
this.ExecuteGenerator(generator, nestedStructure.Name + "Unmarshaller.cs", "Model.Internal.MarshallTransformations");
this._processedUnmarshallers.Add(nestedStructure.Name);
}
else
{
//throw new Exception();
}
}
}
}
private void GenerateUnmarshaller(Shape shape)
{
var lookup = new NestedStructureLookup();
lookup.SearchForNestedStructures(shape);
foreach (var nestedStructure in lookup.NestedStructures)
{
// Skip structure unmarshallers that have already been generated for the parent model
if (IsShapePresentInParentModel(this.Configuration, nestedStructure.Name))
continue;
if (this.Configuration.ServiceModel.Customizations.IsSubstitutedShape(nestedStructure.Name))
continue;
// Document structure don't need a custom unmarshaller, they use
// the 'simple' DocumentMarshaller in AWSSDK.
if (nestedStructure.IsDocument)
continue;
// Skip already processed unmarshallers. This handles the case of structures being returned in mulitiple requests.
if (!this._processedUnmarshallers.Contains(nestedStructure.Name))
{
var generator = GetStructureUnmarshaller();
generator.Structure = nestedStructure;
this.ExecuteGenerator(generator, nestedStructure.Name + "Unmarshaller.cs", "Model.Internal.MarshallTransformations");
this._processedUnmarshallers.Add(nestedStructure.Name);
}
}
}
private void GenerateCustomUnmarshallers(List structures)
{
foreach (var structure in structures)
{
var shape = this.Configuration.ServiceModel.FindShape(structure);
GenerateUnmarshaller(shape);
}
}
///
/// Invokes T4:
///
public static void GenerateDefaultConfigurationModeEnum(GenerationManifest generationManifest, GeneratorOptions options)
{
Console.WriteLine("Generating DefaultConfigurationMode Enum...");
var defaultConfigurationModeFilesRoot = Utils.PathCombineAlt(options.SdkRootFolder, "src", "Core", "Amazon.Runtime");
const string fileName = "DefaultConfigurationMode.generated.cs";
var generator = new DefaultConfigurationModeGenerator
{
DefaultConfigurationModel = generationManifest.DefaultConfiguration
};
var text = generator.TransformText();
WriteFile(defaultConfigurationModeFilesRoot, null, fileName, text);
}
///
/// Generates the endpoint discovery marshaller for the specified operation.
///
/// The operation to generate endpoint discovery marshaller for
void GenerateEndpointDiscoveryMarshaller(Operation operation)
{
if(operation.IsEndpointOperation || !operation.EndpointDiscoveryEnabled)
{
return;
}
var generator = new EndpointDiscoveryMarshaller
{
Operation = operation
};
this.ExecuteGenerator(generator, operation.Name + "EndpointDiscoveryMarshaller.cs", "Model.Internal.MarshallTransformations");
}
private void GenerateExceptions(Operation operation)
{
//Generate a special EventStreamException class that extends EventStreamException
//We need a parameterless constructor to use it in EnumerableEventStream. Only once per service
if (operation.IsEventStreamOutput && !Configuration.GeneratedEventStreamException)
{
var eventStreamExceptionGenerator = new EventStreamExceptionGenerator();
this.ExecuteGenerator(eventStreamExceptionGenerator, this.Configuration.ClassName + "EventStreamException.cs","Model");
Configuration.GeneratedEventStreamException = true;
}
foreach (var exceptionShape in operation.Exceptions)
{
// Skip exceptions that have already been generated for the parent model
if (IsExceptionPresentInParentModel(this.Configuration, exceptionShape.Name) || this._processedStructures.Contains(exceptionShape.Name))
continue;
var generator = new StructureGenerator()
{
ClassName = exceptionShape.Name,
Structure = exceptionShape,
BaseClass = this.Configuration.BaseException
};
this.ExecuteGenerator(generator, exceptionShape.Name + ".cs", "Model");
this._processedStructures.Add(exceptionShape.Name);
var unmarshallerGenerator = GetExceptionUnmarshaller();
unmarshallerGenerator.Structure = exceptionShape;
this.ExecuteGenerator(unmarshallerGenerator, exceptionShape.Name + "Unmarshaller.cs", "Model.Internal.MarshallTransformations");
this._processedUnmarshallers.Add(exceptionShape.Name);
DetermineStructuresToProcess(exceptionShape, false);
GenerateUnmarshaller(exceptionShape);
}
}
public static void GenerateCoreProjects(GenerationManifest generationManifest,
GeneratorOptions options)
{
Console.WriteLine("Updating Core project files.");
string coreFilesRoot = Utils.PathCombineAlt(options.SdkRootFolder, "src", "Core");
var creator = new ProjectFileCreator(options);
creator.ExecuteCore(coreFilesRoot, generationManifest.ProjectFileConfigurations);
foreach (var newProjectKey in creator.CreatedProjectFiles.Keys)
{
NewlyCreatedProjectFiles.Add(newProjectKey, creator.CreatedProjectFiles[newProjectKey]);
}
}
///
/// Generates partial Partition class used to retrieve partition-specific data.
///
public static void GeneratePartitions(GeneratorOptions options)
{
Console.WriteLine("Generate Partition class.");
var coreFilesRoot = Utils.PathCombineAlt(options.SdkRootFolder, "src", "Core");
var writeToFolder = Utils.PathCombineAlt(coreFilesRoot, "Amazon.Runtime", "Internal", "Endpoints", "StandardLibrary");
var partitionsFile = Utils.PathCombineAlt(coreFilesRoot, "partitions.json");
var json = File.ReadAllText(partitionsFile);
var partitions = JsonMapper.ToObject(json);
var generator = new PartitionsTemplate
{
Partitions = partitions
};
var text = generator.TransformText();
WriteFile(writeToFolder, null, "Partition.generated.cs", text);
}
///
/// Method to create/update legacy unit test projects
///
public static void UpdateUnitTestProjects(GenerationManifest generationManifest, GeneratorOptions options)
{
string unitTestRoot = Utils.PathCombineAlt(options.SdkRootFolder, "test", "UnitTests");
var creator = new UnitTestProjectFileCreator(options, generationManifest.UnitTestProjectFileConfigurations);
UpdateUnitTestProjects(generationManifest.ServiceConfigurations, options, unitTestRoot, creator);
}
///
/// Adding Method to create/update service specific unit test projects
///
public static void UpdateUnitTestProjects(GenerationManifest generationManifest, GeneratorOptions options, string serviceTestFilesRoot, ServiceConfiguration serviceConfiguration)
{
Console.WriteLine("Updating unit test project files.");
string unitTestRoot = Utils.PathCombineAlt(serviceTestFilesRoot, "UnitTests");
var creator = new UnitTestProjectFileCreator(options, generationManifest.UnitTestProjectFileConfigurations, serviceConfiguration.ServiceFolderName);
UpdateUnitTestProjects(new[] { serviceConfiguration }, options, unitTestRoot, creator);
}
private static void UpdateUnitTestProjects(IEnumerable serviceConfigurations, GeneratorOptions options, string unitTestRoot, UnitTestProjectFileCreator creator)
{
Console.WriteLine("Updating unit test project files.");
creator.Execute(unitTestRoot, serviceConfigurations, false);
}
public static void UpdateSolutionFiles(GenerationManifest manifest, GeneratorOptions options)
{
Console.WriteLine("Updating solution files.");
var solutionFileCreator = new SolutionFileCreator
{
Options = options,
ProjectFileConfigurations = manifest.ProjectFileConfigurations
};
solutionFileCreator.Execute(NewlyCreatedProjectFiles);
}
public static void UpdateCodeAnalysisSolution(GenerationManifest manifest, GeneratorOptions options)
{
Console.WriteLine("Updating code analysis solution file.");
var creator = new CodeAnalysisSolutionCreator
{
Options = options
};
creator.Execute();
}
public static void UpdateAssemblyVersionInfo(GenerationManifest manifest, GeneratorOptions options)
{
var updater = new CoreAssemblyInfoUpdater(options, manifest);
updater.Execute();
}
public static void UpdateNuGetPackagesInReadme(GenerationManifest manifest, GeneratorOptions options)
{
var nugetPackages = new Dictionary();
foreach (var service in manifest.ServiceConfigurations.OrderBy(x => x.ClassName))
{
// Service like DynamoDB streams are included in a parent service.
if (service.ParentConfig != null || service.IsTestService)
continue;
if (string.IsNullOrEmpty(service.Synopsis))
throw new Exception(string.Format("{0} is missing a synopsis in the manifest.", service.ClassName));
var assemblyName = service.Namespace.Replace("Amazon.", "AWSSDK.");
nugetPackages[assemblyName] = service.Synopsis;
}
NuGetPackageReadmeSection generator = new NuGetPackageReadmeSection();
var session = new Dictionary { { "NugetPackages", nugetPackages } };
generator.Session = session;
var nugetPackagesText = generator.TransformText();
var readmePath = Utils.PathCombineAlt(options.SdkRootFolder, "..", "README.md");
var originalContent = File.ReadAllText(readmePath);
int startPos = originalContent.IndexOf('\n', originalContent.IndexOf("### NuGet Packages")) + 1;
int endPos = originalContent.IndexOf("### Code Generator");
var newContent = originalContent.Substring(0, startPos);
newContent += nugetPackagesText + "\r\n";
newContent += originalContent.Substring(endPos);
File.WriteAllText(readmePath, newContent);
}
///
/// Provides a way to generate the necessary attributes and marshallers/unmarshallers for nested structures to work
///
class NestedStructureLookup
{
public List NestedStructures { get; private set; }
public NestedStructureLookup()
{
NestedStructures = new List();
}
///
/// Function that recursively searches for structures of a given shape
///
/// The shape to look for recursive structures in
public void SearchForNestedStructures(Shape structure)
{
if (NestedStructures.Contains(structure))
return;
if (structure.IsStructure)
NestedStructures.Add(structure);
if (structure.IsList)
{
if (structure.ListShape.IsStructure || structure.ListShape.IsList || structure.ListShape.IsMap)
{
SearchForNestedStructures(structure.ListShape);
}
}
else if (structure.IsMap)
{
if (structure.ValueShape.IsStructure || structure.ValueShape.IsList || structure.ValueShape.IsMap)
{
SearchForNestedStructures(structure.ValueShape);
}
}
else if (structure.IsStructure)
{
foreach (var member in structure.Members)
{
SearchForNestedStructures(member.Shape);
}
}
}
}
///
/// Generates all the POCOs that go in the Model namespace for the structures defined in the service model.
///
void GenerateStructures()
{
var excludedOperations = Configuration.ServiceModel.ExcludedOperations;
foreach (var definition in this._structuresToProcess)
{
// Skip structures that have already been generated for the parent model
if (IsShapePresentInParentModel(this.Configuration, definition.Name))
continue;
if (!this._processedStructures.Contains(definition.Name))
{
// if the shape had a substitution, we can skip generation
if (this.Configuration.ServiceModel.Customizations.IsSubstitutedShape(definition.Name))
continue;
// if the shape is a request or response type and the parent operation
// was excluded, then skip generation
var opName = definition.RelatedOperationName;
if (!string.IsNullOrEmpty(opName) && excludedOperations.Contains(opName))
continue;
var generator = new StructureGenerator()
{
ClassName = definition.Name,
Structure = definition
};
this.ExecuteGenerator(generator, definition.Name + ".cs", "Model");
this._processedStructures.Add(definition.Name);
}
}
}
static bool IsShapePresentInParentModel(ServiceConfiguration config, string shapeName)
{
if (config.IsChildConfig)
{
// Check to see if the structure is present in a parent model
if (config.ParentConfig.ServiceModel.Shapes.SingleOrDefault(
e => e.Name.Equals(shapeName)) != null)
{
return true;
}
}
return false;
}
static bool IsExceptionPresentInParentModel(ServiceConfiguration config, string exceptionName)
{
if (config.IsChildConfig)
{
// Check to see if the exception is present in a parent model
if (config.ParentConfig.ServiceModel.Exceptions.SingleOrDefault(
e => e.Name.Equals(exceptionName)) != null)
{
return true;
}
}
return false;
}
///
/// Generates any missing project files for a service
///
void ExecuteProjectFileGenerators()
{
var creator = new ProjectFileCreator(Options);
creator.Execute(ServiceFilesRoot, this.Configuration, this.ProjectFileConfigurations);
foreach (var newProjectKey in creator.CreatedProjectFiles.Keys)
{
NewlyCreatedProjectFiles.Add(newProjectKey, creator.CreatedProjectFiles[newProjectKey]);
}
}
void GenerateNuspec()
{
var coreVersion = GenerationManifest.CoreVersion;
// we're generating services only, so can automatically add the core runtime
// as a dependency
var awsDependencies = new Dictionary(StringComparer.Ordinal);
if (Configuration.ServiceDependencies != null)
{
var dependencies = Configuration.ServiceDependencies;
foreach (var kvp in dependencies)
{
var service = kvp.Key;
var version = kvp.Value;
var dependentService = GenerationManifest.ServiceConfigurations.FirstOrDefault(x => string.Equals(x.Namespace, "Amazon." + service, StringComparison.InvariantCultureIgnoreCase));
string previewFlag;
if (dependentService != null && dependentService.InPreview)
{
previewFlag = GenerationManifest.PreviewLabel;
}
else if (string.Equals(service, "Core", StringComparison.InvariantCultureIgnoreCase) && GenerationManifest.DefaultToPreview)
{
previewFlag = GenerationManifest.PreviewLabel;
}
else
{
previewFlag = string.Empty;
}
var verTokens = version.Split('.');
var versionRange = string.Format("[{0}{2}, {1}.0{2})", version, int.Parse(verTokens[0]) + 1, previewFlag);
awsDependencies.Add(string.Format("AWSSDK.{0}", service), versionRange);
}
}
var nugetAssemblyName = Configuration.AssemblyTitle;
var nugetAssemblyTitle = Configuration.AssemblyTitle.Replace("AWSSDK.", "AWSSDK - ");
var nugetTitle = nugetAssemblyTitle;
if (!string.IsNullOrEmpty(Configuration.NugetPackageTitleSuffix))
nugetTitle += " " + Configuration.NugetPackageTitleSuffix;
var session = new Dictionary
{
{ "AssemblyName", nugetAssemblyName },
{ "AssemblyTitle", nugetAssemblyTitle },
{ "NetStandardSupport", Configuration.NetStandardSupport },
{ "NetStandardCoreAssemblyName", Configuration.ServiceFolderName },
{ "NuGetTitle", nugetTitle },
{ "AssemblyDescription", Configuration.AssemblyDescription(includePreamble: false, includeBody: false) },
{ "AssemblyVersion", Configuration.ServiceFileVersion },
{ "AWSDependencies", awsDependencies },
{ "BaseName", Configuration.ClassName },
{ "CodeAnalysisServiceFolder", Configuration.Namespace.Replace("Amazon.", "") },
{ "ProjectFileConfigurations", ProjectFileConfigurations},
{ "ExtraTags", Configuration.Tags == null || Configuration.Tags.Count == 0 ? string.Empty : " " + string.Join(" ", Configuration.Tags) },
{ "licenseUrl", Configuration.LicenseUrl },
{ "requireLicenseAcceptance",Configuration.RequireLicenseAcceptance?"true":"false" }
};
if (Configuration.NugetDependencies != null)
session.Add("NugetDependencies", Configuration.NugetDependencies);
session["NuGetPreviewFlag"] = Configuration.InPreview ? this.GenerationManifest.PreviewLabel : "";
var nuspecGenerator = new Nuspec { Session = session };
var text = nuspecGenerator.TransformText();
var nuspecFilename = nugetAssemblyName + ".nuspec";
WriteFile(ServiceFilesRoot, string.Empty, nuspecFilename, text);
}
Version GetMinVersion(Version version)
{
var minVersion = new Version(version.Major, version.Minor);
return minVersion;
}
Version GetMinVersion(string versionText)
{
var version = new Version(versionText);
return GetMinVersion(version);
}
///
/// Generates tests for the customizations of the service
///
void GenerateCustomizationTests()
{
if (this.Configuration.ServiceModel.Customizations.SimpleConstructorsModel.SimpleConstructors.Count > 0)
{
var constructorTests = new SimpleConstructorTests()
{
Config = this.Configuration
};
ExecuteCustomizationTestGenerator(constructorTests, this.Configuration.ClassName + "ConstructorTests.cs", "Constructors");
}
if (this.Configuration.ServiceModel.Customizations.SimpleMethodsModel.SimpleMethods.Count > 0)
{
var methodTests = new SimpleMethodTests()
{
Config = this.Configuration
};
ExecuteCustomizationTestGenerator(methodTests, this.Configuration.ClassName + "MethodTests.cs", "SimpleMethods");
}
}
///
/// Runs the generator and saves the content into _bcl35 directory under the generated files root.
///
/// The generator to use for outputting the text of the cs file
/// The name of the cs file
/// Adds an additional directory for the namespace
void ExecuteGenerator(BaseGenerator generator, string fileName, string subNamespace = null)
{
generator.Config = this.Configuration;
generator.DefaultConfigurationModel = this.GenerationManifest.DefaultConfiguration;
var text = generator.TransformText();
string outputFile;
WriteFile(GeneratedFilesRoot, subNamespace, fileName, text, true, true, out outputFile);
FilesWrittenToGeneratorFolder.Add(outputFile);
}
///
/// Runs the generator and saves the content in the test directory.
///
/// The generator to use for outputting the text of the cs file
/// The name of the cs file
/// Adds an additional directory for the namespace
void ExecuteTestGenerator(BaseGenerator generator, string fileName, string subNamespace = null)
{
generator.Config = this.Configuration;
var text = generator.TransformText();
var outputSubFolder = subNamespace == null ? GeneratedTestsSubFolder : Utils.PathCombineAlt(GeneratedTestsSubFolder, subNamespace);
WriteFile(ServiceUnitTestFilesRoot, outputSubFolder, fileName, text);
}
void ExecuteGeneratorAssemblyInfo()
{
var generator = new AssemblyInfo { Config = this.Configuration };
var text = generator.TransformText();
WriteFile(ServiceFilesRoot, "Properties", "AssemblyInfo.cs", text);
}
void ExecuteExampleGenerator(BaseGenerator generator, string fileName, string subNamespace = null)
{
generator.Config = this.Configuration;
var text = generator.TransformText();
var outputSubFolder = Utils.PathCombineAlt("docgenerator", "AWSSDKDocSamples");
if (subNamespace != null)
outputSubFolder = Utils.PathCombineAlt(outputSubFolder, subNamespace);
var baseOutputDir = Utils.ConvertPathAlt(Path.GetFullPath(TestFilesRoot + string.Format("{0}..{0}..{0}",
Path.AltDirectorySeparatorChar)));
WriteFile(baseOutputDir, outputSubFolder, fileName, text);
}
///
/// Runs the generator and saves the content in the test directory.
///
/// The generator to use for outputting the text of the cs file
/// The name of the cs file
/// Adds an additional directory for the namespace
void ExecuteCustomizationTestGenerator(BaseGenerator generator, string fileName, string subNamespace = null)
{
generator.Config = this.Configuration;
var text = generator.TransformText();
var outputSubFolder = subNamespace == null ? CustomizationTestsSubFolder : Utils.PathCombineAlt(CustomizationTestsSubFolder, subNamespace);
WriteFile(ServiceUnitTestFilesRoot, outputSubFolder, fileName, text);
}
///
/// Generates unit tests for all of the service's paginators
///
void GeneratePaginatorTests()
{
if (this.Configuration.ServiceModel.HasPaginators && this.Configuration.GenerateConstructors)
{
var paginatorTests = new PaginatorTests
{
Config = this.Configuration
};
var text = paginatorTests.TransformText();
var outputSubFolder = PaginatorTestsSubFolder;
WriteFile(ServiceUnitTestFilesRoot, outputSubFolder, this.Configuration.ClassName + "PaginatorTests.cs", text);
}
}
internal static bool WriteFile(string baseOutputDir,
string subNamespace,
string filename,
string content,
bool trimWhitespace = true,
bool replaceTabs = true)
{
string outputFilePath;
return WriteFile(baseOutputDir, subNamespace, filename, content, trimWhitespace, replaceTabs, out outputFilePath);
}
///
/// Writes the contents to disk. The content will by default be trimmed of all white space and
/// all tabs are replaced with spaces to make the output consistent.
///
/// The root folder for the owning service's generated files
/// An optional sub namespace under the service. (e.g. Model or Model.Internal.MarshallTransformations)
/// Filename to right to
/// The contents to write to the file
///
///
/// Returns false if the file already exists and has the same content.
internal static bool WriteFile(string baseOutputDir,
string subNamespace,
string filename,
string content,
bool trimWhitespace,
bool replaceTabs,
out string outputFilePath)
{
var outputDir = !string.IsNullOrEmpty(subNamespace)
? Utils.PathCombineAlt(baseOutputDir, subNamespace.Replace('.', Path.AltDirectorySeparatorChar))
: baseOutputDir;
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
var cleanContent = trimWhitespace ? content.Trim() : content;
if (replaceTabs)
cleanContent = cleanContent.Replace("\t", " ");
outputFilePath = Utils.ConvertPathAlt(Path.GetFullPath(Utils.PathCombineAlt(outputDir, filename)));
if (File.Exists(outputFilePath))
{
var existingContent = File.ReadAllText(outputFilePath);
if (string.Equals(existingContent, cleanContent))
return false;
var outputFilePathFi = new FileInfo(outputFilePath);
// Handle Windows being case insensitive when a service makes a case change for shape name.
// Get a FileInfo that represents the casing of the file on disk
var fi = Directory.GetFiles(outputFilePathFi.DirectoryName)
.Select(x => new FileInfo(x))
.FirstOrDefault(x => string.Equals(x.Name, outputFilePathFi.Name, StringComparison.OrdinalIgnoreCase));
// Compare the casing on disk versus the computed value.
if(fi.FullName != new FileInfo(outputFilePath).FullName)
{
// Casing is different so delete the on disk file so we can create a new file with the correct casing.
File.Delete(outputFilePath);
Console.WriteLine("...deleting existing file that is different by case from new file: {0}", filename);
}
}
File.WriteAllText(outputFilePath, cleanContent);
Console.WriteLine("...created/updated {0}", filename);
return true;
}
///
/// Sets the marshaller of the generator based on the service type
///
/// The marshaller to be set
/// If the service type is a type of json then normalizeMarshallers is set to true, false otherwise
void GetRequestMarshaller(out BaseRequestMarshaller marshaller, out bool normalizeMarshallers)
{
normalizeMarshallers = false;
switch (this.Configuration.ServiceModel.Type)
{
case ServiceType.Rest_Json:
case ServiceType.Json:
marshaller = new JsonRPCRequestMarshaller();
normalizeMarshallers = true;
break;
case ServiceType.Query:
marshaller = new AWSQueryRequestMarshaller();
break;
case ServiceType.Rest_Xml:
marshaller = new RestXmlRequestMarshaller();
break;
default:
throw new Exception("No request marshaller for service type: " + this.Configuration.ServiceModel.Type);
}
}
///
/// Determines the type of marshaller that needs to be generated based on the service model type
///
/// JSONRPCStructureMarshaller for Rest_Json and Json, error otherwise
BaseRequestMarshaller GetStructureMarshaller()
{
switch (this.Configuration.ServiceModel.Type)
{
case ServiceType.Rest_Json:
case ServiceType.Json:
return new JsonRPCStructureMarshaller();
default:
throw new Exception("No structure marshaller for service type: " + this.Configuration.ServiceModel.Type);
}
}
///
/// Determines the type of response unmarshaller to be used based on the service model type
///
/// Either a JsonRPCResponseUnmarshaller, a AWSQueryResponseUnmarshaller, or a RestXmlResponseUnmarshaller. Error otherwise
BaseResponseUnmarshaller GetResponseUnmarshaller()
{
switch (this.Configuration.ServiceModel.Type)
{
case ServiceType.Rest_Json:
case ServiceType.Json:
return new JsonRPCResponseUnmarshaller();
case ServiceType.Query:
if (this.Configuration.ServiceModel.IsEC2Protocol)
return new AWSQueryEC2ResponseUnmarshaller();
return new AWSQueryResponseUnmarshaller();
case ServiceType.Rest_Xml:
return new RestXmlResponseUnmarshaller();
default:
throw new Exception("No response unmarshaller for service type: " + this.Configuration.ServiceModel.Type);
}
}
///
/// Determines the Unmarshaller for structures based on the service model type
///
/// Either JsonRPCStructureUnmarshaller, AWSQueryStructureUnmarshaller, or RestXmlStructureUnmarshaller. Error otherwise
BaseResponseUnmarshaller GetStructureUnmarshaller()
{
switch (this.Configuration.ServiceModel.Type)
{
case ServiceType.Rest_Json:
case ServiceType.Json:
return new JsonRPCStructureUnmarshaller();
case ServiceType.Query:
return new AWSQueryStructureUnmarshaller();
case ServiceType.Rest_Xml:
return new RestXmlStructureUnmarshaller();
default:
throw new Exception("No structure unmarshaller for service type: " + this.Configuration.ServiceModel.Type);
}
}
///
/// Determines the Unmarshaller for structures based on the service model type
///
/// Either JsonRPCStructureUnmarshaller, AWSQueryStructureUnmarshaller, or RestXmlStructureUnmarshaller. Error otherwise
BaseResponseUnmarshaller GetExceptionUnmarshaller()
{
switch (this.Configuration.ServiceModel.Type)
{
case ServiceType.Rest_Json:
case ServiceType.Json:
return new JsonRPCExceptionUnmarshaller();
case ServiceType.Query:
return new AWSQueryExceptionUnmarshaller();
case ServiceType.Rest_Xml:
return new RestXmlExceptionUnmarshaller();
default:
throw new Exception("No structure unmarshaller for service type: " + this.Configuration.ServiceModel.Type);
}
}
void GenerateCodeAnalysisProject()
{
var command = new CodeAnalysisProjectCreator();
command.Execute(CodeAnalysisRoot, this.Configuration);
}
public static void RemoveOrphanedShapesAndServices(HashSet generatedFiles, string sdkRootFolder)
{
var codeGeneratedServiceList = codeGeneratedServiceNames.Distinct();
var srcFolder = Utils.PathCombineAlt(sdkRootFolder, SourceSubFoldername, ServicesSubFoldername);
RemoveOrphanedShapes(generatedFiles, srcFolder);
// Cleanup orphaned Service src artifacts. This is encountered when the service identifier is modified.
RemoveOrphanedServices(srcFolder, codeGeneratedServiceList);
// Cleanup orphaned Service test artifacts. This is encountered when the service identifier is modified.
RemoveOrphanedServices(Utils.PathCombineAlt(sdkRootFolder, TestsSubFoldername, ServicesSubFoldername), codeGeneratedServiceList);
// Cleanup orphaned Service code analysis artifacts. This is encountered when the service identifier is modified.
RemoveOrphanedServices(Utils.PathCombineAlt(sdkRootFolder, CodeAnalysisFoldername, ServicesAnalysisSubFolderName), codeGeneratedServiceList);
}
public static void RemoveOrphanedShapes(HashSet generatedFiles, string srcFolder)
{
// Remove orphaned shapes. Most likely due to taking in a model that was still under development.
foreach (var file in Directory.GetFiles(srcFolder, "*.cs", SearchOption.AllDirectories).OrderBy(f => f))
{
var fullPath = Utils.ConvertPathAlt(Path.GetFullPath(file));
if (fullPath.IndexOf($"/{GeneratedCodeFoldername}/", StringComparison.OrdinalIgnoreCase) < 0)
continue;
if (!generatedFiles.Contains(fullPath))
{
Console.Error.WriteLine("**** Warning: Removing orphaned generated code " + Path.GetFileName(file));
File.Delete(file);
}
}
}
private static void RemoveOrphanedServices(string path, IEnumerable codeGeneratedServiceList)
{
foreach (var directoryName in Directory.GetDirectories(path).OrderBy(d => d))
{
if (!codeGeneratedServiceList.Contains(new DirectoryInfo(directoryName).Name))
{
Directory.Delete(directoryName, true);
}
}
}
///
/// Constructs endpoint constant name from a region code
/// e.g. us-east-1 -> USEast1
///
public static string ConstructEndpointName(string regionCode)
{
var parts = regionCode.Split('-');
var name = parts[0].ToUpper();
for (int i = 1; i < parts.Length; i++)
{
// for backward compatibility, only "northwest" is transformed into upper camel case
var part = parts[i] == "northwest" ? "NorthWest" : parts[i].ToUpperFirstCharacter();
// special case for "Gov" regions, we add "Cloud" to it
part = part == "Gov" ? part + "Cloud" : part;
name += part;
}
return name;
}
public static List ExtractEndpoints(GeneratorOptions options, Func nameConverter, Func codeConverter = null)
{
var coreFilesRoot = Utils.PathCombineAlt(options.SdkRootFolder, "src", "Core");
var endpointsJsonFile = Utils.PathCombineAlt(coreFilesRoot, "endpoints.json");
var endpointsJson = JsonMapper.ToObject(File.ReadAllText(endpointsJsonFile));
var endpoints = new List();
foreach (JsonData partition in endpointsJson["partitions"])
{
JsonData regions = partition["regions"];
foreach (var regionCode in regions.PropertyNames)
{
var regionName = regions[regionCode]["description"].ToString();
endpoints.Add(new EndpointConstant
{
Name = nameConverter(regionCode),
RegionCode = regionCode,
ConvertedRegionCode = codeConverter == null ? regionCode : codeConverter(regionCode),
RegionName = regionName
});
}
}
return endpoints;
}
public static void GenerateEndpoints(GeneratorOptions options)
{
Console.WriteLine("Generating endpoints constants...");
var coreFilesRoot = Utils.PathCombineAlt(options.SdkRootFolder, "src", "Core");
var endpointsFilesRoot = Utils.PathCombineAlt(coreFilesRoot, "RegionEndpoint");
const string fileName = "RegionEndpoint.generated.cs";
var endpoints = ExtractEndpoints(options, ConstructEndpointName);
var generator = new EndpointsGenerator
{
Endpoints = endpoints
};
var text = generator.TransformText();
WriteFile(endpointsFilesRoot, null, fileName, text);
}
///
/// Converts region code to maintain backward compatibility with S3
///
public static string ConvertS3RegionCode(string regionCode)
{
switch (regionCode)
{
case "us-east-1":
return "";
case "eu-west-1":
return "EU";
default:
return regionCode;
}
}
public static void GenerateS3Enumerations(GeneratorOptions options)
{
Console.WriteLine("Generating S3 enumerations constants...");
var srcFilesRoot = Utils.PathCombineAlt(options.SdkRootFolder, "src");
var coreFilesRoot = Utils.PathCombineAlt(srcFilesRoot, "Core");
var generatedFileRoot = Utils.PathCombineAlt(srcFilesRoot, "Services", "S3", "Generated");
const string fileName = "S3Enumerations.cs";
var endpoints = ExtractEndpoints(options, ConstructEndpointName, ConvertS3RegionCode);
var generator = new S3EnumerationsGenerator()
{
Endpoints = endpoints
};
var text = generator.TransformText();
WriteFile(generatedFileRoot, null, fileName, text);
}
}
}