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>
|
/// <summary>
|
||||||
/// Creates a new EntityList instance with no data.
|
/// Creates a new EntityList instance with no data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EntityList() : this(null, false) { }
|
public EntityList() : this(null) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new EntityList instance using the given JSON token as input.
|
/// Creates a new EntityList instance using the given JSON token as input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">JSON array to be used for input. For ease of use, null values are also accepted.</param>
|
/// <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="ArgumentException">The input is not a JSON array.</exception>
|
||||||
/// <exception cref="ArgumentNullException">
|
/// <exception cref="ArgumentNullException">
|
||||||
/// Unintiutively, this exception is thrown if a user-provided configuration value is blank.
|
/// 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">
|
/// <exception cref="FormatException">
|
||||||
/// When enforceTypes is set, this is thrown if an EntityName results in having its Type be Unspecified.
|
/// When enforceTypes is set, this is thrown if an EntityName results in having its Type be Unspecified.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public EntityList(JToken? input, bool enforceTypes) {
|
public EntityList(JToken? input) {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
_innerList = new List<EntityName>().AsReadOnly();
|
_innerList = new List<EntityName>().AsReadOnly();
|
||||||
return;
|
return;
|
||||||
|
@ -50,8 +49,6 @@ public class EntityList : IEnumerable<EntityName> {
|
||||||
foreach (var item in inputArray.Values<string>()) {
|
foreach (var item in inputArray.Values<string>()) {
|
||||||
if (string.IsNullOrWhiteSpace(item)) continue;
|
if (string.IsNullOrWhiteSpace(item)) continue;
|
||||||
var itemName = new EntityName(item);
|
var itemName = new EntityName(item);
|
||||||
if (enforceTypes && itemName.Type == EntityType.Unspecified)
|
|
||||||
throw new FormatException($"The following value is not prefixed: {item}");
|
|
||||||
list.Add(itemName);
|
list.Add(itemName);
|
||||||
}
|
}
|
||||||
_innerList = list.AsReadOnly();
|
_innerList = list.AsReadOnly();
|
||||||
|
@ -82,7 +79,7 @@ public class EntityList : IEnumerable<EntityName> {
|
||||||
} else {
|
} else {
|
||||||
foreach (var r in authorRoles) {
|
foreach (var r in authorRoles) {
|
||||||
if (!string.Equals(r.Name, entry.Name, StringComparison.OrdinalIgnoreCase)) break;
|
if (!string.Equals(r.Name, entry.Name, StringComparison.OrdinalIgnoreCase)) break;
|
||||||
if (keepId) entry.SetId(r.Id);
|
if (keepId) entry.Id = r.Id;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +88,7 @@ public class EntityList : IEnumerable<EntityName> {
|
||||||
if (entry.Id.Value == channel.Id) return true;
|
if (entry.Id.Value == channel.Id) return true;
|
||||||
} else {
|
} else {
|
||||||
if (!string.Equals(channel.Name, entry.Name, StringComparison.OrdinalIgnoreCase)) break;
|
if (!string.Equals(channel.Name, entry.Name, StringComparison.OrdinalIgnoreCase)) break;
|
||||||
if (keepId) entry.SetId(channel.Id);
|
if (keepId) entry.Id = channel.Id;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else { // User
|
} else { // User
|
||||||
|
@ -99,7 +96,7 @@ public class EntityList : IEnumerable<EntityName> {
|
||||||
if (entry.Id.Value == author.Id) return true;
|
if (entry.Id.Value == author.Id) return true;
|
||||||
} else {
|
} else {
|
||||||
if (!string.Equals(author.Username, entry.Name, StringComparison.OrdinalIgnoreCase)) break;
|
if (!string.Equals(author.Username, entry.Name, StringComparison.OrdinalIgnoreCase)) break;
|
||||||
if (keepId) entry.SetId(author.Id);
|
if (keepId) entry.Id = author.Id;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,29 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EntityName {
|
public class EntityName {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity's type, if specified in configuration.
|
/// The entity's type, as specified in configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EntityType Type { get; private set; }
|
public EntityType Type { get; private set; }
|
||||||
|
|
||||||
|
private ulong? _id;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Entity's unique ID value (snowflake). May be null if the value is not known.
|
/// Entity's unique ID value (snowflake). May be null if the value is not known.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Entity's name as specified in configuration. May be null if it was not specified.
|
/// Entity's name as specified in configuration. May be null if it was not specified.
|
||||||
/// This value is not updated during runtime.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>This value is not updated during runtime.</remarks>
|
||||||
public string? Name { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Creates a new object instance from the given input string.
|
/// Creates a new object instance from the given input string.
|
||||||
/// Documentation for the EntityName format can be found elsewhere in this project's documentation.
|
/// 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) {
|
/// <summary>
|
||||||
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.
|
||||||
|
/// </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>
|
/// <summary>
|
||||||
/// Returns the appropriate prefix corresponding to an EntityType.
|
/// Returns the appropriate prefix corresponding to an EntityType.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static char Prefix(EntityType t) => t switch {
|
public static char GetPrefix(EntityType t) => t switch {
|
||||||
EntityType.Role => '&',
|
EntityType.Role => '&',
|
||||||
EntityType.Channel => '#',
|
EntityType.Channel => '#',
|
||||||
EntityType.User => '@',
|
EntityType.User => '@',
|
||||||
|
@ -90,7 +105,7 @@ public class EntityName {
|
||||||
/// Returns a string representation of this item in proper EntityName format.
|
/// Returns a string representation of this item in proper EntityName format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override string ToString() {
|
public override string ToString() {
|
||||||
var pf = Prefix(Type);
|
var pf = GetPrefix(Type);
|
||||||
|
|
||||||
if (Id.HasValue && Name != null)
|
if (Id.HasValue && Name != null)
|
||||||
return $"{pf}{Id.Value}::{Name}";
|
return $"{pf}{Id.Value}::{Name}";
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
/// The type of entity specified in an <see cref="EntityName"/>.
|
/// The type of entity specified in an <see cref="EntityName"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum EntityType {
|
public enum EntityType {
|
||||||
/// <summary>Default value. Is never referenced in regular usage.</summary>
|
|
||||||
Unspecified,
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Userd when the <see cref="EntityName"/> represents a role.
|
/// Userd when the <see cref="EntityName"/> represents a role.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class FilterList {
|
||||||
|
|
||||||
if (incoming.Type != JTokenType.Array)
|
if (incoming.Type != JTokenType.Array)
|
||||||
throw new FormatException("Filtering list must be a JSON 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.
|
// Verify the same for the exemption list.
|
||||||
if (exemptKey != null) {
|
if (exemptKey != null) {
|
||||||
|
@ -85,7 +85,7 @@ public class FilterList {
|
||||||
} else if (incomingEx.Type != JTokenType.Array) {
|
} else if (incomingEx.Type != JTokenType.Array) {
|
||||||
throw new FormatException("Filtering exemption list must be a JSON array.");
|
throw new FormatException("Filtering exemption list must be a JSON array.");
|
||||||
} else {
|
} else {
|
||||||
FilterExemptions = new EntityList(incomingEx, true);
|
FilterExemptions = new EntityList(incomingEx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FilterExemptions = new EntityList();
|
FilterExemptions = new EntityList();
|
||||||
|
|
|
@ -26,13 +26,10 @@ class GuildData {
|
||||||
public GuildData(JObject conf, Dictionary<ulong, DateTimeOffset> _waitingList) {
|
public GuildData(JObject conf, Dictionary<ulong, DateTimeOffset> _waitingList) {
|
||||||
WaitingList = _waitingList;
|
WaitingList = _waitingList;
|
||||||
|
|
||||||
var cfgRole = conf["Role"]?.Value<string>();
|
|
||||||
if (string.IsNullOrWhiteSpace(cfgRole))
|
|
||||||
throw new ModuleLoadException("Role value not specified.");
|
|
||||||
try {
|
try {
|
||||||
TargetRole = new EntityName(cfgRole);
|
TargetRole = new EntityName(conf["Role"]?.Value<string>()!, EntityType.Role);
|
||||||
} catch (ArgumentException) {
|
} catch (Exception) {
|
||||||
throw new ModuleLoadException("Role config value was not properly specified to be a role.");
|
throw new ModuleLoadException("'Role' was not properly specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -29,10 +29,14 @@ abstract class RoleManipulation : CommandConfig {
|
||||||
// "role" - string; The given role that applies to this command.
|
// "role" - string; The given role that applies to this command.
|
||||||
// "successmsg" - string; Messages to display on command success. Overrides default.
|
// "successmsg" - string; Messages to display on command success. Overrides default.
|
||||||
protected RoleManipulation(ModCommands module, JObject config) : base(module, config) {
|
protected RoleManipulation(ModCommands module, JObject config) : base(module, config) {
|
||||||
var rolestr = config[nameof(Role)]?.Value<string>();
|
try {
|
||||||
if (string.IsNullOrWhiteSpace(rolestr)) throw new ModuleLoadException($"'{nameof(Role)}' must be provided.");
|
Role = new EntityName(config[nameof(Role)]?.Value<string>()!, EntityType.Role);
|
||||||
Role = new EntityName(rolestr);
|
} catch (ArgumentNullException) {
|
||||||
if (Role.Type != EntityType.Role) throw new ModuleLoadException($"The value in '{nameof(Role)}' is not a role.");
|
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>();
|
SuccessMessage = config[nameof(SuccessMessage)]?.Value<string>();
|
||||||
|
|
||||||
_usage = $"{Command} `user or user ID`\n" +
|
_usage = $"{Command} `user or user ID`\n" +
|
||||||
|
|
|
@ -9,10 +9,11 @@ class ModuleConfig {
|
||||||
|
|
||||||
public ModuleConfig(JObject config) {
|
public ModuleConfig(JObject config) {
|
||||||
const string RptChError = $"'{nameof(ReportingChannel)}' must be set to a valid channel name.";
|
const string RptChError = $"'{nameof(ReportingChannel)}' must be set to a valid channel name.";
|
||||||
var rptch = config[nameof(ReportingChannel)]?.Value<string>();
|
try {
|
||||||
if (string.IsNullOrWhiteSpace(rptch)) throw new ModuleLoadException(RptChError);
|
ReportingChannel = new EntityName(config[nameof(ReportingChannel)]?.Value<string>()!, EntityType.Channel);
|
||||||
ReportingChannel = new EntityName(rptch);
|
} catch (Exception) {
|
||||||
if (ReportingChannel.Type != EntityType.Channel) throw new ModuleLoadException(RptChError);
|
throw new ModuleLoadException(RptChError);
|
||||||
|
}
|
||||||
|
|
||||||
// Individual logging settings - all default to false
|
// Individual logging settings - all default to false
|
||||||
LogMessageDeletions = config[nameof(LogMessageDeletions)]?.Value<bool>() ?? false;
|
LogMessageDeletions = config[nameof(LogMessageDeletions)]?.Value<bool>() ?? false;
|
||||||
|
|
|
@ -5,11 +5,13 @@ class ModuleConfig {
|
||||||
public EntityName Role { get; }
|
public EntityName Role { get; }
|
||||||
|
|
||||||
public ModuleConfig(JObject conf) {
|
public ModuleConfig(JObject conf) {
|
||||||
var cfgRole = conf[nameof(Role)]?.Value<string>();
|
try {
|
||||||
if (string.IsNullOrWhiteSpace(cfgRole))
|
Role = new EntityName(conf[nameof(Role)]?.Value<string>()!, EntityType.Role);
|
||||||
throw new ModuleLoadException("Role was not specified.");
|
} catch (ArgumentException) {
|
||||||
Role = new EntityName(cfgRole);
|
throw new ModuleLoadException("Role was not properly specified.");
|
||||||
if (Role.Type != EntityType.Role)
|
} catch (FormatException) {
|
||||||
throw new ModuleLoadException("Name specified in configuration is not a role.");
|
throw new ModuleLoadException("Name specified in configuration is not a role.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,11 @@ class ConfDefinition {
|
||||||
|
|
||||||
var rptch = def[nameof(ReportingChannel)]?.Value<string>();
|
var rptch = def[nameof(ReportingChannel)]?.Value<string>();
|
||||||
if (rptch != null) {
|
if (rptch != null) {
|
||||||
ReportingChannel = new EntityName(rptch);
|
try {
|
||||||
if (ReportingChannel.Type != EntityType.Channel)
|
ReportingChannel = new EntityName(rptch, EntityType.Channel);
|
||||||
|
} catch (FormatException) {
|
||||||
throw new ModuleLoadException($"'{nameof(ReportingChannel)}' is not defined as a channel{errpostfx}");
|
throw new ModuleLoadException($"'{nameof(ReportingChannel)}' is not defined as a channel{errpostfx}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regex loading
|
// Regex loading
|
||||||
|
|
|
@ -160,14 +160,14 @@ class ResponseExecutor {
|
||||||
SocketGuildUser? tuser;
|
SocketGuildUser? tuser;
|
||||||
SocketRole? trole;
|
SocketRole? trole;
|
||||||
try {
|
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);
|
if (userName.Id.HasValue) tuser = _guild.GetUser(userName.Id.Value);
|
||||||
else {
|
else {
|
||||||
if (userName.Name == "_") tuser = _user;
|
if (userName.Name == "_") tuser = _user;
|
||||||
else tuser = userName.FindUserIn(_guild);
|
else tuser = userName.FindUserIn(_guild);
|
||||||
}
|
}
|
||||||
if (tuser == null) return FromError($"Unable to find user '{userName.Name}'.");
|
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);
|
if (roleName.Id.HasValue) trole = _guild.GetRole(roleName.Id.Value);
|
||||||
else trole = roleName.FindRoleIn(_guild);
|
else trole = roleName.FindRoleIn(_guild);
|
||||||
if (trole == null) return FromError($"Unable to find role '{roleName.Name}'.");
|
if (trole == null) return FromError($"Unable to find role '{roleName.Name}'.");
|
||||||
|
|
|
@ -17,8 +17,12 @@ class ModuleConfig {
|
||||||
var values = new Dictionary<ulong, ulong>();
|
var values = new Dictionary<ulong, ulong>();
|
||||||
|
|
||||||
foreach (var item in config.Properties()) {
|
foreach (var item in config.Properties()) {
|
||||||
var name = new EntityName(item.Name);
|
EntityName name;
|
||||||
if (name.Type != EntityType.Role) throw new ModuleLoadException($"'{item.Name}' is not specified as a role.");
|
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);
|
var role = name.FindRoleIn(g);
|
||||||
if (role == null) throw new ModuleLoadException($"Unable to find role '{name}'.");
|
if (role == null) throw new ModuleLoadException($"Unable to find role '{name}'.");
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ class ModuleStateService : Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load moderator list
|
// Load moderator list
|
||||||
var mods = new EntityList(guildConf["Moderators"]!, true);
|
var mods = new EntityList(guildConf["Moderators"]!);
|
||||||
|
|
||||||
// Create guild state objects for all existing modules
|
// Create guild state objects for all existing modules
|
||||||
var newStates = new Dictionary<Type, object?>();
|
var newStates = new Dictionary<Type, object?>();
|
||||||
|
|
Loading…
Reference in a new issue