diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-08-04 18:01:05 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-14 19:00:54 -0400 |
commit | ee0ff5687d4002aab20cd91fd28d007d916af36c (patch) | |
tree | 113b44f267630804d0b0c3408800733290b5331f /src/SMAPI.Web | |
parent | f24e7428df15ee8bcc9a2a45de98363670e72231 (diff) | |
download | SMAPI-ee0ff5687d4002aab20cd91fd28d007d916af36c.tar.gz SMAPI-ee0ff5687d4002aab20cd91fd28d007d916af36c.tar.bz2 SMAPI-ee0ff5687d4002aab20cd91fd28d007d916af36c.zip |
add user-friendly doc link & error messages, document validator, improve manifest schema (#654)
Diffstat (limited to 'src/SMAPI.Web')
4 files changed, 89 insertions, 16 deletions
diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index 37393a98..7b755d3b 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -113,6 +113,9 @@ namespace StardewModdingAPI.Web.Controllers schema = JSchema.Parse(System.IO.File.ReadAllText(schemaFile.FullName)); } + // get format doc URL + result.FormatUrl = this.GetExtensionField<string>(schema, "@documentationUrl"); + // validate JSON parsed.IsValid(schema, out IList<ValidationError> rawErrors); var errors = rawErrors @@ -172,13 +175,22 @@ namespace StardewModdingAPI.Web.Controllers /// <param name="indent">The indentation level to apply for inner errors.</param> private string GetFlattenedError(ValidationError error, int indent = 0) { + // get override error + string message = this.GetOverrideError(error.Schema, error.ErrorType); + if (message != null) + return message; + // get friendly representation of main error - string message = error.Message; + message = error.Message; switch (error.ErrorType) { case ErrorType.Enum: message = $"Invalid value. Found '{error.Value}', but expected one of '{string.Join("', '", error.Schema.Enum)}'."; break; + + case ErrorType.Required: + message = $"Missing required fields: {string.Join(", ", (List<string>)error.Value)}."; + break; } // add inner errors @@ -216,5 +228,40 @@ namespace StardewModdingAPI.Web.Controllers return null; } + + /// <summary>Get an override error from the JSON schema, if any.</summary> + /// <param name="schema">The schema or subschema that raised the error.</param> + /// <param name="errorType">The error type.</param> + private string GetOverrideError(JSchema schema, ErrorType errorType) + { + // get override errors + IDictionary<string, string> errors = this.GetExtensionField<Dictionary<string, string>>(schema, "@errorMessages"); + if (errors == null) + return null; + errors = new Dictionary<string, string>(errors, StringComparer.InvariantCultureIgnoreCase); + + // get matching error + return errors.TryGetValue(errorType.ToString(), out string errorPhrase) + ? errorPhrase + : null; + } + + /// <summary>Get an extension field from a JSON schema.</summary> + /// <typeparam name="T">The field type.</typeparam> + /// <param name="schema">The schema whose extension fields to search.</param> + /// <param name="key">The case-insensitive field key.</param> + private T GetExtensionField<T>(JSchema schema, string key) + { + if (schema.ExtensionData != null) + { + foreach (var pair in schema.ExtensionData) + { + if (pair.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)) + return pair.Value.ToObject<T>(); + } + } + + return default; + } } } diff --git a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs index 4c122d4f..2d13bf23 100644 --- a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs +++ b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs @@ -33,6 +33,9 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator /// <summary>An error which occurred while parsing the JSON.</summary> public string ParseError { get; set; } + /// <summary>A web URL to the user-facing format documentation.</summary> + public string FormatUrl { get; set; } + /********* ** Public methods diff --git a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml index 6658e7b9..5c3168e5 100644 --- a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml +++ b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml @@ -95,6 +95,11 @@ else if (Model.PasteID != null) </div> <h2>Validation errors</h2> + @if (Model.FormatUrl != null) + { + <p>See <a href="@Model.FormatUrl">format documentation</a>.</p> + } + @if (Model.Errors.Any()) { <table id="metadata" class="table"> diff --git a/src/SMAPI.Web/wwwroot/schemas/manifest.json b/src/SMAPI.Web/wwwroot/schemas/manifest.json index 06173333..804eb53d 100644 --- a/src/SMAPI.Web/wwwroot/schemas/manifest.json +++ b/src/SMAPI.Web/wwwroot/schemas/manifest.json @@ -3,6 +3,7 @@ "$id": "https://smapi.io/schemas/manifest.json", "title": "SMAPI manifest", "description": "Manifest file for a SMAPI mod or content pack", + "@documentationUrl": "https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest", "type": "object", "properties": { "Name": { @@ -38,7 +39,10 @@ "description": "The DLL filename SMAPI should load for this mod. Mutually exclusive with ContentPackFor.", "type": "string", "pattern": "^[a-zA-Z0-9_.-]+\\.dll$", - "examples": "LookupAnything.dll" + "examples": "LookupAnything.dll", + "@errorMessages": { + "pattern": "Invalid value; must be a filename ending with .dll." + } }, "ContentPackFor": { "title": "Content pack for", @@ -59,12 +63,17 @@ "required": [ "UniqueID" ] }, + "MinimumApiVersion": { + "title": "Minimum API version", + "description": "The minimum SMAPI version needed to use this mod. If a player tries to use the mod with an older SMAPI version, they'll see a friendly message saying they need to update SMAPI. This also serves as a proxy for the minimum game version, since SMAPI itself enforces a minimum game version.", + "$ref": "#/definitions/SemanticVersion" + }, "Dependencies": { "title": "Mod dependencies", "description": "Specifies other mods to load before this mod. If a dependency is required and a player tries to use the mod without the dependency installed, the mod won't be loaded and they'll see a friendly message saying they need to install those.", "type": "array", "items": { - "type": "object", + "type": "object", "properties": { "UniqueID": { "title": "Dependency unique ID", @@ -90,8 +99,26 @@ "type": "array", "items": { "type": "string", - "pattern": "^(Chucklefish:\\d+|Nexus:\\d+|GitHub:[A-Za-z0-9_]+/[A-Za-z0-9_]+|ModDrop:\\d+)$" + "pattern": "^(Chucklefish:\\d+|Nexus:\\d+|GitHub:[A-Za-z0-9_]+/[A-Za-z0-9_]+|ModDrop:\\d+)$", + "@errorMessages": { + "pattern": "Invalid update key; see https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks for more info." + } + } + } + }, + "definitions": { + "SemanticVersion": { + "type": "string", + "pattern": "^(?>(?<major>0|[1-9]\\d*))\\.(?>(?<minor>0|[1-9]\\d*))(?>(?:\\.(?<patch>0|[1-9]\\d*))?)(?:-(?<prerelease>(?>[a-zA-Z0-9]+[\\-\\.]?)+))?$", // derived from SMAPI.Toolkit.SemanticVersion + "examples": [ "1.0.0", "1.0.1-beta.2" ], + "@errorMessages": { + "pattern": "Invalid semantic version; must be formatted like 1.2.0 or 1.2.0-prerelease.tags. See https://semver.org/ for more info." } + }, + "ModID": { + "type": "string", + "pattern": "^[a-zA-Z0-9_.-]+$", // derived from SMAPI.Toolkit.Utilities.PathUtilities.IsSlug + "examples": [ "Pathoschild.LookupAnything" ] } }, @@ -104,17 +131,8 @@ "required": [ "ContentPackFor" ] } ], - - "definitions": { - "SemanticVersion": { - "type": "string", - "pattern": "(?>(?<major>0|[1-9]\\d*))\\.(?>(?<minor>0|[1-9]\\d*))(?>(?:\\.(?<patch>0|[1-9]\\d*))?)(?:-(?<prerelease>(?>[a-zA-Z0-9]+[\\-\\.]?)+))?", // derived from SMAPI.Toolkit.SemanticVersion - "examples": [ "1.0.0", "1.0.1-beta.2" ] - }, - "ModID": { - "type": "string", - "pattern": "^[a-zA-Z0-9_.-]+$", // derived from SMAPI.Toolkit.Utilities.PathUtilities.IsSlug - "examples": [ "Pathoschild.LookupAnything" ] - } + "additionalProperties": false, + "@errorMessages": { + "oneOf": "Can't specify both EntryDll or ContentPackFor, they're mutually exclusive." } } |