tablo_core/limits.rs
1//! `Limits` — the bounds a panel holds each request to.
2
3use topcoat::{
4 context::Cx,
5 router::{Body, BodyLimit, IntoPath, LayerFn, LayerFuture, Next},
6};
7
8/// The bounds a panel holds each request to, which
9/// [`Panel::limits`](crate::Panel::limits) sets. Each one keeps a request to a known size: a
10/// request past one is refused whole, never served in part.
11///
12/// ```rust
13/// # use tablo_core::{Limits, Panel};
14/// let panel = Panel::new("admin").limits(
15/// Limits::default()
16/// .export_rows(50_000)
17/// .body_bytes(50 * 1024 * 1024),
18/// );
19/// # let _ = panel;
20/// ```
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct Limits {
23 pub(crate) export_rows: usize,
24 pub(crate) import_rows: usize,
25 pub(crate) bulk_records: usize,
26 pub(crate) body_bytes: usize,
27}
28
29impl Default for Limits {
30 /// 10,000 exported rows, 1,000 imported rows, 400 selected records and a 10 MiB body.
31 fn default() -> Self {
32 Self {
33 export_rows: 10_000,
34 import_rows: 1_000,
35 bulk_records: 400,
36 body_bytes: 10 * 1024 * 1024,
37 }
38 }
39}
40
41impl Limits {
42 /// The most rows a CSV export holds; a list with more answers 413 rather than a partial file.
43 /// Each row the export counts is read twice, once to count and once to stream.
44 #[must_use]
45 pub fn export_rows(mut self, rows: usize) -> Self {
46 self.export_rows = rows.max(1);
47 self
48 }
49
50 /// The most rows one CSV import creates. Every row writes in the request's one transaction,
51 /// so a longer file is refused whole.
52 #[must_use]
53 pub fn import_rows(mut self, rows: usize) -> Self {
54 self.import_rows = rows.max(1);
55 self
56 }
57
58 /// The most records one bulk action runs on. The selection loads as one `IN` list, which
59 /// some databases bound: SQLite takes 32,766 values. A selection of every matching record
60 /// past it writes nothing and asks the user to narrow the list.
61 #[must_use]
62 pub fn bulk_records(mut self, records: usize) -> Self {
63 self.bulk_records = records.max(1);
64 self
65 }
66
67 /// The most bytes a request body under the panel's prefix holds, uploaded files included; a
68 /// larger body answers 413. A file field's `max_bytes` must stay below it.
69 #[must_use]
70 pub fn body_bytes(mut self, bytes: usize) -> Self {
71 self.body_bytes = bytes.max(1);
72 self
73 }
74}
75
76impl Limits {
77 /// A layer that caps the bodies of the routes under `prefix` at the body limit. The cap is
78 /// the layer's context, so a route's own cap, such as an auth form's, shadows it.
79 pub(crate) fn body_cap(&self, prefix: impl IntoPath) -> LayerFn {
80 LayerFn::new(Some(prefix), pass_through).with(BodyLimit::max(self.body_bytes))
81 }
82}
83
84fn pass_through<'a>(cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
85 next.run(cx, body)
86}
87
88/// The limits of the request's panel, which entering it puts on the context; the defaults
89/// outside one.
90pub(crate) fn of(cx: &Cx) -> Limits {
91 topcoat::context::try_context::<Limits>(cx)
92 .copied()
93 .unwrap_or_default()
94}