Private/DataShape.ps1
|
# The Data Shape: what a Kind's Gatherer promises to hand its Judge. # # ADR-0002 leaves Gatherers untested, and that is acceptable while they decide nothing. It # has a quieter way to fail than a Gatherer that judges: one that renames a property. The # Judge asks for the old name with Get-DataProperty, is handed $null, and does what it was # written to do with $null - says "not available" about a machine that answered. To a # Technician that reads as an honest gap. It is a defect of Gutcheck. ADR-0011. # # So every Kind declares, as $script:<Kind>DataShape, the top-level properties of what its # Gatherer returns, each as one of: # # 'Answered' present, one value, and answered on any working Windows machine # 'Value' present, one value, which may be $null where Windows did not answer # 'List' present, any number of values - none, one, or a list - or $null # 'Optional' may be left out altogether, by a path of the Gatherer that returns early # # A Run holds the data to the shape at the Kind boundary, and says so in a Finding where a # property is missing or a list stands where one value was promised. It does not say so for # an 'Answered' property that is $null: that is how a Gatherer correctly reports that # Windows did not answer, and the Judge already says it. The suite, on a machine it knows # to work, asks for that too (-Strict). function Get-DataShapeMismatch { <# .SYNOPSIS Where gathered data falls short of the Data Shape of its Kind. Pure. .DESCRIPTION One mismatch per property, naming it and its Shortfall: Missing (not on the data at all), List (a list where one value was promised) or, with -Strict, Unanswered (an 'Answered' property that is $null). Properties the shape does not declare are not looked at: the Run stamps CheckName onto the data, and a Gatherer may carry more. #> [CmdletBinding()] [OutputType([psobject])] param( [Parameter(Mandatory)][hashtable]$Shape, [AllowNull()]$Data, [switch]$Strict ) foreach ($name in @($Shape.Keys | Sort-Object)) { $declared = "$($Shape[$name])" if (-not (Test-DataPropertyPresent -InputObject $Data -Name $name)) { if ($declared -ne 'Optional') { New-DataShapeMismatch -Property $name -Shortfall Missing } continue } $value = Get-DataProperty -InputObject $Data -Name $name if ($declared -in @('Answered', 'Value') -and $value -is [Collections.IList]) { New-DataShapeMismatch -Property $name -Shortfall List } elseif ($Strict -and $declared -eq 'Answered' -and ($null -eq $value -or "$value" -eq '')) { New-DataShapeMismatch -Property $name -Shortfall Unanswered } } } function Test-DataPropertyPresent { [CmdletBinding()] [OutputType([bool])] param([AllowNull()]$InputObject, [Parameter(Mandatory)][string]$Name) if ($null -eq $InputObject) { return $false } if ($InputObject -is [Collections.IDictionary]) { return $InputObject.Contains($Name) } [bool]$InputObject.PSObject.Properties[$Name] } function New-DataShapeMismatch { [CmdletBinding()] [OutputType([psobject])] param( [Parameter(Mandatory)][string]$Property, [Parameter(Mandatory)][ValidateSet('Missing', 'List', 'Unanswered')][string]$Shortfall ) [pscustomobject]@{ PSTypeName = 'Gutcheck.DataShapeMismatch' Property = $Property Shortfall = $Shortfall } } |