From caf8cfe7efadfbe50c9a3ced1a20ded9a4632223 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sat, 15 Jun 2024 23:32:58 +0200 Subject: [PATCH] Add JSON schema validation to source gen --- QuestPathGenerator/QuestPathGenerator.csproj | 10 ++++++ QuestPathGenerator/QuestSourceGenerator.cs | 35 ++++++++++++++++++-- QuestPathGenerator/packages.lock.json | 28 ++++++++++++++++ QuestPaths/QuestPaths.csproj | 2 ++ QuestPaths/quest-v1.json | 9 ++++- 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/QuestPathGenerator/QuestPathGenerator.csproj b/QuestPathGenerator/QuestPathGenerator.csproj index b62d28787..ffeca7c18 100644 --- a/QuestPathGenerator/QuestPathGenerator.csproj +++ b/QuestPathGenerator/QuestPathGenerator.csproj @@ -16,6 +16,9 @@ + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -29,9 +32,16 @@ + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + diff --git a/QuestPathGenerator/QuestSourceGenerator.cs b/QuestPathGenerator/QuestSourceGenerator.cs index 8d2f37edb..9a647cf44 100644 --- a/QuestPathGenerator/QuestSourceGenerator.cs +++ b/QuestPathGenerator/QuestSourceGenerator.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Text.Json; +using System.Text.Json.Nodes; +using Json.Schema; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -17,8 +21,16 @@ namespace Questionable.QuestPathGenerator; /// When using a simple text file as a baseline, we can create a non-incremental source generator. /// [Generator] +[SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008")] public class QuestSourceGenerator : ISourceGenerator { + private static readonly DiagnosticDescriptor InvalidJson = new("QSG0001", + "Invalid JSON", + "Invalid quest file {0}", + nameof(QuestSourceGenerator), + DiagnosticSeverity.Error, + true); + public void Initialize(GeneratorInitializationContext context) { // No initialization required for this generator. @@ -28,10 +40,15 @@ public class QuestSourceGenerator : ISourceGenerator { List<(ushort, QuestData)> quests = []; + // Find schema definition + AdditionalText jsonSchemaFile = + context.AdditionalFiles.Single(x => Path.GetFileName(x.Path) == "quest-v1.json"); + var questSchema = JsonSchema.FromText(jsonSchemaFile.GetText()!.ToString()); + // Go through all files marked as an Additional File in file properties. foreach (var additionalFile in context.AdditionalFiles) { - if (additionalFile == null) + if (additionalFile == null || additionalFile == jsonSchemaFile) continue; if (Path.GetExtension(additionalFile.Path) != ".json") @@ -44,7 +61,21 @@ public class QuestSourceGenerator : ISourceGenerator if (text == null) continue; - var quest = JsonSerializer.Deserialize(text.ToString())!; + var questNode = JsonNode.Parse(text.ToString()); + var evaluationResult = questSchema.Evaluate(questNode, new EvaluationOptions() + { + Culture = CultureInfo.InvariantCulture, + OutputFormat = OutputFormat.List + }); + if (!evaluationResult.IsValid) + { + var error = Diagnostic.Create(InvalidJson, + null, + Path.GetFileName(additionalFile.Path)); + context.ReportDiagnostic(error); + } + + var quest = questNode.Deserialize()!; quests.Add((id, quest)); } diff --git a/QuestPathGenerator/packages.lock.json b/QuestPathGenerator/packages.lock.json index a9d050832..c47bf1192 100644 --- a/QuestPathGenerator/packages.lock.json +++ b/QuestPathGenerator/packages.lock.json @@ -2,6 +2,34 @@ "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { + "Json.More.Net": { + "type": "Direct", + "requested": "[2.0.1.2, )", + "resolved": "2.0.1.2", + "contentHash": "uF3QeiaXEfH92emz0/BWUiNtMSfxIIvgynuB0Bf1vF4s8eWTcZitBx9l+g/FDaJk5XxqBv9buQXizXKQcXFG1w==", + "dependencies": { + "System.Text.Json": "8.0.0" + } + }, + "JsonPointer.Net": { + "type": "Direct", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "fm4T5w20AY6C+p5/pJr0vrXRNGgtSfHl34I1LxC9zdPwS9S3j0GiR1Mz/CVPWKDXXGDpCt1APHpCq7kn5adCfA==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Json.More.Net": "2.0.1.2" + } + }, + "JsonSchema.Net": { + "type": "Direct", + "requested": "[7.0.4, )", + "resolved": "7.0.4", + "contentHash": "R0Hk2Tr/np4Q1NO8CBjyQsoiD1iFJyEQP20Sw7JnZCNGJoaSBe+g4b+nZqnBXPQhiqY5LGZ8JZwZkRh/eKZhEQ==", + "dependencies": { + "JsonPointer.Net": "5.0.0" + } + }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Direct", "requested": "[3.3.4, )", diff --git a/QuestPaths/QuestPaths.csproj b/QuestPaths/QuestPaths.csproj index 2a2046478..e4e034f3a 100644 --- a/QuestPaths/QuestPaths.csproj +++ b/QuestPaths/QuestPaths.csproj @@ -22,8 +22,10 @@ + + diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index 9ad2c19be..ac01f7151 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -400,7 +400,14 @@ "if": { "properties": { "InteractionType": { - "const": "Interact" + "anyOf": [ + { + "const": "Interact" + }, + { + "const": "SinglePlayerDuty" + } + ] } } },