Private/PresentationWireValidation.ps1
|
$script:TerminalSlidesWireIntegerTypes = [Collections.Generic.HashSet[string]]::new( [string[]]@( 'System.SByte','System.Byte','System.Int16','System.UInt16','System.Int32','System.UInt32', 'System.Int64','System.UInt64' ), [StringComparer]::Ordinal ) function Assert-TerminalWireObject { param( [AllowNull()][object]$Value, [Parameter(Mandatory)][string[]]$Fields, [Parameter(Mandatory)][string]$Subject ) if ($Value -isnot [Collections.IDictionary]) { throw "$Subject must be an object in the current wire format." } Assert-TerminalExactObjectFields -Value $Value -Fields $Fields -Subject $Subject } function Assert-TerminalWireArray { param([AllowNull()][object]$Value, [Parameter(Mandatory)][string]$Subject) if ($Value -isnot [object[]]) { throw "$Subject must be an array in the current wire format." } } function Assert-TerminalWireString { param([AllowNull()][object]$Value, [Parameter(Mandatory)][string]$Subject, [switch]$AllowNull) if ($null -eq $Value -and $AllowNull) { return } if ($Value -isnot [string]) { throw "$Subject must be a string in the current wire format." } } function Assert-TerminalWireInteger { param([AllowNull()][object]$Value, [Parameter(Mandatory)][string]$Subject) if ($null -eq $Value -or -not $script:TerminalSlidesWireIntegerTypes.Contains($Value.GetType().FullName)) { throw "$Subject must be an integer in the current wire format." } } function Read-TerminalWireInt32 { param( [AllowNull()][object]$Value, [Parameter(Mandatory)][string]$Subject, [int]$Minimum = [int]::MinValue, [int]$Maximum = [int]::MaxValue ) Assert-TerminalWireInteger -Value $Value -Subject $Subject try { $number = [int64]$Value } catch { throw "$Subject must fit in a signed 32-bit integer in the current wire format." } if ($number -lt $Minimum -or $number -gt $Maximum) { throw "$Subject must be between $Minimum and $Maximum in the current wire format." } return [int]$number } function Assert-TerminalWireBoolean { param([AllowNull()][object]$Value, [Parameter(Mandatory)][string]$Subject) if ($Value -isnot [bool]) { throw "$Subject must be a Boolean in the current wire format." } } function Read-TerminalWireEnum { param( [AllowNull()][object]$Value, [Parameter(Mandatory)][Type]$EnumType, [Parameter(Mandatory)][string]$Subject ) Assert-TerminalWireString -Value $Value -Subject $Subject if ($EnumType.GetEnumNames() -cnotcontains $Value) { throw "$Subject has unsupported value '$Value' in the current wire format." } return [Enum]::Parse($EnumType, $Value, $false) } function Read-TerminalWireDomainName { param( [AllowNull()][object]$Value, [Parameter(Mandatory)][string[]]$AllowedValues, [Parameter(Mandatory)][string]$Subject ) Assert-TerminalWireString -Value $Value -Subject $Subject foreach ($allowedValue in $AllowedValues) { if ([string]::Equals($Value, $allowedValue, [StringComparison]::OrdinalIgnoreCase)) { return $allowedValue } } throw "$Subject has unsupported value '$Value' in the current wire format." } function Read-TerminalWireHexColor { param([AllowNull()][object]$Value, [Parameter(Mandatory)][string]$Subject, [switch]$AllowDefault) if ($AllowDefault -and $null -eq $Value) { return $null } Assert-TerminalWireString -Value $Value -Subject $Subject if ($AllowDefault -and [string]::IsNullOrWhiteSpace($Value)) { return $null } try { [void](Convert-HexToRgb -Hex $Value) } catch { throw "$Subject must be a six-digit hexadecimal color in the current wire format." } return '#' + $Value.Trim().TrimStart('#').ToUpperInvariant() } function Assert-TerminalCurrentRegexValue { param([AllowNull()][object]$Value, [switch]$RequireCanonical) if ($Value -isnot [Collections.IDictionary]) { throw 'Persisted regular-expression value is malformed: an object is required in the current wire format.' } $hasTimeout = $Value.Keys -ccontains 'MatchTimeoutTicks' $fields = if ($RequireCanonical -or $hasTimeout) { @('Pattern','Options','MatchTimeoutTicks') } else { @('Pattern','Options') } Assert-TerminalExactObjectFields -Value $Value -Fields $fields -Subject 'Persisted regular-expression value' if ($Value['Pattern'] -isnot [string]) { throw "Persisted regular-expression value is malformed: field 'Pattern' must be a string in the current wire format." } Assert-TerminalWireInteger -Value $Value['Options'] -Subject "Persisted regular-expression value is malformed: field 'Options'" if ($hasTimeout -and $Value['MatchTimeoutTicks'] -isnot [string]) { throw "Persisted regular-expression value is malformed: field 'MatchTimeoutTicks' must be a string in the current wire format." } } function Assert-TerminalCurrentTaggedNode { param( [AllowNull()][object]$Node, [int]$Depth = 0, [bool]$Recurse = $true, [switch]$RequireCanonical ) Assert-TerminalSemanticTraversalDepth -Depth $Depth if ($Node -isnot [Collections.IDictionary]) { throw 'Persisted tagged value must be an object in the current wire format.' } $type = $Node['Type'] if ($type -isnot [string] -or $type.Length -eq 0) { throw "Persisted tagged value is malformed: field 'Type' is required and must be a string in the current wire format." } if (-not $script:TerminalSlidesTaggedValueFields.ContainsKey($type)) { throw "Unsupported persisted value type '$type'." } Assert-TerminalExactObjectFields -Value $Node -Fields $script:TerminalSlidesTaggedValueFields[$type] -Subject 'Persisted tagged value' if ($type -ceq 'Regex') { Assert-TerminalCurrentRegexValue -Value $Node['Value'] -RequireCanonical:$RequireCanonical return } Assert-TerminalCanonicalTaggedScalarValue -Type $type -Value $Node['Value'] if ($type -in 'Null','String','Char','Boolean','SByte','Byte','Int16','UInt16','Int32','UInt32', 'Int64','UInt64','Single','Double','Decimal','DateTime','DateTimeOffset','TimeSpan','Guid','Uri','Version') { return } $collectionField = switch ($type) { { $_ -in 'Map','OrderedMap' } { 'Entries' } 'Object' { 'Properties' } { $_ -in 'Array','ArrayList' } { 'Items' } } if ($type -ceq 'Array') { Assert-TerminalWireString -Value $Node['ElementType'] -Subject 'Persisted array ElementType' } Assert-TerminalWireArray -Value $Node[$collectionField] -Subject "Persisted tagged value is malformed: field '$collectionField'" foreach ($item in $Node[$collectionField]) { if ($type -in 'Map','OrderedMap','Object') { Assert-TerminalWireObject -Value $item -Fields @('Name','Value') -Subject "Persisted $type member" if ($item['Name'] -isnot [string] -or $item['Name'].Length -eq 0) { $subject = if ($type -in 'Map','OrderedMap') { 'Metadata dictionary keys' } else { 'Metadata object properties' } throw "$subject must be non-empty strings in the current wire format." } $child = $item['Value'] } else { $child = $item } if ($child -isnot [Collections.IDictionary]) { throw "Persisted tagged value is malformed: $type child must be a tagged object in the current wire format." } if ($Recurse) { Assert-TerminalCurrentTaggedNode -Node $child -Depth ($Depth + 1) -RequireCanonical:$RequireCanonical } } } function Assert-TerminalMarkdownEnvelope { param([Parameter(Mandatory)][Collections.IDictionary]$Envelope) $markerVersion = $Envelope['MarkerVersion'] if ($null -eq $markerVersion -or -not $script:TerminalSlidesWireIntegerTypes.Contains($markerVersion.GetType().FullName) -or $Envelope['Presentation'] -isnot [Collections.IDictionary]) { throw 'The Markdown TerminalSlides envelope is unsupported.' } switch ([uint64]$markerVersion) { 1 { Assert-TerminalExactObjectFields $Envelope @('MarkerVersion','Presentation') 'Markdown TerminalSlides envelope' } 2 { try { Assert-TerminalExactObjectFields $Envelope @('MarkerVersion','ProjectionHash','Presentation') 'Markdown TerminalSlides envelope' } catch { throw 'The Markdown TerminalSlides envelope is unsupported.' } if ($Envelope['ProjectionHash'] -isnot [string] -or $Envelope['ProjectionHash'] -cnotmatch '\A[0-9a-f]{64}\z') { throw 'The Markdown TerminalSlides envelope is unsupported because ProjectionHash is malformed.' } } default { throw 'The Markdown TerminalSlides envelope is unsupported.' } } } |