From 7b29753290c5a72fa250c16d040f51cb2049a967 Mon Sep 17 00:00:00 2001 From: Noi Date: Mon, 22 Aug 2022 19:25:48 -0700 Subject: [PATCH] Update entity classes EntityList's `enforceTypes` setting was removed, as EntityName enforced entries being unambiguous anyway. Added a way to enforce specific types on instantiation or else throw an exception, and updated all existing uses requiring that check accordingly. --- Common/EntityList.cs | 13 +++----- Common/EntityName.cs | 33 ++++++++++++++----- Common/EntityType.cs | 2 -- Common/FilterList.cs | 4 +-- Modules/EntryRole/GuildData.cs | 9 ++--- .../ModCommands/Commands/RoleManipulation.cs | 12 ++++--- Modules/ModLogs/ModuleConfig.cs | 9 ++--- Modules/PendingOutRole/ModuleConfig.cs | 14 ++++---- Modules/RegexModerator/ConfDefinition.cs | 6 ++-- Modules/RegexModerator/ResponseExecutor.cs | 4 +-- Modules/VoiceRoleSync/ModuleConfig.cs | 8 +++-- Services/ModuleState/ModuleStateService.cs | 2 +- 12 files changed, 68 insertions(+), 48 deletions(-) diff --git a/Common/EntityList.cs b/Common/EntityList.cs index d233a03..4755a6f 100644 --- a/Common/EntityList.cs +++ b/Common/EntityList.cs @@ -22,13 +22,12 @@ public class EntityList : IEnumerable { /// /// Creates a new EntityList instance with no data. /// - public EntityList() : this(null, false) { } + public EntityList() : this(null) { } /// /// Creates a new EntityList instance using the given JSON token as input. /// /// JSON array to be used for input. For ease of use, null values are also accepted. - /// Specifies if all entities defined in configuration must have their type specified. /// The input is not a JSON array. /// /// Unintiutively, this exception is thrown if a user-provided configuration value is blank. @@ -36,7 +35,7 @@ public class EntityList : IEnumerable { /// /// When enforceTypes is set, this is thrown if an EntityName results in having its Type be Unspecified. /// - public EntityList(JToken? input, bool enforceTypes) { + public EntityList(JToken? input) { if (input == null) { _innerList = new List().AsReadOnly(); return; @@ -50,8 +49,6 @@ public class EntityList : IEnumerable { foreach (var item in inputArray.Values()) { if (string.IsNullOrWhiteSpace(item)) continue; var itemName = new EntityName(item); - if (enforceTypes && itemName.Type == EntityType.Unspecified) - throw new FormatException($"The following value is not prefixed: {item}"); list.Add(itemName); } _innerList = list.AsReadOnly(); @@ -82,7 +79,7 @@ public class EntityList : IEnumerable { } else { foreach (var r in authorRoles) { if (!string.Equals(r.Name, entry.Name, StringComparison.OrdinalIgnoreCase)) break; - if (keepId) entry.SetId(r.Id); + if (keepId) entry.Id = r.Id; return true; } } @@ -91,7 +88,7 @@ public class EntityList : IEnumerable { if (entry.Id.Value == channel.Id) return true; } else { if (!string.Equals(channel.Name, entry.Name, StringComparison.OrdinalIgnoreCase)) break; - if (keepId) entry.SetId(channel.Id); + if (keepId) entry.Id = channel.Id; return true; } } else { // User @@ -99,7 +96,7 @@ public class EntityList : IEnumerable { if (entry.Id.Value == author.Id) return true; } else { if (!string.Equals(author.Username, entry.Name, StringComparison.OrdinalIgnoreCase)) break; - if (keepId) entry.SetId(author.Id); + if (keepId) entry.Id = author.Id; return true; } } diff --git a/Common/EntityName.cs b/Common/EntityName.cs index dc7a7bc..5dbaa4a 100644 --- a/Common/EntityName.cs +++ b/Common/EntityName.cs @@ -6,23 +6,29 @@ /// public class EntityName { /// - /// The entity's type, if specified in configuration. + /// The entity's type, as specified in configuration. /// public EntityType Type { get; private set; } + private ulong? _id; /// /// Entity's unique ID value (snowflake). May be null if the value is not known. /// - public ulong? Id { get; private set; } + /// + /// This value may be updated during runtime if the parent was instructed to + /// update the ID for persistence. + /// + public ulong? Id { + get => _id; + internal set => _id ??= value; + } /// /// Entity's name as specified in configuration. May be null if it was not specified. - /// This value is not updated during runtime. /// + /// This value is not updated during runtime. public string? Name { get; private set; } - // TODO elsewhere: find a way to emit a warning if the user specified a name without ID in configuration. - /// /// Creates a new object instance from the given input string. /// Documentation for the EntityName format can be found elsewhere in this project's documentation. @@ -72,14 +78,23 @@ public class EntityName { } } - internal void SetId(ulong id) { - if (!Id.HasValue) Id = id; + /// + /// Creates a new object instance from the given input string. + /// Documentation for the EntityName format can be found elsewhere in this project's documentation. + /// + /// Input string in EntityName format. + /// The expected for this instance. + /// Input string is null or blank. + /// Input string cannot be resolved to an entity type. + /// Input string was resolved to a type other than specified. + public EntityName(string input, EntityType expectedType) : this(input) { + if (Type != expectedType) throw new FormatException("Resolved EntityType does not match expected type."); } /// /// Returns the appropriate prefix corresponding to an EntityType. /// - public static char Prefix(EntityType t) => t switch { + public static char GetPrefix(EntityType t) => t switch { EntityType.Role => '&', EntityType.Channel => '#', EntityType.User => '@', @@ -90,7 +105,7 @@ public class EntityName { /// Returns a string representation of this item in proper EntityName format. /// public override string ToString() { - var pf = Prefix(Type); + var pf = GetPrefix(Type); if (Id.HasValue && Name != null) return $"{pf}{Id.Value}::{Name}"; diff --git a/Common/EntityType.cs b/Common/EntityType.cs index 43e2352..8dd1445 100644 --- a/Common/EntityType.cs +++ b/Common/EntityType.cs @@ -3,8 +3,6 @@ /// The type of entity specified in an . /// public enum EntityType { - /// Default value. Is never referenced in regular usage. - Unspecified, /// /// Userd when the represents a role. /// diff --git a/Common/FilterList.cs b/Common/FilterList.cs index 5dec12d..41ab2d1 100644 --- a/Common/FilterList.cs +++ b/Common/FilterList.cs @@ -75,7 +75,7 @@ public class FilterList { if (incoming.Type != JTokenType.Array) throw new FormatException("Filtering list must be a JSON array."); - FilteredList = new EntityList((JArray)incoming, true); + FilteredList = new EntityList((JArray)incoming); // Verify the same for the exemption list. if (exemptKey != null) { @@ -85,7 +85,7 @@ public class FilterList { } else if (incomingEx.Type != JTokenType.Array) { throw new FormatException("Filtering exemption list must be a JSON array."); } else { - FilterExemptions = new EntityList(incomingEx, true); + FilterExemptions = new EntityList(incomingEx); } } else { FilterExemptions = new EntityList(); diff --git a/Modules/EntryRole/GuildData.cs b/Modules/EntryRole/GuildData.cs index b1b64d6..b87c8ba 100644 --- a/Modules/EntryRole/GuildData.cs +++ b/Modules/EntryRole/GuildData.cs @@ -26,13 +26,10 @@ class GuildData { public GuildData(JObject conf, Dictionary _waitingList) { WaitingList = _waitingList; - var cfgRole = conf["Role"]?.Value(); - if (string.IsNullOrWhiteSpace(cfgRole)) - throw new ModuleLoadException("Role value not specified."); try { - TargetRole = new EntityName(cfgRole); - } catch (ArgumentException) { - throw new ModuleLoadException("Role config value was not properly specified to be a role."); + TargetRole = new EntityName(conf["Role"]?.Value()!, EntityType.Role); + } catch (Exception) { + throw new ModuleLoadException("'Role' was not properly specified."); } try { diff --git a/Modules/ModCommands/Commands/RoleManipulation.cs b/Modules/ModCommands/Commands/RoleManipulation.cs index efc75e3..99e0483 100644 --- a/Modules/ModCommands/Commands/RoleManipulation.cs +++ b/Modules/ModCommands/Commands/RoleManipulation.cs @@ -29,10 +29,14 @@ abstract class RoleManipulation : CommandConfig { // "role" - string; The given role that applies to this command. // "successmsg" - string; Messages to display on command success. Overrides default. protected RoleManipulation(ModCommands module, JObject config) : base(module, config) { - var rolestr = config[nameof(Role)]?.Value(); - if (string.IsNullOrWhiteSpace(rolestr)) throw new ModuleLoadException($"'{nameof(Role)}' must be provided."); - Role = new EntityName(rolestr); - if (Role.Type != EntityType.Role) throw new ModuleLoadException($"The value in '{nameof(Role)}' is not a role."); + try { + Role = new EntityName(config[nameof(Role)]?.Value()!, EntityType.Role); + } catch (ArgumentNullException) { + throw new ModuleLoadException($"'{nameof(Role)}' must be provided."); + } catch (FormatException) { + throw new ModuleLoadException($"The value in '{nameof(Role)}' is not a role."); + } + SuccessMessage = config[nameof(SuccessMessage)]?.Value(); _usage = $"{Command} `user or user ID`\n" + diff --git a/Modules/ModLogs/ModuleConfig.cs b/Modules/ModLogs/ModuleConfig.cs index c7cf0e7..8b83eb5 100644 --- a/Modules/ModLogs/ModuleConfig.cs +++ b/Modules/ModLogs/ModuleConfig.cs @@ -9,10 +9,11 @@ class ModuleConfig { public ModuleConfig(JObject config) { const string RptChError = $"'{nameof(ReportingChannel)}' must be set to a valid channel name."; - var rptch = config[nameof(ReportingChannel)]?.Value(); - if (string.IsNullOrWhiteSpace(rptch)) throw new ModuleLoadException(RptChError); - ReportingChannel = new EntityName(rptch); - if (ReportingChannel.Type != EntityType.Channel) throw new ModuleLoadException(RptChError); + try { + ReportingChannel = new EntityName(config[nameof(ReportingChannel)]?.Value()!, EntityType.Channel); + } catch (Exception) { + throw new ModuleLoadException(RptChError); + } // Individual logging settings - all default to false LogMessageDeletions = config[nameof(LogMessageDeletions)]?.Value() ?? false; diff --git a/Modules/PendingOutRole/ModuleConfig.cs b/Modules/PendingOutRole/ModuleConfig.cs index 94de96a..90c601a 100644 --- a/Modules/PendingOutRole/ModuleConfig.cs +++ b/Modules/PendingOutRole/ModuleConfig.cs @@ -5,11 +5,13 @@ class ModuleConfig { public EntityName Role { get; } public ModuleConfig(JObject conf) { - var cfgRole = conf[nameof(Role)]?.Value(); - if (string.IsNullOrWhiteSpace(cfgRole)) - throw new ModuleLoadException("Role was not specified."); - Role = new EntityName(cfgRole); - if (Role.Type != EntityType.Role) - throw new ModuleLoadException("Name specified in configuration is not a role."); + try { + Role = new EntityName(conf[nameof(Role)]?.Value()!, EntityType.Role); + } catch (ArgumentException) { + throw new ModuleLoadException("Role was not properly specified."); + } catch (FormatException) { + throw new ModuleLoadException("Name specified in configuration is not a role."); + } + } } diff --git a/Modules/RegexModerator/ConfDefinition.cs b/Modules/RegexModerator/ConfDefinition.cs index a26a25a..08e05a4 100644 --- a/Modules/RegexModerator/ConfDefinition.cs +++ b/Modules/RegexModerator/ConfDefinition.cs @@ -34,9 +34,11 @@ class ConfDefinition { var rptch = def[nameof(ReportingChannel)]?.Value(); if (rptch != null) { - ReportingChannel = new EntityName(rptch); - if (ReportingChannel.Type != EntityType.Channel) + try { + ReportingChannel = new EntityName(rptch, EntityType.Channel); + } catch (FormatException) { throw new ModuleLoadException($"'{nameof(ReportingChannel)}' is not defined as a channel{errpostfx}"); + } } // Regex loading diff --git a/Modules/RegexModerator/ResponseExecutor.cs b/Modules/RegexModerator/ResponseExecutor.cs index 6aa6ea2..7d3486d 100644 --- a/Modules/RegexModerator/ResponseExecutor.cs +++ b/Modules/RegexModerator/ResponseExecutor.cs @@ -160,14 +160,14 @@ class ResponseExecutor { SocketGuildUser? tuser; SocketRole? trole; try { - var userName = new EntityName(param[0]); + var userName = new EntityName(param[0], EntityType.User); if (userName.Id.HasValue) tuser = _guild.GetUser(userName.Id.Value); else { if (userName.Name == "_") tuser = _user; else tuser = userName.FindUserIn(_guild); } if (tuser == null) return FromError($"Unable to find user '{userName.Name}'."); - var roleName = new EntityName(param[1]); + var roleName = new EntityName(param[1], EntityType.Role); if (roleName.Id.HasValue) trole = _guild.GetRole(roleName.Id.Value); else trole = roleName.FindRoleIn(_guild); if (trole == null) return FromError($"Unable to find role '{roleName.Name}'."); diff --git a/Modules/VoiceRoleSync/ModuleConfig.cs b/Modules/VoiceRoleSync/ModuleConfig.cs index e9fe9f2..b959b27 100644 --- a/Modules/VoiceRoleSync/ModuleConfig.cs +++ b/Modules/VoiceRoleSync/ModuleConfig.cs @@ -17,8 +17,12 @@ class ModuleConfig { var values = new Dictionary(); foreach (var item in config.Properties()) { - var name = new EntityName(item.Name); - if (name.Type != EntityType.Role) throw new ModuleLoadException($"'{item.Name}' is not specified as a role."); + EntityName name; + try { + name = new EntityName(item.Name, EntityType.Role); + } catch (FormatException) { + throw new ModuleLoadException($"'{item.Name}' is not specified as a role."); + } var role = name.FindRoleIn(g); if (role == null) throw new ModuleLoadException($"Unable to find role '{name}'."); diff --git a/Services/ModuleState/ModuleStateService.cs b/Services/ModuleState/ModuleStateService.cs index 6880aba..1faf1a0 100644 --- a/Services/ModuleState/ModuleStateService.cs +++ b/Services/ModuleState/ModuleStateService.cs @@ -69,7 +69,7 @@ class ModuleStateService : Service { } // Load moderator list - var mods = new EntityList(guildConf["Moderators"]!, true); + var mods = new EntityList(guildConf["Moderators"]!); // Create guild state objects for all existing modules var newStates = new Dictionary();