diff --git a/docs/technical/web.md b/docs/technical/web.md
index 91af2f98..c13e24e9 100644
--- a/docs/technical/web.md
+++ b/docs/technical/web.md
@@ -61,10 +61,29 @@ against the error message (more fragile):
```
Error messages may contain special tokens:
-* `@value` is replaced with the error's value field (which is usually the original field value, but
- not always).
-* If the validation error has exactly one sub-error and the message is set to `$transparent`, the
- sub-error will be displayed instead. (The sub-error itself may be set to `$transparent`, etc.)
+
+* The `@value` token is replaced with the error's value field. This is usually (but not always) the
+ original field value.
+* When an error has child errors, by default they're flattened into one message:
+ ```
+ line | field | error
+ ---- | ---------- | -------------------------------------------------------------------------
+ 4 | Changes[0] | JSON does not match schema from 'then'.
+ | | ==> Changes[0].ToArea.Y: Invalid type. Expected Integer but got String.
+ | | ==> Changes[0].ToArea: Missing required fields: Height.
+ ```
+
+ If you set the message for an error to `$transparent`, the parent error is omitted entirely and
+ the child errors are shown instead:
+ ```
+ line | field | error
+ ---- | ------------------- | ----------------------------------------------
+ 8 | Changes[0].ToArea.Y | Invalid type. Expected Integer but got String.
+ 8 | Changes[0].ToArea | Missing required fields: Height.
+ ```
+
+ The child errors themselves may be marked `$transparent`, etc. If an error has no child errors,
+ this override is ignored.
Caveats:
* To override an error from a `then` block, the `@errorMessages` must be inside the `then` block
diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs
index cd0a6439..4f234449 100644
--- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs
+++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs
@@ -46,6 +46,9 @@ namespace StardewModdingAPI.Web.Controllers
/// The schema ID to use if none was specified.
private string DefaultSchemaID = "manifest";
+ /// A token in an error message which indicates that the child errors should be displayed instead.
+ private readonly string TransparentToken = "$transparent";
+
/*********
** Public methods
@@ -124,7 +127,7 @@ namespace StardewModdingAPI.Web.Controllers
// validate JSON
parsed.IsValid(schema, out IList rawErrors);
var errors = rawErrors
- .Select(this.GetErrorModel)
+ .SelectMany(this.GetErrorModels)
.ToArray();
return this.View("Index", result.AddErrors(errors));
}
@@ -205,21 +208,25 @@ namespace StardewModdingAPI.Web.Controllers
return null;
}
- /// Get a flattened representation representation of a schema validation error and any child errors.
+ /// Get view models representing a schema validation error and any child errors.
/// The error to represent.
- private JsonValidatorErrorModel GetErrorModel(ValidationError error)
+ private IEnumerable GetErrorModels(ValidationError error)
{
// skip through transparent errors
- while (this.GetOverrideError(error) == "$transparent" && error.ChildErrors.Count == 1)
- error = error.ChildErrors[0];
+ if (this.GetOverrideError(error) == this.TransparentToken && error.ChildErrors.Any())
+ {
+ foreach (var model in error.ChildErrors.SelectMany(this.GetErrorModels))
+ yield return model;
+ yield break;
+ }
// get message
string message = this.GetOverrideError(error);
- if (message == null)
+ if (message == null || message == this.TransparentToken)
message = this.FlattenErrorMessage(error);
// build model
- return new JsonValidatorErrorModel(error.LineNumber, error.Path, message, error.ErrorType);
+ yield return new JsonValidatorErrorModel(error.LineNumber, error.Path, message, error.ErrorType);
}
/// Get a flattened, human-readable message for a schema validation error and any child errors.
@@ -229,11 +236,11 @@ namespace StardewModdingAPI.Web.Controllers
{
// get override
string message = this.GetOverrideError(error);
- if (message != null)
+ if (message != null && message != this.TransparentToken)
return message;
// skip through transparent errors
- while (this.GetOverrideError(error) == "$transparent" && error.ChildErrors.Count == 1)
+ while (this.GetOverrideError(error) == this.TransparentToken && error.ChildErrors.Count == 1)
error = error.ChildErrors[0];
// get friendly representation of main error