Private/OpenApi.ps1
function ConvertTo-PodeOAContentTypeSchema { param( [Parameter(ValueFromPipeline=$true)] [hashtable] $Schemas ) if (Test-PodeIsEmpty $Schemas) { return $null } # ensure all content types are valid foreach ($type in $Schemas.Keys) { if ($type -inotmatch '^\w+\/[\w\.\+-]+$') { throw "Invalid content-type found for schema: $($type)" } } # convert each schema to openapi format return (ConvertTo-PodeOAObjectSchema -Schemas $Schemas) } function ConvertTo-PodeOAHeaderSchema { param( [Parameter(ValueFromPipeline=$true)] [hashtable] $Schemas ) if (Test-PodeIsEmpty $Schemas) { return $null } # convert each schema to openapi format return (ConvertTo-PodeOAObjectSchema -Schemas $Schemas) } function ConvertTo-PodeOAObjectSchema { param( [Parameter(ValueFromPipeline=$true)] [hashtable] $Schemas ) # convert each schema to openapi format $obj = @{} foreach ($type in $Schemas.Keys) { $obj[$type] = @{ schema = $null } # add a shared component schema reference if ($Schemas[$type] -is [string]) { if (!(Test-PodeOAComponentSchema -Name $Schemas[$type])) { throw "The OpenApi component schema doesn't exist: $($Schemas[$type])" } $obj[$type].schema = @{ '$ref' = "#/components/schemas/$($Schemas[$type])" } } # add a set schema object else { $obj[$type].schema = ($Schemas[$type] | ConvertTo-PodeOASchemaProperty) } } return $obj } function Test-PodeOAComponentSchema { param( [Parameter(Mandatory=$true)] [string] $Name ) return $PodeContext.Server.OpenAPI.components.schemas.ContainsKey($Name) } function Test-PodeOAComponentResponse { param( [Parameter(Mandatory=$true)] [string] $Name ) return $PodeContext.Server.OpenAPI.components.responses.ContainsKey($Name) } function Test-PodeOAComponentRequestBody { param( [Parameter(Mandatory=$true)] [string] $Name ) return $PodeContext.Server.OpenAPI.components.requestBodies.ContainsKey($Name) } function Test-PodeOAComponentParameter { param( [Parameter(Mandatory=$true)] [string] $Name ) return $PodeContext.Server.OpenAPI.components.parameters.ContainsKey($Name) } function ConvertTo-PodeOASchemaProperty { param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [hashtable] $Property, [switch] $InObject ) # base schema type $schema = @{ type = $Property.type format = $Property.format description = $Property.description default = $Property.default } if ($Property.deprecated) { $schema['deprecated'] = $Property.deprecated } if ($Property.required -and !$InObject) { $schema['required'] = $Property.required } if ($null -ne $Property.meta) { foreach ($key in $Property.meta.Keys) { $schema[$key] = $Property.meta[$key] } } # schema refs if ($Property.type -ieq 'schema') { $schema = @{ '$ref' = "#/components/schemas/$($Property['schema'])" } } # are we using an array? if ($Property.array) { $Property.array = $false $schema = @{ type = 'array' items = ($Property | ConvertTo-PodeOASchemaProperty) } } # are we using an object? if ($Property.object) { $Property.object = $false $schema = @{ type = 'object' properties = (ConvertTo-PodeOASchemaObjectProperty -Properties $Property) } if ($Property.required) { $schema['required'] = @($Property.name) } } if ($Property.type -ieq 'object') { $schema['properties'] = (ConvertTo-PodeOASchemaObjectProperty -Properties $Property.properties) $schema['required'] = @(($Property.properties | Where-Object { $_.required }).name) } return $schema } function ConvertTo-PodeOASchemaObjectProperty { param( [Parameter(Mandatory=$true)] [hashtable[]] $Properties ) $schema = @{} foreach ($prop in $Properties) { $schema[$prop.name] = ($prop | ConvertTo-PodeOASchemaProperty -InObject) } return $schema } function Get-PodeOpenApiDefinitionInternal { param( [Parameter(Mandatory=$true)] [string] $Title, [Parameter()] [string] $Version, [Parameter()] [string] $Description, [Parameter()] [string] $RouteFilter, [Parameter()] [string] $Protocol, [Parameter()] [string] $Address, [Parameter()] [string] $EndpointName, [switch] $RestrictRoutes ) # set the openapi version $def = @{ openapi = '3.0.2' } # metadata $def['info'] = @{ title = $Title version = $Version description = $Description } # servers $def['servers'] = $null if (!$RestrictRoutes -and ($PodeContext.Server.Endpoints.Count -gt 1)) { $def.servers = @(foreach ($endpoint in $PodeContext.Server.Endpoints.Values) { @{ url = $endpoint.Url description = (Protect-PodeValue -Value $endpoint.Description -Default $endpoint.Name) } }) } # components $def['components'] = $PodeContext.Server.OpenAPI.components # auth/security components if ($PodeContext.Server.Authentications.Count -gt 0) { if ($null -eq $def.components.securitySchemes) { $def.components.securitySchemes = @{} } foreach ($authName in $PodeContext.Server.Authentications.Keys) { $authType = (Find-PodeAuth -Name $authName).Scheme $_authName = ($authName -replace '\s+', '') $_authObj = @{} if ($authType.Scheme -ieq 'apikey') { $_authObj = @{ type = $authType.Scheme in = $authType.Arguments.Location.ToLowerInvariant() name = $authType.Arguments.LocationName } } else { $_authObj = @{ type = $authType.Scheme.ToLowerInvariant() scheme = $authType.Name.ToLowerInvariant() } } $def.components.securitySchemes[$_authName] = $_authObj } if ($PodeContext.Server.OpenAPI.Security.Length -gt 0) { $def['security'] = @($PodeContext.Server.OpenAPI.Security.Definition) } } # paths $def['paths'] = [ordered]@{} $filter = "^$($RouteFilter)" foreach ($method in $PodeContext.Server.Routes.Keys) { foreach ($path in ($PodeContext.Server.Routes[$method].Keys | Sort-Object)) { # does it match the route? if ($path -inotmatch $filter) { continue } # the current route $_routes = @($PodeContext.Server.Routes[$method][$path]) if ($RestrictRoutes) { $_routes = @(Get-PodeRoutesByUrl -Routes $_routes -EndpointName $EndpointName) } # continue if no routes if (($_routes.Length -eq 0) -or ($null -eq $_routes[0])) { continue } # get the first route for base definition $_route = $_routes[0] # do nothing if it has no responses set if ($_route.OpenApi.Responses.Count -eq 0) { continue } # add path to defintion if ($null -eq $def.paths[$_route.OpenApi.Path]) { $def.paths[$_route.OpenApi.Path] = @{} } # add path's http method to defintition $def.paths[$_route.OpenApi.Path][$method] = @{ summary = $_route.OpenApi.Summary description = $_route.OpenApi.Description operationId = $_route.OpenApi.OperationId tags = @($_route.OpenApi.Tags) responses = $_route.OpenApi.Responses parameters = $_route.OpenApi.Parameters requestBody = $_route.OpenApi.RequestBody servers = $null security = @($_route.OpenApi.Authentication) } if ($_route.OpenApi.Deprecated) { $def.paths[$_route.OpenApi.Path][$method]['deprecated'] = $_route.OpenApi.Deprecated } # add global authentication for route if (($null -ne $def['security']) -and ($def['security'].Length -gt 0)) { foreach ($sec in $PodeContext.Server.OpenAPI.Security) { if ([string]::IsNullOrWhiteSpace($sec.Route) -or ($sec.Route -ieq '/') -or ($sec.Route -ieq $_route.OpenApi.Path) -or ($_route.OpenApi.Path -imatch "^$($sec.Route)$")) { $def.paths[$_route.OpenApi.Path][$method].security += $sec.Definition } } } if ($def.paths[$_route.OpenApi.Path][$method].security.Length -eq 0) { $def.paths[$_route.OpenApi.Path][$method].Remove('security') } # add any custom server endpoints for route foreach ($_route in $_routes) { if ([string]::IsNullOrWhiteSpace($_route.Endpoint.Address) -or ($_route.Endpoint.Address -ieq '*:*')) { continue } if ($null -eq $def.paths[$_route.OpenApi.Path][$method].servers) { $def.paths[$_route.OpenApi.Path][$method].servers = @() } $serverDef = $null if (![string]::IsNullOrWhiteSpace($_route.Endpoint.Name)) { $serverDef = @{ url = (Get-PodeEndpointByName -Name $_route.Endpoint.Name).Url } } else { $serverDef = @{ url = "$($_route.Endpoint.Protocol)://$($_route.Endpoint.Address)" } } if ($null -ne $serverDef) { $def.paths[$_route.OpenApi.Path][$method].servers += $serverDef } } } } # remove all null values (swagger hates them) $def | Remove-PodeNullKeysFromHashtable return $def } function ConvertTo-PodeOAPropertyFromCmdletParameter { param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [System.Management.Automation.ParameterMetadata] $Parameter ) if ($Parameter.SwitchParameter -or ($Parameter.ParameterType.Name -ieq 'boolean')) { New-PodeOABoolProperty -Name $Parameter.Name } else { switch ($Parameter.ParameterType.Name) { { @('int32', 'int64') -icontains $_ } { New-PodeOAIntProperty -Name $Parameter.Name -Format $_ } { @('double', 'float') -icontains $_ } { New-PodeOANumberProperty -Name $Parameter.Name -Format $_ } } } New-PodeOAStringProperty -Name $Parameter.Name } function Get-PodeOABaseObject { return @{ Path = $null Title = $null components = @{ schemas = @{} responses = @{} requestBodies = @{} parameters = @{} } Security = @() } } function Set-PodeOAAuth { param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [Parameter()] [string[]] $Name ) foreach ($n in @($Name)) { if (!(Test-PodeAuth -Name $n)) { throw "Authentication method does not exist: $($n)" } } foreach ($r in @($Route)) { $r.OpenApi.Authentication = @(foreach ($n in @($Name)) { @{ "$($n -replace '\s+', '')" = @() } }) } } function Set-PodeOAGlobalAuth { param( [Parameter()] [string] $Name, [Parameter()] [string] $Route ) if (!(Test-PodeAuth -Name $Name)) { throw "Authentication method does not exist: $($Name)" } if (Test-PodeIsEmpty $PodeContext.Server.OpenAPI.Security) { $PodeContext.Server.OpenAPI.Security = @() } $PodeContext.Server.OpenAPI.Security += @{ Definition = @{ "$($Name -replace '\s+', '')" = @() } Route = (ConvertTo-PodeRouteRegex -Path $Route) } } |