///|
/// Errors raised while reading typed values out of submitted command options.
pub(all) suberror OptionError {
MissingOption(name~ : String)
WrongType(name~ : String, expected~ : String)
MissingResolved(name~ : String, kind~ : String)
} derive(Debug)
///|
/// Typed reader over the options submitted with an application command.
///
/// Subcommand and subcommand-group levels are flattened away on
/// construction: `path()` reports the traversed names and the value
/// accessors see only the leaf options. Required accessors raise
/// `OptionError`; `_opt` variants return `None` when the option is absent
/// but still raise if a present option has an unexpected shape.
pub struct CommandOptions {
priv path : Array[String]
priv leaves : Map[String, @model.CommandDataOption]
priv resolved : @model.ResolvedData?
}
///|
/// Build a typed reader from submitted command data, flattening any
/// subcommand / subcommand-group nesting into `path()`.
pub fn CommandOptions::from_data(data : @model.CommandData) -> CommandOptions {
let path = []
let mut options = data.options
for ;; {
match options {
Some([only]) if only.typ is (SubCommand | SubCommandGroup) => {
path.push(only.name)
options = only.options
}
_ => break
}
}
let leaves = Map([])
if options is Some(list) {
for opt in list {
leaves[opt.name] = opt
}
}
{ path, leaves, resolved: data.resolved, }
}
///|
/// The subcommand-group / subcommand names traversed during flattening,
/// outermost first (empty for a plain command).
pub fn CommandOptions::path(self : CommandOptions) -> Array[String] {
self.path
}
///|
/// Whether an option named `name` was submitted.
pub fn CommandOptions::has(self : CommandOptions, name : String) -> Bool {
self.leaves.contains(name)
}
///|
/// The name of the option currently focused for autocomplete, if any.
pub fn CommandOptions::focused(self : CommandOptions) -> String? {
for name, opt in self.leaves {
if opt.focused is Some(true) {
return Some(name)
}
}
None
}
///|
/// The focused option and its raw partial value, when present.
pub fn CommandOptions::focused_input(self : CommandOptions) -> SuggestInput? {
for name, opt in self.leaves {
if opt.focused is Some(true) {
return Some({
name_: name,
value_: opt.value.unwrap_or_else(() => Json::null()),
})
}
}
None
}
///|
fn CommandOptions::raw_value(
self : CommandOptions,
name : String,
expected : String,
) -> Json? raise OptionError {
guard self.leaves.get(name) is Some(opt) else { return None }
guard opt.value is Some(value) else { raise WrongType(name~, expected~) }
Some(value)
}
///|
fn CommandOptions::required(
self : CommandOptions,
name : String,
expected : String,
) -> Json raise OptionError {
guard self.raw_value(name, expected) is Some(value) else {
raise MissingOption(name~)
}
value
}
///|
fn json_string(
value : Json,
name : String,
expected : String,
) -> String raise OptionError {
guard value is String(s) else { raise WrongType(name~, expected~) }
s
}
///|
fn json_int64(
value : Json,
name : String,
expected : String,
) -> Int64 raise OptionError {
match value {
Number(_, repr=Some(r)) =>
@string.parse_int64(r.to_string()) catch {
_ => raise WrongType(name~, expected~)
}
Number(n, repr=None) => n.to_int64()
_ => raise WrongType(name~, expected~)
}
}
///|
fn json_double(
value : Json,
name : String,
expected : String,
) -> Double raise OptionError {
guard value is Number(n, ..) else { raise WrongType(name~, expected~) }
n
}
///|
fn json_bool(
value : Json,
name : String,
expected : String,
) -> Bool raise OptionError {
match value {
True => true
False => false
_ => raise WrongType(name~, expected~)
}
}
///|
fn[M] json_id(
value : Json,
name : String,
expected : String,
) -> @model.Id[M] raise OptionError {
guard value is String(s) else { raise WrongType(name~, expected~) }
@model.Id::parse(s.view()) catch {
_ => raise WrongType(name~, expected~)
}
}
///|
/// The required STRING option `name`; raises `OptionError` when absent
/// or not a string.
pub fn CommandOptions::string(
self : CommandOptions,
name : String,
) -> String raise OptionError {
json_string(self.required(name, "string"), name, "string")
}
///|
/// The optional STRING option `name`, or `None` when absent.
pub fn CommandOptions::string_opt(
self : CommandOptions,
name : String,
) -> String? raise OptionError {
match self.raw_value(name, "string") {
Some(value) => Some(json_string(value, name, "string"))
None => None
}
}
///|
/// The required INTEGER option `name`; raises `OptionError` when absent
/// or not an integer.
pub fn CommandOptions::int(
self : CommandOptions,
name : String,
) -> Int64 raise OptionError {
json_int64(self.required(name, "integer"), name, "integer")
}
///|
/// The optional INTEGER option `name`, or `None` when absent.
pub fn CommandOptions::int_opt(
self : CommandOptions,
name : String,
) -> Int64? raise OptionError {
match self.raw_value(name, "integer") {
Some(value) => Some(json_int64(value, name, "integer"))
None => None
}
}
///|
/// The required NUMBER option `name`; raises `OptionError` when absent
/// or not a number.
pub fn CommandOptions::number(
self : CommandOptions,
name : String,
) -> Double raise OptionError {
json_double(self.required(name, "number"), name, "number")
}
///|
/// The optional NUMBER option `name`, or `None` when absent.
pub fn CommandOptions::number_opt(
self : CommandOptions,
name : String,
) -> Double? raise OptionError {
match self.raw_value(name, "number") {
Some(value) => Some(json_double(value, name, "number"))
None => None
}
}
///|
/// The required BOOLEAN option `name`; raises `OptionError` when absent
/// or not a boolean.
pub fn CommandOptions::bool(
self : CommandOptions,
name : String,
) -> Bool raise OptionError {
json_bool(self.required(name, "boolean"), name, "boolean")
}
///|
/// The optional BOOLEAN option `name`, or `None` when absent.
pub fn CommandOptions::bool_opt(
self : CommandOptions,
name : String,
) -> Bool? raise OptionError {
match self.raw_value(name, "boolean") {
Some(value) => Some(json_bool(value, name, "boolean"))
None => None
}
}
///|
/// The required USER option `name` as a `UserId`; pass it to
/// `resolved_user` / `resolved_member` for the full objects.
pub fn CommandOptions::user(
self : CommandOptions,
name : String,
) -> @model.UserId raise OptionError {
json_id(self.required(name, "user id"), name, "user id")
}
///|
/// The optional USER option `name` as a `UserId`, or `None` when absent.
pub fn CommandOptions::user_opt(
self : CommandOptions,
name : String,
) -> @model.UserId? raise OptionError {
match self.raw_value(name, "user id") {
Some(value) => Some(json_id(value, name, "user id"))
None => None
}
}
///|
/// The required CHANNEL option `name` as a `ChannelId`; pass it to
/// `resolved_channel` for the partial channel object.
pub fn CommandOptions::channel(
self : CommandOptions,
name : String,
) -> @model.ChannelId raise OptionError {
json_id(self.required(name, "channel id"), name, "channel id")
}
///|
/// The optional CHANNEL option `name` as a `ChannelId`, or `None` when
/// absent.
pub fn CommandOptions::channel_opt(
self : CommandOptions,
name : String,
) -> @model.ChannelId? raise OptionError {
match self.raw_value(name, "channel id") {
Some(value) => Some(json_id(value, name, "channel id"))
None => None
}
}
///|
/// The required ROLE option `name` as a `RoleId`; pass it to
/// `resolved_role` for the full role object.
pub fn CommandOptions::role(
self : CommandOptions,
name : String,
) -> @model.RoleId raise OptionError {
json_id(self.required(name, "role id"), name, "role id")
}
///|
/// The optional ROLE option `name` as a `RoleId`, or `None` when absent.
pub fn CommandOptions::role_opt(
self : CommandOptions,
name : String,
) -> @model.RoleId? raise OptionError {
match self.raw_value(name, "role id") {
Some(value) => Some(json_id(value, name, "role id"))
None => None
}
}
///|
/// The required MENTIONABLE option `name` as an untyped snowflake; check
/// `resolved_user` / `resolved_role` to learn which kind it is.
pub fn CommandOptions::mentionable(
self : CommandOptions,
name : String,
) -> @model.GenericId raise OptionError {
json_id(self.required(name, "mentionable id"), name, "mentionable id")
}
///|
/// The optional MENTIONABLE option `name`, or `None` when absent.
pub fn CommandOptions::mentionable_opt(
self : CommandOptions,
name : String,
) -> @model.GenericId? raise OptionError {
match self.raw_value(name, "mentionable id") {
Some(value) => Some(json_id(value, name, "mentionable id"))
None => None
}
}
///|
/// Look up an attachment option through the interaction's resolved data.
pub fn CommandOptions::attachment(
self : CommandOptions,
name : String,
) -> @model.Attachment raise OptionError {
let id : @model.AttachmentId = json_id(
self.required(name, "attachment id"),
name,
"attachment id",
)
guard self.resolved is Some(resolved) &&
resolved.attachments is Some(map) &&
map.get(id.to_string()) is Some(attachment) else {
raise MissingResolved(name~, kind="attachment")
}
attachment
}
///|
/// The resolved `User` for a user option, when Discord included it.
pub fn CommandOptions::resolved_user(
self : CommandOptions,
id : @model.UserId,
) -> @model.User? {
guard self.resolved is Some(resolved) && resolved.users is Some(map) else {
return None
}
map.get(id.to_string())
}
///|
/// The resolved partial `GuildMember` for a user option, when available.
pub fn CommandOptions::resolved_member(
self : CommandOptions,
id : @model.UserId,
) -> @model.GuildMember? {
guard self.resolved is Some(resolved) && resolved.members is Some(map) else {
return None
}
map.get(id.to_string())
}
///|
/// The resolved partial `Channel` for a channel option, when available.
pub fn CommandOptions::resolved_channel(
self : CommandOptions,
id : @model.ChannelId,
) -> @model.Channel? {
guard self.resolved is Some(resolved) && resolved.channels is Some(map) else {
return None
}
map.get(id.to_string())
}
///|
/// The resolved `Role` for a role option, when available.
pub fn CommandOptions::resolved_role(
self : CommandOptions,
id : @model.RoleId,
) -> @model.Role? {
guard self.resolved is Some(resolved) && resolved.roles is Some(map) else {
return None
}
map.get(id.to_string())
}