Public/New-OpenApiModule.ps1

<#
.SYNOPSIS
    Generates a PowerShell module of wrapper commands from an OpenAPI 3.0/3.1 or Swagger 2.0 document.

.DESCRIPTION
    New-OpenApiModule turns an OpenAPI document into a module with one command per operation. Each
    command has PowerShell parameters for the operation's path, query, header and cookie parameters
    and for the properties of a JSON request body (or -Body), comment-based help, and calls
    Invoke-OpenApiRequest from tcs.openapi, which handles authentication, serialisation, retry,
    paging and errors. The generated module requires tcs.openapi, so fixes to the request engine
    reach it without regenerating.

    The module is written to <OutputPath>/<ModuleName>:
      <ModuleName>.psd1 / .psm1 manifest and root module
      OpenApi/operations.json operation metadata used by the request engine
      OpenApi/source.json the normalised document (for regeneration diffs)
      Public/<Tag>/<Verb>-<Noun>.ps1 one command per operation
      Public/_Connection/ Set-, Get- and Remove-<Prefix>Context
      Overrides.ps1 created once, never overwritten: your own changes go here
      README.md the list of commands

    Existing generated files are replaced only with -Force; Overrides.ps1 is never replaced. The same
    document and options always give byte-identical files. Problems found in the document and by the
    generator (renamed commands OA040, commands renamed so they do not shadow a core PowerShell
    command OA042, renamed parameters OA041, skipped operations OA070) are
    returned in Findings.

.PARAMETER Path
    The path of the OpenAPI document (JSON, or YAML when powershell-yaml is installed). Read with
    Import-OpenApiDocument.

.PARAMETER Uri
    The URL of the OpenAPI document. Read with Import-OpenApiDocument.

.PARAMETER Document
    A document model returned by Import-OpenApiDocument.

.PARAMETER ModuleName
    The name of the module to create (letters, digits, '.', '_' and '-', starting with a letter). It is
    also the service name of the connection context.

.PARAMETER OutputPath
    The folder in which the module folder is created.

.PARAMETER NounPrefix
    A prefix for every command noun: with 'PetStore', operation getOrder becomes Get-PetStoreOrder.
    The connection commands are Set-/Get-/Remove-<NounPrefix>Context. Without it, the connection commands
    use the PascalCase module name.

    Set it. There is no default prefix, so without one the nouns come straight from the document
    (getItem -> Get-Item) and easily clash with other modules. A name that would shadow a core
    PowerShell command (Get-Item, New-Item, Get-Content, ...) is never generated: it gets the PascalCase
    module name as its prefix instead (Get-<ModuleName>Item) and an OA042 warning finding.

.PARAMETER UnwrapProperty
    The name of a response property that wraps the actual result, such as 'data' for APIs that answer
    { "data": {...}, "traceId": "..." }. For every operation whose first 2xx JSON response schema is an
    object with this property, the command outputs the value of the property instead of the whole
    response (array values item by item), typed with the property's schema name when it has one. It is
    applied only when a response actually has the property, never with -Raw, and not to pageable
    operations, which output the items of each page already. Stored per operation in
    OpenApi/operations.json (UnwrapProperty).

.PARAMETER ModuleVersion
    The version of the generated module. Defaults to 0.1.0.

.PARAMETER Author
    The author written to the manifest. Defaults to 'tcs.openapi'.

.PARAMETER Force
    Replaces generated files that already exist and differ, and removes generated command files for
    operations that are no longer in the document. Overrides.ps1 is never replaced.

.PARAMETER WhatIf
    Shows what would be written without writing anything.

.PARAMETER Confirm
    Asks for confirmation before each file is written.

.INPUTS
    System.Management.Automation.PSObject
    You can pipe a document model from Import-OpenApiDocument.

.OUTPUTS
    Tcs.OpenApi.GenerationResult
    { ModuleName, Path, ManifestPath, Functions (Name, OperationId, File, Action), Findings, Skipped,
    Files (every file with its action) }.

.EXAMPLE
    New-OpenApiModule -Path ./petstore.json -ModuleName PetStore -OutputPath ./out -NounPrefix PetStore

    Generates ./out/PetStore with commands such as Get-PetStorePet.

.EXAMPLE
    $document = Import-OpenApiDocument -Uri 'https://api.example.com/openapi.json'
    $result = New-OpenApiModule -Document $document -ModuleName Example -NounPrefix Ex -OutputPath ./out -Force
    $result.Findings | Where-Object Severity -NE 'Information'

    Regenerates a module from a downloaded document and lists the warnings and errors.

