///|
/// An error raised when an interaction callback cannot be accepted.
pub(all) suberror ResponseGateError {
AlreadyResponded
InvalidCallback(
kind~ : @model.InteractionType,
typ~ : @model.InteractionResponseType
)
} derive(Debug)
///|
priv enum ResponseGateState {
Pending
Sent
Failed
} derive(Eq)
///|
/// A single-use interaction callback gate.
pub struct ResponseGate {
priv kind : @model.InteractionType
priv mut state : ResponseGateState
priv sink : async (@model.InteractionResponse, Array[@dhttp.FileUpload]?) -> Unit
}
///|
/// A callback plus any multipart files captured by an in-memory gate.
pub(all) struct CapturedResponse {
response : @model.InteractionResponse
files : Array[@dhttp.FileUpload]?
}
///|
/// The receiving side of a capture gate.
pub struct ResponseCapture {
priv queue : @aqueue.Queue[CapturedResponse]
}
///|
/// Construct a gate around an injected callback sink.
pub fn ResponseGate::ResponseGate(
kind : @model.InteractionType,
sink : async (@model.InteractionResponse, Array[@dhttp.FileUpload]?) -> Unit,
) -> ResponseGate {
{ kind, state: Pending, sink, }
}
///|
/// Construct a gate that delivers its callback through Discord's REST API.
pub fn ResponseGate::rest(
client : @dhttp.Client,
interaction : @model.Interaction,
) -> ResponseGate {
ResponseGate(interaction.typ, (response, files) => {
client.create_interaction_response(
interaction.id,
interaction.token,
response,
files?,
)
})
}
///|
/// Construct an in-memory gate and a receiver for the first callback value.
pub fn ResponseGate::capture(
interaction : @model.Interaction,
) -> (ResponseGate, ResponseCapture) {
let queue : @aqueue.Queue[CapturedResponse] = Queue(kind=Blocking(1))
let capture = ResponseCapture::{ queue, }
let gate = ResponseGate(interaction.typ, (response, files) => {
queue.put({ response, files, })
})
(gate, capture)
}
///|
fn callback_allowed(
kind : @model.InteractionType,
typ : @model.InteractionResponseType,
) -> Bool {
match kind {
Ping => typ == Pong
ApplicationCommand =>
typ == ChannelMessageWithSource ||
typ == DeferredChannelMessageWithSource ||
typ == Modal ||
typ == LaunchActivity
MessageComponent =>
typ == ChannelMessageWithSource ||
typ == DeferredChannelMessageWithSource ||
typ == DeferredUpdateMessage ||
typ == UpdateMessage ||
typ == Modal ||
typ == LaunchActivity
ApplicationCommandAutocomplete => typ == Autocomplete
ModalSubmit =>
typ == ChannelMessageWithSource ||
typ == DeferredChannelMessageWithSource ||
typ == DeferredUpdateMessage ||
typ == UpdateMessage
Unknown(_) => true
}
}
///|
/// Validate and deliver the one allowed initial interaction callback.
pub async fn ResponseGate::send(
self : ResponseGate,
response : @model.InteractionResponse,
files? : Array[@dhttp.FileUpload],
) -> Unit {
if self.state != Pending {
raise ResponseGateError::AlreadyResponded
}
if !callback_allowed(self.kind, response.typ) {
raise ResponseGateError::InvalidCallback(kind=self.kind, typ=response.typ)
}
self.state = Sent
errdefer {
self.state = Failed
}
(self.sink)(response, files)
}
///|
/// Whether this gate has attempted its single callback delivery.
pub fn ResponseGate::responded(self : ResponseGate) -> Bool {
self.state != Pending
}
///|
/// Wait for and remove the captured callback.
pub async fn ResponseCapture::get(self : ResponseCapture) -> CapturedResponse {
self.queue.get()
}
///|
/// Remove the captured callback without waiting, if one is available.
pub fn ResponseCapture::try_get(
self : ResponseCapture,
) -> CapturedResponse? raise {
self.queue.try_get()
}