Private/Invoke-MyWebApiRequest.ps1
|
function Invoke-MyWebApiRequest { # Single choke point for every REST cmdlet: auth, URL build, envelope unwrap, paging. [CmdletBinding()] param( [Parameter(Mandatory)][string] $Method, [Parameter(Mandatory)][string] $Path, [hashtable] $Query, [object] $Body, [string] $TradePlatform, [Nullable[guid]] $CacheId, [int] $CacheTimeout, [string] $IdempotencyKey, [switch] $All ) if (-not $script:MyWebApiContext) { throw 'Not connected. Call Connect-MyWebApi first.' } $ctx = $script:MyWebApiContext # * Safe property read for the v2 envelope. Under `Set-StrictMode -Version Latest` # a direct `$resp.error` throws PropertyNotFoundException when the server OMITS # a null field (e.g. success responses carry no `error` key). Reading via # PSObject.Properties returns $null for an absent property instead of throwing. function Get-EnvProp($obj, $name) { if ($null -eq $obj) { return $null } $p = $obj.PSObject.Properties[$name] if ($p) { return $p.Value } return $null } # Resolve {tradePlatform} from arg or session default. if ($Path -like '*{tradePlatform}*') { $tp = if ($TradePlatform) { $TradePlatform } else { $ctx.DefaultTradePlatform } if (-not $tp) { throw 'No trade platform: pass -TradePlatform or set -DefaultTradePlatform on Connect-MyWebApi.' } $Path = $Path.Replace('{tradePlatform}', [uri]::EscapeDataString($tp)) } $token = Get-MyWebApiToken $headers = @{ Authorization = "Bearer $token"; Accept = 'application/json' } if ($IdempotencyKey) { $headers['Idempotency-Key'] = $IdempotencyKey } $q = @{} if ($Query) { foreach ($k in $Query.Keys) { if ($null -ne $Query[$k]) { $q[$k] = $Query[$k] } } } if ($CacheId) { $q['cacheId'] = $CacheId.ToString() } if ($CacheTimeout) { $q['cacheTimeout'] = $CacheTimeout } $accumulated = [System.Collections.Generic.List[object]]::new() $cursor = $null do { if ($All -and $cursor) { $q['cursor'] = $cursor } # * Array/collection query values (e.g. -Logins 1,2) must serialize as repeated keys # (logins=1&logins=2), matching ASP.NET Core's default query-string binding for array # parameters. Scalars keep the single "key=value" form. $qsParts = [System.Collections.Generic.List[string]]::new() foreach ($kv in $q.GetEnumerator()) { foreach ($v in @($kv.Value)) { $qsParts.Add(("{0}={1}" -f $kv.Key, [uri]::EscapeDataString([string]$v))) } } $qs = $qsParts -join '&' $uri = "$($ctx.BaseUrl)$Path" if ($qs) { $uri = "$uri`?$qs" } $irmArgs = @{ Method = $Method; Uri = $uri; Headers = $headers } if ($null -ne $Body) { $irmArgs.Body = ($Body | ConvertTo-Json -Depth 20); $irmArgs.ContentType = 'application/json' } $resp = Invoke-RestMethod @irmArgs $errObj = Get-EnvProp $resp 'error' if ($errObj) { $metaObj = Get-EnvProp $resp 'meta' $activityId = Get-EnvProp $metaObj 'activityId' $code = Get-EnvProp $errObj 'code' $errMessage = Get-EnvProp $errObj 'message' $managerCode = Get-EnvProp $errObj 'managerCode' $msg = if ($errMessage) { $errMessage } else { "v2 error: $code" } $rec = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("$msg (code=$code; activityId=$activityId; managerCode=$managerCode)"), "MyWebApiError,$code", [System.Management.Automation.ErrorCategory]::InvalidOperation, $null) $PSCmdlet.ThrowTerminatingError($rec) } $dataObj = Get-EnvProp $resp 'data' if ($All) { if ($null -ne $dataObj) { foreach ($item in @($dataObj)) { $accumulated.Add($item) } } $metaObj = Get-EnvProp $resp 'meta' $paging = Get-EnvProp $metaObj 'paging' $cursor = Get-EnvProp $paging 'nextCursor' $more = [bool]($paging -and (Get-EnvProp $paging 'hasMore') -and $cursor) } else { return $dataObj } } while ($All -and $more) return $accumulated.ToArray() } |