functions/ConvertFrom-ARSwagger.ps1

function ConvertFrom-ARSwagger {
<#
    .SYNOPSIS
        Parse a swagger file and generate commands from it.
     
    .DESCRIPTION
        Parse a swagger file and generate commands from it.
        Only supports the JSON format of swagger file.
     
    .PARAMETER Path
        Path to the swagger file(s) to process.
     
    .PARAMETER TransformPath
        Path to a folder containing psd1 transform files.
        These can be used to override or add to individual entries from the swagger file.
        For example, you can add help URI, fix missing descriptions, add parameter help or attributes...
     
    .PARAMETER RestCommand
        Name of the command executing the respective REST queries.
        All autogenerated commands will call this command to execute.
     
    .PARAMETER ModulePrefix
        A prefix to add to all commands generated from this command.
     
    .PARAMETER PathPrefix
        Swagger files may include the same first uri segments in all endpoints.
        While this could be just passed through, you can also remove them using this parameter.
        It is then assumed, that the command used in the RestCommand is aware of this and adds it again to the request.
        Example:
        All endpoints in the swagger-file start with "/api/"
        "/api/users", "/api/machines", "/api/software", ...
        In that case, it could make sense to remove the "/api/" part from all commands and just include it in the invokation command.
     
    .PARAMETER ServiceName
        Adds the servicename to the commands generated.
        When exported, they will be hardcoded to execute as that service.
        This simplifies the configuration of the output, but prevents using multiple connections to different instances or under different privileges at the same time.
     
    .EXAMPLE
        PS C:\> Get-ChildItem .\swaggerfiles | ConvertFrom-ARSwagger -Transformpath .\transform -RestCommand Invoke-ARRestRequest -ModulePrefix Mg -PathPrefix '/api/'
         
        Picks up all items in the subfolder "swaggerfiles" and converts it to PowerShell command objects.
        Applies all transforms in the subfolder transform.
        Uses the "Invoke-ARRestRequest" command for all rest requests.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')]
        [Alias('FullName')]
        [string]
        $Path,

        [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')]
        [string]
        $TransformPath,

        [Parameter(Mandatory = $true)]
        [string]
        $RestCommand,

        [string]
        $ModulePrefix,

        [string]
        $PathPrefix,
        
        [string]
        $ServiceName
    )

    begin {
        #region Functions
        function Copy-ParameterConfig {
            [CmdletBinding()]
            param (
                [Hashtable]
                $Config,

                $Parameter
            )

            if ($Config.Help) { $Parameter.Help = $Config.Help }
            if ($Config.Name) { $Parameter.Name = $Config.Name }
            if ($Config.Alias) { $Parameter.Alias = $Config.Alias }
            if ($Config.Weight) { $Parameter.Weight = $Config.Weight }
            if ($Config.ParameterType) { $Parameter.ParameterType = $Config.ParameterType }
            if ($Config.ContainsKey('ValueFromPipeline')) { $Parameter.ValueFromPipeline = $Config.ValueFromPipeline }
            if ($Config.ParameterSet) { $Parameter.ParameterSet = $Config.ParameterSet }
        }
        
        function New-Parameter {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
            [CmdletBinding()]
            param (
                [string]
                $Name,

                [string]
                $Help,

                [string]
                $ParameterType,

                [AllowEmptyString()]
                [AllowNull()]
                [string]
                $ParameterFormat,

                [bool]
                $Mandatory,

                [ParameterType]
                $Type
            )

            $parameter = [CommandParameter]::new(
                $Name,
                $Help,
                $ParameterType,
                $Mandatory,
                $Type
            )
            if ($parameter.ParameterType -eq "integer") {
                $parameter.ParameterType = $ParameterFormat
            }
            $parameter
        }
        
        function Resolve-ParameterReference {
            [CmdletBinding()]
            param (
                [string]
                $Ref,
                
                $SwaggerObject
            )
            
            # "#/components/parameters/top"
            $segments = $Ref | Set-String -OldValue '^#/' | Split-String -Separator '/'
            $paramValue = $SwaggerObject
            foreach ($segment in $segments) {
                $paramValue = $paramValue.$segment
            }
            $paramValue
        }
        #endregion Functions

        $commands = @{ }
        $overrides = @{ }
        if ($TransformPath) {
            foreach ($file in Get-ChildItem -Path $TransformPath -Filter *.psd1) {
                $data = Import-PSFPowerShellDataFile -Path $file.FullName
                foreach ($key in $data.Keys) {
                    $overrides[$key] = $data.$key
                }
            }
        }

        $verbs = @{
            get    = "Get"
            put    = "New"
            post   = "Set"
            patch  = "Set"
            delete = "Remove"
        }
    }
    process {
        #region Process Swagger file
        foreach ($file in Resolve-PSFPath -Path $Path) {
            $data = Get-Content -Path $file | ConvertFrom-Json
            foreach ($endpoint in $data.paths.PSObject.Properties | Sort-Object { $_.Name.Length }, Name) {
                $endpointPath = ($endpoint.Name -replace "^$PathPrefix" -replace '/{[\w\s\d+-]+}$').Trim("/")
                $effectiveEndpointPath = ($endpoint.Name -replace "^$PathPrefix" -replace '\s' ).Trim("/")
                foreach ($method in $endpoint.Value.PSObject.Properties) {
                    $commandKey = $endpointPath, $method.Name -join ":"
                    Write-PSFMessage "Processing Command: $($commandKey)"
                    #region Case: Existing Command
                    if ($commands[$commandKey]) {
                        $commandObject = $commands[$commandKey]
                        $parameterSetName = $method.Value.operationId
                        $commandObject.ParameterSets[$parameterSetName] = $method.Value.description

                        #region Parameters
                        foreach ($parameter in $method.Value.parameters) {
                            if ($parameter.'$ref') {
                                $parameter = Resolve-ParameterReference -Ref $parameter.'$ref' -SwaggerObject $data
                                if (-not $parameter) {
                                    Write-PSFMessage -Level Warning -Message " Unable to resolve referenced parameter $($parameter.'$ref')"
                                    continue
                                }
                            }
                            
                            Write-PSFMessage " Processing Parameter: $($parameter.Name) ($($parameter.in))"
                            switch ($parameter.in) {
                                #region Body
                                body {
                                    foreach ($property in $parameter.schema.properties.PSObject.Properties) {
                                        if ($commandObject.Parameters[$property.Value.title]) {
                                            $commandObject.Parameters[$property.Value.title].ParameterSet += @($parameterSetName)
                                            continue
                                        }

                                        $parameterParam = @{
                                            Name            = $property.Value.title
                                            Help            = $property.Value.description
                                            ParameterType   = $property.Value.type
                                            ParameterFormat = $property.Value.format
                                            Mandatory       = $parameter.schema.required -contains $property.Value.title
                                            Type            = 'Body'
                                        }
                                        $commandObject.Parameters[$property.Value.title] = New-Parameter @parameterParam
                                        $commandObject.Parameters[$property.Value.title].ParameterSet = @($parameterSetName)
                                    }
                                }
                                #endregion Body

                                #region Path
                                path {
                                    if ($commandObject.Parameters[($parameter.name -replace '\s')]) {
                                        $commandObject.Parameters[($parameter.name -replace '\s')].ParameterSet += @($parameterSetName)
                                        continue
                                    }

                                    $parameterParam = @{
                                        Name            = $parameter.Name -replace '\s'
                                        Help            = $parameter.Description
                                        ParameterType   = 'string'
                                        ParameterFormat = $parameter.format
                                        Mandatory       = $parameter.required -as [bool]
                                        Type            = 'Path'
                                    }
                                    $commandObject.Parameters[($parameter.name -replace '\s')] = New-Parameter @parameterParam
                                    $commandObject.Parameters[($parameter.name -replace '\s')].ParameterSet = @($parameterSetName)
                                }
                                #endregion Path

                                #region Query
                                query {
                                    if ($commandObject.Parameters[$parameter.name]) {
                                        $commandObject.Parameters[$parameter.name].ParameterSet += @($parameterSetName)
                                        continue
                                    }
                                    
                                    $parameterType = $parameter.type
                                    if (-not $parameterType -and $parameter.schema.type) {
                                        $parameterType = $parameter.schema.type
                                        if ($parameter.schema.type -eq "array" -and $parameter.schema.items.type) {
                                            $parameterType = '{0}[]' -f $parameter.schema.items.type
                                        }
                                    }
                                    $parameterParam = @{
                                        Name            = $parameter.Name
                                        Help            = $parameter.Description
                                        ParameterType   = $parameterType
                                        ParameterFormat = $parameter.format
                                        Mandatory       = $parameter.required -as [bool]
                                        Type            = 'Query'
                                    }
                                    $commandObject.Parameters[$parameter.name] = New-Parameter @parameterParam
                                    $commandObject.Parameters[$parameter.name].ParameterSet = @($parameterSetName)
                                }
                                #endregion Query
                            }
                        }
                        #endregion Parameters
                    }
                    #endregion Case: Existing Command

                    #region Case: New Command
                    else {
                        $commandNouns = foreach ($element in $endpointPath -split "/") {
                            if ($element -like "{*}") { continue }
                            [cultureinfo]::CurrentUICulture.TextInfo.ToTitleCase($element) -replace 's$' -replace '\$'
                        }
                        $commandObject = [Command]@{
                            Name          = "$($verbs[$method.Name])-$($ModulePrefix)$($commandNouns -join '')"
                            Synopsis      = $method.Value.summary
                            Description   = $method.Value.description
                            Method        = $method.Name
                            EndpointUrl   = $effectiveEndpointPath
                            RestCommand   = $RestCommand
                            ParameterSets = @{
                                'default' = $method.Value.description
                            }
                        }
                        if ($ServiceName) { $commandObject.ServiceName = $ServiceName }
                        $commands[$commandKey] = $commandObject

                        foreach ($property in $commands[$commandKey].PSObject.Properties) {
                            if ($property.Name -eq 'Parameters') { continue }
                            if ($overrides.$commandKey.$($property.Name)) { $commandObject.$($property.Name) = $overrides.$commandKey.$($property.Name) }
                        }

                        #region Parameters
                        foreach ($parameter in $method.Value.parameters) {
                            if ($parameter.'$ref') {
                                $parameter = Resolve-ParameterReference -Ref $parameter.'$ref' -SwaggerObject $data
                                if (-not $parameter) {
                                    Write-PSFMessage -Level Warning -Message " Unable to resolve referenced parameter $($parameter.'$ref')"
                                    continue
                                }
                            }
                            
                            Write-PSFMessage " Processing Parameter: $($parameter.Name) ($($parameter.in))"
                            switch ($parameter.in) {
                                #region Body
                                body {
                                    foreach ($property in $parameter.schema.properties.PSObject.Properties) {
                                        $parameterParam = @{
                                            Name            = $property.Value.title
                                            Help            = $property.Value.description
                                            ParameterType   = $property.Value.type
                                            ParameterFormat = $property.Value.format
                                            Mandatory       = $parameter.schema.required -contains $property.Value.title
                                            Type            = 'Body'
                                        }
                                        $commandObject.Parameters[$property.Value.title] = New-Parameter @parameterParam
                                    }
                                }
                                #endregion Body

                                #region Path
                                path {
                                    $parameterParam = @{
                                        Name            = $parameter.Name -replace '\s'
                                        Help            = $parameter.Description
                                        ParameterType   = 'string'
                                        ParameterFormat = $parameter.format
                                        Mandatory       = $parameter.required -as [bool]
                                        Type            = 'Path'
                                    }
                                    $commandObject.Parameters[($parameter.name -replace '\s')] = New-Parameter @parameterParam
                                }
                                #endregion Path

                                #region Query
                                query {
                                    $parameterType = $parameter.type
                                    if (-not $parameterType -and $parameter.schema.type) {
                                        $parameterType = $parameter.schema.type
                                        if ($parameter.schema.type -eq "array" -and $parameter.schema.items.type) {
                                            $parameterType = '{0}[]' -f $parameter.schema.items.type
                                        }
                                    }
                                    
                                    $parameterParam = @{
                                        Name            = $parameter.Name
                                        Help            = $parameter.Description
                                        ParameterType   = $parameterType
                                        ParameterFormat = $parameter.format
                                        Mandatory       = $parameter.required -as [bool]
                                        Type            = 'Query'
                                    }
                                    $commandObject.Parameters[$parameter.name] = New-Parameter @parameterParam
                                }
                                #endregion Query
                            }
                        }
                        #endregion Parameters

                        #region Parameter Overrides
                        foreach ($parameterName in $overrides.globalParameters.Keys) {
                            if (-not $commandObject.Parameters[$parameterName]) { continue }

                            Copy-ParameterConfig -Config $overrides.globalParameters[$parameterName] -Parameter $commandObject.Parameters[$parameterName]
                        }
                        foreach ($partialPath in $overrides.scopedParameters.Keys) {
                            if ($effectiveEndpointPath -notlike $partialPath) { continue }
                            foreach ($parameterPair in $overrides.scopedParameters.$($partialPath).GetEnumerator()) {
                                if (-not $commandObject.Parameters[$parameterPair.Name]) { continue }

                                Copy-ParameterConfig -Parameter $commandObject.Parameters[$parameterPair.Name] -Config $parameterPair.Value
                            }
                        }
                        foreach ($parameterName in $overrides.$commandKey.Parameters.Keys) {
                            if (-not $commandObject.Parameters[$parameterName]) {
                                Write-PSFMessage -Level Warning -Message "Invalid override parameter: $parameterName - unable to find parameter on $($commandObject.Name)" -Target $commandObject
                                continue
                            }
                            
                            Copy-ParameterConfig -Config $overrides.$commandKey.Parameters[$parameterName] -Parameter $commandObject.Parameters[$parameterName]
                        }
                        #endregion Parameter Overrides
                    }
                    #endregion Case: New Command

                    Write-PSFMessage -Message "Finished processing $($endpointPath) : $($method.Name) --> $($commandObject.Name)" -Target $commandObject -Data @{
                        Overrides     = $overrides
                        CommandObject = $commandObject
                    } -Tag done
                }
            }
        }
        #endregion Process Swagger file
    }
    end {
        $commands.Values
    }
}