1use std::rc::Rc;
2use std::str::FromStr as _;
3
4use serde::{
5 Deserialize, Serialize,
6 de::{DeserializeOwned, IntoDeserializer as _},
7};
8
9use anyhow::Context as _;
10
11use actix_web::http;
12use actix_web::http::header;
13use actix_web::web;
14
15use crate::misc::fmt_ext;
16use crate::misc::serde_ext::bytes_wrapper;
17use crate::servers::server;
18
19pub type Result<T> = std::result::Result<T, ErrorCode>;
20
21pub struct Responder<EP: EndpointDetails>(pub EP::ResponseType);
24
25impl<EP: EndpointDetails> actix_web::Responder for Responder<EP> {
26 type Body = <CachedResponse<EP> as actix_web::Responder>::Body;
27
28 fn respond_to(self, req: &actix_web::HttpRequest) -> actix_web::HttpResponse<Self::Body> {
29 self.into_cached().respond_to(req)
30 }
31}
32
33impl<EP: EndpointDetails> Responder<EP> {
34 pub fn into_cached(self) -> CachedResponse<EP> {
35 let payload = self.0.into_payload();
36 let mut content_type = payload.content_type();
37
38 let body = payload.into_body().unwrap_or_else(|err| {
39 log::error!(
40 "failed to serialize payload for {method} {url}: {err:#}",
41 method = EP::METHOD,
42 url = EP::PATH
43 );
44
45 content_type = Some(header::ContentType::json());
46 Some(
47 serde_json::to_vec_pretty(&Result::<()>::Err(ErrorCode::InternalError))
48 .unwrap()
49 .into(),
50 )
51 });
52
53 CachedResponse {
54 body,
55 content_type,
56 ep: std::marker::PhantomData,
57 }
58 }
59}
60
61#[derive(Debug)]
63pub struct CachedResponse<EP: EndpointDetails> {
64 content_type: Option<header::ContentType>,
65 body: Option<bytes::Bytes>,
66 ep: std::marker::PhantomData<EP>,
67}
68
69impl<EP: EndpointDetails> Clone for CachedResponse<EP> {
70 fn clone(&self) -> Self {
71 Self {
72 content_type: self.content_type.clone(),
73 body: self.body.clone(),
74 ep: std::marker::PhantomData::<EP>,
75 }
76 }
77}
78
79impl<EP: EndpointDetails> CachedResponse<EP> {
80 pub fn response_builder(&self) -> actix_web::HttpResponseBuilder {
82 let mut rb = actix_web::HttpResponse::Ok();
83
84 if let Some(ct) = &self.content_type {
85 rb.content_type(ct.clone());
86 }
87
88 if EP::immutable_response() {
89 rb.insert_header(header::CacheControl(vec![
90 header::CacheDirective::MaxAge(i32::MAX as u32),
91 header::CacheDirective::Extension("immutable".to_string(), None),
93 ]));
94 }
95
96 rb
97 }
98}
99
100impl<EP: EndpointDetails> actix_web::Responder for CachedResponse<EP> {
101 type Body = actix_web::body::BoxBody;
102
103 fn respond_to(self, _req: &actix_web::HttpRequest) -> actix_web::HttpResponse<Self::Body> {
104 let mut rb = self.response_builder();
105
106 match self.body {
107 Some(bytes) => rb.body(bytes),
108 None => rb.finish(),
109 }
110 }
111}
112
113pub trait ResultExt {
115 type Ok;
116 type Err;
117
118 fn into_ec<F: FnOnce(Self::Err) -> ErrorCode>(self, f: F) -> Result<Self::Ok>;
122}
123
124impl<T, E> ResultExt for std::result::Result<T, E> {
125 type Ok = T;
126 type Err = E;
127
128 fn into_ec<F: FnOnce(E) -> ErrorCode>(self, f: F) -> Result<T> {
129 match self {
130 Ok(v) => Result::Ok(v),
131 Err(err) => Result::Err(f(err)),
132 }
133 }
134}
135
136impl<T> ResultExt for Option<T> {
137 type Ok = T;
138 type Err = ();
139
140 fn into_ec<F: FnOnce(()) -> ErrorCode>(self, f: F) -> Result<T> {
141 match self {
142 Some(v) => Result::Ok(v),
143 None => Result::Err(f(())),
144 }
145 }
146}
147
148pub trait ApiResultExt: Sized {
150 type Ok;
151
152 fn into_ec(self) -> Result<Self::Ok>;
153
154 fn retryable(self) -> std::result::Result<Option<Self::Ok>, ErrorCode> {
157 match self.into_ec() {
158 Ok(v) => Ok(Some(v)),
159 Err(ec) => {
160 if ec.info().retryable == Some(true) {
161 log::trace!("ignoring retryable error: {ec}");
162 Ok(None)
163 } else {
164 Err(ec)
165 }
166 }
167 }
168 }
169
170 fn into_server_result(self) -> Result<Self::Ok> {
173 self.into_ec().map_err(ErrorCode::into_server_error)
174 }
175}
176
177impl<T> ApiResultExt for Result<T> {
178 type Ok = T;
179
180 fn into_ec(self) -> Result<T> {
181 self
182 }
183}
184
185#[derive(Clone, Copy, Serialize, Deserialize, Debug, thiserror::Error, PartialEq, Eq)]
189#[serde(deny_unknown_fields)]
190pub enum ErrorCode {
191 #[error(
192 "the request you sent failed for now, but please do retry the exact same request again"
193 )]
194 PleaseRetry,
195
196 #[error("encountered an unexpected problem")]
197 InternalError,
198
199 #[error("something is wrong with the request")]
200 BadRequest,
201}
202use ErrorCode::*;
203
204pub struct ErrorInfo {
206 pub retryable: Option<bool>,
212}
213
214impl ErrorCode {
215 pub fn info(&self) -> ErrorInfo {
217 match self {
218 PleaseRetry => ErrorInfo {
219 retryable: Some(true),
220 },
221 InternalError | BadRequest => ErrorInfo {
222 retryable: Some(false),
223 },
224 }
225 }
226
227 pub fn into_server_error(self) -> ErrorCode {
230 match self {
231 InternalError => InternalError,
232 PleaseRetry => PleaseRetry,
233 err => {
234 if err.info().retryable == Some(true) {
235 PleaseRetry
236 } else {
237 InternalError
238 }
239 }
240 }
241 }
242}
243
244pub trait PayloadTrait: Sized {
246 type JsonType: Serialize + DeserializeOwned + core::fmt::Debug;
247
248 fn to_payload(&self) -> Payload<&Self::JsonType>;
250
251 fn into_payload(self) -> Payload<Self::JsonType>;
253
254 fn from_payload(payload: Payload<Self::JsonType>) -> anyhow::Result<Self>;
255}
256
257#[derive(Debug)]
259pub enum Payload<JsonType> {
260 None,
261
262 Json(JsonType),
264
265 Octets(bytes::Bytes),
267}
268
269impl<T> Payload<T> {
270 pub fn content_type(&self) -> Option<header::ContentType> {
272 match self {
273 Payload::None => None,
274 Payload::Json(..) => Some(header::ContentType::json()),
275 Payload::Octets(..) => Some(header::ContentType::octet_stream()),
276 }
277 }
278
279 pub fn into_body(self) -> anyhow::Result<Option<bytes::Bytes>>
281 where
282 T: Serialize,
283 {
284 match self {
285 Payload::None => Ok(None),
286 Payload::Octets(bytes) => Ok(Some(bytes)),
287 Payload::Json(tp) => Ok(Some(
288 serde_json::to_vec_pretty(&tp)
289 .with_context(|| {
290 format!("failed to convert {} to JSON", std::any::type_name::<T>())
291 })?
292 .into(),
293 )),
294 }
295 }
296
297 pub async fn from_client_response<S>(
299 mut resp: awc::ClientResponse<S>,
300 ) -> anyhow::Result<Payload<T>>
301 where
302 S: futures::stream::Stream<
303 Item = std::result::Result<bytes::Bytes, awc::error::PayloadError>,
304 >,
305 T: DeserializeOwned,
306 {
307 let Some(content_type_hv) = resp.headers().get(http::header::CONTENT_TYPE) else {
308 anyhow::bail!("no Content-Type in response",);
309 };
310
311 let content_type = mime::Mime::from_str(
312 content_type_hv
313 .to_str()
314 .context("Content-Type value not utf8")?,
315 )
316 .context("could not parse Content-Type value")?;
317
318 match (content_type.type_(), content_type.subtype()) {
319 (mime::APPLICATION, mime::JSON) => Ok(Payload::Json({
320 let body: bytes::Bytes = resp
321 .body()
322 .await
323 .context("failed to receive response body")?;
324
325 serde_json::from_slice::<T>(&body).with_context(|| {
326 format!(
327 "could not deserialize JSON {json} to type {tp} ",
328 tp = std::any::type_name::<T>(),
329 json = crate::misc::fmt_ext::Bytes(&body),
330 )
331 })?
332 })),
333 (mime::APPLICATION, mime::OCTET_STREAM) => Ok(Payload::Octets(
334 resp.body().await.context("problem loading body")?,
335 )),
336 _ => {
337 anyhow::bail!(
338 "expected Content-Type {} or {}, but got {content_type}",
339 mime::APPLICATION_JSON,
340 mime::APPLICATION_OCTET_STREAM
341 )
342 }
343 }
344 }
345}
346
347impl<T> std::fmt::Display for Payload<T>
348where
349 T: Serialize,
350{
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 match self {
353 Payload::None => write!(f, ""),
354 Payload::Json(t) => write!(f, "{}", fmt_ext::Json(t)),
355 Payload::Octets(b) => write!(f, "{} bytes", b.len()),
356 }
357 }
358}
359
360impl<T> PayloadTrait for Payload<T>
361where
362 T: Serialize + DeserializeOwned + core::fmt::Debug,
363{
364 type JsonType = T;
365
366 fn to_payload(&self) -> Payload<&T> {
367 match self {
368 Payload::None => Payload::None,
369 Payload::Json(t) => Payload::Json(t),
370 Payload::Octets(b) => Payload::Octets(b.clone()), }
372 }
373
374 fn into_payload(self) -> Payload<T> {
375 self
376 }
377
378 fn from_payload(payload: Payload<T>) -> anyhow::Result<Self> {
379 Ok(payload)
380 }
381}
382
383impl<T> PayloadTrait for T
384where
385 T: Serialize + DeserializeOwned + core::fmt::Debug,
386{
387 type JsonType = T;
388
389 fn to_payload(&self) -> Payload<&T> {
390 Payload::Json(self)
391 }
392
393 fn into_payload(self) -> Payload<T> {
394 Payload::Json(self)
395 }
396
397 fn from_payload(payload: Payload<T>) -> anyhow::Result<T> {
398 let Payload::Json(res) = payload else {
399 anyhow::bail!("expected, but did not get, application/json");
400 };
401
402 Ok(res)
403 }
404}
405
406pub struct NoPayload;
408
409impl PayloadTrait for NoPayload {
410 type JsonType = ();
411
412 fn to_payload(&self) -> Payload<&()> {
413 Payload::None
414 }
415
416 fn into_payload(self) -> Payload<()> {
417 Payload::None
418 }
419
420 fn from_payload(payload: Payload<()>) -> anyhow::Result<Self> {
421 let Payload::None = payload else {
422 anyhow::bail!("expected no payload");
423 };
424
425 Ok(NoPayload)
426 }
427}
428
429pub struct BytesPayload(pub bytes::Bytes);
432
433impl PayloadTrait for BytesPayload {
434 type JsonType = ();
435
436 fn to_payload(&self) -> Payload<&()> {
437 Payload::Octets(self.0.clone()) }
439
440 fn into_payload(self) -> Payload<()> {
441 Payload::Octets(self.0)
442 }
443
444 fn from_payload(payload: Payload<()>) -> anyhow::Result<Self> {
445 let Payload::Octets(bytes) = payload else {
446 anyhow::bail!("expected, but did not get, bytes payload (application/octet-stream)");
447 };
448
449 Ok(BytesPayload(bytes))
450 }
451}
452
453pub trait ResultPayloadTrait: PayloadTrait<JsonType = Result<Self::OkType>> {
459 type OkType: Serialize + DeserializeOwned + core::fmt::Debug;
460
461 fn from_ec(ec: ErrorCode) -> Self {
463 Self::from_payload(Payload::Json(Err(ec))).unwrap()
464 }
465
466 fn into_result(self) -> Result<Self> {
468 match self.into_payload() {
469 Payload::Json(Err(ec)) => Err(ec),
470 oth => Ok(Self::from_payload(oth).unwrap()),
471 }
472 }
473
474 fn from_result(res: Result<Self>) -> Self {
475 res.unwrap_or_else(Self::from_ec)
476 }
477}
478
479impl<T> ResultPayloadTrait for Result<T>
480where
481 T: Serialize + DeserializeOwned + core::fmt::Debug,
482{
483 type OkType = T;
484}
485
486impl<T> ResultPayloadTrait for Payload<Result<T>>
487where
488 T: Serialize + DeserializeOwned + core::fmt::Debug,
489{
490 type OkType = T;
491}
492
493pub trait EndpointDetails {
495 type RequestType: PayloadTrait;
496 type ResponseType: ResultPayloadTrait;
497
498 const METHOD: http::Method;
499 const PATH: &'static str;
500
501 fn immutable_response() -> bool {
503 false
504 }
505
506 fn add_to<App, F, Args: actix_web::FromRequest + 'static>(
514 app: &Rc<App>,
515 sc: &mut web::ServiceConfig,
516 handler: F,
517 ) where
518 server::AppMethod<App, F, Self>: actix_web::Handler<Args>,
519 <server::AppMethod<App, F, Self> as actix_web::Handler<Args>>::Output:
520 'static + actix_web::Responder,
521 {
522 sc.route(
523 Self::PATH,
524 web::method(Self::METHOD).to(server::AppMethod::new(app, handler)),
525 );
526 }
527
528 fn caching_add_to<App, F>(app: &Rc<App>, sc: &mut web::ServiceConfig, handler: F)
550 where
551 F: Fn(&App) -> Self::ResponseType,
552 Self: Sized + 'static,
553 {
554 let cached = Responder::<Self>(handler(app)).into_cached();
555
556 sc.route(
558 Self::PATH,
559 web::method(Self::METHOD).to(move || {
560 let cached = cached.clone(); async { cached }
562 }),
563 );
564 }
565}
566
567macro_rules! wrap_dalek_type {
569 {$type:ident, $wrapped_type:path, derive( $($derive:tt)* ), $visitor_type:path } => {
570 #[doc = "Wrapper around [`"]
571 #[doc = stringify!($wrapped_type)]
572 #[doc = "`] enforcing base16 serialization."]
573 #[derive(Clone, Debug, Serialize, Deserialize, $( $derive )* )]
574 #[serde(transparent)]
575 pub struct $type {
576 inner: bytes_wrapper::BytesWrapper<
577 $wrapped_type,
578 bytes_wrapper::ChangeVisitorType<
579 (bytes_wrapper::B16Encoding,),
580 { $visitor_type as isize },
581 >,
582 >,
583 }
584
585 impl From<$wrapped_type> for $type {
586 fn from(inner: $wrapped_type) -> Self {
587 Self {
588 inner: inner.into(),
589 }
590 }
591 }
592
593 impl std::str::FromStr for $type {
595 type Err = serde::de::value::Error;
596
597 fn from_str(s : &str) -> std::result::Result<Self, Self::Err> {
598 Self::deserialize(s.into_deserializer())
599 }
600 }
601
602 impl core::ops::Deref for $type {
603 type Target = $wrapped_type;
604
605 fn deref(&self) -> &Self::Target {
606 &self.inner
607 }
608 }
609
610 impl core::ops::DerefMut for $type {
611 fn deref_mut(&mut self) -> &mut Self::Target {
612 &mut self.inner
613 }
614 }
615
616 impl std::fmt::Display for $type {
617 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618 self.serialize(f)
619 }
620 }
621 }
622}
623
624wrap_dalek_type! {
625 VerifyingKey, ed25519_dalek::VerifyingKey,
626 derive(PartialEq, Eq),
627 bytes_wrapper::VisitorType::BorrowedByteArray
628}
629
630wrap_dalek_type! {
631 SigningKey, ed25519_dalek::SigningKey,
632 derive(),
633 bytes_wrapper::VisitorType::BorrowedByteArray
634}
635
636wrap_dalek_type! {
637 Scalar, curve25519_dalek::scalar::Scalar,
638 derive(PartialEq, Eq),
639 bytes_wrapper::VisitorType::ByteSequence
640}
641
642wrap_dalek_type! {
643 CurvePoint, curve25519_dalek::ristretto::CompressedRistretto,
644 derive(PartialEq, Eq),
645 bytes_wrapper::VisitorType::ByteSequence
646}
647
648impl SigningKey {
649 pub fn generate() -> Self {
650 ed25519_dalek::SigningKey::generate(&mut aead::rand_core::OsRng).into()
651 }
652}
653
654impl Scalar {
655 pub fn random() -> Self {
656 curve25519_dalek::scalar::Scalar::random(&mut aead::rand_core::OsRng).into()
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn serde_scalar() {
666 assert_eq!(
667 Scalar::deserialize(
668 serde::de::value::StrDeserializer::<serde::de::value::Error>::new(
669 &"ff00000000000000000000000000000000000000000000000000000000000000",
670 ),
671 )
672 .unwrap(),
673 curve25519_dalek::scalar::Scalar::from(255u8).into(),
674 );
675
676 let s: Scalar = curve25519_dalek::scalar::Scalar::from(1u8).into();
677 assert_eq!(
678 &serde_json::to_string(&s).unwrap(),
679 "\"0100000000000000000000000000000000000000000000000000000000000000\""
680 );
681 }
682}