Private/Generator/Get-OpenApiGenParameterType.ps1

function Get-OpenApiGenParameterType {
    <#
    .SYNOPSIS
        Maps a schema to a PowerShell parameter type, validation attributes and an example value.

    .DESCRIPTION
        string -> string (date-time -> datetime, binary -> object), integer -> int (int32) or long,
        number -> double, boolean -> switch when optional and not in the path, otherwise bool,
        array -> element type[], object -> hashtable, anything else -> object. Nullable schemas get
        [AllowNull()]. enum -> [ValidateSet()] (array items too), pattern -> [ValidatePattern()]
        (case-sensitive; skipped when it is not a valid .NET pattern), minimum/maximum ->
        [ValidateRange()] (exclusiveMinimum/exclusiveMaximum as a flag or as the bound), minLength/maxLength -> [ValidateLength()], minItems/maxItems ->
        [ValidateCount()].
        A schema stub without a type (RefName, no properties) is looked up in -Schemas first.
        Returns { TypeName, IsSwitch, Attributes (attribute source lines), ExampleText (PowerShell
        source for a sample value) }.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter()]
        [AllowNull()]
        [object]$Schema,

        [Parameter(Mandatory = $true)]
        [ValidateSet('path', 'query', 'header', 'cookie', 'body')]
        [string]$In,

        [Parameter()]
        [switch]$Required,

        [Parameter()]
        [AllowNull()]
        [object]$Example,

        [Parameter()]
        [AllowNull()]
        [object]$Schemas
    )

    $invariant = [System.Globalization.CultureInfo]::InvariantCulture
    # A stub without a type needs the named schema to find out what it is
    $nullable = ($null -ne $Schema -and $Schema.Nullable -eq $true)
    if ($null -ne $Schema -and [string]::IsNullOrEmpty([string]$Schema.Type)) {
        $Schema = Resolve-OpenApiGenSchema -Schema $Schema -Schemas $Schemas
    }
    $scalarType = {
        param($ItemSchema)
        if ($null -eq $ItemSchema) {
            return 'object'
        }
        $oneOf = @($ItemSchema.OneOf) + @($ItemSchema.AnyOf) | Where-Object -FilterScript { $null -ne $_ }
        if (@($oneOf).Count -gt 0) {
            return 'object'
        }
        switch ([string]$ItemSchema.Type) {
            'string' {
                if ($ItemSchema.Format -eq 'date-time') { return 'datetime' }
                if ($ItemSchema.Format -eq 'binary') { return 'object' }
                return 'string'
            }
            'integer' {
                if ($ItemSchema.Format -eq 'int32') { return 'int' }
                return 'long'
            }
            'number' { return 'double' }
            'boolean' { return 'bool' }
            'object' { return 'hashtable' }
            'array' { return 'array' }
        }
        if (@(Get-OpenApiGenMapEntry -Map $ItemSchema.Properties).Count -gt 0) {
            return 'hashtable'
        }
        return 'object'
    }

    $baseType = & $scalarType $Schema
    $valueSchema = $Schema
    $typeName = $baseType
    if ($baseType -eq 'array') {
        $valueSchema = $Schema.Items
        if ($null -ne $valueSchema -and [string]::IsNullOrEmpty([string]$valueSchema.Type)) {
            $valueSchema = Resolve-OpenApiGenSchema -Schema $valueSchema -Schemas $Schemas
        }
        $elementType = & $scalarType $valueSchema
        if ($elementType -eq 'array') {
            $typeName = 'object[]'
            $valueSchema = $null
        }
        else {
            $typeName = $elementType + '[]'
        }
    }
    $elementName = $typeName.TrimEnd('[', ']')
    $isSwitch = ($typeName -eq 'bool') -and (-not $Required) -and ($In -ne 'path')
    if ($isSwitch) {
        $typeName = 'switch'
    }

    $attributes = New-Object -TypeName System.Collections.ArrayList
    if (($nullable -or ($null -ne $Schema -and $Schema.Nullable -eq $true)) -and -not $isSwitch) {
        [void]$attributes.Add('[AllowNull()]')
    }

    if ($null -ne $valueSchema -and -not $isSwitch -and $elementName -ne 'bool') {
        # enum
        $enumValues = @($valueSchema.Enum | Where-Object -FilterScript { $null -ne $_ -and -not ($_ -is [bool]) })
        if ($enumValues.Count -gt 0 -and @('string', 'int', 'long', 'double') -contains $elementName) {
            $literals = @()
            foreach ($value in $enumValues) {
                $text = [string]$value
                if ($value -is [System.IFormattable]) {
                    $text = $value.ToString($null, $invariant)
                }
                $literal = ConvertTo-OpenApiGenLiteral -Value $text
                if ($literals -notcontains $literal) {
                    $literals += $literal
                }
            }
            [void]$attributes.Add('[ValidateSet(' + ($literals -join ', ') + ')]')
        }
        elseif ($elementName -eq 'string') {
            # pattern and length
            if (-not [string]::IsNullOrEmpty([string]$valueSchema.Pattern)) {
                $validPattern = $true
                try {
                    [void](New-Object -TypeName System.Text.RegularExpressions.Regex -ArgumentList ([string]$valueSchema.Pattern))
                }
                catch {
                    $validPattern = $false
                }
                if ($validPattern) {
                    [void]$attributes.Add('[ValidatePattern(' + (ConvertTo-OpenApiGenLiteral -Value ([string]$valueSchema.Pattern)) + ', Options = ''None'')]')
                }
            }
            if ($null -ne $valueSchema.MinLength -or $null -ne $valueSchema.MaxLength) {
                $minLength = 0
                $maxLength = [int]::MaxValue
                if ($null -ne $valueSchema.MinLength) { $minLength = [int][math]::Min([double]$valueSchema.MinLength, [int]::MaxValue) }
                if ($null -ne $valueSchema.MaxLength) { $maxLength = [int][math]::Min([double]$valueSchema.MaxLength, [int]::MaxValue) }
                if ($minLength -le $maxLength -and $maxLength -gt 0) {
                    [void]$attributes.Add('[ValidateLength(' + $minLength.ToString($invariant) + ', ' + $maxLength.ToString($invariant) + ')]')
                }
            }
        }
        $hasBound = ($null -ne $valueSchema.Minimum -or $null -ne $valueSchema.Maximum -or ($null -ne $valueSchema.ExclusiveMinimum -and -not ($valueSchema.ExclusiveMinimum -is [bool])) -or ($null -ne $valueSchema.ExclusiveMaximum -and -not ($valueSchema.ExclusiveMaximum -is [bool])))
        if (@('int', 'long', 'double') -contains $elementName -and $hasBound -and $enumValues.Count -eq 0) {
            if ($elementName -eq 'double') {
                $low = -1.7976931348623157E+308
                $high = 1.7976931348623157E+308
                foreach ($bound in @($valueSchema.Minimum, $valueSchema.ExclusiveMinimum)) {
                    if ($null -ne $bound -and -not ($bound -is [bool])) { $low = [double]$bound }
                }
                foreach ($bound in @($valueSchema.Maximum, $valueSchema.ExclusiveMaximum)) {
                    if ($null -ne $bound -and -not ($bound -is [bool])) { $high = [double]$bound }
                }
                $format = {
                    param([double]$Number)
                    $text = $Number.ToString('R', $invariant)
                    if ($text -notmatch '[.E]') {
                        $text += '.0'
                    }
                    return $text
                }
                if ($low -le $high) {
                    [void]$attributes.Add('[ValidateRange(' + (& $format $low) + ', ' + (& $format $high) + ')]')
                }
            }
            else {
                # Same-typed literals: int, or long with the 'l' suffix
                $limit = [double][long]::MaxValue
                $suffix = 'l'
                if ($elementName -eq 'int') {
                    $limit = [double][int]::MaxValue
                    $suffix = ''
                }
                $low = - $limit
                $high = $limit
                # exclusiveMinimum/Maximum: a flag (OAS 3.0) or the bound itself (OAS 3.1)
                $minimum = $valueSchema.Minimum
                $minimumExclusive = ($valueSchema.ExclusiveMinimum -is [bool]) -and $valueSchema.ExclusiveMinimum
                if ($null -ne $valueSchema.ExclusiveMinimum -and -not ($valueSchema.ExclusiveMinimum -is [bool])) {
                    $minimum = $valueSchema.ExclusiveMinimum
                    $minimumExclusive = $true
                }
                $maximum = $valueSchema.Maximum
                $maximumExclusive = ($valueSchema.ExclusiveMaximum -is [bool]) -and $valueSchema.ExclusiveMaximum
                if ($null -ne $valueSchema.ExclusiveMaximum -and -not ($valueSchema.ExclusiveMaximum -is [bool])) {
                    $maximum = $valueSchema.ExclusiveMaximum
                    $maximumExclusive = $true
                }
                if ($null -ne $minimum) {
                    $low = [math]::Ceiling([double]$minimum)
                    if ($minimumExclusive -and $low -eq [double]$minimum) { $low++ }
                }
                if ($null -ne $maximum) {
                    $high = [math]::Floor([double]$maximum)
                    if ($maximumExclusive -and $high -eq [double]$maximum) { $high-- }
                }
                $low = [math]::Max($low, - $limit)
                $high = [math]::Min($high, $limit)
                $format = {
                    param([double]$Number)
                    if ($Number -ge [double][long]::MaxValue) {
                        return '9223372036854775807' + $suffix
                    }
                    if ($Number -le - [double][long]::MaxValue) {
                        return '-9223372036854775807' + $suffix
                    }
                    return ([long]$Number).ToString($invariant) + $suffix
                }
                if ($low -le $high) {
                    [void]$attributes.Add('[ValidateRange(' + (& $format $low) + ', ' + (& $format $high) + ')]')
                }
            }
        }
    }
    if ($baseType -eq 'array' -and ($null -ne $Schema.MinItems -or $null -ne $Schema.MaxItems)) {
        $minItems = 0
        $maxItems = [int]::MaxValue
        if ($null -ne $Schema.MinItems) { $minItems = [int][math]::Min([double]$Schema.MinItems, [int]::MaxValue) }
        if ($null -ne $Schema.MaxItems) { $maxItems = [int][math]::Min([double]$Schema.MaxItems, [int]::MaxValue) }
        if ($minItems -le $maxItems -and $maxItems -gt 0) {
            [void]$attributes.Add('[ValidateCount(' + $minItems.ToString($invariant) + ', ' + $maxItems.ToString($invariant) + ')]')
        }
    }

    # Example value, as PowerShell source
    $sample = $Example
    if ($null -eq $sample -and $null -ne $Schema -and $null -ne $Schema.Example -and $typeName -notlike '*[[]]') {
        $sample = $Schema.Example
    }
    if ($null -eq $sample -and $null -ne $valueSchema) {
        $enumSample = @($valueSchema.Enum | Where-Object -FilterScript { $null -ne $_ })
        if ($enumSample.Count -gt 0) {
            $sample = $enumSample[0]
        }
        elseif ($null -ne $valueSchema.Default) {
            $sample = $valueSchema.Default
        }
    }
    if ($sample -is [System.Collections.IEnumerable] -and -not ($sample -is [string])) {
        $sample = $null
    }
    $exampleText = $null
    switch ($elementName) {
        'string' {
            $text = 'example'
            if ($null -ne $sample -and -not ($sample -is [System.Collections.IDictionary]) -and -not ($sample -is [System.Management.Automation.PSCustomObject])) {
                $text = [string]$sample
                if ($sample -is [System.IFormattable]) { $text = $sample.ToString($null, $invariant) }
            }
            $exampleText = ConvertTo-OpenApiGenLiteral -Value $text
        }
        'datetime' { $exampleText = '(Get-Date)' }
        { @('int', 'long', 'double') -contains $_ } {
            $number = 1
            if ($null -ne $sample) {
                $parsed = 0.0
                if ([double]::TryParse([string]$sample, [System.Globalization.NumberStyles]::Float, $invariant, [ref]$parsed)) {
                    $number = $parsed
                }
            }
            $exampleText = ([double]$number).ToString('R', $invariant)
        }
        'bool' { $exampleText = '$true' }
        'hashtable' { $exampleText = '@{}' }
        default { $exampleText = "'example'" }
    }
    if ($typeName.EndsWith('[]')) {
        $exampleText = '@(' + $exampleText + ')'
    }
    if ($isSwitch) {
        $exampleText = $null
    }

    return [pscustomobject]@{
        TypeName    = $typeName
        IsSwitch    = $isSwitch
        Attributes  = $attributes.ToArray()
        ExampleText = $exampleText
    }
}