Public/Export-DhYamlSchema.ps1
|
function Export-DhYamlSchema { <# .SYNOPSIS Writes (or returns) a JSON Schema for DashHtml layout YAML. .DESCRIPTION Generated from the LIVE cmdlet metadata (the same source ConvertFrom-DhYaml uses), so it cannot drift from the module: top-level keys come from New-DhDashboard, each entry of tables / globalFilters / alertBanners / links from its Add-* / Set-* cmdlet, and every block is one entry of a oneOf keyed by its 'type'. Types follow the parameter types (switch = boolean, int = integer, ValidateSet = enum). Keys are accepted in PascalCase, camelCase and lowercase (the reader is case-insensitive). Unknown keys are rejected, because the reader would otherwise ignore a typo silently. Keys the writer itself adds and the reader ignores (NavSegments, NavSeqNo, dataRows) are allowed. Use it in an editor with a first line # yaml-language-server: $schema=dashhtml.schema.json or validate a layout: ConvertFrom-DhYaml-style YAML -> object -> ConvertTo-Json -Depth 50 | Test-Json -Schema (Get-Content dashhtml.schema.json -Raw) .PARAMETER OutFile Write the schema to this file (UTF-8, no BOM, LF). Without it the schema is returned as a JSON string. .PARAMETER AsObject Return the schema as an ordered hashtable instead of text. .EXAMPLE Export-DhYamlSchema -OutFile .\dashhtml.schema.json #> [CmdletBinding()] param([string] $OutFile, [switch] $AsObject) # PowerShell's [ordered] is case-insensitive: Id and id would collapse into one key, so property maps are ordinal. $newMap = { [System.Collections.Specialized.OrderedDictionary]::new([System.StringComparer]::Ordinal) } $casings = { param([string] $Name) $camel = $Name.Substring(0, 1).ToLowerInvariant() + $Name.Substring(1) @($Name, $camel, $Name.ToLowerInvariant()) | Select-Object -Unique # Pascal, camel, and the all-lowercase the writer uses for summary options } $propsFor = { param([string] $Cmdlet, [string[]] $Skip, [hashtable] $Rename) $props = & $newMap $meta = (Get-Command $Cmdlet -ErrorAction Stop).Parameters foreach ($n in ($meta.Keys | Sort-Object)) { if ($n -in $script:DhCommonParams -or $n -in $Skip) { continue } $shape = ConvertTo-DhJsonSchemaType -Parameter $meta[$n] $key = if ($Rename -and $Rename.ContainsKey($n)) { $Rename[$n] } else { $n } foreach ($k in (& $casings $key)) { $props[$k] = $shape } } $props } $internal = & $newMap # keys the writer adds and the reader ignores foreach ($n in 'NavSegments', 'NavSeqNo', 'dataRows') { $internal[$n] = [ordered]@{ description = 'written by ConvertTo-DhYaml; ignored when reading' } } # ---- top level $top = & $propsFor 'New-DhDashboard' @() @{} foreach ($k in 'autoRefresh') { $top[$k] = [ordered]@{ oneOf = @([ordered]@{ type = 'integer'; minimum = 0 }, [ordered]@{ type = 'string' }) } } $top['infoFields'] = [ordered]@{ type = 'array'; items = [ordered]@{ type = 'object'; properties = [ordered]@{ label = @{ type = 'string' }; value = @{} }; required = @('label') } } $top['summary'] = [ordered]@{ type = 'object'; properties = (& $propsFor 'Add-DhSummary' @('Report') @{}); additionalProperties = $false } $top['summary'].properties['items'] = [ordered]@{ type = 'array' } $top['summary'].properties['Items'] = [ordered]@{ type = 'array' } $sectionSchema = { param([string] $Cmdlet, [string[]] $Skip, [hashtable] $Rename, [string[]] $Extra) $p = & $propsFor $Cmdlet (@('Report') + $Skip) $Rename foreach ($k in $internal.Keys) { $p[$k] = $internal[$k] } foreach ($k in $Extra) { $p[$k] = [ordered]@{} } [ordered]@{ type = 'object'; properties = $p; additionalProperties = $false } } $top['tables'] = [ordered]@{ type = 'array'; items = (& $sectionSchema 'Add-DhTable' @() @{ TableId = 'Id' } @('id', 'TableId', 'tableId', 'data', 'dataFile')) } $top['globalFilters'] = [ordered]@{ type = 'array'; items = (& $sectionSchema 'Add-DhGlobalFilter' @() @{} @()) } $top['alertBanners'] = [ordered]@{ type = 'array'; items = (& $sectionSchema 'Add-DhAlertBanner' @() @{} @()) } $top['links'] = [ordered]@{ type = 'array'; items = (& $sectionSchema 'Set-DhTableLink' @() @{} @()) } # ---- blocks: one oneOf entry per cmdlet, discriminated by 'type' $byCmd = [ordered]@{} foreach ($t in ($script:DhTypeToCmdlet.Keys | Sort-Object)) { $c = $script:DhTypeToCmdlet[$t] if (-not $byCmd.Contains($c)) { $byCmd[$c] = [System.Collections.Generic.List[string]]::new() } $byCmd[$c].Add($t) } $variants = foreach ($c in $byCmd.Keys) { $p = & $newMap; $p['type'] = [ordered]@{ enum = @($byCmd[$c]) } foreach ($kv in (& $propsFor $c @('Report') @{}).GetEnumerator()) { $p[$kv.Key] = $kv.Value } foreach ($k in $internal.Keys) { $p[$k] = $internal[$k] } foreach ($k in 'Thresholds', 'Badge', 'Mode') { if (-not $p.Contains($k)) { $p[$k] = [ordered]@{} } } # written for some blocks, ignored when reading [ordered]@{ title = $c; type = 'object'; properties = $p; required = @('type'); additionalProperties = $false } } $top['blocks'] = [ordered]@{ type = 'array'; items = [ordered]@{ oneOf = @($variants) } } $schema = [ordered]@{ '$schema' = 'http://json-schema.org/draft-07/schema#' title = 'DashHtml layout' description = 'Layout file for ConvertFrom-DhYaml (DashHtml.Design). Generated by Export-DhYamlSchema from the live cmdlet metadata.' type = 'object' properties = $top anyOf = @([ordered]@{ required = @('title') }, [ordered]@{ required = @('Title') }) additionalProperties = $false } if ($AsObject) { return $schema } $json = ($schema | ConvertTo-Json -Depth 50) -replace "`r`n", "`n" if ($OutFile) { # a relative path is relative to the PowerShell location; .NET would resolve it against the process directory $OutFile = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutFile) [IO.File]::WriteAllText($OutFile, $json + "`n", (New-Object Text.UTF8Encoding($false))) Write-Verbose "Export-DhYamlSchema: wrote $OutFile" } else { $json } } |