.EXAMPLE
    New-OpenApiModule -Path ./sitemanager.json -ModuleName UniFi.SiteManager -NounPrefix UniFi -UnwrapProperty data -OutputPath ./out

    Generates a module whose commands return the 'data' property of the API's responses (Get-UniFiHostById
    returns the host, not the { data, httpStatusCode, traceId } envelope).

.EXAMPLE
    New-OpenApiModule -Path ./api.json -ModuleName Example -NounPrefix Ex -OutputPath ./out -WhatIf

    Shows the files that would be written.

.NOTES
    Author: Nigel Tatschner
    Company: TheCodeSaiyan

    The generated code runs on Windows PowerShell 5.1 and PowerShell 7.

.LINK
    Import-OpenApiDocument

.LINK
    Invoke-OpenApiRequest
#>

function New-OpenApiModule {
    [CmdletBinding(DefaultParameterSetName = 'Document', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType('Tcs.OpenApi.GenerationResult')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Path')]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory = $true, ParameterSetName = 'Uri')]
        [ValidateNotNullOrEmpty()]
        [uri]$Uri,

        [Parameter(Mandatory = $true, ParameterSetName = 'Document', ValueFromPipeline = $true)]
        [ValidateNotNull()]
        [psobject]$Document,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[A-Za-z][A-Za-z0-9._-]*$')]
        [string]$ModuleName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$OutputPath,

        [Parameter()]
        [ValidatePattern('^[A-Za-z][A-Za-z0-9]*$')]
        [string]$NounPrefix,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$UnwrapProperty,

        [Parameter()]
        [version]$ModuleVersion,

        [Parameter()]
        [string]$Author,

        [Parameter()]
        [switch]$Force
    )

    process {
        $parameterSetName = $PSCmdlet.ParameterSetName
        Invoke-TcsCommand -ScriptBlock {
            if ($parameterSetName -ne 'Document') {
                $importCommand = Get-Command -Name 'Import-OpenApiDocument' -CommandType Function, Cmdlet -ErrorAction SilentlyContinue
                if ($null -eq $importCommand) {
                    throw 'New-OpenApiModule -Path and -Uri need Import-OpenApiDocument, which is not available. Import the document yourself and pass it with -Document.'
                }
                if ($parameterSetName -eq 'Path') {
                    $Document = & $importCommand -Path $Path
                }
                else {
                    $Document = & $importCommand -Uri $Uri
                }
            }
            if ($null -eq $Document.Operations -and $null -eq $Document.PSObject.Properties['Operations']) {
                throw 'The document is not an OpenAPI document model: it has no Operations. Use Import-OpenApiDocument to read the document.'
            }

            $moduleRoot = Split-Path -Path $PSScriptRoot -Parent
            $option = Resolve-OpenApiGenOption -ModuleName $ModuleName -OutputPath ($PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutputPath)) -NounPrefix $NounPrefix -UnwrapProperty $UnwrapProperty -ModuleVersion $ModuleVersion -Author $Author -GeneratorVersion (Get-OpenApiGenVersion -ModuleRoot $moduleRoot)
            $templates = Get-OpenApiGenTemplate -Path (Join-Path -Path $moduleRoot -ChildPath 'Templates')
            $plan = New-OpenApiGenPlan -Document $Document -Option $option -Template $templates
            $written = @(Write-OpenApiGenPlan -Plan $plan -Force:$Force -WhatIf:$WhatIfPreference)

            $actions = @{}
            foreach ($file in $written) {
                $actions[$file.RelativePath] = $file.Action
            }
            $functions = @(foreach ($function in $plan.Functions) {
                    [pscustomobject]@{
                        PSTypeName  = 'Tcs.OpenApi.GeneratedFunction'
                        Name        = $function.Name
                        OperationId = $function.OperationId
                        File        = $function.File
                        Action      = $actions[$function.File]
                    }
                })
            [pscustomobject]@{
                PSTypeName   = 'Tcs.OpenApi.GenerationResult'
                ModuleName   = $plan.ModuleName
                Path         = $plan.ModulePath
                ManifestPath = $plan.ManifestPath
                Functions    = $functions
                Findings     = @(@($Document.Findings) + @($plan.Findings) | Where-Object -FilterScript { $null -ne $_ })
                Skipped      = @($plan.Skipped)
                Files        = $written
            }
        }
    }
}