PSChristmasTree Configuration Contract
Overview
This document specifies the configuration schema and contract that all configuration sources must adhere to. Understanding this contract is essential for: - Extending the module - Building custom configuration stores - Debugging configuration-related issues - Maintaining backward compatibility
Configuration Schema (v3)
Root Structure
@{
ConfigVersion = [int] # Currently 3 (required)
Language = [hashtable]
Tree = [hashtable]
Decorations = [hashtable]
Colors = [hashtable]
Display = [hashtable]
Audio = [hashtable]
Messages = [hashtable]
}
Language Section
Language = @{
UICulture = [string] # e.g., "en-US", "fr-FR"
# Default: Current system culture
# Used for message localization
}
Contract:
- Must be a non-null hashtable
- UICulture must be a valid culture identifier or empty string
- Empty string falls back to system default
Tree Section
Tree = @{
Style = [string] # One of: 'Classic', 'Minimal', 'Wide', 'Custom'
# Default: 'Classic'
CustomPath = [string] # Path to custom tree ASCII file
# Empty if Style != 'Custom'
# File must exist and contain ASCII art
}
Contract:
- Style must be one of the four valid styles or falls back to 'Classic'
- CustomPath is only meaningful when Style is 'Custom'
- Path validation happens at runtime in Get-ChristmasTree
- Invalid paths revert to Classic style
Decorations Section
Decorations = @{
Mode = [string] # One of: 'Default', 'Custom'
# Default: 'Default'
IncludeDefaults = [bool] # Include built-in decorations with custom ones?
# Default: $true
Defaults = [hashtable] # Symbol => Color mappings (built-in reference)
# @{ 'O' = 'random'; '|___|' = 'Red' }
Map = [hashtable] # User custom symbol => Color mappings
# Example: @{ '*' = 'Yellow'; 'i' = 'Blue' }
}
Contract:
- Mode must be 'Default' or 'Custom'
- Defaults is read-only reference, never modified by user config
- Map contains only valid symbol patterns and valid colors
- Colors can be any valid [System.ConsoleColor] name or the keyword 'random'
- Invalid colors are silently dropped with warnings
- Empty map when Mode is 'Default'
Colors Section
Colors = @{
Mode = [string] # One of: 'Multicolor', 'Single', 'Palette'
# Default: 'Multicolor'
SingleColor = [string] # Console color name (only used in Single mode)
# Default: 'Green'
Palette = [array] # Array of valid console colors
# Default: All console colors
}
Contract:
- Mode determines which color property is active
- When Mode is 'Multicolor': Palette = all console colors
- When Mode is 'Single': Palette = @(SingleColor)
- When Mode is 'Palette': Palette = user-specified colors
- Invalid colors are filtered out
- Empty Palette falls back to all colors
- SingleColor must be a valid console color or falls back to 'Green'
Display Section
Display = @{
AnimationLoopNumber = [int] # How many frames to render
# Range: 1 to 2147483647
# Default: 50
AnimationSpeed = [int] # Milliseconds per frame
# Range: 1 to 2147483647
# Default: 300
HideCursor = [bool] # Hide blinking cursor during display?
# Default: $true
}
Contract:
- All values must be positive integers >= 1
- Values < 1 fall back to defaults
- Very large values are accepted (up to int.MaxValue)
- HideCursor is boolean (coerced from any input)
Audio Section
Audio = @{
PlayCarol = [int] # Number of times to play Christmas carol
# 0 = no music
# 1+ = repeat count
# Default: 0
}
Contract: - Must be non-negative integer - Negative values fall back to 0 - Zero means no music is played - Positive integers repeat the carol N times
Messages Section
Messages = @{
Show = [bool] # Display greeting messages below tree?
# Default: $true
Custom = [hashtable] # Custom message overrides
# Keys: 'MerryChristmas', 'MessageForDevelopers', 'HappyNewYear', 'CodeWord'
# Values: [string]
}
Contract:
- Show is boolean (coerced)
- Custom contains only recognized keys (unknown keys ignored)
- Custom message values must be non-empty strings
- Empty strings in Custom are ignored
- Default messages loaded from locales/Messages.psd1 per UICulture
Configuration Sources & Precedence
Configuration is resolved from three sources in order of precedence:
-
Runtime Overrides (highest priority)
Parameters passed toShow-PSChristmasTree
Example:-AnimationSpeed 100 -
Saved Configuration
User configuration file ($PROFILE\PSChristmasTree\config.json)
Persisted bySet-PSChristmasTreeConfig -
Built-in Defaults (lowest priority)
FromGet-PSChristmasTreeDefaultConfig
Resolution Function
$effective = Resolve-PSChristmasTreeEffectiveConfig -SavedConfig $saved -RuntimeOverrides $PSBoundParameters
Returns effective configuration with all three levels merged, with runtime overrides taking precedence.
Key Contract Guarantees
1. Complete Structure
ConvertTo-PSChristmasTreeConfig always returns a complete, valid v3 structure with all required sections present.
2. Type Safety
Every value is coerced to its declared type: - Strings are trimmed and validated - Numbers are validated as integers with range checking - Booleans are coerced from any type
3. Fallback Behavior
Invalid values never cause exceptions; they silently fall back to defaults with optional warnings.
4. Immutability of Defaults
Decorations.Defaults is read-only reference data never modified by config normalization.
5. Flat Legacy Keys
For backward compatibility, Resolve-PSChristmasTreeEffectiveConfig also exposes flat keys:
$effective['AnimationSpeed'] # Flat key (int)
$effective['Display']['AnimationSpeed'] # Nested key (hashtable)
Both always point to the same value (kept in sync).
Configuration File Format (JSON)
The configuration file stored on disk uses v3 nested structure:
{
"ConfigVersion": 3,
"Language": {
"UICulture": "en-US"
},
"Tree": {
"Style": "Classic",
"CustomPath": ""
},
"Decorations": {
"Mode": "Default",
"IncludeDefaults": true,
"Defaults": {
"O": "random",
"|___|": "Red"
},
"Map": {}
},
"Colors": {
"Mode": "Multicolor",
"SingleColor": "Green",
"Palette": [
"Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta"
]
},
"Display": {
"AnimationLoopNumber": 50,
"AnimationSpeed": 300,
"HideCursor": true
},
"Audio": {
"PlayCarol": 0
},
"Messages": {
"Show": true,
"Custom": {}
}
}
Contract: - Must be valid JSON (enforced with error handling) - Unknown keys are ignored - Missing keys use defaults - File corrupted → warning + null return
Extension Points
Custom Configuration Stores
Implement the config store port (adapter pattern):
$store = @{
Load = { return $jsonString }
Save = { param($jsonString); ... }
Delete = { return [bool] }
}
Read-PSChristmasTreeConfig -Store $store
Custom Tree Styles
$customTree = Get-ChristmasTree -Style Custom -CustomTreePath 'C:\my-tree.txt'
Custom Decoration Maps
$config = @{ Decorations = @{ Map = @{ 'O' = 'Red'; '*' = 'Yellow' } } }
Migration from Legacy (v1/v2)
Legacy flat format:
@{
AnimationLoopNumber = 10
TreeStyle = 'Wide'
ColorMode = 'Single'
}
Automatically migrated to v3:
@{
Display = @{ AnimationLoopNumber = 10 }
Tree = @{ Style = 'Wide' }
Colors = @{ Mode = 'Single' }
}
Migration happens transparently in ConvertTo-PSChristmasTreeConfig.
Testing the Contract
Recommended Test Coverage
- Null/Empty Inputs → All sections handle gracefully
- Invalid Values → Fallback to defaults with warnings
- Mixed Valid/Invalid → Valid parts apply, invalid parts ignored
- Boundary Values → Min/max integers, empty collections
- Incomplete Structures → Missing keys filled with defaults
- Round-trip → Write → Read returns same effective config
Example Test
# Invalid values should be sanitized
$config = ConvertTo-PSChristmasTreeConfig -PartialConfig @{
Display = @{ AnimationLoopNumber = -5 }
Colors = @{ SingleColor = 'NotAColor' }
}
$config['Display']['AnimationLoopNumber'] | Should -Be 50 # Fallback
$config['Colors']['SingleColor'] | Should -Be 'Green' # Fallback
Version History
- v3 (current): Nested structure with full normalization
- v2: Legacy flat structure (auto-migrated)
- v1: Initial format (auto-migrated)