Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add json-schemas for Configuration json files #2532

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4d7e6de
added json-schema for config/dns.json
psyirius Aug 7, 2024
26256e1
[build-script]: strip $schema from config jsons before embedding
psyirius Aug 7, 2024
5a60625
added json-schema for config/themes.json
psyirius Aug 7, 2024
483baea
json-schema for config/themes.json required fields
psyirius Aug 7, 2024
6fedea1
added json-schema for config/tweaks.json
psyirius Aug 7, 2024
c61b1e8
added json-schema for config/preset.json
psyirius Aug 7, 2024
5e2acad
added json-schema for config/feature.json
psyirius Aug 7, 2024
8844b85
json-schema adjustments
psyirius Aug 7, 2024
e2a870f
added json-schema for config/applications.json
psyirius Aug 7, 2024
e7d0637
Merge branch 'main' into config-schema
psyirius Aug 7, 2024
632f295
Merge branch 'main' into config-schema
psyirius Aug 7, 2024
14efc4f
Merge branch 'main' into config-schema
psyirius Aug 7, 2024
b9b617e
Merge branch 'main' into config-schema
psyirius Aug 8, 2024
77e5c25
Merge branch 'main' into config-schema
psyirius Aug 9, 2024
50cb1be
Merge branch 'main' into config-schema
psyirius Aug 14, 2024
31ec62f
Merge branch 'main' into config-schema
ChrisTitusTech Aug 28, 2024
be9fae1
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
01f4198
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
97bf952
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
e6dbc81
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
26a2b3d
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
c79fa58
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
bf56639
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
4452177
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
6616437
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
5d53b96
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
0d9b6f3
Update configs.Tests.ps1
ChrisTitusTech Aug 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion Compile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,35 @@ Get-ChildItem "$workingdir\functions" -Recurse -File | ForEach-Object {
Update-Progress "Adding: Config *.json" 40
Get-ChildItem "$workingdir\config" | Where-Object {$psitem.extension -eq ".json"} | ForEach-Object {
$json = (Get-Content $psitem.FullName).replace("'","''")
$jsonAsObject = $json | convertfrom-json

# Replace every XML Special Character so it'll render correctly in final build
# Only do so if json files has content to be displayed (for example the applications, tweaks, features json files)
# Make an Array List containing every name at first level of Json File
[PSCustomObject]$jsonAsObject = $json | convertfrom-json

# Remove properties like $schema and such from the json object (we don't need it at this point)
@(
"`$schema"
) | ForEach-Object {
$jsonAsObject.PSObject.Properties.Remove($_) | Out-Null
}

$firstLevelJsonList = [System.Collections.ArrayList]::new()
$jsonAsObject.PSObject.Properties.Name | ForEach-Object {$null = $firstLevelJsonList.Add($_)}
# Note:
# Avoid using HTML Entity Codes, for example '”' (stands for "Right Double Quotation Mark"),
# Use **HTML decimal/hex codes instead**, as using HTML Entity Codes will result in XML parse Error when running the compiled script.
for ($i = 0; $i -lt $firstLevelJsonList.Count; $i += 1) {
$firstLevelName = $firstLevelJsonList[$i]
if ($jsonAsObject.$firstLevelName.content -ne $null) {
$jsonAsObject.$firstLevelName.content = $jsonAsObject.$firstLevelName.content.replace('&','&#38;').replace('“','&#8220;').replace('”','&#8221;').replace("'",'&#39;').replace('<','&#60;').replace('>','&#62;').replace('—','&#8212;')
$jsonAsObject.$firstLevelName.content = $jsonAsObject.$firstLevelName.content.replace('&#39;&#39;',"&#39;") # resolves the Double Apostrophe caused by the first replace function in the main loop
}
if ($jsonAsObject.$firstLevelName.description -ne $null) {
$jsonAsObject.$firstLevelName.description = $jsonAsObject.$firstLevelName.description.replace('&','&#38;').replace('“','&#8220;').replace('”','&#8221;').replace("'",'&#39;').replace('<','&#60;').replace('>','&#62;').replace('—','&#8212;')
$jsonAsObject.$firstLevelName.description = $jsonAsObject.$firstLevelName.description.replace('&#39;&#39;',"&#39;") # resolves the Double Apostrophe caused by the first replace function in the main loop
}
}
Comment on lines +81 to +96
Copy link
Contributor

@og-mrk og-mrk Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@psyirius @ChrisTitusTech This code for handling special characters is not needed anymore, as we're dynamically generating the UI, therefore skipping the XAML Reader in the process, so I recommend removing it plus any comments related to it.


# Add 'WPFInstall' as a prefix to every entry-name in 'applications.json' file
if ($psitem.Name -eq "applications.json") {
Expand Down
1 change: 1 addition & 0 deletions config/applications.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "../schemas/config/applications.json",
"1password": {
"category": "Utilities",
"choco": "1password",
Expand Down
1 change: 1 addition & 0 deletions config/dns.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "../schemas/config/dns.json",
"Google":{
"Primary": "8.8.8.8",
"Secondary": "8.8.4.4",
Expand Down
1 change: 1 addition & 0 deletions config/feature.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "../schemas/config/feature.json",
"WPFFeaturesdotnet": {
"Content": "All .Net Framework (2,3,4)",
"Description": ".NET and .NET Framework is a developer platform made up of tools, programming languages, and libraries for building many different types of applications.",
Expand Down
1 change: 1 addition & 0 deletions config/preset.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "../schemas/config/preset.json",
"Standard": [
"WPFTweaksAH",
"WPFTweaksConsumerFeatures",
Expand Down
1 change: 1 addition & 0 deletions config/themes.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "../schemas/config/themes.json",
"_default": {
"CustomDialogFontSize": "12",
"CustomDialogFontSizeHeader": "14",
Expand Down
1 change: 1 addition & 0 deletions config/tweaks.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "../schemas/config/tweaks.json",
"WPFTweaksAH": {
"Content": "Disable Activity History",
"Description": "This erases recent docs, clipboard, and run history.",
Expand Down
193 changes: 129 additions & 64 deletions pester/configs.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,81 +1,146 @@
# Import Config Files
$global:importedconfigs = @{}
Get-ChildItem .\config | Where-Object {$_.Extension -eq ".json"} | ForEach-Object {
$global:importedconfigs[$psitem.BaseName] = Get-Content $psitem.FullName | ConvertFrom-Json
}
# Enable verbose output
$VerbosePreference = "Continue"

# Define Test-Schema function
function Test-Schema {
param (
$Object,
$Schema
)

#===========================================================================
# Tests - Application Installs
#===========================================================================
$errors = @()

Describe "Config Files" -ForEach @(
@{
name = "applications"
config = $('{
"winget": "value",
"choco": "value",
"category": "value",
"content": "value",
"description": "value",
"link": "value"
}' | ConvertFrom-Json)
},
@{
name = "tweaks"
undo = $true
}
) {
Context "$name config file" {
It "Imports with no errors" {
$global:importedconfigs.$name | should -Not -BeNullOrEmpty
$Object.PSObject.Properties | ForEach-Object {
$propName = $_.Name
$propValue = $_.Value

$propSchema = $Schema.Properties[$propName]
if (-not $propSchema) {
$errors += "Property '$propName' is not defined in the schema"
return
}
if ($config) {
It "Imports should be the correct structure" {
$applications = $global:importedconfigs.$name | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name
$template = $config | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name
$result = New-Object System.Collections.Generic.List[System.Object]
Foreach ($application in $applications) {
$compare = $global:importedconfigs.$name.$application | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name
if ($(Compare-Object $compare $template) -ne $null) {
$result.Add($application)
}
}

$result | Select-String "WPF*" | should -BeNullOrEmpty
switch ($propSchema.Type) {
"String" {
if ($propValue -isnot [string]) {
$errors += "Property '$propName' should be a string but is $($propValue.GetType())"
}
}
"Object" {
if ($propValue -isnot [PSCustomObject]) {
$errors += "Property '$propName' should be an object but is $($propValue.GetType())"
} else {
$errors += Test-Schema -Object $propValue -Schema $propSchema
}
}
}
if($undo) {
It "Tweaks should contain original Value" {
$tweaks = $global:importedconfigs.$name | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty name
$result = New-Object System.Collections.Generic.List[System.Object]
}

foreach ($requiredProp in $Schema.Required) {
if (-not $Object.PSObject.Properties.Name.Contains($requiredProp)) {
$errors += "Required property '$requiredProp' is missing"
}
}

foreach ($tweak in $tweaks) {
$Originals = @(
@{
name = "registry"
value = "OriginalValue"
},
@{
name = "service"
value = "OriginalType"
},
@{
name = "ScheduledTask"
value = "OriginalState"
return $errors
}

# Import Config Files
$global:importedConfigs = @{}
Get-ChildItem .\config -Filter *.json | ForEach-Object {
try {
$global:importedConfigs[$_.BaseName] = Get-Content $_.FullName | ConvertFrom-Json
Write-Verbose "Successfully imported config file: $($_.FullName)"
} catch {
Write-Error "Failed to import config file: $($_.FullName). Error: $_"
}
}

Describe "Config Files Validation" {
BeforeAll {
$script:configSchemas = @{
applications = @{
Type = "Object"
Properties = @{
winget = @{ Type = "String" }
choco = @{ Type = "String" }
category = @{ Type = "String" }
content = @{ Type = "String" }
description = @{ Type = "String" }
link = @{ Type = "String" }
}
Required = @("winget", "choco", "category", "content", "description", "link")
}
tweaks = @{
Type = "Object"
Properties = @{
registry = @{
Type = "Object"
Properties = @{
Path = @{ Type = "String" }
Name = @{ Type = "String" }
Type = @{ Type = "String" }
Value = @{ Type = "String" }
OriginalValue = @{ Type = "String" }
}
)
Foreach ($original in $Originals) {
$TotalCount = ($global:importedconfigs.$name.$tweak.$($original.name)).count
$OriginalCount = ($global:importedconfigs.$name.$tweak.$($original.name).$($original.value) | Where-Object {$_}).count
if($TotalCount -ne $OriginalCount) {
$result.Add("$Tweak,$($original.name)")
Required = @("Path", "Name", "Type", "Value", "OriginalValue")
}
service = @{
Type = "Object"
Properties = @{
Name = @{ Type = "String" }
StartupType = @{ Type = "String" }
OriginalType = @{ Type = "String" }
}
Required = @("Name", "StartupType", "OriginalType")
}
ScheduledTask = @{
Type = "Object"
Properties = @{
Name = @{ Type = "String" }
State = @{ Type = "String" }
OriginalState = @{ Type = "String" }
}
Required = @("Name", "State", "OriginalState")
}
}
$result | Select-String "WPF*" | should -BeNullOrEmpty
}
}
}

Context "Config File Structure" {
It "Should import all config files without errors" {
$global:importedConfigs | Should -Not -BeNullOrEmpty -Because "No config files were imported successfully"
}

It "Should have the correct structure for all configs" {
$testSchemaScriptBlock = ${function:Test-Schema}.ToString()

$results = $configSchemas.Keys | ForEach-Object -Parallel {
$configName = $_
$importedConfigs = $using:global:importedConfigs
$configSchemas = $using:configSchemas
$config = $importedConfigs[$configName]
$schema = $configSchemas[$configName]

if (-not $config) {
return "Config file '$configName' is missing or empty"
}

$testSchemaFunction = [ScriptBlock]::Create($using:testSchemaScriptBlock)
& $testSchemaFunction -Object $config -Schema $schema
} -ThrottleLimit 4

$results | Should -BeNullOrEmpty -Because "The following schema violations were found: $($results -join '; ')"
}
}
}

# Summarize test results
$testResults = Invoke-Pester -PassThru
if ($testResults.FailedCount -gt 0) {
Write-Error "Tests failed. $($testResults.FailedCount) out of $($testResults.TotalCount) tests failed."
exit 1
} else {
Write-Output "All tests passed successfully!"
}
55 changes: 55 additions & 0 deletions schemas/config/applications.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"url": {
"type": "string",
"format": "uri"
}
},
"patternProperties": {
"^[a-zA-Z_][a-zA-Z0-9_]*$": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"description": {
"type": "string"
},
"category": {
"type": "string",
"enum": [
"Utilities",
"Document",
"Pro Tools",
"Multimedia Tools",
"Development",
"Games",
"Microsoft Tools",
"Browsers",
"Communications"
]
},
"choco": {
"type": "string"
},
"winget": {
"type": "string"
},
"link": {
"$ref": "#/definitions/url"
}
},
"required": [
"content",
"description",
"category",
"link",
"choco",
"winget"
],
"additionalProperties": false
}
}
}
29 changes: 29 additions & 0 deletions schemas/config/dns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"patternProperties": {
"^[a-zA-Z_][a-zA-Z0-9_]*$": {
"type": "object",
"properties": {
"Primary": {
"type": "string",
"format": "ipv4"
},
"Secondary": {
"type": "string",
"format": "ipv4"
},
"Primary6": {
"type": "string",
"format": "ipv6"
},
"Secondary6": {
"type": "string",
"format": "ipv6"
}
},
"required": ["Primary", "Secondary", "Primary6", "Secondary6"],
"additionalProperties": false
}
}
}
Loading