summaryrefslogtreecommitdiff
path: root/src/SMAPI.Toolkit/Serialization
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-01 18:16:09 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-01 18:16:09 -0400
commitc8ad50dad1d706a1901798f9396f6becfea36c0e (patch)
tree28bd818a5db39ec5ece1bd141a28de955950463b /src/SMAPI.Toolkit/Serialization
parent451b70953ff4c0b1b27ae0de203ad99379b45b2a (diff)
parentf78093bdb58d477b400cde3f19b70ffd6ddf833d (diff)
downloadSMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.gz
SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.bz2
SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Toolkit/Serialization')
-rw-r--r--src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs4
-rw-r--r--src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs8
-rw-r--r--src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs12
-rw-r--r--src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs20
-rw-r--r--src/SMAPI.Toolkit/Serialization/InternalExtensions.cs6
-rw-r--r--src/SMAPI.Toolkit/Serialization/JsonHelper.cs24
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/Manifest.cs101
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs33
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs46
-rw-r--r--src/SMAPI.Toolkit/Serialization/SParseException.cs2
10 files changed, 185 insertions, 71 deletions
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs
index 5cabe9d8..faaeedea 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param>
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<ManifestContentPackFor>(reader);
}
@@ -42,7 +42,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs
index 7b88d6b7..c499a2c6 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs
@@ -35,13 +35,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param>
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
List<ManifestDependency> result = new List<ManifestDependency>();
foreach (JObject obj in JArray.Load(reader).Children<JObject>())
{
- string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID));
- string minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion));
+ string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID))!; // will be validated separately if null
+ string? minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion));
bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true;
result.Add(new ManifestDependency(uniqueID, minVersion, required));
}
@@ -52,7 +52,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
index cf69104d..c32c3185 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
@@ -39,15 +39,17 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param>
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
string path = reader.Path;
switch (reader.TokenType)
{
case JsonToken.StartObject:
return this.ReadObject(JObject.Load(reader));
+
case JsonToken.String:
return this.ReadString(JToken.Load(reader).Value<string>(), path);
+
default:
throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path}).");
}
@@ -57,7 +59,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(value?.ToString());
}
@@ -73,7 +75,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion));
int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion));
- string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
+ string? prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
return new SemanticVersion(major, minor, patch, prereleaseTag: prereleaseTag);
}
@@ -81,11 +83,11 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <summary>Read a JSON string.</summary>
/// <param name="str">The JSON string value.</param>
/// <param name="path">The path to the current JSON node.</param>
- private ISemanticVersion ReadString(string str, string path)
+ private ISemanticVersion? ReadString(string str, string path)
{
if (string.IsNullOrWhiteSpace(str))
return null;
- if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion version))
+ if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion? version))
throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path}).");
return version;
}
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs
index ccc5158b..1c59f5e7 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs
@@ -25,21 +25,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
}
- /// <summary>Writes the JSON representation of the object.</summary>
- /// <param name="writer">The JSON writer.</param>
- /// <param name="value">The value.</param>
- /// <param name="serializer">The calling serializer.</param>
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- throw new InvalidOperationException("This converter does not write JSON.");
- }
-
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The JSON reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param>
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
string path = reader.Path;
switch (reader.TokenType)
@@ -58,6 +49,15 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
}
}
+ /// <summary>Writes the JSON representation of the object.</summary>
+ /// <param name="writer">The JSON writer.</param>
+ /// <param name="value">The value.</param>
+ /// <param name="serializer">The calling serializer.</param>
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ throw new InvalidOperationException("This converter does not write JSON.");
+ }
+
/*********
** Protected methods
diff --git a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs
index 10f88dde..78297035 100644
--- a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs
+++ b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs
@@ -10,12 +10,12 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <typeparam name="T">The value type.</typeparam>
/// <param name="obj">The JSON object to search.</param>
/// <param name="fieldName">The field name.</param>
- public static T ValueIgnoreCase<T>(this JObject obj, string fieldName)
+ public static T? ValueIgnoreCase<T>(this JObject obj, string fieldName)
{
- JToken token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
+ JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
return token != null
? token.Value<T>()
- : default(T);
+ : default;
}
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
index 00db9903..3c9308f2 100644
--- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
+++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
@@ -14,7 +15,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
** Accessors
*********/
/// <summary>The JSON settings to use when serializing and deserializing files.</summary>
- public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings
+ public JsonSerializerSettings JsonSettings { get; } = new()
{
Formatting = Formatting.Indented,
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
@@ -36,7 +37,12 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <returns>Returns false if the file doesn't exist, else true.</returns>
/// <exception cref="ArgumentException">The given <paramref name="fullPath"/> is empty or invalid.</exception>
/// <exception cref="JsonReaderException">The file contains invalid JSON.</exception>
- public bool ReadJsonFileIfExists<TModel>(string fullPath, out TModel result)
+ public bool ReadJsonFileIfExists<TModel>(string fullPath,
+#if NET5_0_OR_GREATER
+ [NotNullWhen(true)]
+#endif
+ out TModel? result
+ )
{
// validate
if (string.IsNullOrWhiteSpace(fullPath))
@@ -48,9 +54,9 @@ namespace StardewModdingAPI.Toolkit.Serialization
{
json = File.ReadAllText(fullPath);
}
- catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException)
+ catch (Exception ex) when (ex is DirectoryNotFoundException or FileNotFoundException)
{
- result = default(TModel);
+ result = default;
return false;
}
@@ -58,7 +64,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
try
{
result = this.Deserialize<TModel>(json);
- return true;
+ return result != null;
}
catch (Exception ex)
{
@@ -88,7 +94,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath));
// create directory if needed
- string dir = Path.GetDirectoryName(fullPath);
+ string dir = Path.GetDirectoryName(fullPath)!;
if (dir == null)
throw new ArgumentException("The file path is invalid.", nameof(fullPath));
if (!Directory.Exists(dir))
@@ -106,7 +112,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{
try
{
- return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings);
+ return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings)
+ ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
}
catch (JsonReaderException)
{
@@ -115,7 +122,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{
try
{
- return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings);
+ return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings)
+ ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
}
catch { /* rethrow original error */ }
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
index 46b654a5..da3ad608 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
@@ -1,5 +1,6 @@
+using System;
using System.Collections.Generic;
-using System.Runtime.Serialization;
+using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialization.Converters;
@@ -12,48 +13,45 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
** Accessors
*********/
/// <summary>The mod name.</summary>
- public string Name { get; set; }
+ public string Name { get; }
/// <summary>A brief description of the mod.</summary>
- public string Description { get; set; }
+ public string Description { get; }
/// <summary>The mod author's name.</summary>
- public string Author { get; set; }
+ public string Author { get; }
/// <summary>The mod version.</summary>
- public ISemanticVersion Version { get; set; }
+ public ISemanticVersion Version { get; }
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
- public ISemanticVersion MinimumApiVersion { get; set; }
+ public ISemanticVersion? MinimumApiVersion { get; }
/// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary>
- public string EntryDll { get; set; }
+ public string? EntryDll { get; }
/// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="Manifest.EntryDll"/>.</summary>
[JsonConverter(typeof(ManifestContentPackForConverter))]
- public IManifestContentPackFor ContentPackFor { get; set; }
+ public IManifestContentPackFor? ContentPackFor { get; }
/// <summary>The other mods that must be loaded before this mod.</summary>
[JsonConverter(typeof(ManifestDependencyArrayConverter))]
- public IManifestDependency[] Dependencies { get; set; }
+ public IManifestDependency[] Dependencies { get; }
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
- public string[] UpdateKeys { get; set; }
+ public string[] UpdateKeys { get; private set; }
/// <summary>The unique mod ID.</summary>
- public string UniqueID { get; set; }
+ public string UniqueID { get; }
/// <summary>Any manifest fields which didn't match a valid field.</summary>
[JsonExtensionData]
- public IDictionary<string, object> ExtraFields { get; set; }
+ public IDictionary<string, object> ExtraFields { get; } = new Dictionary<string, object>();
/*********
** Public methods
*********/
- /// <summary>Construct an instance.</summary>
- public Manifest() { }
-
/// <summary>Construct an instance for a transitional content pack.</summary>
/// <param name="uniqueID">The unique mod ID.</param>
/// <param name="name">The mod name.</param>
@@ -61,24 +59,71 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <param name="description">A brief description of the mod.</param>
/// <param name="version">The mod version.</param>
/// <param name="contentPackFor">The modID which will read this as a content pack.</param>
- public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string contentPackFor = null)
+ public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string? contentPackFor = null)
+ : this(
+ uniqueId: uniqueID,
+ name: name,
+ author: author,
+ description: description,
+ version: version,
+ minimumApiVersion: null,
+ entryDll: null,
+ contentPackFor: contentPackFor != null
+ ? new ManifestContentPackFor(contentPackFor, null)
+ : null,
+ dependencies: null,
+ updateKeys: null
+ )
+ { }
+
+ /// <summary>Construct an instance for a transitional content pack.</summary>
+ /// <param name="uniqueId">The unique mod ID.</param>
+ /// <param name="name">The mod name.</param>
+ /// <param name="author">The mod author's name.</param>
+ /// <param name="description">A brief description of the mod.</param>
+ /// <param name="version">The mod version.</param>
+ /// <param name="minimumApiVersion">The minimum SMAPI version required by this mod, if any.</param>
+ /// <param name="entryDll">The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</param>
+ /// <param name="contentPackFor">The modID which will read this as a content pack.</param>
+ /// <param name="dependencies">The other mods that must be loaded before this mod.</param>
+ /// <param name="updateKeys">The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</param>
+ [JsonConstructor]
+ public Manifest(string uniqueId, string name, string author, string description, ISemanticVersion version, ISemanticVersion? minimumApiVersion, string? entryDll, IManifestContentPackFor? contentPackFor, IManifestDependency[]? dependencies, string[]? updateKeys)
{
- this.Name = name;
- this.Author = author;
- this.Description = description;
+ this.UniqueID = this.NormalizeWhitespace(uniqueId);
+ this.Name = this.NormalizeWhitespace(name);
+ this.Author = this.NormalizeWhitespace(author);
+ this.Description = this.NormalizeWhitespace(description);
this.Version = version;
- this.UniqueID = uniqueID;
- this.UpdateKeys = new string[0];
- this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor };
+ this.MinimumApiVersion = minimumApiVersion;
+ this.EntryDll = this.NormalizeWhitespace(entryDll);
+ this.ContentPackFor = contentPackFor;
+ this.Dependencies = dependencies ?? Array.Empty<IManifestDependency>();
+ this.UpdateKeys = updateKeys ?? Array.Empty<string>();
+ }
+
+ /// <summary>Override the update keys loaded from the mod info.</summary>
+ /// <param name="updateKeys">The new update keys to set.</param>
+ internal void OverrideUpdateKeys(params string[] updateKeys)
+ {
+ this.UpdateKeys = updateKeys;
}
- /// <summary>Normalize the model after it's deserialized.</summary>
- /// <param name="context">The deserialization context.</param>
- [OnDeserialized]
- public void OnDeserialized(StreamingContext context)
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Normalize whitespace in a raw string.</summary>
+ /// <param name="input">The input to strip.</param>
+#if NET5_0_OR_GREATER
+ [return: NotNullIfNotNull("input")]
+#endif
+ private string? NormalizeWhitespace(string? input)
{
- this.Dependencies ??= new IManifestDependency[0];
- this.UpdateKeys ??= new string[0];
+ return input
+ ?.Trim()
+ .Replace("\r", "")
+ .Replace("\n", "");
}
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs
index 1eb80889..f7dc8aa8 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace StardewModdingAPI.Toolkit.Serialization.Models
{
/// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary>
@@ -7,9 +9,36 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
** Accessors
*********/
/// <summary>The unique ID of the mod which can read this content pack.</summary>
- public string UniqueID { get; set; }
+ public string UniqueID { get; }
/// <summary>The minimum required version (if any).</summary>
- public ISemanticVersion MinimumVersion { get; set; }
+ public ISemanticVersion? MinimumVersion { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="uniqueId">The unique ID of the mod which can read this content pack.</param>
+ /// <param name="minimumVersion">The minimum required version (if any).</param>
+ public ManifestContentPackFor(string uniqueId, ISemanticVersion? minimumVersion)
+ {
+ this.UniqueID = this.NormalizeWhitespace(uniqueId);
+ this.MinimumVersion = minimumVersion;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Normalize whitespace in a raw string.</summary>
+ /// <param name="input">The input to strip.</param>
+#if NET5_0_OR_GREATER
+ [return: NotNullIfNotNull("input")]
+#endif
+ private string? NormalizeWhitespace(string? input)
+ {
+ return input?.Trim();
+ }
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
index 00f168f4..fa254ea7 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
@@ -1,3 +1,6 @@
+using System.Diagnostics.CodeAnalysis;
+using Newtonsoft.Json;
+
namespace StardewModdingAPI.Toolkit.Serialization.Models
{
/// <summary>A mod dependency listed in a mod manifest.</summary>
@@ -7,13 +10,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
** Accessors
*********/
/// <summary>The unique mod ID to require.</summary>
- public string UniqueID { get; set; }
+ public string UniqueID { get; }
/// <summary>The minimum required version (if any).</summary>
- public ISemanticVersion MinimumVersion { get; set; }
+ public ISemanticVersion? MinimumVersion { get; }
/// <summary>Whether the dependency must be installed to use the mod.</summary>
- public bool IsRequired { get; set; }
+ public bool IsRequired { get; }
/*********
@@ -23,13 +26,40 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <param name="uniqueID">The unique mod ID to require.</param>
/// <param name="minimumVersion">The minimum required version (if any).</param>
/// <param name="required">Whether the dependency must be installed to use the mod.</param>
- public ManifestDependency(string uniqueID, string minimumVersion, bool required = true)
+ public ManifestDependency(string uniqueID, string? minimumVersion, bool required = true)
+ : this(
+ uniqueID: uniqueID,
+ minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion)
+ ? new SemanticVersion(minimumVersion)
+ : null,
+ required: required
+ )
+ { }
+
+ /// <summary>Construct an instance.</summary>
+ /// <param name="uniqueID">The unique mod ID to require.</param>
+ /// <param name="minimumVersion">The minimum required version (if any).</param>
+ /// <param name="required">Whether the dependency must be installed to use the mod.</param>
+ [JsonConstructor]
+ public ManifestDependency(string uniqueID, ISemanticVersion? minimumVersion, bool required = true)
{
- this.UniqueID = uniqueID;
- this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion)
- ? new SemanticVersion(minimumVersion)
- : null;
+ this.UniqueID = this.NormalizeWhitespace(uniqueID);
+ this.MinimumVersion = minimumVersion;
this.IsRequired = required;
}
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Normalize whitespace in a raw string.</summary>
+ /// <param name="input">The input to strip.</param>
+#if NET5_0_OR_GREATER
+ [return: NotNullIfNotNull("input")]
+#endif
+ private string? NormalizeWhitespace(string? input)
+ {
+ return input?.Trim();
+ }
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/SParseException.cs b/src/SMAPI.Toolkit/Serialization/SParseException.cs
index 5f58b5b8..c2b3f68e 100644
--- a/src/SMAPI.Toolkit/Serialization/SParseException.cs
+++ b/src/SMAPI.Toolkit/Serialization/SParseException.cs
@@ -11,7 +11,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <summary>Construct an instance.</summary>
/// <param name="message">The error message.</param>
/// <param name="ex">The underlying exception, if any.</param>
- public SParseException(string message, Exception ex = null)
+ public SParseException(string message, Exception? ex = null)
: base(message, ex) { }
}
}