borderless_runtime/http/
ledger.rs1pub use super::*;
2use crate::db::controller::Controller;
3use crate::log_shim::*;
4use borderless::http::queries::Pagination;
5use borderless::{BorderlessId, ContractId};
6use borderless_kv_store::{backend::lmdb::Lmdb, Db};
7use http::method::Method;
8use serde::Deserialize;
9use std::convert::Infallible;
10use std::future::Future;
11use std::{
12 pin::Pin,
13 task::{Context, Poll},
14 time::Instant,
15};
16
17#[derive(Clone)]
19pub struct LedgerService<S = Lmdb>
20where
21 S: Db + 'static,
22{
23 db: S,
24}
25
26impl<S> LedgerService<S>
27where
28 S: Db + 'static,
29{
30 pub fn new(db: S) -> Self {
31 Self { db }
32 }
33
34 async fn process_rq(&self, req: Request) -> crate::Result<Response> {
35 let start = Instant::now();
36 let path = req.uri().path().to_string();
37 let result = match *req.method() {
38 Method::GET => self.process_get_rq(req).await,
39 Method::POST => self.process_post_rq(req).await,
40 _ => Ok(method_not_allowed()),
41 };
42 let elapsed = start.elapsed();
43 match &result {
45 Ok(res) => info!(
46 "Request success. path={path}. Time elapsed: {elapsed:?}, status={}",
47 res.status()
48 ),
49 Err(e) => warn!("Request failed. path={path}. Time elapsed: {elapsed:?}, error={e}"),
50 }
51 result
52 }
53
54 async fn process_get_rq(&self, req: Request) -> crate::Result<Response> {
55 let segs: Vec<&str> = req
57 .uri()
58 .path()
59 .trim_start_matches('/')
60 .split('/')
61 .filter(|s| !s.is_empty())
62 .collect();
63
64 let query = req.uri().query();
65
66 let controller = Controller::new(&self.db);
67 let pagination = Pagination::from_query(query).unwrap_or_default();
68 match segs.as_slice() {
69 [] => {
71 let res = controller.ledger().all_paginated(pagination)?;
72 Ok(json_response(&res))
73 }
74 ["ids"] => {
76 let ids = controller.ledger().all_ids_paginated(pagination)?;
77 Ok(json_response(&ids))
78 }
79 [id_str] => {
80 let ledger_id = match id_str.parse::<u64>() {
81 Ok(id) => id,
82 Err(e) => return Ok(bad_request(e.to_string())),
83 };
84 let ledger = controller.ledger().select(ledger_id);
85 Ok(json_response(&ledger.meta()?.map(|m| m.into_dto())))
86 }
87 [id_str, "entries"] => {
88 let ledger_id = match id_str.parse::<u64>() {
89 Ok(id) => id,
90 Err(e) => return Ok(bad_request(e.to_string())),
91 };
92 let ledger = controller.ledger().select(ledger_id);
93 let entries = ledger.get_entries_paginated(pagination)?;
94 Ok(json_response(&entries))
95 }
96 _ => Ok(reject_404()),
97 }
98 }
99
100 async fn process_post_rq(&self, req: Request) -> crate::Result<Response> {
101 let (parts, payload) = req.into_parts();
103 if !check_json_content(&parts) {
104 return Ok(unsupported_media_type());
105 }
106
107 let path = parts.uri.path();
109
110 let query = parts.uri.query();
112 let pagination = Pagination::from_query(query).unwrap_or_default();
113
114 let payload = match serde_json::from_slice::<LedgerQuery>(&payload) {
115 Ok(p) => p,
116 Err(e) => return Ok(bad_request(e.to_string())),
117 };
118
119 let controller = Controller::new(&self.db);
121 let ledger_id = match payload.to_ledger_id() {
122 Some(id) => id,
123 None => {
124 return Ok(bad_request(
125 "must specify either ledger-id or creditor / debitor pair".to_string(),
126 ))
127 }
128 };
129 let ledger = controller.ledger().select(ledger_id);
130
131 match path {
134 "/" | "" => match payload.contract_id {
135 Some(cid) => Ok(json_response(&ledger.meta_for_contract(cid)?)),
136 None => Ok(json_response(&ledger.meta()?.map(|m| m.into_dto()))),
137 },
138 "/entries" | "entries" => match payload.contract_id {
139 Some(cid) => Ok(json_response(
140 &ledger.get_contract_paginated(cid, pagination)?,
141 )),
142 None => Ok(json_response(&ledger.get_entries_paginated(pagination)?)),
143 },
144 _ => Ok(reject_404()),
145 }
146 }
147}
148
149#[derive(Deserialize)]
151pub struct LedgerQuery {
152 creditor: Option<BorderlessId>,
153 debitor: Option<BorderlessId>,
154 ledger_id: Option<u64>,
155 contract_id: Option<ContractId>,
156}
157
158impl LedgerQuery {
159 fn to_ledger_id(&self) -> Option<u64> {
160 if self.ledger_id.is_some() {
161 return self.ledger_id;
162 }
163 match (self.creditor, self.debitor) {
164 (Some(c), Some(d)) => Some(c.merge_compact(&d)),
165 _ => None,
166 }
167 }
168}
169
170impl<S> Service<Request> for LedgerService<S>
171where
172 S: Db + 'static,
173{
174 type Response = Response;
175 type Error = Infallible;
176 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
177
178 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
179 Poll::Ready(Ok(()))
180 }
181
182 fn call(&mut self, req: Request) -> Self::Future {
183 let this = self.clone();
184 let fut = async move {
185 let result: Response = match this.process_rq(req).await {
186 Ok(r) => r,
187 Err(e) => into_server_error(e),
188 };
189 Ok(result)
190 };
191 Box::pin(fut)
192 }
193}