181 lines
7 KiB
C#
181 lines
7 KiB
C#
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<ElementId, JsonNode> _questNodes = new Dictionary<ElementId, JsonNode>();
|
|
|
|
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<ValidationIssue> 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() ?? "<root>").Select(delegate(IGrouping<string, EvaluationResults> g)
|
|
{
|
|
string[] messages = (from m in g.SelectMany((EvaluationResults r) => r.Errors?.Values ?? Enumerable.Empty<string>())
|
|
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(" - <unknown>: 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<EvaluationResults> 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();
|
|
}
|
|
}
|