Public/OpenApi.ps1
<#
.SYNOPSIS Enables the OpenAPI default route in Pode. .DESCRIPTION Enables the OpenAPI default route in Pode, as well as setting up details like Title and API Version. .PARAMETER Path An optional custom route path to access the OpenAPI definition. (Default: /openapi) .PARAMETER Title The Title of the API. (Deprecated - Use Add-PodeOAInfo) .PARAMETER Version The Version of the API. (Deprecated - Use Add-PodeOAInfo) The OpenAPI Specification is versioned using Semantic Versioning 2.0.0 (semver) and follows the semver specification. https://semver.org/spec/v2.0.0.html .PARAMETER Description A short description of the API. (Deprecated - Use Add-PodeOAInfo) CommonMark syntax MAY be used for rich text representation. https://spec.commonmark.org/ .PARAMETER OpenApiVersion Specify OpenApi Version (default: 3.0.3) .PARAMETER RouteFilter An optional route filter for routes that should be included in the definition. (Default: /*) .PARAMETER Middleware Like normal Routes, an array of Middleware that will be applied to the route. .PARAMETER EndpointName The EndpointName of an Endpoint(s) to bind the static Route against. .PARAMETER Authentication The name of an Authentication method which should be used as middleware on this Route. .PARAMETER Role One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. .PARAMETER Group One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. .PARAMETER Scope One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. .PARAMETER RestrictRoutes If supplied, only routes that are available on the Requests URI will be used to generate the OpenAPI definition. .PARAMETER ServerEndpoint If supplied, will be used as URL base to generate the OpenAPI definition. The parameter is created by New-PodeOpenApiServerEndpoint .PARAMETER Mode Define the way the OpenAPI definition file is accessed, the value can be View or Download. (Default: View) .PARAMETER NoCompress If supplied, generate the OpenApi Json version in human readible form. .PARAMETER MarkupLanguage Define the default markup language for the OpenApi spec ('Json', 'Json-Compress', 'Yaml') .PARAMETER EnableSchemaValidation If supplied enable Test-PodeOAJsonSchemaCompliance cmdlet that provide support for opeapi parameter schema validation .PARAMETER Depth Define the default depth used by any JSON,YAML OpenAPI conversion (default 20) .PARAMETER DisableMinimalDefinitions If supplied the OpenApi decument will include only the route validated by Set-PodeOARouteInfo. Any other not OpenApi route will be excluded. .PARAMETER NoDefaultResponses If supplied, it will disable the default OpenAPI response with the new provided. .PARAMETER DefaultResponses If supplied, it will replace the default OpenAPI response with the new provided.(Default: @{'200' = @{ description = 'OK' };'default' = @{ description = 'Internal server error' }} ) .PARAMETER DefinitionTag A string representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Enable-PodeOpenApi -Title 'My API' -Version '1.0.0' -RouteFilter '/api/*' .EXAMPLE Enable-PodeOpenApi -Title 'My API' -Version '1.0.0' -RouteFilter '/api/*' -RestrictRoutes .EXAMPLE Enable-PodeOpenApi -Path '/docs/openapi' -NoCompress -Mode 'Donwload' -DisableMinimalDefinitions #> function Enable-PodeOpenApi { [CmdletBinding()] param( [ValidateNotNullOrEmpty()] [string] $Path = '/openapi', [Parameter(ParameterSetName = 'Deprecated')] [string] $Title, [Parameter(ParameterSetName = 'Deprecated')] [ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$')] [string] $Version, [Parameter(ParameterSetName = 'Deprecated')] [string] $Description, [ValidateSet('3.1.0', '3.0.3', '3.0.2', '3.0.1', '3.0.0')] [string] $OpenApiVersion = '3.0.3', [ValidateNotNullOrEmpty()] [string] $RouteFilter = '/*', [Parameter()] [string[]] $EndpointName, [Parameter()] [object[]] $Middleware, [Parameter()] [Alias('Auth')] [string] $Authentication, [Parameter()] [string[]] $Role, [Parameter()] [string[]] $Group, [Parameter()] [string[]] $Scope, [switch] $RestrictRoutes, [Parameter()] [ValidateSet('View', 'Download')] [String] $Mode = 'view', [Parameter()] [ValidateSet('Json', 'Json-Compress', 'Yaml')] [String] $MarkupLanguage = 'Json', [Parameter()] [switch] $EnableSchemaValidation, [Parameter()] [ValidateRange(1, 100)] [int] $Depth = 20, [Parameter()] [switch] $DisableMinimalDefinitions, [Parameter(Mandatory, ParameterSetName = 'DefaultResponses')] [hashtable] $DefaultResponses, [Parameter(Mandatory, ParameterSetName = 'NoDefaultResponses')] [switch] $NoDefaultResponses, [Parameter()] [string] $DefinitionTag ) if (Test-PodeIsEmpty -Value $DefinitionTag) { $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag } if ($Description -or $Version -or $Title) { if (! $Version) { $Version = '0.0.0' } # WARNING: Title, Version, and Description on 'Enable-PodeOpenApi' are deprecated. Please use 'Add-PodeOAInfo' instead Write-PodeHost $PodeLocale.deprecatedTitleVersionDescriptionWarningMessage -ForegroundColor Yellow } if ( $DefinitionTag -ine $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag] = Get-PodeOABaseObject } $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.enableMinimalDefinitions = !$DisableMinimalDefinitions.IsPresent # initialise openapi info $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].Version = $OpenApiVersion $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].Path = $Path if ($OpenApiVersion.StartsWith('3.0')) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.version = 3.0 } elseif ($OpenApiVersion.StartsWith('3.1')) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.version = 3.1 } $meta = @{ RouteFilter = $RouteFilter RestrictRoutes = $RestrictRoutes NoCompress = ($MarkupLanguage -ine 'Json-Compress') Mode = $Mode MarkupLanguage = $MarkupLanguage DefinitionTag = $DefinitionTag } if ( $Title) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.title = $Title } if ($Version) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.version = $Version } if ($Description ) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.description = $Description } if ( $EnableSchemaValidation.IsPresent) { #Test-Json has been introduced with version 6.1.0 if ($PSVersionTable.PSVersion -ge [version]'6.1.0') { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaValidation = $EnableSchemaValidation.IsPresent } else { # Schema validation required PowerShell version 6.1.0 or greater throw ($PodeLocale.schemaValidationRequiresPowerShell610ExceptionMessage) } } if ( $Depth) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth = $Depth } $openApiCreationScriptBlock = { param($meta) $format = $WebEvent.Query['format'] $mode = $WebEvent.Query['mode'] $DefinitionTag = $meta.DefinitionTag if (!$mode) { $mode = $meta.Mode } elseif (@('download', 'view') -inotcontains $mode) { Write-PodeHtmlResponse -Value "Mode $mode not valid" -StatusCode 400 return } if ($WebEvent.path -ilike '*.json') { if ($format) { Show-PodeErrorPage -Code 400 -ContentType 'text/html' -Description 'Format query not valid when the file extension is used' return } $format = 'json' } elseif ($WebEvent.path -ilike '*.yaml') { if ($format) { Show-PodeErrorPage -Code 400 -ContentType 'text/html' -Description 'Format query not valid when the file extension is used' return } $format = 'yaml' } elseif (!$format) { $format = $meta.MarkupLanguage.ToLower() } elseif (@('yaml', 'json', 'json-Compress') -inotcontains $format) { Show-PodeErrorPage -Code 400 -ContentType 'text/html' -Description "Format $format not valid" return } if ($mode -ieq 'download') { # Set-PodeResponseAttachment -Path Add-PodeHeader -Name 'Content-Disposition' -Value "attachment; filename=openapi.$format" } # generate the openapi definition $def = Get-PodeOpenApiDefinitionInternal ` -EndpointName $WebEvent.Endpoint.Name ` -DefinitionTag $DefinitionTag ` -MetaInfo $meta # write the openapi definition if ($format -ieq 'yaml') { if ($mode -ieq 'view') { Write-PodeTextResponse -Value (ConvertTo-PodeYaml -InputObject $def -depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth) -ContentType 'application/yaml; charset=utf-8' #Changed to be RFC 9512 compliant } else { Write-PodeYamlResponse -Value $def -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth } } else { Write-PodeJsonResponse -Value $def -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth -NoCompress:$meta.NoCompress } } # add the OpenAPI route Add-PodeRoute -Method Get -Path $Path -ArgumentList $meta -Middleware $Middleware ` -ScriptBlock $openApiCreationScriptBlock -EndpointName $EndpointName ` -Authentication $Authentication -Role $Role -Scope $Scope -Group $Group Add-PodeRoute -Method Get -Path "$Path.json" -ArgumentList $meta -Middleware $Middleware ` -ScriptBlock $openApiCreationScriptBlock -EndpointName $EndpointName ` -Authentication $Authentication -Role $Role -Scope $Scope -Group $Group Add-PodeRoute -Method Get -Path "$Path.yaml" -ArgumentList $meta -Middleware $Middleware ` -ScriptBlock $openApiCreationScriptBlock -EndpointName $EndpointName ` -Authentication $Authentication -Role $Role -Scope $Scope -Group $Group #set new DefaultResponses if ($NoDefaultResponses.IsPresent) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.defaultResponses = [ordered]@{} } elseif ($DefaultResponses) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.defaultResponses = $DefaultResponses } $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.enabled = $true if ($EndpointName) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.EndpointName = $EndpointName } } <# .SYNOPSIS Creates an OpenAPI Server Object. .DESCRIPTION Creates an OpenAPI Server Object. .PARAMETER Url A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in `{`brackets`}`. .PARAMETER Description An optional string describing the host designated by the URL. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. .PARAMETER Variables A map between a variable name and its value. The value is used for substitution in the server's URL template. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeOAServerEndpoint -Url 'https://myserver.io/api' -Description 'My test server' .EXAMPLE Add-PodeOAServerEndpoint -Url '/api' -Description 'My local server' .EXAMPLE Add-PodeOAServerEndpoint -Url "https://{username}.gigantic-server.com:{port}/{basePath}" -Description "The production API server" ` -Variable @{ username = @{ default = 'demo' description = 'this value is assigned by the service provider, in this example gigantic-server.com' } port = @{ enum = @('System.Object[]') # Assuming 'System.Object[]' is a placeholder for actual values default = 8443 } basePath = @{ default = 'v2' } } } #> function Add-PodeOAServerEndpoint { param ( [Parameter(Mandatory = $true)] [ValidatePattern('^(https?://|/).+')] [string] $Url, [string] $Description, [System.Collections.Specialized.OrderedDictionary] $Variables, [string[]] $DefinitionTag ) # If the DefinitionTag is empty, use the selected tag from Pode's OpenAPI context if (Test-PodeIsEmpty -Value $DefinitionTag) { $DefinitionTag = @($PodeContext.Server.OpenAPI.SelectedDefinitionTag) } # Loop through each tag to add the server object to the corresponding OpenAPI definition foreach ($tag in $DefinitionTag) { # If the 'servers' array for the tag doesn't exist, initialize it as an empty array if (! $PodeContext.Server.OpenAPI.Definitions[$tag].servers) { $PodeContext.Server.OpenAPI.Definitions[$tag].servers = @() } # Create an ordered hashtable representing the server object with the URL $lUrl = [ordered]@{url = $Url } # If a description is provided, add it to the server object if ($Description) { $lUrl.description = $Description } # If variables are provided, add them to the server object if ($Variables) { $lUrl.variables = $Variables } # Check if the URL is a local endpoint (not starting with 'http(s)://') if ($lUrl.url -notmatch '^(?i)https?://') { # Loop through existing server URLs in the definition foreach ($srv in $PodeContext.Server.OpenAPI.Definitions[$tag].servers) { # If there's already a local endpoint, throw an exception, as only one local endpoint is allowed per definition # Both are defined as local OpenAPI endpoints, but only one local endpoint is allowed per API definition. if ($srv.url -notmatch '^(?i)https?://') { throw ($PodeLocale.LocalEndpointConflictExceptionMessage -f $Url, $srv.url) } } } # Add the new server object to the OpenAPI definition for the current tag $PodeContext.Server.OpenAPI.Definitions[$tag].servers += $lUrl } } <# .SYNOPSIS Gets the OpenAPI definition. .DESCRIPTION Gets the OpenAPI definition for custom use in routes, or other functions. .PARAMETER Format Return the definition in a specific format 'Json', 'Json-Compress', 'Yaml', 'HashTable' .PARAMETER Title The Title of the API. (Default: the title supplied in Enable-PodeOpenApi) .PARAMETER Version The Version of the API. (Default: the version supplied in Enable-PodeOpenApi) .PARAMETER Description A Description of the API. (Default: the description supplied into Enable-PodeOpenApi) .PARAMETER RouteFilter An optional route filter for routes that should be included in the definition. (Default: /*) .PARAMETER RestrictRoutes If supplied, only routes that are available on the Requests URI will be used to generate the OpenAPI definition. .PARAMETER DefinitionTag A string representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE $defInJson = Get-PodeOADefinition -Json #> function Get-PodeOADefinition { [CmdletBinding()] param( [ValidateSet('Json', 'Json-Compress', 'Yaml', 'HashTable')] [string] $Format = 'HashTable', [string] $Title, [string] $Version, [string] $Description, [ValidateNotNullOrEmpty()] [string] $RouteFilter = '/*', [switch] $RestrictRoutes, [string] $DefinitionTag ) $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $meta = @{ RouteFilter = $RouteFilter RestrictRoutes = $RestrictRoutes } if ($RestrictRoutes) { $meta = @{ RouteFilter = $RouteFilter RestrictRoutes = $RestrictRoutes } } else { $meta = @{} } if ($Title) { $meta.Title = $Title } if ($Version) { $meta.Version = $Version } if ($Description) { $meta.Description = $Description } $oApi = Get-PodeOpenApiDefinitionInternal -MetaInfo $meta -EndpointName $WebEvent.Endpoint.Name -DefinitionTag $DefinitionTag switch ($Format.ToLower()) { 'json' { return ConvertTo-Json -InputObject $oApi -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth } 'json-compress' { return ConvertTo-Json -InputObject $oApi -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth -Compress } 'yaml' { return ConvertTo-PodeYaml -InputObject $oApi -depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth } Default { return $oApi } } } <# .SYNOPSIS Adds a response definition to the supplied route. .DESCRIPTION Adds a response definition to the supplied route. .PARAMETER Route The route to add the response definition, usually from -PassThru on Add-PodeRoute. .PARAMETER StatusCode The HTTP StatusCode for the response.To define a range of response codes, this field MAY contain the uppercase wildcard character `X`. For example, `2XX` represents all response codes between `[200-299]`. Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`. If a response is defined using an explicit code, the explicit code definition takes precedence over the range definition for that code. .PARAMETER Content The content-types and schema the response returns (the schema is created using the Property functions). Alias: ContentSchemas .PARAMETER Headers The header name and schema the response returns (the schema is created using Add-PodeOAComponentHeader cmd-let). Alias: HeaderSchemas .PARAMETER Description A Description of the response. (Default: the HTTP StatusCode description) .PARAMETER Reference A Reference Name of an existing component response to use. .PARAMETER Links A Response link definition .PARAMETER Default If supplied, the response will be used as a default response - this overrides the StatusCode supplied. .PARAMETER PassThru If supplied, the route passed in will be returned for further chaining. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Content @{ 'application/json' = (New-PodeOAIntProperty -Name 'userId' -Object) } .EXAMPLE Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Content @{ 'application/json' = 'UserIdSchema' } .EXAMPLE Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Reference 'OKResponse' #> function Add-PodeOAResponse { [CmdletBinding(DefaultParameterSetName = 'Schema')] [OutputType([hashtable[]])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [Parameter(Mandatory = $true, ParameterSetName = 'Schema')] [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [ValidatePattern('^([1-5][0-9][0-9]|[1-5]XX)$')] [string] $StatusCode, [Parameter(ParameterSetName = 'Schema')] [Parameter(ParameterSetName = 'SchemaDefault')] [Alias('ContentSchemas')] [hashtable] $Content, [Alias('HeaderSchemas')] [AllowEmptyString()] [ValidateNotNullOrEmpty()] [ValidateScript({ $_ -is [string] -or $_ -is [string[]] -or $_ -is [hashtable] -or $_ -is [System.Collections.Specialized.OrderedDictionary] })] $Headers, [Parameter(Mandatory = $false, ParameterSetName = 'Schema')] [Parameter(Mandatory = $false, ParameterSetName = 'SchemaDefault')] [string] $Description, [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [Parameter(ParameterSetName = 'ReferenceDefault')] [string] $Reference, [Parameter(Mandatory = $true, ParameterSetName = 'ReferenceDefault')] [Parameter(Mandatory = $true, ParameterSetName = 'SchemaDefault')] [switch] $Default, [Parameter(ParameterSetName = 'Schema')] [Parameter(ParameterSetName = 'SchemaDefault')] [System.Collections.Specialized.OrderedDictionary ] $Links, [switch] $PassThru, [string[]] $DefinitionTag ) begin { # Initialize an array to hold piped-in values $pipelineValue = @() } process { # Add the current piped-in value to the array $pipelineValue += $_ } end { # Set Route to the array of values if ($pipelineValue.Count -gt 1) { $Route = $pipelineValue } # override status code with default if ($Default) { $code = 'default' } else { $code = "$($StatusCode)" } # add the respones to the routes foreach ($r in @($Route)) { $oaDefinitionTag = Test-PodeRouteOADefinitionTag -Route $r -DefinitionTag $DefinitionTag foreach ($tag in $oaDefinitionTag) { if (! $r.OpenApi.Responses.$tag) { $r.OpenApi.Responses.$tag = [ordered]@{} } $r.OpenApi.Responses.$tag[$code] = New-PodeOResponseInternal -DefinitionTag $tag -Params $PSBoundParameters } } if ($PassThru) { return $Route } } } <# .SYNOPSIS Remove a response definition from the supplied route. .DESCRIPTION Remove a response definition from the supplied route. .PARAMETER Route The route to remove the response definition, usually from -PassThru on Add-PodeRoute. .PARAMETER StatusCode The HTTP StatusCode for the response to remove. .PARAMETER Default If supplied, the response will be used as a default response - this overrides the StatusCode supplied. .PARAMETER PassThru If supplied, the route passed in will be returned for further chaining. .EXAMPLE Add-PodeRoute -PassThru | Remove-PodeOAResponse -StatusCode 200 .EXAMPLE Add-PodeRoute -PassThru | Remove-PodeOAResponse -StatusCode 201 -Default #> function Remove-PodeOAResponse { [CmdletBinding()] [OutputType([hashtable[]])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [Parameter(Mandatory = $true)] [int] $StatusCode, [switch] $Default, [switch] $PassThru ) begin { # Initialize an array to hold piped-in values $pipelineValue = @() } process { # Add the current piped-in value to the array $pipelineValue += $_ } end { # Set Route to the array of values if ($pipelineValue.Count -gt 1) { $Route = $pipelineValue } # override status code with default $code = "$($StatusCode)" if ($Default) { $code = 'default' } # remove the respones from the routes foreach ($r in $Route) { if ($r.OpenApi.Responses.Keys -Contains $code) { $null = $r.OpenApi.Responses.Remove($code) } } if ($PassThru) { return $Route } } } <# .SYNOPSIS Sets the OpenAPI request definition for a route. .DESCRIPTION Configures the OpenAPI request properties for a specified route, including parameters and request body definition. This function defines how the route should handle incoming requests in accordance with OpenAPI standards. .PARAMETER Route The route to set a request definition for. This is typically passed through from -PassThru on Add-PodeRoute. .PARAMETER Parameters Defines the parameters for the request, provided by ConvertTo-PodeOAParameter. .PARAMETER RequestBody Specifies the body schema for the request, provided by New-PodeOARequestBody. .PARAMETER AllowNonStandardBody Allows methods like DELETE and GET to include a request body, which is generally discouraged by RFC 7231. This can be used to relax the default restriction and enable a body for HTTP methods that don’t typically support it. .PARAMETER PassThru If specified, returns the original route object for additional chaining after setting the request properties. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeRoute -PassThru | Set-PodeOARequest -RequestBody (New-PodeOARequestBody -Schema 'UserIdBody') -AllowNonStandardBody Sets the request body for a route and allows non-standard HTTP methods like DELETE to use a request body. #> function Set-PodeOARequest { [CmdletBinding()] [OutputType([hashtable[]])] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [hashtable[]] $Parameters, [hashtable] $RequestBody, [switch] $PassThru, [switch] $AllowNonStandardBody, [string[]] $DefinitionTag ) begin { # Initialize an array to hold piped-in values $pipelineValue = @() } process { # Add the current piped-in value to the array $pipelineValue += $_ } end { # Set Route to the array of values if ($pipelineValue.Count -gt 1) { $Route = $pipelineValue } foreach ($r in $Route) { $oaDefinitionTag = Test-PodeRouteOADefinitionTag -Route $r -DefinitionTag $DefinitionTag foreach ($tag in $oaDefinitionTag) { if (($null -ne $Parameters) -and ($Parameters.Length -gt 0)) { $r.OpenApi.Parameters[$tag] = @($Parameters) } if ($null -ne $RequestBody) { # Check if AllowNonStandardBody is used or if the method is typically allowed to have a body if (! $AllowNonStandardBody -and ('POST', 'PUT', 'PATCH') -inotcontains $r.Method) { #'{0}' operations cannot have a Request Body. Use -AllowNonStandardBody to override this restriction. throw ($PodeLocale.getRequestBodyNotAllowedExceptionMessage -f $r.Method) } $r.OpenApi.RequestBody = $RequestBody } } } if ($PassThru) { return $Route } } } <# .SYNOPSIS Creates a Request Body definition for routes. .DESCRIPTION Creates a Request Body definition for routes from the supplied content-types and schemas. .PARAMETER Reference A reference name from an existing component request body. Alias: Reference .PARAMETER Content The content of the request body. The key is a media type or media type range and the value describes it. For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* Alias: ContentSchemas .PARAMETER Description A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation. .PARAMETER Required Determines if the request body is required in the request. Defaults to false. .PARAMETER Properties Use to force the use of the properties keyword under a schema. Commonly used to specify a multipart/form-data multi file .PARAMETER Examples Supplied an Example of the media type. The example object SHOULD be in the correct format as specified by the media type. The `example` field is mutually exclusive of the `examples` field. Furthermore, if referencing a `schema` which contains an example, the `example` value SHALL _override_ the example provided by the schema. .PARAMETER Encoding This parameter give you control over the serialization of parts of multipart request bodies. This attribute is only applicable to multipart and application/x-www-form-urlencoded request bodies. Use New-PodeOAEncodingObject to define the encode .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE New-PodeOARequestBody -Content @{ 'application/json' = (New-PodeOAIntProperty -Name 'userId' -Object) } .EXAMPLE New-PodeOARequestBody -Content @{ 'application/json' = 'UserIdSchema' } .EXAMPLE New-PodeOARequestBody -Reference 'UserIdBody' .EXAMPLE New-PodeOARequestBody -Content @{'multipart/form-data' = New-PodeOAStringProperty -name 'id' -format 'uuid' | New-PodeOAObjectProperty -name 'address' -NoProperties | New-PodeOAObjectProperty -name 'historyMetadata' -Description 'metadata in XML format' -NoProperties | New-PodeOAStringProperty -name 'profileImage' -Format Binary | New-PodeOAObjectProperty } -Encoding ( New-PodeOAEncodingObject -Name 'historyMetadata' -ContentType 'application/xml; charset=utf-8' | New-PodeOAEncodingObject -Name 'profileImage' -ContentType 'image/png, image/jpeg' -Headers ( New-PodeOAIntProperty -name 'X-Rate-Limit-Limit' -Description 'The number of allowed requests in the current period' -Default 3 -Enum @(1,2,3) ) ) #> function New-PodeOARequestBody { [CmdletBinding(DefaultParameterSetName = 'BuiltIn' )] [OutputType([hashtable])] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [string] $Reference, [Parameter(Mandatory = $true, ParameterSetName = 'BuiltIn')] [Alias('ContentSchemas')] [hashtable] $Content, [Parameter(ParameterSetName = 'BuiltIn')] [string] $Description, [Parameter(ParameterSetName = 'BuiltIn')] [switch] $Required, [Parameter(ParameterSetName = 'BuiltIn')] [switch] $Properties, [System.Collections.Specialized.OrderedDictionary] $Examples, [hashtable[]] $Encoding, [string[]] $DefinitionTag ) $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $result = [ordered]@{} foreach ($tag in $DefinitionTag) { switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'builtin' { $param = [ordered]@{content = ConvertTo-PodeOAObjectSchema -DefinitionTag $tag -Content $Content -Properties:$Properties } if ($Required.IsPresent) { $param['required'] = $Required.IsPresent } if ( $Description) { $param['description'] = $Description } if ($Examples) { if ( $Examples.'*/*') { $Examples['"*/*"'] = $Examples['*/*'] $Examples.Remove('*/*') } foreach ($k in $Examples.Keys ) { if (!($param.content.Keys -contains $k)) { $param.content[$k] = [ordered]@{} } $param.content[$k].examples = $Examples.$k } } } 'reference' { Test-PodeOAComponentInternal -Field requestBodies -DefinitionTag $tag -Name $Reference -PostValidation $param = [ordered]@{ '$ref' = "#/components/requestBodies/$Reference" } } } if ($Encoding) { if (([string]$Content.keys[0]) -match '(?i)^(multipart.*|application\/x-www-form-urlencoded)$' ) { $r = [ordered]@{} foreach ( $e in $Encoding) { $key = [string]$e.Keys $elems = [ordered]@{} foreach ($v in $e[$key].Keys) { if ($v -ieq 'headers') { $elems.headers = ConvertTo-PodeOAHeaderProperty -Headers $e[$key].headers } else { $elems.$v = $e[$key].$v } } $r.$key = $elems } $param.Content.$($Content.keys[0]).encoding = $r } else { # The encoding attribute only applies to multipart and application/x-www-form-urlencoded request bodies throw ($PodeLocale.encodingAttributeOnlyAppliesToMultipartExceptionMessage) } } $result[$tag] = $param } return $result } <# .SYNOPSIS Validate a parameter with a provided schema. .DESCRIPTION Validate the parameter of a method against it's own schema .PARAMETER Json The object in Json format to validate .PARAMETER SchemaReference The schema name to use to validate the property. .PARAMETER DefinitionTag A string representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .OUTPUTS result: true if the object is validate positively message: any validation issue .EXAMPLE $UserInfo = Test-PodeOAJsonSchemaCompliance -Json $UserInfo -SchemaReference 'UserIdSchema'} #> function Test-PodeOAJsonSchemaCompliance { param ( [Parameter(Mandatory = $true)] [System.Object] $Json, [Parameter(Mandatory = $true)] [string] $SchemaReference, [string] $DefinitionTag ) if ($DefinitionTag) { if (! ($PodeContext.Server.OpenApi.Definitions.Keys -icontains $DefinitionTag)) { # DefinitionTag does not exist. throw ($PodeLocale.definitionTagNotDefinedExceptionMessage -f $DefinitionTag) } } else { $DefinitionTag = $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag } # if Powershell edition is Desktop the test cannot be done. By default everything is good if ($PSVersionTable.PSEdition -eq 'Desktop') { return $true } if ($Json -isnot [string]) { $json = ConvertTo-Json -InputObject $Json -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth } if (!$PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaValidation) { # 'Test-PodeOAComponentchema' need to be enabled using 'Enable-PodeOpenApi -EnableSchemaValidation' throw ($PodeLocale.testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage) } if (!(Test-PodeOAComponentSchemaJson -Name $SchemaReference -DefinitionTag $DefinitionTag)) { # The OpenApi component schema doesn't exist throw ($PodeLocale.openApiComponentSchemaDoesNotExistExceptionMessage -f $SchemaReference) } if ($PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaJson[$SchemaReference].available) { [string[]] $message = @() $result = Test-Json -Json $Json -Schema $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaJson[$SchemaReference].json -ErrorVariable jsonValidationErrors -ErrorAction SilentlyContinue if ($jsonValidationErrors) { foreach ($item in $jsonValidationErrors) { $message += $item } } } else { $result = $false $message = 'Validation of schema with oneof or anyof is not supported' } return @{result = $result; message = $message } } <# .SYNOPSIS Converts an OpenAPI property into a Request Parameter. .DESCRIPTION Converts an OpenAPI property (such as from New-PodeOAIntProperty) into a Request Parameter. .PARAMETER In Where in the Request can the parameter be found? .PARAMETER Property The Property that need converting (such as from New-PodeOAIntProperty). .PARAMETER Reference The name of an existing component parameter to be reused. Alias: ComponentParameter .PARAMETER Name Assign a name to the parameter .PARAMETER ContentType The content-types to be use with component schema .PARAMETER Schema The component schema to use. .PARAMETER Description A Description of the property. .PARAMETER Explode If supplied, controls how arrays are serialized in query parameters .PARAMETER AllowReserved If supplied, determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding. This property only applies to parameters with an in value of query. The default value is false. .PARAMETER Required If supplied, the object will be treated as Required where supported.(Applicable only to ContentSchema) .PARAMETER AllowEmptyValue If supplied, allow the parameter to be empty .PARAMETER Style If supplied, defines how multiple values are delimited. Possible styles depend on the parameter location: path, query, header or cookie. .PARAMETER Deprecated If supplied, specifies that a parameter is deprecated and SHOULD be transitioned out of usage. Default value is false. .PARAMETER Example Example of the parameter's potential value. The example SHOULD match the specified schema and encoding properties if present. The Example parameter is mutually exclusive of the Examples parameter. Furthermore, if referencing a Schema that contains an example, the Example value SHALL _override_ the example provided by the schema. To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary. .PARAMETER Examples Examples of the parameter's potential value. Each example SHOULD contain a value in the correct format as specified in the parameter encoding. The Examples parameter is mutually exclusive of the Example parameter. Furthermore, if referencing a Schema that contains an example, the Examples value SHALL _override_ the example provided by the schema. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE New-PodeOAIntProperty -Name 'userId' | ConvertTo-PodeOAParameter -In Query .EXAMPLE ConvertTo-PodeOAParameter -Reference 'UserIdParam' .EXAMPLE ConvertTo-PodeOAParameter -In Header -ContentSchemas @{ 'application/json' = 'UserIdSchema' } #> function ConvertTo-PodeOAParameter { [CmdletBinding(DefaultParameterSetName = 'Reference')] param( [Parameter( Mandatory = $true, ParameterSetName = 'Schema')] [Parameter(Mandatory = $true, ParameterSetName = 'Properties')] [Parameter(Mandatory = $true, ParameterSetName = 'ContentSchema')] [Parameter( Mandatory = $true, ParameterSetName = 'ContentProperties')] [ValidateSet('Cookie', 'Header', 'Path', 'Query')] [string] $In, [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'Properties')] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'ContentProperties')] [ValidateNotNull()] [hashtable] $Property, [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [Alias('ComponentParameter')] [string] $Reference, [Parameter( ParameterSetName = 'Schema')] [Parameter(ParameterSetName = 'Properties')] [Parameter(ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'ContentProperties')] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Schema')] [Parameter(Mandatory = $true, ParameterSetName = 'ContentSchema')] [Alias('ComponentSchema')] [String] $Schema, [Parameter( Mandatory = $true, ParameterSetName = 'ContentSchema')] [Parameter( Mandatory = $true, ParameterSetName = 'ContentProperties')] [String] $ContentType, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'Properties')] [Parameter( ParameterSetName = 'ContentProperties')] [String] $Description, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'Properties')] [Switch] $Explode, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'Properties')] [Parameter( ParameterSetName = 'ContentProperties')] [Switch] $Required, [Parameter( ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'Properties')] [Switch] $AllowEmptyValue, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'Properties')] [Switch] $AllowReserved, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'Properties')] [Parameter( ParameterSetName = 'ContentProperties')] [object] $Example, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'Properties')] [Parameter( ParameterSetName = 'ContentProperties')] [System.Collections.Specialized.OrderedDictionary] $Examples, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'Properties')] [ValidateSet('Simple', 'Label', 'Matrix', 'Query', 'Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' )] [string] $Style, [Parameter( ParameterSetName = 'Schema')] [Parameter( ParameterSetName = 'ContentSchema')] [Parameter( ParameterSetName = 'Properties')] [Parameter( ParameterSetName = 'ContentProperties')] [Switch] $Deprecated, [string[]] $DefinitionTag ) begin { $pipelineItemCount = 0 } process { $pipelineItemCount++ } end { if ($pipelineItemCount -gt 1) { throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name)) } $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag if ($PSCmdlet.ParameterSetName -ieq 'ContentSchema' -or $PSCmdlet.ParameterSetName -ieq 'Schema') { if (Test-PodeIsEmpty $Schema) { return $null } Test-PodeOAComponentInternal -Field schemas -DefinitionTag $DefinitionTag -Name $Schema -PostValidation if (!$Name ) { $Name = $Schema } $prop = [ordered]@{ in = $In.ToLowerInvariant() name = $Name } if ($In -ieq 'Header' -and $PodeContext.Server.Security.autoHeaders) { Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value $Schema -Append } if ($AllowEmptyValue.IsPresent ) { $prop['allowEmptyValue'] = $AllowEmptyValue.IsPresent } if ($Required.IsPresent ) { $prop['required'] = $Required.IsPresent } if ($Description ) { $prop.description = $Description } if ($Deprecated.IsPresent ) { $prop.deprecated = $Deprecated.IsPresent } if ($ContentType ) { # ensure all content types are valid if ($ContentType -inotmatch '^[\w-]+\/[\w\.\+-]+$') { # Invalid 'content-type' found for schema: $type throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $type) } $prop.content = [ordered]@{ $ContentType = [ordered]@{ schema = [ordered]@{ '$ref' = "#/components/schemas/$($Schema )" } } } if ($Example ) { $prop.content.$ContentType.example = $Example } elseif ($Examples) { $prop.content.$ContentType.examples = $Examples } } else { $prop.schema = [ordered]@{ '$ref' = "#/components/schemas/$($Schema )" } if ($Style) { switch ($in.ToLower()) { 'path' { if (@('Simple', 'Label', 'Matrix' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } 'query' { if (@('Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } 'header' { if (@('Simple' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } 'cookie' { if (@('Form' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } } $prop['style'] = $Style.Substring(0, 1).ToLower() + $Style.Substring(1) } if ($Explode.IsPresent ) { $prop['explode'] = $Explode.IsPresent } if ($AllowEmptyValue.IsPresent ) { $prop['allowEmptyValue'] = $AllowEmptyValue.IsPresent } if ($AllowReserved.IsPresent) { $prop['allowReserved'] = $AllowReserved.IsPresent } if ($Example ) { $prop.example = $Example } elseif ($Examples) { $prop.examples = $Examples } } } elseif ($PSCmdlet.ParameterSetName -ieq 'Reference') { # return a reference Test-PodeOAComponentInternal -Field parameters -DefinitionTag $DefinitionTag -Name $Reference -PostValidation $prop = [ordered]@{ '$ref' = "#/components/parameters/$Reference" } foreach ($tag in $DefinitionTag) { if ($PodeContext.Server.OpenAPI.Definitions[$tag].components.parameters.$Reference.In -eq 'Header' -and $PodeContext.Server.Security.autoHeaders) { Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value $Reference -Append } } } else { if (!$Name ) { if ($Property.name) { $Name = $Property.name } else { # The OpenApi parameter requires a name to be specified throw ($PodeLocale.openApiParameterRequiresNameExceptionMessage) } } if ($In -ieq 'Header' -and $PodeContext.Server.Security.autoHeaders -and $Name ) { Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value $Name -Append } # build the base parameter $prop = [ordered]@{ in = $In.ToLowerInvariant() name = $Name } $sch = [ordered]@{} if ($Property.array) { $sch.type = 'array' $sch.items = [ordered]@{ type = $Property.type } if ($Property.format) { $sch.items.format = $Property.format } } else { $sch.type = $Property.type if ($Property.format) { $sch.format = $Property.format } } if ($ContentType) { if ($ContentType -inotmatch '^[\w-]+\/[\w\.\+-]+$') { # Invalid 'content-type' found for schema: $type throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $type) } $prop.content = [ordered]@{ $ContentType = [ordered] @{ schema = $sch } } } else { $prop.schema = $sch } if ($Example -and $Examples) { # Parameters 'Examples' and 'Example' are mutually exclusive throw ($PodeLocale.parametersMutuallyExclusiveExceptionMessage -f 'Examples' , 'Example' ) } if ($AllowEmptyValue.IsPresent ) { $prop['allowEmptyValue'] = $AllowEmptyValue.IsPresent } if ($Description ) { $prop.description = $Description } elseif ($Property.description) { $prop.description = $Property.description } if ($Required.IsPresent ) { $prop.required = $Required.IsPresent } elseif ($Property.required) { $prop.required = $Property.required } if ($Deprecated.IsPresent ) { $prop.deprecated = $Deprecated.IsPresent } elseif ($Property.deprecated) { $prop.deprecated = $Property.deprecated } if (!$ContentType) { if ($Style) { switch ($in.ToLower()) { 'path' { if (@('Simple', 'Label', 'Matrix' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } 'query' { if (@('Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } 'header' { if (@('Simple' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } 'cookie' { if (@('Form' ) -inotcontains $Style) { # OpenApi request Style cannot be $Style for a $in parameter throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in) } break } } $prop['style'] = $Style.Substring(0, 1).ToLower() + $Style.Substring(1) } if ($Explode.IsPresent ) { $prop['explode'] = $Explode.IsPresent } if ($AllowReserved.IsPresent) { $prop['allowReserved'] = $AllowReserved.IsPresent } if ($Example ) { $prop['example'] = $Example } elseif ($Examples) { $prop['examples'] = $Examples } if ($Property.default -and !$prop.required ) { $prop.schema['default'] = $Property.default } if ($Property.enum) { if ($Property.array) { $prop.schema.items['enum'] = $Property.enum } else { $prop.schema['enum'] = $Property.enum } } } else { if ($Example ) { $prop.content.$ContentType.example = $Example } elseif ($Examples) { $prop.content.$ContentType.examples = $Examples } } } if ($In -ieq 'Path' -and !$prop.required ) { # If the parameter location is 'Path', the switch parameter 'Required' is mandatory throw ($PodeLocale.pathParameterRequiresRequiredSwitchExceptionMessage) } return $prop } } <# .SYNOPSIS Sets metadate for the supplied route. .DESCRIPTION Sets metadate for the supplied route, such as Summary and Tags. .PARAMETER Route The route to update info, usually from -PassThru on Add-PodeRoute. .PARAMETER Summary A quick Summary of the route. .PARAMETER Description A longer Description of the route. .PARAMETER ExternalDoc If supplied, add an additional external documentation for this operation. The parameter is created by Add-PodeOAExternalDoc .PARAMETER OperationId Sets the OperationId of the route. .PARAMETER Tags An array of Tags for the route, mostly for grouping. .PARAMETER Deprecated If supplied, the route will be flagged as deprecated. .PARAMETER PassThru If supplied, the route passed in will be returned for further chaining. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeRoute -PassThru | Set-PodeOARouteInfo -Summary 'A quick summary' -Tags 'Admin' #> function Set-PodeOARouteInfo { [CmdletBinding()] [OutputType([hashtable[]])] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [string] $Summary, [string] $Description, [System.Collections.Specialized.OrderedDictionary] $ExternalDoc, [string] $OperationId, [string[]] $Tags, [switch] $Deprecated, [switch] $PassThru, [string[]] $DefinitionTag ) begin { # Initialize an array to hold piped-in values $pipelineValue = @() } process { # Add the current piped-in value to the array $pipelineValue += $_ } end { # Set Route to the array of values if ($pipelineValue.Count -gt 1) { $Route = $pipelineValue } $defaultTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($r in @($Route)) { if ($DefinitionTag) { if ((Compare-Object -ReferenceObject $r.OpenApi.DefinitionTag -DifferenceObject $DefinitionTag).Count -ne 0) { if ($r.OpenApi.IsDefTagConfigured ) { # Definition Tag for a Route cannot be changed. throw ($PodeLocale.definitionTagChangeNotAllowedExceptionMessage) } $r.OpenApi.DefinitionTag = $defaultTag $r.OpenApi.IsDefTagConfigured = $true } } else { if (! $r.OpenApi.IsDefTagConfigured ) { $r.OpenApi.DefinitionTag = $defaultTag $r.OpenApi.IsDefTagConfigured = $true } } if ($OperationId) { if ($Route.Count -gt 1) { # OperationID:$OperationId has to be unique and cannot be applied to an array throw ($PodeLocale.operationIdMustBeUniqueForArrayExceptionMessage -f $OperationId) } foreach ($tag in $defaultTag) { if ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId -ccontains $OperationId) { # OperationID:$OperationId has to be unique throw ($PodeLocale.operationIdMustBeUniqueExceptionMessage -f $OperationId) } $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId += $OperationId } $r.OpenApi.OperationId = $OperationId } if ($Summary) { $r.OpenApi.Summary = $Summary } if ($Description) { $r.OpenApi.Description = $Description } if ($Tags) { $r.OpenApi.Tags = $Tags } if ($ExternalDocs) { $r.OpenApi.ExternalDocs = $ExternalDoc } $r.OpenApi.Swagger = $true if ($Deprecated.IsPresent) { $r.OpenApi.Deprecated = $Deprecated.IsPresent } } if ($PassThru) { return $Route } } } <# .SYNOPSIS Adds a route that enables a viewer to display OpenAPI docs, such as Swagger, ReDoc, RapiDoc, StopLight, Explorer, RapiPdf or Bookmarks. .DESCRIPTION Adds a route that enables a viewer to display OpenAPI docs, such as Swagger, ReDoc, RapiDoc, StopLight, Explorer, RapiPdf or Bookmarks. .LINK https://github.com/mrin9/RapiPdf .LINK https://github.com/Authress-Engineering/openapi-explorer .LINK https://github.com/stoplightio/elements .LINK https://github.com/rapi-doc/RapiDoc .LINK https://github.com/Redocly/redoc .LINK https://github.com/swagger-api/swagger-ui .PARAMETER Type The Type of OpenAPI viewer to use. .PARAMETER Path The route Path where the docs can be accessed. (Default: "/$Type") .PARAMETER OpenApiUrl The URL where the OpenAPI definition can be retrieved. (Default is the OpenAPI path from Enable-PodeOpenApi) .PARAMETER Middleware Like normal Routes, an array of Middleware that will be applied. .PARAMETER Title The title of the web page. (Default is the OpenAPI title from Enable-PodeOpenApi) .PARAMETER DarkMode If supplied, the page will be rendered using a dark theme (this is not supported for all viewers). .PARAMETER EndpointName The EndpointName of an Endpoint(s) to bind the static Route against.This parameter is normally not required. The Endpoint is retrieved by the OpenAPI DefinitionTag .PARAMETER Authentication The name of an Authentication method which should be used as middleware on this Route. .PARAMETER Role One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method. .PARAMETER Group One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method. .PARAMETER Scope One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method. .PARAMETER Bookmarks If supplied, create a new documentation bookmarks page .PARAMETER Editor If supplied, enable the Swagger-Editor .PARAMETER NoAdvertise If supplied, it is not going to state the documentation URL at the startup of the server .PARAMETER DefinitionTag A string representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Enable-PodeOAViewer -Type Swagger -DarkMode .EXAMPLE Enable-PodeOAViewer -Type ReDoc -Title 'Some Title' -OpenApi 'http://some-url/openapi' .EXAMPLE Enable-PodeOAViewer -Bookmarks Adds a route that enables a viewer to display with links to any documentation tool associated with the OpenApi. #> function Enable-PodeOAViewer { [CmdletBinding(DefaultParameterSetName = 'Doc')] param( [Parameter(Mandatory = $true, ParameterSetName = 'Doc')] [ValidateSet('Swagger', 'ReDoc', 'RapiDoc', 'StopLight', 'Explorer', 'RapiPdf' )] [string] $Type, [string] $Path, [string] $OpenApiUrl, [object[]] $Middleware, [string] $Title, [switch] $DarkMode, [string[]] $EndpointName, [Parameter()] [Alias('Auth')] [string] $Authentication, [Parameter()] [string[]] $Role, [Parameter()] [string[]] $Group, [Parameter()] [string[]] $Scope, [Parameter(Mandatory = $true, ParameterSetName = 'Bookmarks')] [switch] $Bookmarks, [Parameter( ParameterSetName = 'Bookmarks')] [switch] $NoAdvertise, [Parameter(Mandatory = $true, ParameterSetName = 'Editor')] [switch] $Editor, [string] $DefinitionTag ) $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag # If no EndpointName try to reetrieve the EndpointName from the DefinitionTag if exist if ([string]::IsNullOrWhiteSpace($EndpointName) -and $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.EndpointName) { $EndpointName = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.EndpointName } # error if there's no OpenAPI URL $OpenApiUrl = Protect-PodeValue -Value $OpenApiUrl -Default $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].Path if ([string]::IsNullOrWhiteSpace($OpenApiUrl)) { # No OpenAPI URL supplied for $Type throw ($PodeLocale.noOpenApiUrlSuppliedExceptionMessage -f $Type) } # fail if no title $Title = Protect-PodeValue -Value $Title -Default $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.Title if ([string]::IsNullOrWhiteSpace($Title)) { # No title supplied for $Type page throw ($PodeLocale.noTitleSuppliedForPageExceptionMessage -f $Type) } if ($Editor.IsPresent) { # set a default path $Path = Protect-PodeValue -Value $Path -Default '/editor' if ([string]::IsNullOrWhiteSpace($Title)) { # No route path supplied for $Type page throw ($PodeLocale.noRoutePathSuppliedForPageExceptionMessage -f $Type) } if (Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag) { # This version on Swagger-Editor doesn't support OpenAPI 3.1 throw ($PodeLocale.swaggerEditorDoesNotSupportOpenApi31ExceptionMessage) } # setup meta info $meta = @{ Title = $Title OpenApi = "$($OpenApiUrl)?format=yaml" DarkMode = $DarkMode DefinitionTag = $DefinitionTag SwaggerEditorDist = 'https://unpkg.com/swagger-editor-dist@4' } Add-PodeRoute -Method Get -Path $Path ` -Middleware $Middleware -ArgumentList $meta ` -EndpointName $EndpointName -Authentication $Authentication ` -Role $Role -Scope $Scope -Group $Group ` -ScriptBlock { param($meta) $Data = @{ Title = $meta.Title OpenApi = $meta.OpenApi SwaggerEditorDist = $meta.SwaggerEditorDist } $podeRoot = Get-PodeModuleMiscPath Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, 'default-swagger-editor.html.pode')) -Data $Data } $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer['editor'] = $Path } elseif ($Bookmarks.IsPresent) { # set a default path $Path = Protect-PodeValue -Value $Path -Default '/bookmarks' if ([string]::IsNullOrWhiteSpace($Title)) { # No route path supplied for $Type page throw ($PodeLocale.noRoutePathSuppliedForPageExceptionMessage -f $Type) } # setup meta info $meta = @{ Title = $Title OpenApi = $OpenApiUrl DarkMode = $DarkMode DefinitionTag = $DefinitionTag } $route = Add-PodeRoute -Method Get -Path $Path ` -Middleware $Middleware -ArgumentList $meta ` -EndpointName $EndpointName -Authentication $Authentication ` -Role $Role -Scope $Scope -Group $Group ` -PassThru -ScriptBlock { param($meta) $Data = @{ Title = $meta.Title OpenApi = $meta.OpenApi } $DefinitionTag = $meta.DefinitionTag foreach ($type in $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer.Keys) { $Data[$type] = $true $Data["$($type)_path"] = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer[$type] } $podeRoot = Get-PodeModuleMiscPath Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, 'default-doc-bookmarks.html.pode')) -Data $Data } if (! $NoAdvertise.IsPresent) { $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.bookmarks = @{ path = $Path route = @() openApiUrl = $OpenApiUrl } $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.bookmarks.route += $route } } else { if ($Type -ieq 'RapiPdf' -and (Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag)) { # The Document tool RapidPdf doesn't support OpenAPI 3.1 throw ($PodeLocale.rapidPdfDoesNotSupportOpenApi31ExceptionMessage) } # set a default path $Path = Protect-PodeValue -Value $Path -Default "/$($Type.ToLowerInvariant())" if ([string]::IsNullOrWhiteSpace($Title)) { # No route path supplied for $Type page throw ($PodeLocale.noRoutePathSuppliedForPageExceptionMessage -f $Type) } # setup meta info $meta = @{ Type = $Type.ToLowerInvariant() Title = $Title OpenApi = $OpenApiUrl DarkMode = $DarkMode } $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer[$($meta.Type)] = $Path # add the viewer route Add-PodeRoute -Method Get -Path $Path -Middleware $Middleware -ArgumentList $meta ` -EndpointName $EndpointName -Authentication $Authentication ` -Role $Role -Scope $Scope -Group $Group ` -ScriptBlock { param($meta) $podeRoot = Get-PodeModuleMiscPath if ( $meta.DarkMode) { $Theme = 'dark' } else { $Theme = 'light' } Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, "default-$($meta.Type).html.pode")) -Data @{ Title = $meta.Title OpenApi = $meta.OpenApi DarkMode = $meta.DarkMode Theme = $Theme } } } } <# .SYNOPSIS Define an external docs reference. .DESCRIPTION Define an external docs reference. .PARAMETER url The link to the external documentation .PARAMETER Description A Description of the external documentation. .EXAMPLE $swaggerDoc = New-PodeOAExternalDoc -Description 'Find out more about Swagger' -Url 'http://swagger.io' Add-PodeRoute -PassThru | Set-PodeOARouteInfo -Summary 'A quick summary' -Tags 'Admin' -ExternalDoc $swaggerDoc .EXAMPLE $swaggerDoc = New-PodeOAExternalDoc -Description 'Find out more about Swagger' -Url 'http://swagger.io' Add-PodeOATag -Name 'user' -Description 'Operations about user' -ExternalDoc $swaggerDoc #> function New-PodeOAExternalDoc { param( [Parameter(Mandatory = $true)] [ValidateScript({ $_ -imatch '^https?://.+' })] $Url, [string] $Description ) $param = [ordered]@{} if ($Description) { $param.description = $Description } $param['url'] = $Url return $param } <# .SYNOPSIS Add an external docs reference to the OpenApi document. .DESCRIPTION Add an external docs reference to the OpenApi document. .PARAMETER ExternalDoc An externalDoc object .PARAMETER Name The Name of the reference. .PARAMETER url The link to the external documentation .PARAMETER Description A Description of the external documentation. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeOAExternalDoc -Name 'SwaggerDocs' -Description 'Find out more about Swagger' -Url 'http://swagger.io' .EXAMPLE $ExtDoc = New-PodeOAExternalDoc -Name 'SwaggerDocs' -Description 'Find out more about Swagger' -Url 'http://swagger.io' $ExtDoc|Add-PodeOAExternalDoc #> function Add-PodeOAExternalDoc { [CmdletBinding(DefaultParameterSetName = 'Pipe')] param( [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true, ParameterSetName = 'Pipe')] [System.Collections.Specialized.OrderedDictionary ] $ExternalDoc, [Parameter(Mandatory = $true, ParameterSetName = 'NewRef')] [ValidateScript({ $_ -imatch '^https?://.+' })] $Url, [Parameter(ParameterSetName = 'NewRef')] [string] $Description, [string[]] $DefinitionTag ) begin { $pipelineItemCount = 0 } process { $pipelineItemCount++ } end { if ($pipelineItemCount -gt 1) { throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name)) } $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { if ($PSCmdlet.ParameterSetName -ieq 'NewRef') { $param = [ordered]@{url = $Url } if ($Description) { $param.description = $Description } $PodeContext.Server.OpenAPI.Definitions[$tag].externalDocs = $param } else { $PodeContext.Server.OpenAPI.Definitions[$tag].externalDocs = $ExternalDoc } } } } <# .SYNOPSIS Creates a OpenAPI Tag reference property. .DESCRIPTION Creates a new OpenAPI tag reference. .PARAMETER Name The Name of the tag. .PARAMETER Description A Description of the tag. .PARAMETER ExternalDoc If supplied, the tag references an existing external documentation reference. The parameter is created by Add-PodeOAExternalDoc .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeOATag -Name 'store' -Description 'Access to Petstore orders' -ExternalDoc 'SwaggerDocs' #> function Add-PodeOATag { param( [Parameter(Mandatory = $true)] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Name, [string] $Description, [System.Collections.Specialized.OrderedDictionary] $ExternalDoc, [string[]] $DefinitionTag ) $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { $param = [ordered]@{ 'name' = $Name } if ($Description) { $param.description = $Description } if ($ExternalDoc) { $param.externalDocs = $ExternalDoc } $PodeContext.Server.OpenAPI.Definitions[$tag].tags[$Name] = $param } } <# .SYNOPSIS Creates an OpenAPI metadata. .DESCRIPTION Creates an OpenAPI metadata like TermOfService, license and so on. The metadata MAY be used by the clients if needed, and MAY be presented in editing or documentation generation tools for convenience. .PARAMETER Title The Title of the API. .PARAMETER Version The Version of the API. The OpenAPI Specification is versioned using Semantic Versioning 2.0.0 (semver) and follows the semver specification. https://semver.org/spec/v2.0.0.html .PARAMETER Description A short description of the API. CommonMark syntax MAY be used for rich text representation. https://spec.commonmark.org/ .PARAMETER TermsOfService A URL to the Terms of Service for the API. MUST be in the format of a URL. .PARAMETER LicenseName The license name used for the API. .PARAMETER LicenseUrl A URL to the license used for the API. MUST be in the format of a URL. .PARAMETER ContactName The identifying name of the contact person/organization. .PARAMETER ContactEmail The email address of the contact person/organization. MUST be in the format of an email address. .PARAMETER ContactUrl The URL pointing to the contact information. MUST be in the format of a URL. .PARAMETER DefinitionTag A string representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeOAInfo -TermsOfService 'http://swagger.io/terms/' -License 'Apache 2.0' -LicenseUrl 'http://www.apache.org/licenses/LICENSE-2.0.html' -ContactName 'API Support' -ContactEmail 'apiteam@swagger.io' -ContactUrl 'http://example.com/support' #> function Add-PodeOAInfo { param( [string] $Title, [ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$')] [string] $Version , [string] $Description, [ValidateScript({ $_ -imatch '^https?://.+' })] [string] $TermsOfService, [string] $LicenseName, [ValidateScript({ $_ -imatch '^https?://.+' })] [string] $LicenseUrl, [string] $ContactName, [ValidateScript({ $_ -imatch '^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$' })] [string] $ContactEmail, [ValidateScript({ $_ -imatch '^https?://.+' })] [string] $ContactUrl, [string] $DefinitionTag ) $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $Info = [ordered]@{} if ($LicenseName) { $Info.license = [ordered]@{ 'name' = $LicenseName } } if ($LicenseUrl) { if ( $Info.license ) { $Info.license.url = $LicenseUrl } else { # The OpenAPI object 'license' required the property 'name'. Use -LicenseName parameter. throw ($PodeLocale.openApiLicenseObjectRequiresNameExceptionMessage) } } if ($Title) { $Info.title = $Title } elseif ( $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.title) { $Info.title = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.title } if ($Version) { $Info.version = $Version } elseif ( $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.version) { $Info.version = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.version } else { $Info.version = '1.0.0' } if ($Description ) { $Info.description = $Description } elseif ( $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.description) { $Info.description = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.description } if ($TermsOfService) { $Info['termsOfService'] = $TermsOfService } if ($ContactName -or $ContactEmail -or $ContactUrl ) { $Info['contact'] = [ordered]@{} if ($ContactName) { $Info['contact'].name = $ContactName } if ($ContactEmail) { $Info['contact'].email = $ContactEmail } if ($ContactUrl) { $Info['contact'].url = $ContactUrl } } $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info = $Info } <# .SYNOPSIS Creates a new OpenAPI example. .DESCRIPTION Creates a new OpenAPI example. .PARAMETER ParamsList Used to pipeline multiple properties .PARAMETER ContentType The Media Content Type associated with the Example. Alias: MediaType .PARAMETER Name The Name of the Example. .PARAMETER Summary Short description for the example .PARAMETER Description Long description for the example. .PARAMETER Reference A reference to a reusable component example .PARAMETER Value Embedded literal example. The value Parameter and ExternalValue parameter are mutually exclusive. To represent examples of media types that cannot naturally represented in JSON or YAML, use a string value to contain the example, escaping where necessary. .PARAMETER ExternalValue A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents. The -Value parameter and -ExternalValue parameter are mutually exclusive. | .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE New-PodeOAExample -ContentType 'text/plain' -Name 'user' -Summary = 'User Example in Plain text' -ExternalValue = 'http://foo.bar/examples/user-example.txt' .EXAMPLE $example = New-PodeOAExample -ContentType 'application/json' -Name 'user' -Summary = 'User Example' -ExternalValue = 'http://foo.bar/examples/user-example.json' | New-PodeOAExample -ContentType 'application/xml' -Name 'user' -Summary = 'User Example in XML' -ExternalValue = 'http://foo.bar/examples/user-example.xml' #> function New-PodeOAExample { [CmdletBinding(DefaultParameterSetName = 'Inbuilt')] [OutputType([System.Collections.Specialized.OrderedDictionary ])] param( [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true, ParameterSetName = 'Inbuilt')] [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true, ParameterSetName = 'Reference')] [System.Collections.Specialized.OrderedDictionary ] $ParamsList, [Parameter()] [Alias('MediaType')] [string] $ContentType, [Parameter(Mandatory = $true, ParameterSetName = 'Inbuilt')] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Name, [Parameter( ParameterSetName = 'Inbuilt')] [string] $Summary, [Parameter( ParameterSetName = 'Inbuilt')] [string] $Description, [Parameter( ParameterSetName = 'Inbuilt')] [object] $Value, [Parameter( ParameterSetName = 'Inbuilt')] [string] $ExternalValue, [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Reference, [string[]] $DefinitionTag ) begin { $pipelineValue = [ordered]@{} if (Test-PodeIsEmpty -Value $DefinitionTag) { $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag } if ($PSCmdlet.ParameterSetName -ieq 'Reference') { Test-PodeOAComponentInternal -Field examples -DefinitionTag $DefinitionTag -Name $Reference -PostValidation $Name = $Reference $Example = [ordered]@{'$ref' = "#/components/examples/$Reference" } } else { if ( $ExternalValue -and $Value) { # Parameters 'ExternalValue' and 'Value' are mutually exclusive throw ($PodeLocale.parametersMutuallyExclusiveExceptionMessage -f 'ExternalValue', 'Value') } $Example = [ordered]@{ } if ($Summary) { $Example.summary = $Summary } if ($Description) { $Example.description = $Description } if ($Value) { $Example.value = $Value } elseif ($ExternalValue) { $Example.externalValue = $ExternalValue } else { # Parameters 'Value' or 'ExternalValue' are mandatory throw ($PodeLocale.parametersValueOrExternalValueMandatoryExceptionMessage) } } $param = [ordered]@{} if ($ContentType) { $param.$ContentType = [ordered]@{ $Name = $Example } } else { $param.$Name = $Example } } process { if ($_) { $pipelineValue += $_ } } end { $examples = [ordered]@{} if ($pipelineValue.Count -gt 0) { # foreach ($p in $pipelineValue) { $examples = $pipelineValue # } } else { return $param } $key = [string]$param.Keys[0] if ($examples.Keys -contains $key) { $examples[$key] += $param[$key] } else { $examples += $param } return $examples } } <# .SYNOPSIS Adds a single encoding definition applied to a single schema property. .DESCRIPTION A single encoding definition applied to a single schema property. .PARAMETER EncodingList Used by pipe .PARAMETER Title The Name of the associated encoded property . .PARAMETER ContentType Content-Type for encoding a specific property. Default value depends on the property type: for `string` with `format` being `binary` – `application/octet-stream`; for other primitive types – `text/plain`; for `object` - `application/json`; for `array` – the default is defined based on the inner type. The value can be a specific media type (e.g. `application/json`), a wildcard media type (e.g. `image/*`), or a comma-separated list of the two types. .PARAMETER Headers A map allowing additional information to be provided as headers, for example `Content-Disposition`. `Content-Type` is described separately and SHALL be ignored in this section. This property SHALL be ignored if the request body media type is not a `multipart`. .PARAMETER Style Describes how a specific property value will be serialized depending on its type. See [Parameter Object](#parameterObject) for details on the [`style`](#parameterStyle) property. The behavior follows the same values as `query` parameters, including default values. This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`. .PARAMETER Explode When enabled, property values of type `array` or `object` generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect. When [`style`](#encodingStyle) is `form`, the `Explode` is set to `true`. This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`. .PARAMETER AllowReserved Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2) `:/?#[]@!$&'()*+,;=` to be included without percent-encoding. This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`. .EXAMPLE New-PodeOAEncodingObject -Name 'profileImage' -ContentType 'image/png, image/jpeg' -Headers ( New-PodeOAIntProperty -name 'X-Rate-Limit-Limit' -Description 'The number of allowed requests in the current period' -Default 3 -Enum @(1,2,3) -Maximum 3 ) #> function New-PodeOAEncodingObject { param ( [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )] [hashtable[]] $EncodingList, [Parameter(Mandatory = $true)] [Alias('Name')] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Title, [string] $ContentType, [hashtable[]] $Headers, [ValidateSet('Simple', 'Label', 'Matrix', 'Query', 'Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' )] [string] $Style, [switch] $Explode, [switch] $AllowReserved ) begin { $encoding = [ordered]@{ $Title = [ordered]@{} } if ($ContentType) { $encoding.$Title.contentType = $ContentType } if ($Style) { $encoding.$Title.style = $Style } if ($Headers) { $encoding.$Title.headers = $Headers } if ($Explode.IsPresent ) { $encoding.$Title.explode = $Explode.IsPresent } if ($AllowReserved.IsPresent ) { $encoding.$Title.allowReserved = $AllowReserved.IsPresent } $collectedInput = [System.Collections.Generic.List[hashtable]]::new() } process { if ($EncodingList) { $collectedInput.AddRange($EncodingList) } } end { if ($collectedInput) { return $collectedInput + $encoding } else { return $encoding } } } <# .SYNOPSIS Adds OpenAPI callback configurations to routes in a Pode web application. .PARAMETER Route The route to update info, usually from -PassThru on Add-PodeRoute. .DESCRIPTION The Add-PodeOACallBack function is used for defining OpenAPI callback configurations for routes in a Pode server. It enables setting up API specifications including detailed parameters, request body schemas, and response structures for various HTTP methods. .PARAMETER Path Specifies the callback path, usually a relative URL. The key that identifies the Path Item Object is a runtime expression evaluated in the context of a runtime HTTP request/response to identify the URL for the callback request. A simple example is `$request.body#/url`. The runtime expression allows complete access to the HTTP message, including any part of a body that a JSON Pointer (RFC6901) can reference. More information on JSON Pointer can be found at [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901). .PARAMETER Name Alias for 'Name'. A unique identifier for the callback. It must be a valid string of alphanumeric characters, periods (.), hyphens (-), and underscores (_). .PARAMETER Reference A reference to a reusable CallBack component. .PARAMETER Method Defines the HTTP method for the callback (e.g., GET, POST, PUT). Supports standard HTTP methods and a wildcard (*) for all methods. .PARAMETER Parameters The Parameter definitions the request uses (from ConvertTo-PodeOAParameter). .PARAMETER RequestBody Defines the schema of the request body. Can be set using New-PodeOARequestBody. .PARAMETER Responses Defines the possible responses for the callback. Can be set using New-PodeOAResponse. .PARAMETER DefinitionTag A array of string representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .PARAMETER PassThru If supplied, the route passed in will be returned for further chaining. .EXAMPLE Add-PodeOACallBack -Title 'test' -Path '{$request.body#/id}' -Method Post ` -RequestBody (New-PodeOARequestBody -Content @{'*/*' = (New-PodeOAStringProperty -Name 'id')}) ` -Response ( New-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet' -Array) New-PodeOAResponse -StatusCode 400 -Description 'Invalid ID supplied' | New-PodeOAResponse -StatusCode 404 -Description 'Pet not found' | New-PodeOAResponse -Default -Description 'Something is wrong' ) This example demonstrates adding a POST callback to handle a request body and define various responses based on different status codes. .NOTES Ensure that the provided parameters match the expected schema and formats of Pode and OpenAPI specifications. The function is useful for dynamically configuring and documenting API callbacks in a Pode server environment. #> function Add-PodeOACallBack { [CmdletBinding(DefaultParameterSetName = 'inbuilt')] [OutputType([hashtable[]])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [Parameter(Mandatory = $true , ParameterSetName = 'inbuilt')] [Parameter(Mandatory = $false, ParameterSetName = 'Reference')] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [string] $Reference, [Parameter(Mandatory = $true , ParameterSetName = 'inbuilt')] [string] $Path, [Parameter(Mandatory = $true, ParameterSetName = 'inbuilt')] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')] [string] $Method, [Parameter(ParameterSetName = 'inbuilt')] [hashtable[]] $Parameters, [Parameter(ParameterSetName = 'inbuilt')] [hashtable] $RequestBody, [Parameter(ParameterSetName = 'inbuilt')] [hashtable] $Responses, [switch] $PassThru, [string[]] $DefinitionTag ) begin { # Initialize an array to hold piped-in values $pipelineValue = @() } process { # Add the current piped-in value to the array $pipelineValue += $_ } end { # Set Route to the array of values if ($pipelineValue.Count -gt 1) { $Route = $pipelineValue } foreach ($r in @($Route)) { $oaDefinitionTag = Test-PodeRouteOADefinitionTag -Route $r -DefinitionTag $DefinitionTag foreach ($tag in $oaDefinitionTag) { if ($Reference) { Test-PodeOAComponentInternal -Field callbacks -DefinitionTag $tag -Name $Reference -PostValidation if (!$Name) { $Name = $Reference } if (! ($r.OpenApi.CallBacks.Keys -Contains $tag)) { $r.OpenApi.CallBacks[$tag] = [ordered]@{} } $r.OpenApi.CallBacks[$tag].$Name = [ordered]@{ '$ref' = "#/components/callbacks/$Reference" } } else { if (! ($r.OpenApi.CallBacks.Keys -Contains $tag)) { $r.OpenApi.CallBacks[$tag] = [ordered]@{} } $r.OpenApi.CallBacks[$tag].$Name = New-PodeOAComponentCallBackInternal -Params $PSBoundParameters -DefinitionTag $tag } } } if ($PassThru) { return $Route } } } <# .SYNOPSIS Adds a response definition to the Callback. .DESCRIPTION Adds a response definition to the Callback. .PARAMETER ResponseList Hidden parameter used to pipe multiple CallBacksResponses .PARAMETER StatusCode The HTTP StatusCode for the response.To define a range of response codes, this field MAY contain the uppercase wildcard character `X`. For example, `2XX` represents all response codes between `[200-299]`. Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`. If a response is defined using an explicit code, the explicit code definition takes precedence over the range definition for that code. .PARAMETER Content The content-types and schema the response returns (the schema is created using the Property functions). Alias: ContentSchemas .PARAMETER Headers The header name and schema the response returns (the schema is created using Add-PodeOAComponentHeader cmd-let). Alias: HeaderSchemas .PARAMETER Description A Description of the response. (Default: the HTTP StatusCode description) .PARAMETER Reference A Reference Name of an existing component response to use. .PARAMETER Links A Response link definition .PARAMETER Default If supplied, the response will be used as a default response - this overrides the StatusCode supplied. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE New-PodeOAResponse -StatusCode 200 -Content ( New-PodeOAContentMediaType -ContentType 'application/json' -Content(New-PodeOAIntProperty -Name 'userId' -Object) ) .EXAMPLE New-PodeOAResponse -StatusCode 200 -Content @{ 'application/json' = 'UserIdSchema' } .EXAMPLE New-PodeOAResponse -StatusCode 200 -Reference 'OKResponse' .EXAMPLE Add-PodeOACallBack -Title 'test' -Path '$request.body#/id' -Method Post -RequestBody ( New-PodeOARequestBody -Content (New-PodeOAContentMediaType -ContentType '*/*' -Content (New-PodeOAStringProperty -Name 'id')) ) ` -Response ( New-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet' -Array) | New-PodeOAResponse -StatusCode 400 -Description 'Invalid ID supplied' | New-PodeOAResponse -StatusCode 404 -Description 'Pet not found' | New-PodeOAResponse -Default -Description 'Something is wrong' ) #> function New-PodeOAResponse { [CmdletBinding(DefaultParameterSetName = 'Schema')] [OutputType([hashtable])] param( [Parameter(ValueFromPipeline = $true , Position = 0, DontShow = $true )] [hashtable] $ResponseList, [Parameter(Mandatory = $true, ParameterSetName = 'Schema')] [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [ValidatePattern('^([1-5][0-9][0-9]|[1-5]XX)$')] [string] $StatusCode, [Parameter(ParameterSetName = 'Schema')] [Parameter(ParameterSetName = 'SchemaDefault')] [Alias('ContentSchemas')] [hashtable] $Content, [Alias('HeaderSchemas')] [AllowEmptyString()] [ValidateNotNullOrEmpty()] [ValidateScript({ $_ -is [string] -or $_ -is [string[]] -or $_ -is [hashtable] })] $Headers, [Parameter(Mandatory = $true, ParameterSetName = 'Schema')] [Parameter(Mandatory = $true, ParameterSetName = 'SchemaDefault')] [string] $Description , [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [Parameter(ParameterSetName = 'ReferenceDefault')] [string] $Reference, [Parameter(Mandatory = $true, ParameterSetName = 'ReferenceDefault')] [Parameter(Mandatory = $true, ParameterSetName = 'SchemaDefault')] [switch] $Default, [Parameter(ParameterSetName = 'Schema')] [Parameter(ParameterSetName = 'SchemaDefault')] [System.Collections.Specialized.OrderedDictionary ] $Links, [string[]] $DefinitionTag ) begin { if (Test-PodeIsEmpty -Value $DefinitionTag) { $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag } # override status code with default if ($Default) { $code = 'default' } else { $code = "$($StatusCode)" } $response = [ordered]@{} } process { foreach ($tag in $DefinitionTag) { if (! $response.$tag) { $response.$tag = [ordered] @{} } $response[$tag][$code] = New-PodeOResponseInternal -DefinitionTag $tag -Params $PSBoundParameters } } end { if ($ResponseList) { foreach ($tag in $DefinitionTag) { if (! ($ResponseList.Keys -Contains $tag )) { $ResponseList[$tag] = [ordered] @{} } $response[$tag].GetEnumerator() | ForEach-Object { $ResponseList[$tag][$_.Key] = $_.Value } } return $ResponseList } else { return $response } } } <# .SYNOPSIS Creates media content type definitions for OpenAPI specifications. .DESCRIPTION The New-PodeOAContentMediaType function generates media content type definitions suitable for use in OpenAPI specifications. It supports various media types and allows for the specification of content as either a single object or an array of objects. .PARAMETER ContentType An array of strings specifying the media types to be defined. Media types should conform to standard MIME types (e.g., 'application/json', 'image/png'). The function validates these media types against a regular expression to ensure they are properly formatted. Alias: MediaType .PARAMETER Content The content definition for the media type. This could be an object representing the structure of the content expected for the specified media types. .PARAMETER Array A switch parameter, used in the 'Array' parameter set, to indicate that the content should be treated as an array. .PARAMETER UniqueItems A switch parameter, used in the 'Array' parameter set, to specify that items in the array should be unique. .PARAMETER MinItems Used in the 'Array' parameter set to specify the minimum number of items that should be present in the array. .PARAMETER MaxItems Used in the 'Array' parameter set to specify the maximum number of items that should be present in the array. .PARAMETER Title Used in the 'Array' parameter set to provide a title for the array content. .PARAMETER Upload If provided configure the media for an upload changing the result based on the OpenApi version .PARAMETER ContentEncoding Define the content encoding for upload (Default Binary) .PARAMETER PartContentMediaType Define the content encoding for multipart upload .EXAMPLE Add-PodeRoute -PassThru -Method get -Path '/pet/findByStatus' -Authentication 'Login-OAuth2' -Scope 'read' -ScriptBlock { Write-PodeJsonResponse -Value 'done' -StatusCode 200 } | Set-PodeOARouteInfo -Summary 'Finds Pets by status' -Description 'Multiple status values can be provided with comma separated strings' -Tags 'pet' -OperationId 'findPetsByStatus' -PassThru | Set-PodeOARequest -PassThru -Parameters @( (New-PodeOAStringProperty -Name 'status' -Description 'Status values that need to be considered for filter' -Default 'available' -Enum @('available', 'pending', 'sold') | ConvertTo-PodeOAParameter -In Query) ) | Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet' -Array -UniqueItems) -PassThru | Add-PodeOAResponse -StatusCode 400 -Description 'Invalid status value' This example demonstrates the use of New-PodeOAContentMediaType in defining a GET route '/pet/findByStatus' in an OpenAPI specification. The route includes request parameters and responses with media content types for 'application/json' and 'application/xml'. .EXAMPLE $content = [ordered]@{ type = 'string' } $mediaType = 'application/json' New-PodeOAContentMediaType -ContentType $mediaType -Content $content This example creates a media content type definition for 'application/json' with a simple string content type. .EXAMPLE $content = [ordered]@{ type = 'object'; properties = [ordered]@{ name = @{ type = 'string' } } } $mediaTypes = 'application/json', 'application/xml' New-PodeOAContentMediaType -ContentType $mediaTypes -Content $content -Array -MinItems 1 -MaxItems 5 -Title 'UserList' This example demonstrates defining an array of objects for both 'application/json' and 'application/xml' media types, with a specified range for the number of items and a title. .EXAMPLE Add-PodeRoute -PassThru -Method get -Path '/pet/findByStatus' -Authentication 'Login-OAuth2' -Scope 'read' -ScriptBlock { Write-PodeJsonResponse -Value 'done' -StatusCode 200 } | Set-PodeOARouteInfo -Summary 'Finds Pets by status' -Description 'Multiple status values can be provided with comma separated strings' -Tags 'pet' -OperationId 'findPetsByStatus' -PassThru | Set-PodeOARequest -PassThru -Parameters @( (New-PodeOAStringProperty -Name 'status' -Description 'Status values that need to be considered for filter' -Default 'available' -Enum @('available', 'pending', 'sold') | ConvertTo-PodeOAParameter -In Query) ) | Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet' -Array -UniqueItems) -PassThru | Add-PodeOAResponse -StatusCode 400 -Description 'Invalid status value' This example demonstrates the use of New-PodeOAContentMediaType in defining a GET route '/pet/findByStatus' in an OpenAPI specification. The route includes request parameters and responses with media content types for 'application/json' and 'application/xml'. .NOTES This function is useful for dynamically creating media type specifications in OpenAPI documentation, providing flexibility in defining the expected content structure for different media types. #> function New-PodeOAContentMediaType { [CmdletBinding(DefaultParameterSetName = 'inbuilt')] [OutputType([System.Collections.Specialized.OrderedDictionary])] param ( [Parameter()] [Alias('MediaType')] [string[]] $ContentType = '*/*', [object] $Content, [Parameter( Mandatory = $true, ParameterSetName = 'Array')] [switch] $Array, [Parameter(ParameterSetName = 'Array')] [switch] $UniqueItems, [Parameter(ParameterSetName = 'Array')] [int] $MinItems, [Parameter(ParameterSetName = 'Array')] [int] $MaxItems, [Parameter(ParameterSetName = 'Array')] [string] $Title, [Parameter(Mandatory = $true, ParameterSetName = 'Upload')] [switch] $Upload, [Parameter( ParameterSetName = 'Upload')] [ValidateSet('Binary', 'Base64')] [string] $ContentEncoding = 'Binary', [Parameter( ParameterSetName = 'Upload')] [string] $PartContentMediaType ) $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $props = [ordered]@{} foreach ($media in $ContentType) { if ($media -inotmatch '^(application|audio|image|message|model|multipart|text|video|\*)\/[\w\.\-\*]+(;[\s]*(charset|boundary)=[\w\.\-\*]+)*$') { # Invalid 'content-type' found for schema: $media throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $media) } if ($Upload.IsPresent) { if ( $media -ieq 'multipart/form-data' -and $Content) { $Content = [ordered]@{'__upload' = [ordered]@{ 'content' = $Content 'partContentMediaType' = $PartContentMediaType } } } else { $Content = [ordered]@{'__upload' = [ordered]@{ 'contentEncoding' = $ContentEncoding } } } } else { if ($null -eq $Content ) { $Content = [ordered]@{} } } if ($Array.IsPresent) { $props[$media] = @{ __array = $true __content = $Content __upload = $Upload } if ($MinItems) { $props[$media].__minItems = $MinItems } if ($MaxItems) { $props[$media].__maxItems = $MaxItems } if ($Title) { $props[$media].__title = $Title } if ($UniqueItems.IsPresent) { $props[$media].__uniqueItems = $UniqueItems.IsPresent } } else { $props[$media] = $Content } } return $props } <# .SYNOPSIS Adds a response link to an existing list of OpenAPI response links. .DESCRIPTION The New-PodeOAResponseLink function is designed to add a new response link to an existing OrderedDictionary of OpenAPI response links. It can be used to define complex response structures with links to other operations or references, and it supports adding multiple links through pipeline input. .PARAMETER LinkList An OrderedDictionary of existing response links. This parameter is intended for use with pipeline input, allowing the function to add multiple links to the collection. It is hidden from standard help displays to emphasize its use primarily in pipeline scenarios. .PARAMETER Name Mandatory. A unique name for the response link. Must be a valid string composed of alphanumeric characters, periods (.), hyphens (-), and underscores (_). .PARAMETER Description A brief description of the response link. CommonMark syntax may be used for rich text representation. For more information on CommonMark syntax, see [CommonMark Specification](https://spec.commonmark.org/). .PARAMETER OperationId The name of an existing, resolvable OpenAPI Specification (OAS) operation, as defined with a unique `operationId`. This parameter is mandatory when using the 'OperationId' parameter set and is mutually exclusive of the `OperationRef` field. It is used to specify the unique identifier of the operation the link is associated with. .PARAMETER OperationRef A relative or absolute URI reference to an OAS operation. This parameter is mandatory when using the 'OperationRef' parameter set and is mutually exclusive of the `OperationId` field. It MUST point to an Operation Object. Relative `operationRef` values MAY be used to locate an existing Operation Object in the OpenAPI specification. .PARAMETER Reference A Reference Name of an existing component link to use. .PARAMETER Parameters A map representing parameters to pass to an operation as specified with `operationId` or identified via `operationRef`. The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation. Parameter names can be qualified using the parameter location syntax `[{in}.]{name}` for operations that use the same parameter name in different locations (e.g., path.id). .PARAMETER RequestBody A string representing the request body to use as a request body when calling the target. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE $links = New-PodeOAResponseLink -LinkList $links -Name 'address' -OperationId 'getUserByName' -Parameters @{'username' = '$request.path.username'} Add-PodeOAResponse -StatusCode 200 -Content @{'application/json' = 'User'} -Links $links This example demonstrates creating and adding a link named 'address' associated with the operation 'getUserByName' to an OrderedDictionary of links. The updated dictionary is then used in the 'Add-PodeOAResponse' function to define a response with a status code of 200. .NOTES The function supports adding links either by specifying an 'OperationId' or an 'OperationRef', making it versatile for different OpenAPI specification needs. It's important to match the parameters and response structures as per the OpenAPI specification to ensure the correct functionality of the API documentation. #> function New-PodeOAResponseLink { [CmdletBinding(DefaultParameterSetName = 'OperationId')] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(ValueFromPipeline = $true , Position = 0, DontShow = $true )] [System.Collections.Specialized.OrderedDictionary ] $LinkList, [Parameter( Mandatory = $false, ParameterSetName = 'Reference')] [Parameter( Mandatory = $true, ParameterSetName = 'OperationRef')] [Parameter( Mandatory = $true, ParameterSetName = 'OperationId')] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Name, [Parameter( ParameterSetName = 'OperationRef')] [Parameter( ParameterSetName = 'OperationId')] [string] $Description, [Parameter(Mandatory = $true, ParameterSetName = 'OperationId')] [string] $OperationId, [Parameter(Mandatory = $true, ParameterSetName = 'OperationRef')] [string] $OperationRef, [Parameter( ParameterSetName = 'OperationRef')] [Parameter( ParameterSetName = 'OperationId')] [hashtable] $Parameters, [Parameter( ParameterSetName = 'OperationRef')] [Parameter( ParameterSetName = 'OperationId')] [string] $RequestBody, [Parameter(Mandatory = $true, ParameterSetName = 'Reference')] [string] $Reference, [string[]] $DefinitionTag ) begin { if (Test-PodeIsEmpty -Value $DefinitionTag) { $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag } if ($Reference) { Test-PodeOAComponentInternal -Field links -DefinitionTag $DefinitionTag -Name $Reference -PostValidation if (!$Name) { $Name = $Reference } $link = [ordered]@{ $Name = [ordered]@{ '$ref' = "#/components/links/$Reference" } } } else { $link = [ordered]@{ $Name = New-PodeOAResponseLinkInternal -Params $PSBoundParameters } } } process { } end { if ($LinkList) { $link.GetEnumerator() | ForEach-Object { $LinkList[$_.Key] = $_.Value } return $LinkList } else { return [System.Collections.Specialized.OrderedDictionary] $link } } } <# .SYNOPSIS Sets metadate for the supplied route. .DESCRIPTION Sets metadate for the supplied route, such as Summary and Tags. .PARAMETER Route The route to update info, usually from -PassThru on Add-PodeRoute. .PARAMETER Path The URI path for the Route. .PARAMETER Method The HTTP Method of this Route, multiple can be supplied. .PARAMETER Servers A list of external endpoint. created with New-PodeOAServerEndpoint .PARAMETER PassThru If supplied, the route passed in will be returned for further chaining. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeOAExternalRoute -PassThru -Method Get -Path '/peta/:id' -Servers ( New-PodeOAServerEndpoint -Url 'http://ext.server.com/api/v12' -Description 'ext test server' | New-PodeOAServerEndpoint -Url 'http://ext13.server.com/api/v12' -Description 'ext test server 13' ) | Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID' -OperationId 'getPetsById' -PassThru | Set-PodeOARequest -PassThru -Parameters @( (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) | Add-PodeOAResponse -StatusCode 200 -Description 'pet response' -Content (@{ '*/*' = New-PodeOASchemaProperty -ComponentSchema 'Pet' -array }) -PassThru | Add-PodeOAResponse -Default -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru .EXAMPLE Add-PodeRoute -PassThru -Method Get -Path '/peta/:id' -ScriptBlock { Write-PodeJsonResponse -Value 'done' -StatusCode 200 } | Add-PodeOAExternalRoute -PassThru -Servers ( New-PodeOAServerEndpoint -Url 'http://ext.server.com/api/v12' -Description 'ext test server' | New-PodeOAServerEndpoint -Url 'http://ext13.server.com/api/v12' -Description 'ext test server 13' ) | Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID' -OperationId 'getPetsById' -PassThru | Set-PodeOARequest -PassThru -Parameters @( (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) | Add-PodeOAResponse -StatusCode 200 -Description 'pet response' -Content (@{ '*/*' = New-PodeOASchemaProperty -ComponentSchema 'Pet' -array }) -PassThru | Add-PodeOAResponse -Default -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru #> function Add-PodeOAExternalRoute { [CmdletBinding(DefaultParameterSetName = 'Pipeline')] [OutputType([hashtable[]], ParameterSetName = 'Pipeline')] [OutputType([hashtable], ParameterSetName = 'builtin')] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Pipeline')] [ValidateNotNullOrEmpty()] [hashtable[]] $Route, [Parameter(Mandatory = $true , ParameterSetName = 'BuiltIn')] [string] $Path, [Parameter(Mandatory = $true)] [ValidateScript({ $_.Count -gt 0 })] [hashtable[]] $Servers, [Parameter(Mandatory = $true, ParameterSetName = 'BuiltIn')] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')] [string] $Method, [switch] $PassThru, [Parameter( ParameterSetName = 'BuiltIn')] [string[]] $DefinitionTag ) begin { # Initialize an array to hold piped-in values $pipelineValue = @() } process { if ($PSCmdlet.ParameterSetName -eq 'Pipeline') { # Add the current piped-in value to the array $pipelineValue += $_ } } end { $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'builtin' { # ensure the route has appropriate slashes $Path = Update-PodeRouteSlash -Path $Path $OpenApiPath = ConvertTo-PodeOARoutePath -Path $Path $Path = Resolve-PodePlaceholder -Path $Path $extRoute = @{ Method = $Method.ToLower() Path = $Path Local = $false OpenApi = @{ Path = $OpenApiPath Responses = [ordered]@{} Parameters = [ordered]@{} RequestBody = [ordered]@{} callbacks = [ordered]@{} Authentication = @() Servers = $Servers DefinitionTag = $DefinitionTag } } foreach ($tag in $DefinitionTag) { #add the default OpenApi responses if ( $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses) { $extRoute.OpenApi.Responses = Copy-PodeObjectDeepClone -InputObject $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses } if (! (Test-PodeOAComponentExternalPath -DefinitionTag $tag -Name $Path)) { $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.externalPath[$Path] = [ordered]@{} } $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.externalPath.$Path[$Method] = $extRoute } if ($PassThru) { return $extRoute } } 'pipeline' { # Set Route to the array of values if ($pipelineValue.Count -gt 1) { $Route = $pipelineValue } foreach ($r in $Route) { $r.OpenApi.Servers = $Servers } if ($PassThru) { return $Route } } } } } <# .SYNOPSIS Creates an OpenAPI Server Object. .DESCRIPTION Creates an OpenAPI Server Object to use with Add-PodeOAExternalRoute .PARAMETER ServerEndpointList Used for piping .PARAMETER Url A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in `{`brackets`}`. .PARAMETER Description An optional string describing the host designated by the URL. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. .EXAMPLE New-PodeOAServerEndpoint -Url 'https://myserver.io/api' -Description 'My test server' .EXAMPLE New-PodeOAServerEndpoint -Url '/api' -Description 'My local server' #> function New-PodeOAServerEndpoint { param ( [Parameter(ValueFromPipeline = $true , Position = 0, DontShow = $true )] [hashtable[]] $ServerEndpointList, [Parameter(Mandatory = $true)] [ValidatePattern('^(https?://|/).+')] [string] $Url, [string] $Description ) begin { $lUrl = [ordered]@{url = $Url } if ($Description) { $lUrl.description = $Description } $collectedInput = [System.Collections.Generic.List[hashtable]]::new() } process { if ($ServerEndpointList) { $collectedInput.AddRange($ServerEndpointList) } } end { if ($ServerEndpointList) { return $collectedInput + $lUrl } else { return $lUrl } } } <# .SYNOPSIS Sets metadate for the supplied route. .DESCRIPTION Sets metadate for the supplied route, such as Summary and Tags. .PARAMETER Name Alias for 'Name'. A unique identifier for the webhook. It must be a valid string of alphanumeric characters, periods (.), hyphens (-), and underscores (_). .PARAMETER Method The HTTP Method of this Route, multiple can be supplied. .PARAMETER PassThru If supplied, the route passed in will be returned for further chaining. .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Add-PodeOAWebhook -PassThru -Method Get | Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID' -OperationId 'getPetsById' -PassThru | Set-PodeOARequest -PassThru -Parameters @( (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) | Add-PodeOAResponse -StatusCode 200 -Description 'pet response' -Content (@{ '*/*' = New-PodeOASchemaProperty -ComponentSchema 'Pet' -array }) -PassThru | Add-PodeOAResponse -Default -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru #> function Add-PodeOAWebhook { param( [Parameter(Mandatory = $true)] [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')] [string] $Name, [Parameter(Mandatory = $true )] [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')] [string] $Method, [switch] $PassThru, [string[]] $DefinitionTag ) $_definitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $refRoute = @{ Method = $Method.ToLower() NotPrepared = $true OpenApi = @{ Responses = [ordered]@{} Parameters = [ordered]@{} RequestBody = [ordered]@{} callbacks = [ordered]@{} Authentication = @() DefinitionTag = $_definitionTag IsDefTagConfigured = ($null -ne $DefinitionTag) #Definition Tag has been configured (Not default) } } foreach ($tag in $_definitionTag) { if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $tag ) { # The Webhooks feature is not supported in OpenAPI v3.0.x throw ($PodeLocale.webhooksFeatureNotSupportedInOpenApi30ExceptionMessage) } $PodeContext.Server.OpenAPI.Definitions[$tag].webhooks[$Name] = $refRoute } if ($PassThru) { return $refRoute } } <# .SYNOPSIS Select a group of OpenAPI Definions for modification. .DESCRIPTION Select a group of OpenAPI Definions for modification. .PARAMETER Tag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. If Tag is empty or null the default Definition is selected .PARAMETER ScriptBlock The ScriptBlock that will modified the group. .EXAMPLE Select-PodeOADefinition -Tag 'v3', 'v3.1' -Script { New-PodeOAIntProperty -Name 'id'-Format Int64 -Example 10 -Required | New-PodeOAIntProperty -Name 'petId' -Format Int64 -Example 198772 -Required | New-PodeOAIntProperty -Name 'quantity' -Format Int32 -Example 7 -Required | New-PodeOAStringProperty -Name 'shipDate' -Format Date-Time | New-PodeOAStringProperty -Name 'status' -Description 'Order Status' -Required -Example 'approved' -Enum @('placed', 'approved', 'delivered') | New-PodeOABoolProperty -Name 'complete' | New-PodeOAObjectProperty -XmlName 'order' | Add-PodeOAComponentSchema -Name 'Order' New-PodeOAContentMediaType -ContentType 'application/json', 'application/xml' -Content 'Pet' | Add-PodeOAComponentRequestBody -Name 'Pet' -Description 'Pet object that needs to be added to the store' } #> function Select-PodeOADefinition { [CmdletBinding()] param( [string[]] $Tag, [Parameter(Mandatory = $true)] [scriptblock] $Scriptblock ) if (Test-PodeIsEmpty $Scriptblock) { # No ScriptBlock supplied throw ($PodeLocale.noScriptBlockSuppliedExceptionMessage) } if (Test-PodeIsEmpty -Value $Tag) { $Tag = $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag } else { $Tag = Test-PodeOADefinitionTag -Tag $Tag } # check for scoped vars $Scriptblock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $Scriptblock -PSSession $PSCmdlet.SessionState $PodeContext.Server.OpenApi.DefinitionTagSelectionStack.Push($PodeContext.Server.OpenAPI.SelectedDefinitionTag) $PodeContext.Server.OpenAPI.SelectedDefinitionTag = $Tag $null = Invoke-PodeScriptBlock -ScriptBlock $Scriptblock -UsingVariables $usingVars -Splat $PodeContext.Server.OpenAPI.SelectedDefinitionTag = $PodeContext.Server.OpenApi.DefinitionTagSelectionStack.Pop() } <# .SYNOPSIS Renames an existing OpenAPI definition tag in Pode. .DESCRIPTION This function renames an existing OpenAPI definition tag to a new tag name. If the specified tag is the default definition tag, it updates the default tag as well. It ensures that the new tag name does not already exist and that the function is not used within a Select-PodeOADefinition ScriptBlock. .PARAMETER Tag The current tag name of the OpenAPI definition. If not specified, the default definition tag is used. .PARAMETER NewTag The new tag name for the OpenAPI definition. This parameter is mandatory. .EXAMPLE Rename-PodeOADefinitionTag -Tag 'oldTag' -NewTag 'newTag' Rename a specific OpenAPI definition tag .EXAMPLE Rename-PodeOADefinitionTag -NewTag 'newDefaultTag' Rename the default OpenAPI definition tag .NOTES This function will throw an error if: - It is used inside a Select-PodeOADefinition ScriptBlock. - The new tag name already exists. - The current tag name does not exist. #> function Rename-PodeOADefinitionTag { param ( [Parameter(Mandatory = $false)] [string]$Tag, [Parameter(Mandatory = $true)] [string]$NewTag ) # Check if the function is being used inside a Select-PodeOADefinition ScriptBlock if ($PodeContext.Server.OpenApi.DefinitionTagSelectionStack.Count -gt 0) { throw ($PodeLocale.renamePodeOADefinitionTagExceptionMessage) } # Check if the new tag name already exists in the OpenAPI definitions if ($PodeContext.Server.OpenAPI.Definitions.ContainsKey($NewTag)) { throw ($PodeLocale.openApiDefinitionAlreadyExistsExceptionMessage -f $NewTag ) } # If the Tag parameter is null or whitespace, use the default definition tag if ([string]::IsNullOrWhiteSpace($Tag)) { $Tag = $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag = $NewTag # Update the default definition tag } else { # Test if the specified tag exists in the OpenAPI definitions Test-PodeOADefinitionTag -Tag $Tag } # Rename the definition tag in the OpenAPI definitions $PodeContext.Server.OpenAPI.Definitions[$NewTag] = $PodeContext.Server.OpenAPI.Definitions[$Tag] $PodeContext.Server.OpenAPI.Definitions.Remove($Tag) # Update the selected definition tag if it matches the old tag if ($PodeContext.Server.OpenAPI.SelectedDefinitionTag -eq $Tag) { $PodeContext.Server.OpenAPI.SelectedDefinitionTag = $NewTag } } <# .SYNOPSIS Check if a Definition exist .DESCRIPTION Check if a Definition exist. If the parameter Tag is empty or Null $PodeContext.Server.OpenAPI.SelectedDefinitionTag is returned .PARAMETER Tag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE Test-PodeOADefinitionTag -Tag 'v3', 'v3.1' #> function Test-PodeOADefinitionTag { param ( [Parameter(Mandatory = $false)] [string[]] $Tag ) if ($Tag -and $Tag.Count -gt 0) { foreach ($t in $Tag) { if (! ($PodeContext.Server.OpenApi.Definitions.Keys -icontains $t)) { # DefinitionTag does not exist. throw ($PodeLocale.definitionTagNotDefinedExceptionMessage -f $t) } } return $Tag } else { return $PodeContext.Server.OpenAPI.SelectedDefinitionTag } } <# .SYNOPSIS Validate the OpenAPI definition if all Reference are satisfied .DESCRIPTION Validate the OpenAPI definition if all Reference are satisfied .PARAMETER DefinitionTag An Array of strings representing the unique tag for the API specification. This tag helps distinguish between different versions or types of API specifications within the application. You can use this tag to reference the specific API documentation, schema, or version that your function interacts with. .EXAMPLE if ((Test-PodeOADefinition -DefinitionTag 'v3').count -eq 0){ Write-PodeHost "The OpenAPI definition is valid" } #> function Test-PodeOADefinition { param ( [string[]] $DefinitionTag ) if (! ($DefinitionTag -and $DefinitionTag.Count -gt 0)) { $DefinitionTag = $PodeContext.Server.OpenAPI.Definitions.keys } $result = @{ valid = $true issues = @{ } } foreach ($tag in $DefinitionTag) { if ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.enabled) { if ([string]::IsNullOrWhiteSpace( $PodeContext.Server.OpenAPI.Definitions[$tag].info.title) -or [string]::IsNullOrWhiteSpace( $PodeContext.Server.OpenAPI.Definitions[$tag].info.version)) { $result.valid = $false } $result.issues[$tag] = @{ title = [string]::IsNullOrWhiteSpace( $PodeContext.Server.OpenAPI.Definitions[$tag].info.title) version = [string]::IsNullOrWhiteSpace( $PodeContext.Server.OpenAPI.Definitions[$tag].info.version) components = [ordered]@{} definition = '' } foreach ($field in $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation.keys) { foreach ($name in $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field].keys) { if (! (Test-PodeOAComponentInternal -DefinitionTag $tag -Field $field -Name $name)) { $result.issues[$tag].components["#/components/$field/$name"] = $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field][$name] $result.valid = $false } } } try { Get-PodeOADefinition -DefinitionTag $tag | Out-Null } catch { $result.issues[$tag].definition = $_.Exception.Message } } } return $result } |