use super::GrantMode;
use super::lys_pass_adapter::{LiveAnswer, LiveLysAsk};
use lys_pass::deliberate::CheckAnswer;
use lys_pass::drafts::{DraftRequest, request_digest};
use lys_pass::{Client, Error, Mode, Target};
pub struct HttpLysAsk {
client: Client,
handle: tokio::runtime::Handle,
}
impl std::fmt::Debug for HttpLysAsk {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("HttpLysAsk").finish_non_exhaustive()
}
}
impl HttpLysAsk {
pub fn new(issuer: &str, handle: tokio::runtime::Handle) -> Result<Self, Error> {
let issuer = issuer
.parse()
.map_err(|_| Error::Invalid("issuer is not a URL"))?;
Ok(Self {
client: Client::new(reqwest::Client::default(), issuer)?,
handle,
})
}
fn block<T, F>(&self, future: F) -> Result<T, Error>
where
T: Send,
F: std::future::Future<Output = Result<T, Error>> + Send,
{
let handle = &self.handle;
std::thread::scope(|scope| scope.spawn(move || handle.block_on(future)).join())
.unwrap_or_else(|_| Err(Error::CannotAsk("the live ask did not complete")))
}
fn draft(
&self,
pass: &str,
holder: &str,
target: &Target,
grant: &str,
judgment: &CheckAnswer,
) -> Result<String, Error> {
let words = format!(
"{holder} asks {} on {}:{}",
target.action, target.kind, target.id
);
let draft = DraftRequest {
operation: format!(
"liminal:{holder}:{}:{}:{}",
target.kind, target.id, target.action
),
grant: grant.to_owned(),
target: Target::new(&target.kind, &target.id, &target.action)?,
request_digest: request_digest(&words),
words,
};
self.block(self.client.create_draft(pass, &draft, judgment))
.map(|created| created.draft)
}
}
impl LiveLysAsk for HttpLysAsk {
fn ask(
&self,
pass: &str,
holder: &str,
target: &Target,
_now: u64,
) -> Result<LiveAnswer, Error> {
let answer =
self.block(
self.client
.check_batch(pass, holder, std::slice::from_ref(target), None),
)?;
let Some(result) = answer.results.into_iter().next() else {
return Err(Error::CannotAsk("the live answer carried no result"));
};
match (
result.allowed,
result.mode.as_ref(),
result.grant.as_deref(),
result.refusal.as_deref(),
) {
(true, Some(Mode::Outright), Some(grant), None) => Ok(LiveAnswer::Outright {
grant: grant.to_owned(),
}),
(false, Some(Mode::ByDraft), Some(grant), None) => Ok(LiveAnswer::Held {
mode: GrantMode::ByDraft,
grant: grant.to_owned(),
draft: self.draft(pass, holder, target, grant, &result)?,
}),
(false, Some(Mode::ByTwo), Some(grant), None) => Ok(LiveAnswer::Held {
mode: GrantMode::ByTwo,
grant: grant.to_owned(),
draft: self.draft(pass, holder, target, grant, &result)?,
}),
(false, None, None, Some(refusal)) => Ok(LiveAnswer::Refused {
refusal: refusal.to_owned(),
}),
_ => Err(Error::CannotAsk("inconsistent live permission answer")),
}
}
}