using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.Json.Nodes; using Json.Schema; using Questionable.Model; using Questionable.Model.Questing; using Questionable.QuestPaths; namespace Questionable.Validation.Validators; internal sealed class JsonSchemaValidator : IQuestValidator { private readonly Dictionary _questNodes = new Dictionary(); private JsonSchema? _questSchema; public JsonSchemaValidator() { bool flag = false; DirectoryInfo directoryInfo = FindProjectRoot(); if (directoryInfo != null) { RegisterFolderForSchemas(directoryInfo); flag = true; } if (!flag) { SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aethernetshard.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonAethernetShard).AsTask().Result); SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aetheryte.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonAetheryte).AsTask().Result); SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonClassJob).AsTask().Result); SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonCompletionFlags).AsTask().Result); SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"), JsonSchema.FromStream(AssemblyModelLoader.CommonVector3).AsTask().Result); SchemaRegistry.Global.Register(new Uri("https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/QuestPaths/quest-v1.json"), JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result); } static DirectoryInfo? FindProjectRoot() { try { string location = Assembly.GetExecutingAssembly().Location; if (string.IsNullOrEmpty(location)) { return null; } for (DirectoryInfo directoryInfo2 = new DirectoryInfo(Path.GetDirectoryName(location) ?? location); directoryInfo2 != null; directoryInfo2 = directoryInfo2.Parent) { if (directoryInfo2.GetDirectories("QuestPaths", SearchOption.TopDirectoryOnly).Length != 0 || directoryInfo2.GetDirectories("Questionable.Model", SearchOption.TopDirectoryOnly).Length != 0 || directoryInfo2.GetDirectories("Questionable", SearchOption.TopDirectoryOnly).Length != 0) { return directoryInfo2; } } } catch { } return null; } static void RegisterFolderForSchemas(DirectoryInfo folder) { try { RegisterLocalIfExistsFromPath(Find("common-aethernetshard.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aethernetshard.json"); RegisterLocalIfExistsFromPath(Find("common-aetheryte.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-aetheryte.json"); RegisterLocalIfExistsFromPath(Find("common-classjob.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-classjob.json"); RegisterLocalIfExistsFromPath(Find("common-completionflags.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-completionflags.json"); RegisterLocalIfExistsFromPath(Find("common-vector3.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"); RegisterLocalIfExistsFromPath(Find("quest-v1.json"), "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/QuestPaths/quest-v1.json"); static void RegisterLocalIfExistsFromPath(string? path, string registrationUri) { if (!string.IsNullOrEmpty(path) && File.Exists(path)) { JsonSchema document = JsonSchema.FromText(File.ReadAllText(path)); SchemaRegistry.Global.Register(new Uri(registrationUri), document); } } } catch { } string? Find(string fileName) { return Directory.EnumerateFiles(folder.FullName, fileName, SearchOption.AllDirectories).FirstOrDefault(); } } } public IEnumerable Validate(Quest quest) { if (_questSchema == null) { _questSchema = JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result; } if (!_questNodes.TryGetValue(quest.Id, out JsonNode value)) { yield break; } EvaluationResults evaluationResults = _questSchema.Evaluate(value, new EvaluationOptions { Culture = CultureInfo.InvariantCulture, OutputFormat = OutputFormat.List }); if (evaluationResults.IsValid) { yield break; } var array = (from r in GetInvalidResults(evaluationResults).ToArray() group r by r.InstanceLocation?.ToString() ?? "").Select(delegate(IGrouping g) { string[] messages = (from m in g.SelectMany((EvaluationResults r) => r.Errors?.Values ?? Enumerable.Empty()) where !string.IsNullOrWhiteSpace(m) select m.Trim()).Distinct().ToArray(); return new { Path = g.Key, Messages = messages }; }).ToArray(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("JSON Validation failed:"); if (array.Length == 0) { stringBuilder.AppendLine(" - : validation failed"); } else { var array2 = array; foreach (var anon in array2) { string value2 = ((anon.Messages.Length != 0) ? string.Join("; ", anon.Messages) : "validation failed"); StringBuilder stringBuilder2 = stringBuilder; IFormatProvider invariantCulture = CultureInfo.InvariantCulture; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(5, 2, stringBuilder2, invariantCulture); handler.AppendLiteral(" - "); handler.AppendFormatted(anon.Path); handler.AppendLiteral(": "); handler.AppendFormatted(value2); stringBuilder2.AppendLine(invariantCulture, ref handler); } } yield return new ValidationIssue { ElementId = quest.Id, Sequence = null, Step = null, Type = EIssueType.InvalidJsonSchema, Severity = EIssueSeverity.Error, Description = stringBuilder.ToString().TrimEnd() }; static IEnumerable GetInvalidResults(EvaluationResults result) { if (!result.IsValid) { yield return result; } if (result.HasDetails) { foreach (EvaluationResults detail in result.Details) { foreach (EvaluationResults invalidResult in GetInvalidResults(detail)) { yield return invalidResult; } } } } } public void Enqueue(ElementId elementId, JsonNode questNode) { _questNodes[elementId] = questNode; } public void Reset() { _questNodes.Clear(); } }