move validation logic out of Manifest model
This avoids tightly coupling higher logic to the implementation class, since we can validate the interface.
This commit is contained in:
parent
55eec58eaf
commit
346fddda67
|
@ -9,6 +9,7 @@ using Microsoft.Build.Framework;
|
||||||
using Microsoft.Build.Utilities;
|
using Microsoft.Build.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using StardewModdingAPI.ModBuildConfig.Framework;
|
using StardewModdingAPI.ModBuildConfig.Framework;
|
||||||
|
using StardewModdingAPI.Toolkit.Framework;
|
||||||
using StardewModdingAPI.Toolkit.Serialization;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
using StardewModdingAPI.Toolkit.Serialization.Models;
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
using StardewModdingAPI.Toolkit.Utilities;
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
@ -91,7 +92,8 @@ namespace StardewModdingAPI.ModBuildConfig
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out manifest);
|
new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out manifest);
|
||||||
} catch (JsonReaderException ex)
|
}
|
||||||
|
catch (JsonReaderException ex)
|
||||||
{
|
{
|
||||||
// log the inner exception, otherwise the message will be generic
|
// log the inner exception, otherwise the message will be generic
|
||||||
Exception exToShow = ex.InnerException ?? ex;
|
Exception exToShow = ex.InnerException ?? ex;
|
||||||
|
@ -100,7 +102,7 @@ namespace StardewModdingAPI.ModBuildConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the manifest's fields
|
// validate the manifest's fields
|
||||||
if (!manifest.TryValidate(out string error))
|
if (!ManifestValidator.TryValidate(manifest, out string error))
|
||||||
{
|
{
|
||||||
this.Log.LogError($"[mod build package] The mod manifest is invalid: {error}");
|
this.Log.LogError($"[mod build package] The mod manifest is invalid: {error}");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Toolkit.Framework
|
||||||
|
{
|
||||||
|
/// <summary>Validates manifest fields.</summary>
|
||||||
|
public static class ManifestValidator
|
||||||
|
{
|
||||||
|
/// <summary>Try to validate a manifest's fields. Fails if any invalid field is found.</summary>
|
||||||
|
/// <param name="manifest">The manifest to validate.</param>
|
||||||
|
/// <param name="error">The error message to display to the user.</param>
|
||||||
|
/// <returns>Returns whether the manifest was validated successfully.</returns>
|
||||||
|
public static bool TryValidate(IManifest manifest, out string error)
|
||||||
|
{
|
||||||
|
// validate DLL / content pack fields
|
||||||
|
bool hasDll = !string.IsNullOrWhiteSpace(manifest.EntryDll);
|
||||||
|
bool isContentPack = manifest.ContentPackFor != null;
|
||||||
|
|
||||||
|
// validate field presence
|
||||||
|
if (!hasDll && !isContentPack)
|
||||||
|
{
|
||||||
|
error = $"manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasDll && isContentPack)
|
||||||
|
{
|
||||||
|
error = $"manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate DLL filename format
|
||||||
|
if (hasDll && manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any())
|
||||||
|
{
|
||||||
|
error = $"manifest has invalid filename '{manifest.EntryDll}' for the EntryDLL field.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate content pack ID
|
||||||
|
else if (isContentPack && string.IsNullOrWhiteSpace(manifest.ContentPackFor!.UniqueID))
|
||||||
|
{
|
||||||
|
error = $"manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate required fields
|
||||||
|
{
|
||||||
|
List<string> missingFields = new List<string>(3);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(manifest.Name))
|
||||||
|
missingFields.Add(nameof(IManifest.Name));
|
||||||
|
if (manifest.Version == null || manifest.Version.ToString() == "0.0.0")
|
||||||
|
missingFields.Add(nameof(IManifest.Version));
|
||||||
|
if (string.IsNullOrWhiteSpace(manifest.UniqueID))
|
||||||
|
missingFields.Add(nameof(IManifest.UniqueID));
|
||||||
|
|
||||||
|
if (missingFields.Any())
|
||||||
|
{
|
||||||
|
error = $"manifest is missing required fields ({string.Join(", ", missingFields)}).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate ID format
|
||||||
|
if (!PathUtilities.IsSlug(manifest.UniqueID))
|
||||||
|
{
|
||||||
|
error = "manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate dependencies
|
||||||
|
foreach (IManifestDependency? dependency in manifest.Dependencies)
|
||||||
|
{
|
||||||
|
// null dependency
|
||||||
|
if (dependency == null)
|
||||||
|
{
|
||||||
|
error = $"manifest has a null entry under {nameof(IManifest.Dependencies)}.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// missing ID
|
||||||
|
if (string.IsNullOrWhiteSpace(dependency.UniqueID))
|
||||||
|
{
|
||||||
|
error = $"manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid ID
|
||||||
|
if (!PathUtilities.IsSlug(dependency.UniqueID))
|
||||||
|
{
|
||||||
|
error = $"manifest has a {nameof(IManifest.Dependencies)} entry with an invalid {nameof(IManifestDependency.UniqueID)} field (IDs must only contain letters, numbers, underscores, periods, or hyphens).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using StardewModdingAPI.Toolkit.Serialization.Converters;
|
using StardewModdingAPI.Toolkit.Serialization.Converters;
|
||||||
using StardewModdingAPI.Toolkit.Utilities;
|
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Serialization.Models
|
namespace StardewModdingAPI.Toolkit.Serialization.Models
|
||||||
{
|
{
|
||||||
|
@ -106,95 +103,6 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
|
||||||
this.UpdateKeys = updateKeys ?? Array.Empty<string>();
|
this.UpdateKeys = updateKeys ?? Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Try to validate a manifest's fields. Fails if any invalid field is found.</summary>
|
|
||||||
/// <param name="error">The error message to display to the user.</param>
|
|
||||||
/// <returns>Returns whether the manifest was validated successfully.</returns>
|
|
||||||
public bool TryValidate(out string error)
|
|
||||||
{
|
|
||||||
// validate DLL / content pack fields
|
|
||||||
bool hasDll = !string.IsNullOrWhiteSpace(this.EntryDll);
|
|
||||||
bool isContentPack = this.ContentPackFor != null;
|
|
||||||
|
|
||||||
// validate field presence
|
|
||||||
if (!hasDll && !isContentPack)
|
|
||||||
{
|
|
||||||
error = $"manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hasDll && isContentPack)
|
|
||||||
{
|
|
||||||
error = $"manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate DLL filename format
|
|
||||||
if (hasDll && this.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any())
|
|
||||||
{
|
|
||||||
error = $"manifest has invalid filename '{this.EntryDll}' for the EntryDLL field.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate content pack ID
|
|
||||||
else if (isContentPack && string.IsNullOrWhiteSpace(this.ContentPackFor!.UniqueID))
|
|
||||||
{
|
|
||||||
error = $"manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate required fields
|
|
||||||
{
|
|
||||||
List<string> missingFields = new List<string>(3);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(this.Name))
|
|
||||||
missingFields.Add(nameof(IManifest.Name));
|
|
||||||
if (this.Version == null || this.Version.ToString() == "0.0.0")
|
|
||||||
missingFields.Add(nameof(IManifest.Version));
|
|
||||||
if (string.IsNullOrWhiteSpace(this.UniqueID))
|
|
||||||
missingFields.Add(nameof(IManifest.UniqueID));
|
|
||||||
|
|
||||||
if (missingFields.Any())
|
|
||||||
{
|
|
||||||
error = $"manifest is missing required fields ({string.Join(", ", missingFields)}).";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate ID format
|
|
||||||
if (!PathUtilities.IsSlug(this.UniqueID))
|
|
||||||
{
|
|
||||||
error = "manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens).";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate dependencies
|
|
||||||
foreach (IManifestDependency? dependency in this.Dependencies)
|
|
||||||
{
|
|
||||||
// null dependency
|
|
||||||
if (dependency == null)
|
|
||||||
{
|
|
||||||
error = $"manifest has a null entry under {nameof(IManifest.Dependencies)}.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// missing ID
|
|
||||||
if (string.IsNullOrWhiteSpace(dependency.UniqueID))
|
|
||||||
{
|
|
||||||
error = $"manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalid ID
|
|
||||||
if (!PathUtilities.IsSlug(dependency.UniqueID))
|
|
||||||
{
|
|
||||||
error = $"manifest has a {nameof(IManifest.Dependencies)} entry with an invalid {nameof(IManifestDependency.UniqueID)} field (IDs must only contain letters, numbers, underscores, periods, or hyphens).";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error = "";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Override the update keys loaded from the mod info.</summary>
|
/// <summary>Override the update keys loaded from the mod info.</summary>
|
||||||
/// <param name="updateKeys">The new update keys to set.</param>
|
/// <param name="updateKeys">The new update keys to set.</param>
|
||||||
internal void OverrideUpdateKeys(params string[] updateKeys)
|
internal void OverrideUpdateKeys(params string[] updateKeys)
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using StardewModdingAPI.Toolkit;
|
using StardewModdingAPI.Toolkit;
|
||||||
|
using StardewModdingAPI.Toolkit.Framework;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModData;
|
using StardewModdingAPI.Toolkit.Framework.ModData;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
||||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||||
|
@ -138,7 +139,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate manifest
|
// validate manifest
|
||||||
if (mod.Manifest is Manifest manifest && !manifest.TryValidate(out string manifestError))
|
if (!ManifestValidator.TryValidate(mod.Manifest, out string manifestError))
|
||||||
{
|
{
|
||||||
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {manifestError}");
|
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {manifestError}");
|
||||||
continue;
|
continue;
|
||||||
|
|
Loading…
Reference in New Issue