Private/Document/Get-OpenApiPaging.ps1

function Get-OpenApiPaging {
    <#
    .SYNOPSIS
        Works out how an operation pages: { Kind ('nextLink'|'linkHeader'), ItemsProperty, NextLinkProperty },
        { Kind 'token', ItemsProperty, TokenParameter, TokenProperty } or $null.
    .DESCRIPTION
        1. x-ms-pageable: nextLinkName (null means one page only, so no paging) and itemName (default 'value').
        2. The first 2xx JSON response (200 first) is an object with exactly one array property and a string
           property named nextLink, next or @odata.nextLink (any case) -> 'nextLink'.
        3. The operation has a query parameter named nextToken, pageToken, next_token, page_token, cursor,
           continuationToken or continuation_token (any case) and that first 2xx JSON response is an object
           with exactly one array property and a string property named like the parameter, or nextToken,
           nextPageToken, next_page_token, next_token, next_cursor or nextCursor (any case) -> 'token'.
        4. A 2xx response declares a 'Link' header -> 'linkHeader' (ItemsProperty is the single array
           property of an object body, or $null when the body is the array).
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [System.Collections.IDictionary]$Operation,

        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]]$Responses,

        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [object[]]$Parameters
    )

    if ($Operation.Contains('x-ms-pageable')) {
        $pageable = $Operation['x-ms-pageable']
        if ($pageable -isnot [System.Collections.IDictionary] -or $null -eq $pageable['nextLinkName']) {
            return $null
        }
        $itemName = 'value'
        if ($null -ne $pageable['itemName']) {
            $itemName = [string]$pageable['itemName']
        }
        return [pscustomobject]@{
            PSTypeName       = 'Tcs.OpenApi.Paging'
            Kind             = 'nextLink'
            ItemsProperty    = $itemName
            NextLinkProperty = [string]$pageable['nextLinkName']
        }
    }

    # Success responses, 200 first (foreach loops rather than pipelines: this runs for every operation)
    $success = [System.Collections.Generic.List[object]]::new()
    foreach ($response in $Responses) {
        if ($response.StatusCode -eq '200') {
            $success.Insert(0, $response)
        }
        elseif ($response.StatusCode -match '^2') {
            $success.Add($response)
        }
    }
    $nextLinkNames = @('nextLink', 'next', '@odata.nextLink')
    $tokenParameterNames = @('nextToken', 'pageToken', 'next_token', 'page_token', 'cursor', 'continuationToken', 'continuation_token')
    $tokenPropertyNames = @('nextToken', 'nextPageToken', 'next_page_token', 'next_token', 'next_cursor', 'nextCursor')
    $tokenParameter = $null
    foreach ($parameter in @($Parameters)) {
        if ($null -ne $parameter -and $parameter.In -eq 'query' -and $tokenParameterNames -contains $parameter.Name) {
            $tokenParameter = [string]$parameter.Name
            break
        }
    }
    $arrayProperty = $null
    $bodyIsArray = $false
    foreach ($response in $success) {
        $json = $null
        foreach ($media in $response.Content) {
            if ($media.ContentType -match '^[^/]+/([^/;]+\+)?json\s*(;|$)' -or $media.ContentType -eq '*/*') {
                $json = $media
                break
            }
        }
        if ($null -eq $json -or $null -eq $json.Schema) {
            continue
        }
        $schema = $json.Schema
        if ($schema.Type -eq 'array') {
            $bodyIsArray = $true
            break
        }
        if ($null -eq $schema.Properties) {
            continue
        }
        $arrays = [System.Collections.Generic.List[string]]::new()
        $link = $null
        $tokenProperty = $null
        $tokenRank = [int]::MaxValue
        foreach ($name in $schema.Properties.Keys) {
            $property = $schema.Properties[$name]
            if ($null -eq $property) {
                continue
            }
            $isString = ($property.Type -eq 'string' -or $null -eq $property.Type)
            if ($property.Type -eq 'array') {
                $arrays.Add($name)
            }
            elseif ($null -eq $link -and $nextLinkNames -contains $name -and $isString) {
                $link = $name
            }
            elseif ($null -ne $tokenParameter -and $isString) {
                # The property named like the parameter first, then the usual names in list order
                $rank = [int]::MaxValue
                if ($name -eq $tokenParameter) {
                    $rank = -1
                }
                elseif ($tokenPropertyNames -contains $name) {
                    $rank = [array]::IndexOf(@($tokenPropertyNames | ForEach-Object -Process { $_.ToLowerInvariant() }), $name.ToLowerInvariant())
                }
                if ($rank -lt $tokenRank) {
                    $tokenRank = $rank
                    $tokenProperty = $name
                }
            }
        }
        if ($arrays.Count -eq 1) {
            $arrayProperty = $arrays[0]
            if ($null -ne $link) {
                return [pscustomobject]@{
                    PSTypeName       = 'Tcs.OpenApi.Paging'
                    Kind             = 'nextLink'
                    ItemsProperty    = $arrayProperty
                    NextLinkProperty = $link
                }
            }
            if ($null -ne $tokenProperty) {
                return [pscustomobject]@{
                    PSTypeName     = 'Tcs.OpenApi.Paging'
                    Kind           = 'token'
                    ItemsProperty  = $arrayProperty
                    TokenParameter = $tokenParameter
                    TokenProperty  = $tokenProperty
                }
            }
        }
        break
    }

    foreach ($response in $success) {
        if ($null -eq $response.Headers) {
            continue
        }
        foreach ($name in $response.Headers.Keys) {
            if ($name -eq 'Link') {
                $items = $null
                if (-not $bodyIsArray) {
                    $items = $arrayProperty
                }
                return [pscustomobject]@{
                    PSTypeName       = 'Tcs.OpenApi.Paging'
                    Kind             = 'linkHeader'
                    ItemsProperty    = $items
                    NextLinkProperty = $null
                }
            }
        }
    }
    return $null
}