1use super::GrantMode;
14use super::lys_pass_adapter::{LiveAnswer, LiveLysAsk};
15use lys_pass::deliberate::CheckAnswer;
16use lys_pass::drafts::{DraftRequest, request_digest};
17use lys_pass::{Client, Error, Mode, Target};
18
19pub struct HttpLysAsk {
21 client: Client,
22 handle: tokio::runtime::Handle,
23 asker: Option<String>,
26}
27
28impl std::fmt::Debug for HttpLysAsk {
29 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 formatter.debug_struct("HttpLysAsk").finish_non_exhaustive()
31 }
32}
33
34impl HttpLysAsk {
35 pub fn new(issuer: &str, handle: tokio::runtime::Handle) -> Result<Self, Error> {
40 let issuer = issuer
41 .parse()
42 .map_err(|_| Error::Invalid("issuer is not a URL"))?;
43 Ok(Self {
44 client: Client::new(reqwest::Client::default(), issuer)?,
45 handle,
46 asker: None,
47 })
48 }
49
50 #[must_use]
53 pub fn asking_as(mut self, credential: String) -> Self {
54 self.asker = Some(credential);
55 self
56 }
57
58 fn block<T, F>(&self, future: F) -> Result<T, Error>
60 where
61 T: Send,
62 F: std::future::Future<Output = Result<T, Error>> + Send,
63 {
64 let handle = &self.handle;
65 std::thread::scope(|scope| scope.spawn(move || handle.block_on(future)).join())
66 .unwrap_or_else(|_| Err(Error::CannotAsk("the live ask did not complete")))
67 }
68
69 fn draft(
72 &self,
73 pass: &str,
74 holder: &str,
75 target: &Target,
76 grant: &str,
77 judgment: &CheckAnswer,
78 ) -> Result<String, Error> {
79 let words = format!(
80 "{holder} asks {} on {}:{}",
81 target.action, target.kind, target.id
82 );
83 let draft = DraftRequest {
84 operation: format!(
85 "liminal:{holder}:{}:{}:{}",
86 target.kind, target.id, target.action
87 ),
88 grant: grant.to_owned(),
89 target: Target::new(&target.kind, &target.id, &target.action)?,
90 request_digest: request_digest(&words),
91 words,
92 };
93 self.block(self.client.create_draft(pass, &draft, judgment))
94 .map(|created| created.draft)
95 }
96}
97
98impl LiveLysAsk for HttpLysAsk {
99 fn ask(
100 &self,
101 pass: &str,
102 holder: &str,
103 target: &Target,
104 _now: u64,
105 ) -> Result<LiveAnswer, Error> {
106 let answer = self.block(self.client.check_batch(
107 self.asker.as_deref().unwrap_or(pass),
108 holder,
109 std::slice::from_ref(target),
110 None,
111 ))?;
112 let Some(result) = answer.results.into_iter().next() else {
113 return Err(Error::CannotAsk("the live answer carried no result"));
114 };
115 match (
116 result.allowed,
117 result.mode.as_ref(),
118 result.grant.as_deref(),
119 result.refusal.as_deref(),
120 ) {
121 (true, Some(Mode::Outright), Some(grant), None) => Ok(LiveAnswer::Outright {
122 grant: grant.to_owned(),
123 }),
124 (false, Some(Mode::ByDraft), Some(grant), None) => Ok(LiveAnswer::Held {
125 mode: GrantMode::ByDraft,
126 grant: grant.to_owned(),
127 draft: self.draft(pass, holder, target, grant, &result)?,
128 }),
129 (false, Some(Mode::ByTwo), Some(grant), None) => Ok(LiveAnswer::Held {
130 mode: GrantMode::ByTwo,
131 grant: grant.to_owned(),
132 draft: self.draft(pass, holder, target, grant, &result)?,
133 }),
134 (false, None, None, Some(refusal)) => Ok(LiveAnswer::Refused {
135 refusal: refusal.to_owned(),
136 }),
137 _ => Err(Error::CannotAsk("inconsistent live permission answer")),
138 }
139 }
140}