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 /// `complaint` (`REASON_MAX_BYTES` through 0.115)
57 pub const COMPLAINT_MAX_BYTES: usize = 4_096;
58 pub const CONSTITUTIONAL_REF_MAX_BYTES: usize = 128;
59}
60
61/// A proposal designation's note
62pub mod designation {
63 /// `note` (`REASON_MAX_CHARS` through 0.115)
64 pub const NOTE_MAX_CHARS: usize = 1_000;
65}
66
67/// An appeal (Governance Protocol §3.4: "up to 16,384 characters")
68pub mod appeal {
69 pub const STATEMENT_MAX_CHARS: usize = 16_384;
70}
71
72/// Text that counts as empty for a field that may not be: nothing, or only
73/// whitespace
74pub trait Blank {
75 fn is_blank(&self) -> bool;
76}
77
78impl Blank for str {
79 fn is_blank(&self) -> bool {
80 self.trim().is_empty()
81 }
82}
83
84impl Blank for String {
85 fn is_blank(&self) -> bool {
86 self.as_str().is_blank()
87 }
88}
89
90impl<T: Blank> Blank for Option<T> {
91 /// An absent field is not blank: whether it may be absent is the
92 /// type's business
93 fn is_blank(&self) -> bool {
94 self.as_ref().is_some_and(Blank::is_blank)
95 }
96}
97
98/// A garde `custom` rule: the field may not be empty or only whitespace
99pub fn not_blank<T: Blank + ?Sized>(value: &T, _: &()) -> garde::Result {
100 if value.is_blank() {
101 Err(garde::Error::new("may not be empty or only whitespace"))
102 } else {
103 Ok(())
104 }
105}
106
107/// What a write path relays when [`garde::Validate::validate`] refuses a
108/// request: each field's problem on a line, as `field: problem`, field name
109/// included
110pub fn report_text(report: &garde::Report) -> String {
111 report.to_string().trim_end().to_owned()
112}