/// <summary>
        /// Processes resources that can point to artifacts in S3.
        /// </summary>
        /// <param name="resource">The resource.</param>
        /// <param name="templatePath">The template path.</param>
        /// <param name="workingDirectory">The working directory.</param>
        /// <returns><c>true</c> if the containing template should be modified (to point to S3); else <c>false</c></returns>
        /// <exception cref="InvalidDataException">Unsupported derivative of FileSystemInfo</exception>
        /// <exception cref="MissingMethodException">Missing constructor for the replacement template artifact.</exception>
        private async Task <bool> ProcessResource(
            IResource resource,
            string templatePath,
            string workingDirectory)
        {
            var templateModified = false;

            foreach (var propertyToCheck in PackagedResources[resource.Type])
            {
                ResourceUploadSettings resourceToUpload;

                // See if we have a lambda
                var lambdaResource = new LambdaArtifact(this.pathResolver, resource, this.logger, this.platform, templatePath);

                if (lambdaResource.ArtifactType != LambdaArtifactType.NotLambda)
                {
                    // We do
                    using (var packager = LambdaPackager.CreatePackager(lambdaResource, this.s3Util, this.logger, this.platform))
                    {
                        resourceToUpload = await packager.Package(workingDirectory);

                        if (resourceToUpload == null)
                        {
                            // Lambda syntax does not imply a template modification
                            // i.e. it is inline code or already an S3 reference.
                            continue;
                        }

                        // The template will be altered to an S3 location,
                        // however the zip may or may not be uploaded.
                        templateModified = true;
                    }
                }
                else
                {
                    string resourceFile;

                    try
                    {
                        resourceFile = (string)resource.GetResourcePropertyValue(propertyToCheck.PropertyPath);
                    }
                    catch (FormatException)
                    {
                        if (!propertyToCheck.Required)
                        {
                            // Property is missing, but CloudFormation does not require it.
                            continue;
                        }

                        throw;
                    }

                    if (resourceFile == null)
                    {
                        // Property was not found, or was not a value type.
                        continue;
                    }

                    var fsi = ResolveFileSystemResource(this.pathResolver, templatePath, resourceFile);

                    if (fsi == null)
                    {
                        // Property value did not resolve to a path in the file system
                        continue;
                    }

                    templateModified = true;
                    switch (fsi)
                    {
                    case FileInfo fi:

                        // Property value points to a file
                        resourceToUpload = await ArtifactPackager.PackageFile(
                            fi,
                            workingDirectory,
                            propertyToCheck.Zip,
                            this.s3Util,
                            this.logger);

                        break;

                    case DirectoryInfo di:

                        // Property value points to a directory, which must always be zipped.
                        resourceToUpload = await ArtifactPackager.PackageDirectory(
                            di,
                            workingDirectory,
                            this.s3Util,
                            this.logger);

                        break;

                    default:

                        // Should never get here, but shuts up a bunch of compiler/R# warnings
                        throw new InvalidDataException(
                                  $"Unsupported derivative of FileSystemInfo: {fsi.GetType().FullName}");
                    }
                }

                if (!resourceToUpload.HashMatch)
                {
                    resourceToUpload.KeyPrefix = this.s3Util.KeyPrefix;
                    resourceToUpload.Metadata  = this.s3Util.Metadata;

                    await this.s3Util.UploadResourceToS3Async(resourceToUpload);
                }

                if (propertyToCheck.ReplacementType == typeof(string))
                {
                    // Insert the URI directly
                    resource.UpdateResourceProperty(propertyToCheck.PropertyPath, resourceToUpload.S3Artifact.Url);
                }
                else
                {
                    // Create an instance of the new mapping
                    // ReSharper disable once StyleCop.SA1305
                    var s3Location = propertyToCheck.ReplacementType.GetConstructor(new[] { typeof(S3Artifact) })
                                     ?.Invoke(new object[] { resourceToUpload.S3Artifact });

                    if (s3Location == null)
                    {
                        throw new MissingMethodException(propertyToCheck.ReplacementType.FullName, ".ctor(S3Artifact)");
                    }

                    // and set on the resource property
                    resource.UpdateResourceProperty(propertyToCheck.PropertyPath, s3Location);
                }
            }

            return(templateModified);
        }
        /// <summary>
        /// Package the artifact
        /// </summary>
        /// <param name="workingDirectory">Working directory to use for packaging</param>
        /// <returns><see cref="ResourceUploadSettings"/>; else <c>null</c> if nothing to upload (hash sums match)</returns>
        public async Task <ResourceUploadSettings> Package(string workingDirectory)
        {
            this.ValidateHandler();

            // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - Intentionally so, all other cases are effectively the default
            switch (this.LambdaArtifact.ArtifactType)
            {
            case LambdaArtifactType.ZipFile:

                // Already zipped
                FileInfo lambdaFile       = this.LambdaArtifact;
                var      resourceToUpload = new ResourceUploadSettings {
                    File = lambdaFile, Hash = lambdaFile.MD5()
                };

                await this.S3.ObjectChangedAsync(resourceToUpload);

                // Template will always be modified, however the resource may not need upload.
                return(resourceToUpload);

            case LambdaArtifactType.Inline:
            case LambdaArtifactType.FromS3:
                // Template is unchanged if code is inline or already in S3
                return(null);

            default:

                var dependencies = this.LambdaArtifact.LoadDependencies();

                if (!dependencies.Any())
                {
                    switch (this.LambdaArtifact.ArtifactType)
                    {
                    case LambdaArtifactType.CodeFile:

                        return(await ArtifactPackager.PackageFile(
                                   this.LambdaArtifact,
                                   workingDirectory,
                                   true,
                                   this.S3,
                                   this.Logger));

                    case LambdaArtifactType.Directory:

                        return(await ArtifactPackager.PackageDirectory(
                                   this.LambdaArtifact,
                                   workingDirectory,
                                   this.S3,
                                   this.Logger));
                    }
                }

                // If we get here, there are dependencies to process
                var packageDirectory = this.PreparePackage(workingDirectory);
                return(await ArtifactPackager.PackageDirectory(
                           new DirectoryInfo(packageDirectory),
                           workingDirectory,
                           this.S3,
                           this.Logger));
            }
        }