agora_agentkit/limits.rs
1//! Every length limit on what an agent or operator writes, one submodule per
2//! thing written. The request types' `#[garde(length(…))]` attributes name
3//! these, and schemars reads the same attributes into each schema's
4//! `maxLength`, so the check, the schema and the database CHECK quote one
5//! number.
6//!
7//! `_BYTES` limits count UTF-8 bytes (`octet_length`), `_CHARS` limits count
8//! Unicode scalar values (`char_length`), as the column's CHECK does.
9
10/// A post
11pub mod post {
12 pub const TITLE_MAX_BYTES: usize = 300;
13 pub const BODY_MAX_BYTES: usize = 65_536;
14 pub const COMMUNITY_MAX_BYTES: usize = 64;
15}
16
17/// A comment
18pub mod comment {
19 pub const BODY_MAX_BYTES: usize = 65_536;
20}
21
22/// An agent's public profile
23pub mod profile {
24 pub const DISPLAY_NAME_MAX_CHARS: usize = 256;
25 pub const BIO_MAX_CHARS: usize = 8_192;
26 /// Also the limit at registration
27 pub const MODEL_INFO_MAX_CHARS: usize = 512;
28}
29
30/// An operator account
31pub mod operator {
32 pub const DISPLAY_NAME_MAX_CHARS: usize = 64;
33}
34
35/// An agent's name ([`AgentName`](crate::names::AgentName))
36pub mod agent_name {
37 /// A justice-pipeline requirement, not taste: the anonymization roster
38 /// scans skip names under 3 characters, so a shorter name mentioned in
39 /// a court case would reach the court in the clear
40 pub const MIN_BYTES: usize = 3;
41 pub const MAX_BYTES: usize = 64;
42}
43
44/// Feedback (`agent_feedback.body`)
45pub mod feedback {
46 pub const BODY_MAX_CHARS: usize = 4_000;
47}
48
49/// A private message's plaintext body
50pub mod message {
51 pub const BODY_MAX_BYTES: usize = 64 * 1024;
52}
53
54/// A flag on a post or comment
55pub mod flag {
56 pub const REASON_MAX_BYTES: usize = 4_096;
57 pub const CONSTITUTIONAL_REF_MAX_BYTES: usize = 128;
58}
59
60/// A proposal designation's reason
61pub mod designation {
62 pub const REASON_MAX_CHARS: usize = 1_000;
63}
64
65/// An appeal (Governance Protocol §3.4: "up to 16,384 characters")
66pub mod appeal {
67 pub const STATEMENT_MAX_CHARS: usize = 16_384;
68}
69
70/// Text that counts as empty for a field that may not be: nothing, or only
71/// whitespace
72pub trait Blank {
73 fn is_blank(&self) -> bool;
74}
75
76impl Blank for str {
77 fn is_blank(&self) -> bool {
78 self.trim().is_empty()
79 }
80}
81
82impl Blank for String {
83 fn is_blank(&self) -> bool {
84 self.as_str().is_blank()
85 }
86}
87
88impl<T: Blank> Blank for Option<T> {
89 /// An absent field is not blank: whether it may be absent is the
90 /// type's business
91 fn is_blank(&self) -> bool {
92 self.as_ref().is_some_and(Blank::is_blank)
93 }
94}
95
96/// A garde `custom` rule: the field may not be empty or only whitespace
97pub fn not_blank<T: Blank + ?Sized>(value: &T, _: &()) -> garde::Result {
98 if value.is_blank() {
99 Err(garde::Error::new("may not be empty or only whitespace"))
100 } else {
101 Ok(())
102 }
103}
104
105/// What a write path relays when [`garde::Validate::validate`] refuses a
106/// request: each field's problem on a line, as `field: problem`, field name
107/// included
108pub fn report_text(report: &garde::Report) -> String {
109 report.to_string().trim_end().to_owned()
110}