From b0d0de8947ed0934d65144c0bc9521c0d76d6d00 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 26 Feb 2024 12:45:22 +0100 Subject: [PATCH 1/5] Add `GenerateConfigSchemas` option The default value of this option is `false` to make it an opt-in feature. External tools, that want to use this feature, would have to set this value to `true`. --- src/SMAPI/Framework/Models/SConfig.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 40bdb1304..9a1b145f7 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -26,7 +26,8 @@ internal class SConfig [nameof(RewriteMods)] = true, [nameof(FixHarmony)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux, - [nameof(SuppressHarmonyDebugMode)] = true + [nameof(SuppressHarmonyDebugMode)] = true, + [nameof(GenerateConfigSchemas)] = false, }; /// The default values for , to log changes if different. @@ -99,6 +100,8 @@ internal class SConfig /// The mod IDs SMAPI should load after any other mods. public HashSet ModsToLoadLate { get; set; } + /// Whether to generate config.schema.json files for external tools like mod managers. + public bool GenerateConfigSchemas { get; set; } /******** ** Public methods @@ -122,7 +125,8 @@ internal class SConfig /// /// /// - public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? fixHarmony, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate) + /// + public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? fixHarmony, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate, bool? generateConfigSchemas) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -142,6 +146,7 @@ public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsole this.SuppressUpdateChecks = new HashSet(suppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.ModsToLoadEarly = new HashSet(modsToLoadEarly ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.ModsToLoadLate = new HashSet(modsToLoadLate ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + this.GenerateConfigSchemas = generateConfigSchemas ?? (bool)SConfig.DefaultValues[nameof(this.GenerateConfigSchemas)]; } /// Override the value of . From f937ae5cf5c67cba815e296292d9820f697ba12c Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 26 Feb 2024 13:14:25 +0100 Subject: [PATCH 2/5] Generate JSON schema using `Newtonsoft.Json.Schema` --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 1 + src/SMAPI.Toolkit/Serialization/JsonHelper.cs | 33 +++++++++++++++++++ src/SMAPI/Framework/ModHelpers/DataHelper.cs | 14 ++++++-- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 22 ++++++++++++- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/IDataHelper.cs | 8 +++++ 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 2ad7798a7..2d9c76f88 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -11,6 +11,7 @@ + diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 208cd6567..0552a23e1 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -4,6 +4,8 @@ using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Schema; +using Newtonsoft.Json.Schema.Generation; using StardewModdingAPI.Toolkit.Serialization.Converters; namespace StardewModdingAPI.Toolkit.Serialization @@ -17,6 +19,13 @@ public class JsonHelper /// The JSON settings to use when serializing and deserializing files. public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings(); + private readonly JSchemaGenerator _schemaGenerator = new(); + + private readonly JSchemaWriterSettings _schemaWriterSettings = new() + { + Version = SchemaVersion.Draft2019_09, + ReferenceHandling = JSchemaWriterReferenceHandling.Never + }; /********* ** Public methods @@ -111,6 +120,30 @@ public void WriteJsonFile(string fullPath, TModel model) File.WriteAllText(fullPath, json); } + /// Generate a schema and save to a JSON file. + /// The model type. + /// The absolute file path. + /// The given path is empty or invalid. + public void WriteJsonSchemaFile(string fullPath) + where TModel : class + { + // validate + if (string.IsNullOrWhiteSpace(fullPath)) + throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); + + // create directory if needed + string dir = Path.GetDirectoryName(fullPath)!; + if (dir == null) + throw new ArgumentException("The file path is invalid.", nameof(fullPath)); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + JSchema schema = this._schemaGenerator.Generate(typeof(TModel)); + string output = schema.ToString(this._schemaWriterSettings); + + File.WriteAllText(fullPath, output); + } + /// Deserialize JSON text if possible. /// The model type. /// The raw JSON text. diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 2eaa940a7..dd8cd373e 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -67,9 +67,19 @@ public void WriteJsonFile(string path, TModel? data) File.Delete(path); } + /// + public void WriteJsonSchemaFile(string path, TModel data) where TModel : class + { + if (!PathUtilities.IsSafeRelativePath(path)) + throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing)."); + + path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path)); + this.JsonHelper.WriteJsonSchemaFile(path); + } + /**** - ** Save file - ****/ + ** Save file + ****/ /// public TModel? ReadSaveData(string key) where TModel : class diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index d1cf357e9..13efbd683 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -8,6 +8,12 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Provides simplified APIs for writing mods. internal class ModHelper : BaseHelper, IModHelper, IDisposable { + /********* + ** Fields + *********/ + /// Whether to generate config.schema.json files. + private readonly bool _generateConfigSchemas; + /********* ** Accessors *********/ @@ -65,9 +71,10 @@ internal class ModHelper : BaseHelper, IModHelper, IDisposable /// An API for accessing private game code. /// Provides multiplayer utilities. /// An API for reading translations stored in the mod's i18n folder. + /// Whether to generate config.schema.json files. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) + public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, bool generateConfigSchemas) : base(mod) { // validate directory @@ -89,6 +96,7 @@ public ModHelper(IModMetadata mod, string modDirectory, Func curren this.Multiplayer = multiplayer ?? throw new ArgumentNullException(nameof(multiplayer)); this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper)); this.Events = events; + this._generateConfigSchemas = generateConfigSchemas; } /**** @@ -100,6 +108,12 @@ public TConfig ReadConfig() { TConfig config = this.Data.ReadJsonFile("config.json") ?? new TConfig(); this.WriteConfig(config); // create file or fill in missing fields + + if (this._generateConfigSchemas) + { + this.WriteConfigSchema(config); + } + return config; } @@ -110,6 +124,12 @@ public void WriteConfig(TConfig config) this.Data.WriteJsonFile("config.json", config); } + private void WriteConfigSchema(TConfig config) + where TConfig : class, new() + { + this.Data.WriteJsonSchemaFile("config.schema.json", config); + } + /**** ** Disposal ****/ diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 0a6221c84..a894fa7a2 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -2050,7 +2050,7 @@ IContentPack[] GetContentPacks() IModRegistry modRegistryHelper = new ModRegistryHelper(mod, this.ModRegistry, proxyFactory, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(mod, this.Multiplayer); - modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); + modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, this.Settings.GenerateConfigSchemas); } // init mod diff --git a/src/SMAPI/IDataHelper.cs b/src/SMAPI/IDataHelper.cs index 7ddf851ef..23d026e31 100644 --- a/src/SMAPI/IDataHelper.cs +++ b/src/SMAPI/IDataHelper.cs @@ -27,6 +27,14 @@ public interface IDataHelper void WriteJsonFile(string path, TModel? data) where TModel : class; + /// Save the schema of the data to a JSON file in the mod's folder. + /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. + /// The file path relative to the mod folder. + /// The arbitrary data to save. + /// The is not relative or contains directory climbing (../). + void WriteJsonSchemaFile(string path, TModel data) + where TModel : class; + /**** ** Save file ****/ From e2e8c20825ff098952e662c2a5dee6a3f76f9208 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Jul 2024 17:26:07 -0400 Subject: [PATCH 3/5] tweak schema generation code & update installer --- build/deploy-local-smapi.targets | 1 + build/unix/prepare-install-package.sh | 2 +- build/windows/prepare-install-package.ps1 | 2 +- src/SMAPI.Toolkit/Serialization/JsonHelper.cs | 37 ++++++++++--------- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 14 ++++--- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 24 ++++-------- src/SMAPI/Framework/Models/SConfig.cs | 5 ++- src/SMAPI/IDataHelper.cs | 5 +-- src/SMAPI/SMAPI.config.json | 9 ++++- 9 files changed, 52 insertions(+), 47 deletions(-) diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index bd84ee11b..1f3bb3129 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -22,6 +22,7 @@ This assumes `find-game-folder.targets` has already been imported and validated. + diff --git a/build/unix/prepare-install-package.sh b/build/unix/prepare-install-package.sh index bd7df36a8..e2baad31b 100755 --- a/build/unix/prepare-install-package.sh +++ b/build/unix/prepare-install-package.sh @@ -134,7 +134,7 @@ for folder in ${folders[@]}; do cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do + for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Newtonsoft.Json.Schema.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do cp "$smapiBin/$name" "$bundlePath/smapi-internal" done diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1 index 08ed88fca..16b35cead 100644 --- a/build/windows/prepare-install-package.ps1 +++ b/build/windows/prepare-install-package.ps1 @@ -155,7 +155,7 @@ foreach ($folder in $folders) { cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { + foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Newtonsoft.Json.Schema.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { cp "$smapiBin/$name" "$bundlePath/smapi-internal" } diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 0552a23e1..a1ae915af 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -14,19 +14,26 @@ namespace StardewModdingAPI.Toolkit.Serialization public class JsonHelper { /********* - ** Accessors + ** Fields *********/ - /// The JSON settings to use when serializing and deserializing files. - public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings(); - - private readonly JSchemaGenerator _schemaGenerator = new(); + /// The JSON schema generator to use when creating a schema file. + private readonly JSchemaGenerator SchemaGenerator = new(); - private readonly JSchemaWriterSettings _schemaWriterSettings = new() + /// The JSON settings to use when creating a schema file. + private readonly JSchemaWriterSettings SchemaWriterSettings = new() { Version = SchemaVersion.Draft2019_09, ReferenceHandling = JSchemaWriterReferenceHandling.Never }; + + /********* + ** Accessors + *********/ + /// The JSON settings to use when serializing and deserializing files. + public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings(); + + /********* ** Public methods *********/ @@ -109,9 +116,7 @@ public void WriteJsonFile(string fullPath, TModel model) throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // create directory if needed - string dir = Path.GetDirectoryName(fullPath)!; - if (dir == null) - throw new ArgumentException("The file path is invalid.", nameof(fullPath)); + string dir = Path.GetDirectoryName(fullPath) ?? throw new ArgumentException("The file path is invalid.", nameof(fullPath)); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); @@ -120,7 +125,7 @@ public void WriteJsonFile(string fullPath, TModel model) File.WriteAllText(fullPath, json); } - /// Generate a schema and save to a JSON file. + /// Save a data model schema to a JSON file. /// The model type. /// The absolute file path. /// The given path is empty or invalid. @@ -132,16 +137,14 @@ public void WriteJsonSchemaFile(string fullPath) throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // create directory if needed - string dir = Path.GetDirectoryName(fullPath)!; - if (dir == null) - throw new ArgumentException("The file path is invalid.", nameof(fullPath)); + string dir = Path.GetDirectoryName(fullPath) ?? throw new ArgumentException("The file path is invalid.", nameof(fullPath)); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - JSchema schema = this._schemaGenerator.Generate(typeof(TModel)); - string output = schema.ToString(this._schemaWriterSettings); - - File.WriteAllText(fullPath, output); + // write file + JSchema schema = this.SchemaGenerator.Generate(typeof(TModel)); + string json = schema.ToString(this.SchemaWriterSettings); + File.WriteAllText(fullPath, json); } /// Deserialize JSON text if possible. diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index dd8cd373e..69b20b1ac 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -28,7 +28,7 @@ internal class DataHelper : BaseHelper, IDataHelper /// Construct an instance. /// The mod using this instance. /// The absolute path to the mod folder. - /// The absolute path to the mod folder. + /// Encapsulates SMAPI's JSON file parsing. public DataHelper(IModMetadata mod, string modFolderPath, JsonHelper jsonHelper) : base(mod) { @@ -67,19 +67,21 @@ public void WriteJsonFile(string path, TModel? data) File.Delete(path); } - /// - public void WriteJsonSchemaFile(string path, TModel data) where TModel : class + /// + public void WriteJsonSchemaFile(string path) + where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) - throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing)."); + throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonSchemaFile)} with a relative path (without directory climbing)."); path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path)); + this.JsonHelper.WriteJsonSchemaFile(path); } /**** - ** Save file - ****/ + ** Save file + ****/ /// public TModel? ReadSaveData(string key) where TModel : class diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 13efbd683..e48052aab 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -11,8 +11,9 @@ internal class ModHelper : BaseHelper, IModHelper, IDisposable /********* ** Fields *********/ - /// Whether to generate config.schema.json files. - private readonly bool _generateConfigSchemas; + /// Whether to generate a config.schema.json file based on the mod's config model when it's loaded or saved. + private readonly bool GenerateConfigSchema; + /********* ** Accessors @@ -71,10 +72,10 @@ internal class ModHelper : BaseHelper, IModHelper, IDisposable /// An API for accessing private game code. /// Provides multiplayer utilities. /// An API for reading translations stored in the mod's i18n folder. - /// Whether to generate config.schema.json files. + /// Whether to generate a config.schema.json file based on the mod's config model when it's loaded or saved. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, bool generateConfigSchemas) + public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, bool generateConfigSchema) : base(mod) { // validate directory @@ -96,7 +97,7 @@ public ModHelper(IModMetadata mod, string modDirectory, Func curren this.Multiplayer = multiplayer ?? throw new ArgumentNullException(nameof(multiplayer)); this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper)); this.Events = events; - this._generateConfigSchemas = generateConfigSchemas; + this.GenerateConfigSchema = generateConfigSchema; } /**** @@ -108,12 +109,6 @@ public TConfig ReadConfig() { TConfig config = this.Data.ReadJsonFile("config.json") ?? new TConfig(); this.WriteConfig(config); // create file or fill in missing fields - - if (this._generateConfigSchemas) - { - this.WriteConfigSchema(config); - } - return config; } @@ -122,12 +117,9 @@ public void WriteConfig(TConfig config) where TConfig : class, new() { this.Data.WriteJsonFile("config.json", config); - } - private void WriteConfigSchema(TConfig config) - where TConfig : class, new() - { - this.Data.WriteJsonSchemaFile("config.schema.json", config); + if (this.GenerateConfigSchema) + this.Data.WriteJsonSchemaFile("config.schema.json"); } /**** diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 9a1b145f7..5f4e8e66d 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -27,7 +27,7 @@ internal class SConfig [nameof(FixHarmony)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux, [nameof(SuppressHarmonyDebugMode)] = true, - [nameof(GenerateConfigSchemas)] = false, + [nameof(GenerateConfigSchemas)] = false }; /// The default values for , to log changes if different. @@ -100,9 +100,10 @@ internal class SConfig /// The mod IDs SMAPI should load after any other mods. public HashSet ModsToLoadLate { get; set; } - /// Whether to generate config.schema.json files for external tools like mod managers. + /// Whether to generate config.schema.json files for external tools like mod managers. public bool GenerateConfigSchemas { get; set; } + /******** ** Public methods ********/ diff --git a/src/SMAPI/IDataHelper.cs b/src/SMAPI/IDataHelper.cs index 23d026e31..ff778bc24 100644 --- a/src/SMAPI/IDataHelper.cs +++ b/src/SMAPI/IDataHelper.cs @@ -27,12 +27,11 @@ public interface IDataHelper void WriteJsonFile(string path, TModel? data) where TModel : class; - /// Save the schema of the data to a JSON file in the mod's folder. + /// Save a data model schema to a JSON file in the mod's folder. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The file path relative to the mod folder. - /// The arbitrary data to save. /// The is not relative or contains directory climbing (../). - void WriteJsonSchemaFile(string path, TModel data) + void WriteJsonSchemaFile(string path) where TModel : class; /**** diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 55f9869b6..42c3339cf 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -172,5 +172,12 @@ in future SMAPI versions. * the mod author. */ "ModsToLoadEarly": [], - "ModsToLoadLate": [] + "ModsToLoadLate": [], + + /** + * Whether to generate a `config.schema.json` file next to each mod's `config.json` file. + * + * This can be used by separate tools like mod managers to enable config editing features. + */ + "GenerateConfigSchemas": false } From 9bc45d6f0832e476d0fc998719b25a57cc644dff Mon Sep 17 00:00:00 2001 From: erri120 Date: Sun, 7 Jul 2024 12:28:54 +0200 Subject: [PATCH 4/5] Replace Newtonsoft.Json.Schema with NJsonSchema --- build/deploy-local-smapi.targets | 2 +- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI.Toolkit/Serialization/JsonHelper.cs | 24 ++++--------------- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index 1f3bb3129..5da661714 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -22,7 +22,7 @@ This assumes `find-game-folder.targets` has already been imported and validated. - + diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 2d9c76f88..028df7fc4 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index a1ae915af..539ef344e 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Schema; -using Newtonsoft.Json.Schema.Generation; +using NJsonSchema; using StardewModdingAPI.Toolkit.Serialization.Converters; namespace StardewModdingAPI.Toolkit.Serialization @@ -13,20 +11,6 @@ namespace StardewModdingAPI.Toolkit.Serialization /// Encapsulates SMAPI's JSON file parsing. public class JsonHelper { - /********* - ** Fields - *********/ - /// The JSON schema generator to use when creating a schema file. - private readonly JSchemaGenerator SchemaGenerator = new(); - - /// The JSON settings to use when creating a schema file. - private readonly JSchemaWriterSettings SchemaWriterSettings = new() - { - Version = SchemaVersion.Draft2019_09, - ReferenceHandling = JSchemaWriterReferenceHandling.Never - }; - - /********* ** Accessors *********/ @@ -61,7 +45,7 @@ public static JsonSerializerSettings CreateDefaultSettings() /// The file contains invalid JSON. public bool ReadJsonFileIfExists(string fullPath, #if NET6_0_OR_GREATER - [NotNullWhen(true)] + [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] #endif out TModel? result ) @@ -142,8 +126,8 @@ public void WriteJsonSchemaFile(string fullPath) Directory.CreateDirectory(dir); // write file - JSchema schema = this.SchemaGenerator.Generate(typeof(TModel)); - string json = schema.ToString(this.SchemaWriterSettings); + JsonSchema schema = JsonSchema.FromType(); + string json = schema.ToJson(); File.WriteAllText(fullPath, json); } From 130fdbaa1bcf440bdd939d69ad4fffb920b51a1a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 Jul 2024 12:07:57 -0400 Subject: [PATCH 5/5] update installer & deploy for NJsonSchema --- build/deploy-local-smapi.targets | 2 ++ build/unix/prepare-install-package.sh | 2 +- build/windows/prepare-install-package.ps1 | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index 5da661714..3fc4720d2 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -21,8 +21,10 @@ This assumes `find-game-folder.targets` has already been imported and validated. + + diff --git a/build/unix/prepare-install-package.sh b/build/unix/prepare-install-package.sh index e2baad31b..c7d7a7756 100755 --- a/build/unix/prepare-install-package.sh +++ b/build/unix/prepare-install-package.sh @@ -134,7 +134,7 @@ for folder in ${folders[@]}; do cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Newtonsoft.Json.Schema.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do + for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Namotion.Reflection.dll" "Newtonsoft.Json.dll" "NJsonSchema.dll" "NJsonSchema.Annotations.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do cp "$smapiBin/$name" "$bundlePath/smapi-internal" done diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1 index 16b35cead..d02070ef5 100644 --- a/build/windows/prepare-install-package.ps1 +++ b/build/windows/prepare-install-package.ps1 @@ -155,7 +155,7 @@ foreach ($folder in $folders) { cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Newtonsoft.Json.Schema.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { + foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Namotion.Reflection.dll", "Newtonsoft.Json.dll", "NJsonSchema.dll", "NJsonSchema.Annotations.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { cp "$smapiBin/$name" "$bundlePath/smapi-internal" }