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.
This commit is contained in:
parent
b4db1fcff8
commit
7b29753290
12 changed files with 68 additions and 48 deletions
|
@ -22,13 +22,12 @@ public class EntityList : IEnumerable<EntityName> {
|
|||
/// <summary>
|
||||
/// Creates a new EntityList instance with no data.
|
||||
/// </summary>
|
||||
public EntityList() : this(null, false) { }
|
||||
public EntityList() : this(null) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new EntityList instance using the given JSON token as input.
|
||||
/// </summary>
|
||||
/// <param name="input">JSON array to be used for input. For ease of use, null values are also accepted.</param>
|
||||
/// <param name="enforceTypes">Specifies if all entities defined in configuration must have their type specified.</param>
|
||||
/// <exception cref="ArgumentException">The input is not a JSON array.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Unintiutively, this exception is thrown if a user-provided configuration value is blank.
|
||||
|
@ -36,7 +35,7 @@ public class EntityList : IEnumerable<EntityName> {
|
|||
/// <exception cref="FormatException">
|
||||
/// When enforceTypes is set, this is thrown if an EntityName results in having its Type be Unspecified.
|
||||
/// </exception>
|
||||
public EntityList(JToken? input, bool enforceTypes) {
|
||||
public EntityList(JToken? input) {
|
||||
if (input == null) {
|
||||
_innerList = new List<EntityName>().AsReadOnly();
|
||||
return;
|
||||
|
@ -50,8 +49,6 @@ public class EntityList : IEnumerable<EntityName> {
|
|||
foreach (var item in inputArray.Values<string>()) {
|
||||
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<EntityName> {
|
|||
} 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<EntityName> {
|
|||
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<EntityName> {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,23 +6,29 @@
|
|||
/// </summary>
|
||||
public class EntityName {
|
||||
/// <summary>
|
||||
/// The entity's type, if specified in configuration.
|
||||
/// The entity's type, as specified in configuration.
|
||||
/// </summary>
|
||||
public EntityType Type { get; private set; }
|
||||
|
||||
private ulong? _id;
|
||||
/// <summary>
|
||||
/// Entity's unique ID value (snowflake). May be null if the value is not known.
|
||||
/// </summary>
|
||||
public ulong? Id { get; private set; }
|
||||
/// <remarks>
|
||||
/// This value may be updated during runtime if the parent <see cref="EntityList"/> was instructed to
|
||||
/// update the ID for persistence.
|
||||
/// </remarks>
|
||||
public ulong? Id {
|
||||
get => _id;
|
||||
internal set => _id ??= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity's name as specified in configuration. May be null if it was not specified.
|
||||
/// This value is not updated during runtime.
|
||||
/// </summary>
|
||||
/// <remarks>This value is not updated during runtime.</remarks>
|
||||
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.
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
/// <summary>
|
||||
/// Creates a new object instance from the given input string.
|
||||
/// Documentation for the EntityName format can be found elsewhere in this project's documentation.
|
||||
/// </summary>
|
||||
/// <param name="input">Input string in EntityName format.</param>
|
||||
/// <param name="expectedType">The <see cref="EntityType"/> expected for this instance.</param>
|
||||
/// <exception cref="ArgumentNullException">Input string is null or blank.</exception>
|
||||
/// <exception cref="ArgumentException">Input string cannot be resolved to an entity type.</exception>
|
||||
/// <exception cref="FormatException">Input string was resolved to a type other than specified.</exception>
|
||||
public EntityName(string input, EntityType expectedType) : this(input) {
|
||||
if (Type != expectedType) throw new FormatException("Resolved EntityType does not match expected type.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the appropriate prefix corresponding to an EntityType.
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
public override string ToString() {
|
||||
var pf = Prefix(Type);
|
||||
var pf = GetPrefix(Type);
|
||||
|
||||
if (Id.HasValue && Name != null)
|
||||
return $"{pf}{Id.Value}::{Name}";
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
/// The type of entity specified in an <see cref="EntityName"/>.
|
||||
/// </summary>
|
||||
public enum EntityType {
|
||||
/// <summary>Default value. Is never referenced in regular usage.</summary>
|
||||
Unspecified,
|
||||
/// <summary>
|
||||
/// Userd when the <see cref="EntityName"/> represents a role.
|
||||
/// </summary>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -26,13 +26,10 @@ class GuildData {
|
|||
public GuildData(JObject conf, Dictionary<ulong, DateTimeOffset> _waitingList) {
|
||||
WaitingList = _waitingList;
|
||||
|
||||
var cfgRole = conf["Role"]?.Value<string>();
|
||||
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<string>()!, EntityType.Role);
|
||||
} catch (Exception) {
|
||||
throw new ModuleLoadException("'Role' was not properly specified.");
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -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<string>();
|
||||
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<string>()!, 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<string>();
|
||||
|
||||
_usage = $"{Command} `user or user ID`\n" +
|
||||
|
|
|
@ -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<string>();
|
||||
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<string>()!, EntityType.Channel);
|
||||
} catch (Exception) {
|
||||
throw new ModuleLoadException(RptChError);
|
||||
}
|
||||
|
||||
// Individual logging settings - all default to false
|
||||
LogMessageDeletions = config[nameof(LogMessageDeletions)]?.Value<bool>() ?? false;
|
||||
|
|
|
@ -5,11 +5,13 @@ class ModuleConfig {
|
|||
public EntityName Role { get; }
|
||||
|
||||
public ModuleConfig(JObject conf) {
|
||||
var cfgRole = conf[nameof(Role)]?.Value<string>();
|
||||
if (string.IsNullOrWhiteSpace(cfgRole))
|
||||
throw new ModuleLoadException("Role was not specified.");
|
||||
Role = new EntityName(cfgRole);
|
||||
if (Role.Type != EntityType.Role)
|
||||
try {
|
||||
Role = new EntityName(conf[nameof(Role)]?.Value<string>()!, 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.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,12 @@ class ConfDefinition {
|
|||
|
||||
var rptch = def[nameof(ReportingChannel)]?.Value<string>();
|
||||
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
|
||||
var opts = RegexOptions.Compiled | RegexOptions.CultureInvariant;
|
||||
|
|
|
@ -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}'.");
|
||||
|
|
|
@ -17,8 +17,12 @@ class ModuleConfig {
|
|||
var values = new Dictionary<ulong, ulong>();
|
||||
|
||||
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}'.");
|
||||
|
||||
|
|
|
@ -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<Type, object?>();
|
||||
|
|
Loading…
Reference in a new issue