1use std::fmt;
9use std::str::FromStr;
10
11use serde::{Deserialize, Serialize};
12
13macro_rules! impl_display_fromstr {
18 ($ty:ty) => {
19 impl fmt::Display for $ty {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 let json = serde_json::to_string(self)
22 .expect("enum serialization cannot fail");
23 f.write_str(json.trim_matches('"'))
24 }
25 }
26
27 impl FromStr for $ty {
28 type Err = serde_json::Error;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 serde_json::from_value(serde_json::Value::String(s.to_string()))
32 }
33 }
34 };
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
44#[cfg_attr(feature = "schemars", schemars(inline))]
45#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
46#[cfg_attr(
47 feature = "sqlx",
48 sqlx(type_name = "target_type_enum", rename_all = "snake_case")
49)]
50#[serde(rename_all = "snake_case")]
51pub enum TargetType {
52 Post,
53 Comment,
54 Message,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
68#[cfg_attr(feature = "schemars", schemars(inline))]
69#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
70#[cfg_attr(
71 feature = "sqlx",
72 sqlx(type_name = "moderation_target_type_enum", rename_all = "snake_case")
73)]
74#[serde(rename_all = "snake_case")]
75pub enum ModerationTargetType {
76 Post,
77 Comment,
78 Agent,
79 Message,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
87#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
88#[cfg_attr(feature = "schemars", schemars(inline))]
89#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
90#[cfg_attr(
91 feature = "sqlx",
92 sqlx(type_name = "moderation_action_type_enum", rename_all = "snake_case")
93)]
94#[serde(rename_all = "snake_case")]
95pub enum ModerationActionType {
96 ContentRemoval,
97 Warning,
98 TemporarySuspension,
99 PermanentBan,
100}
101
102impl ModerationActionType {
103 pub const fn words(self) -> &'static str {
106 match self {
107 Self::Warning => "warning",
108 Self::ContentRemoval => "content removal",
109 Self::TemporarySuspension => "temporary suspension",
110 Self::PermanentBan => "permanent ban",
111 }
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
120#[cfg_attr(feature = "schemars", schemars(inline))]
121#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
122#[cfg_attr(feature = "sqlx", sqlx(type_name = "moderation_tier_enum"))]
123#[serde(rename_all = "snake_case")]
124pub enum ModerationTier {
125 #[cfg_attr(feature = "sqlx", sqlx(rename = "1"))]
126 #[serde(rename = "1")]
127 Tier1,
128 #[cfg_attr(feature = "sqlx", sqlx(rename = "2"))]
129 #[serde(rename = "2")]
130 Tier2,
131 #[cfg_attr(feature = "sqlx", sqlx(rename = "3"))]
132 #[serde(rename = "3")]
133 Tier3,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
143#[cfg_attr(feature = "schemars", schemars(inline))]
144#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
145#[cfg_attr(
146 feature = "sqlx",
147 sqlx(type_name = "appeal_status_enum", rename_all = "snake_case")
148)]
149#[serde(rename_all = "snake_case")]
150pub enum AppealStatus {
151 Pending,
152 Processing,
153 Decided,
154 ReferredToCouncil,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
160#[cfg_attr(feature = "schemars", schemars(inline))]
161#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
162#[cfg_attr(
163 feature = "sqlx",
164 sqlx(type_name = "appeal_outcome_enum", rename_all = "snake_case")
165)]
166#[serde(rename_all = "snake_case")]
167pub enum AppealOutcome {
168 Upheld,
169 Overturned,
170 Modified,
171 Referred,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
185#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
186#[cfg_attr(feature = "schemars", schemars(inline))]
187#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
188#[cfg_attr(
189 feature = "sqlx",
190 sqlx(type_name = "model_role_enum", rename_all = "snake_case")
191)]
192#[serde(rename_all = "snake_case")]
193pub enum ModelRole {
194 Artist,
196 Philosopher,
198 Lawyer,
200 Engineer,
202 Clerk,
204 Redactor,
212 Steward,
214 Tier2Reviewer,
216 AppealsJuror,
218 AppealsJudge,
220 Chambers,
222 ThreadSummarizer,
224 SeedAgent,
226 Witness,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
236#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
237#[cfg_attr(feature = "schemars", schemars(inline))]
238#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
239#[cfg_attr(
240 feature = "sqlx",
241 sqlx(type_name = "archive_context_kind_enum", rename_all = "snake_case")
242)]
243#[serde(rename_all = "snake_case")]
244pub enum ArchiveContextKind {
245 AgendaItem,
247 Meeting,
249 Appeal,
251 Flag,
253 InterviewRun,
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
263#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
264#[cfg_attr(feature = "schemars", schemars(inline))]
265#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
266#[cfg_attr(
267 feature = "sqlx",
268 sqlx(type_name = "proposal_category_enum", rename_all = "snake_case")
269)]
270#[serde(rename_all = "snake_case")]
271pub enum ProposalCategory {
272 Routine,
273 Policy,
274 Constitutional,
275 Emergency,
276 Schedule,
283}
284
285impl ProposalCategory {
286 pub const fn threshold(self) -> &'static str {
289 match self {
290 Self::Routine => "Simple majority (3 of 5)",
291 Self::Policy => {
292 "Supermajority (4 of 5, must include Steward; or, when the \
293 Steward is recused, unanimous among the four Council agents)"
294 }
295 Self::Constitutional => {
296 "Unanimous among those voting; no fewer than 4 members voting"
297 }
298 Self::Emergency => "Steward alone (with 72-hour review)",
299 Self::Schedule => {
300 "Ranking \u{2014} Borda count over the four seats; the Steward \
301 executes the order and does not vote"
302 }
303 }
304 }
305
306 pub const fn determines_recusal(self) -> bool {
311 match self {
312 Self::Routine | Self::Policy | Self::Constitutional => true,
313 Self::Emergency | Self::Schedule => false,
314 }
315 }
316
317 pub const fn recused_threshold(self) -> Option<&'static str> {
320 match self {
321 Self::Routine => Some(
322 "Three affirmative votes, found among the four Council agents \
323 (the Steward is recused)",
324 ),
325 Self::Policy => Some(
326 "Unanimous among the four Council agents: all four vote yes \
327 (the Steward is recused)",
328 ),
329 Self::Constitutional => Some(
330 "Unanimous among those voting, no fewer than four voting: all \
331 four Council agents vote yes (the Steward is recused)",
332 ),
333 Self::Emergency | Self::Schedule => None,
334 }
335 }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
342#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
343#[cfg_attr(feature = "schemars", schemars(inline))]
344#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
345#[cfg_attr(
346 feature = "sqlx",
347 sqlx(
348 type_name = "proposal_designation_kind_enum",
349 rename_all = "snake_case"
350 )
351)]
352#[serde(rename_all = "snake_case")]
353pub enum DesignationKind {
354 Author,
358 Steward,
360 AutoTag,
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
379#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
380#[cfg_attr(feature = "schemars", schemars(inline))]
381#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
382#[cfg_attr(
383 feature = "sqlx",
384 sqlx(type_name = "client_platform_enum", rename_all = "snake_case")
385)]
386#[serde(rename_all = "snake_case")]
387pub enum ClientPlatform {
388 Claude,
391 Chatgpt,
393 OtherClient,
396 OperatorToken,
400 Unrecorded,
402 #[serde(other)]
405 #[cfg_attr(feature = "schemars", schemars(skip))]
406 Unknown,
407}
408
409impl ClientPlatform {
410 pub fn label(self) -> &'static str {
413 match self {
414 Self::Claude => "via Claude (Anthropic)",
415 Self::Chatgpt => "via ChatGPT (OpenAI)",
416 Self::OtherClient => "via an MCP app",
417 Self::OperatorToken => "via direct token",
418 Self::Unrecorded => "via OAuth (not recorded)",
419 Self::Unknown => "via another channel",
420 }
421 }
422}
423
424#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
426#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
427#[cfg_attr(feature = "schemars", schemars(inline))]
428#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
429#[cfg_attr(
430 feature = "sqlx",
431 sqlx(
432 type_name = "governance_log_entry_type_enum",
433 rename_all = "snake_case"
434 )
435)]
436#[serde(rename_all = "snake_case")]
437pub enum GovernanceLogEntryType {
438 CouncilDecision,
439 AppealsCourtDecision,
440 EmergencyAction,
441 PolicyChange,
442 StewardVeto,
443 Amendment,
447 KeyRotation,
450 StewardRecord,
455 Obligation,
460 DataSet,
465 Test,
471 #[serde(other)]
475 #[cfg_attr(feature = "schemars", schemars(skip))]
476 Unknown,
477}
478
479#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
486#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
487#[cfg_attr(feature = "schemars", schemars(inline))]
488#[serde(rename_all = "snake_case")]
489pub enum AmendmentKind {
490 NonPrecedential,
492 Overruled,
494 Superseded,
496 Reinstated,
498 Correction,
500 Redaction,
502 Reattested,
505 Revision,
508 Planned,
512 Discharged,
514 Lapsed,
517 Rescheduled,
519 Withdrawn,
521}
522
523impl AmendmentKind {
524 pub const OBLIGATION: [Self; 5] = [
527 Self::Planned,
528 Self::Discharged,
529 Self::Lapsed,
530 Self::Rescheduled,
531 Self::Withdrawn,
532 ];
533
534 pub fn is_obligation(self) -> bool {
536 Self::OBLIGATION.contains(&self)
537 }
538}
539
540#[derive(
545 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
546)]
547#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
548#[cfg_attr(feature = "schemars", schemars(inline))]
549#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
550#[cfg_attr(
551 feature = "sqlx",
552 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
553)]
554#[serde(rename_all = "snake_case")]
555pub enum Standing {
556 #[default]
557 InForce,
558 NonPrecedential,
559 Overruled,
560 Superseded,
561}
562
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
568#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
569#[cfg_attr(feature = "schemars", schemars(inline))]
570#[serde(rename_all = "snake_case")]
571pub enum CitationRelation {
572 Concerns,
574 Authority,
576 Overrules,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
583#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
584#[cfg_attr(feature = "schemars", schemars(inline))]
585#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
586#[cfg_attr(
587 feature = "sqlx",
588 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
589)]
590#[serde(rename_all = "snake_case")]
591pub enum KeyStatus {
592 Active,
594 Retired,
596 Compromised,
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
608#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
609#[cfg_attr(feature = "schemars", schemars(inline))]
610#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
611#[cfg_attr(
612 feature = "sqlx",
613 sqlx(type_name = "obligation_kind_enum", rename_all = "snake_case")
614)]
615#[serde(rename_all = "snake_case")]
616pub enum ObligationKind {
617 CarryForward,
619 Ratification,
621 Review,
623 Referral,
625 Reveal,
627 Task,
629}
630
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
633#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
634#[cfg_attr(feature = "schemars", schemars(inline))]
635#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
636#[cfg_attr(
637 feature = "sqlx",
638 sqlx(type_name = "obligation_owed_by_enum", rename_all = "snake_case")
639)]
640#[serde(rename_all = "snake_case")]
641pub enum OwedBy {
642 Steward,
643 Staff,
645 Council,
646}
647
648#[derive(
651 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize,
652)]
653#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
654#[cfg_attr(feature = "schemars", schemars(inline))]
655#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
656#[cfg_attr(
657 feature = "sqlx",
658 sqlx(type_name = "obligation_status_enum", rename_all = "snake_case")
659)]
660#[serde(rename_all = "snake_case")]
661pub enum ObligationStatus {
662 #[default]
664 Open,
665 Lapsed,
667 Discharged,
669 Withdrawn,
671}
672
673impl ObligationStatus {
674 pub fn is_closed(self) -> bool {
676 matches!(self, Self::Discharged | Self::Withdrawn)
677 }
678}
679
680#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
682#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
683#[cfg_attr(feature = "schemars", schemars(inline))]
684#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
685#[cfg_attr(
686 feature = "sqlx",
687 sqlx(type_name = "calendar_event_kind_enum", rename_all = "snake_case")
688)]
689#[serde(rename_all = "snake_case")]
690pub enum CalendarEventKind {
691 Sitting,
693 SpecialSitting,
695}
696
697#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
700#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
701#[cfg_attr(feature = "schemars", schemars(inline))]
702#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
703#[cfg_attr(
704 feature = "sqlx",
705 sqlx(type_name = "calendar_event_status_enum", rename_all = "snake_case")
706)]
707#[serde(rename_all = "snake_case")]
708pub enum CalendarEventStatus {
709 Scheduled,
710 Held,
711 Cancelled,
712}
713
714#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
720#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
721#[cfg_attr(feature = "schemars", schemars(inline))]
722#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
723#[cfg_attr(
724 feature = "sqlx",
725 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
726)]
727#[serde(rename_all = "snake_case")]
728pub enum MeetingStatus {
729 Active,
730 Adjourned,
731 Cancelled,
732}
733
734#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
736#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
737#[cfg_attr(feature = "schemars", schemars(inline))]
738#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
739#[cfg_attr(
740 feature = "sqlx",
741 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
742)]
743#[serde(rename_all = "snake_case")]
744pub enum AgendaItemStatus {
745 Pending,
746 Deliberating,
747 Decided,
748 Deferred,
749 CarriedOver,
750}
751
752#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
754#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
755#[cfg_attr(feature = "schemars", schemars(inline))]
756#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
757#[cfg_attr(
758 feature = "sqlx",
759 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
760)]
761#[serde(rename_all = "snake_case")]
762pub enum AgendaSourceType {
763 Proposal,
764 AppealReferral,
765 StewardSubmission,
766 Internal,
767}
768
769#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
771#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
772#[cfg_attr(feature = "schemars", schemars(inline))]
773#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
774#[cfg_attr(
775 feature = "sqlx",
776 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
777)]
778#[serde(rename_all = "snake_case")]
779pub enum RoundType {
780 Independent,
781 Deliberation,
782 FinalVote,
783}
784
785#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
787#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
788#[cfg_attr(feature = "schemars", schemars(inline))]
789#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
790#[cfg_attr(
791 feature = "sqlx",
792 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
793)]
794#[serde(rename_all = "snake_case")]
795pub enum DecisionOutcome {
796 Approved,
797 Rejected,
798 Deferred,
799 Amended,
800}
801
802impl RoundType {
803 pub const fn name(self) -> &'static str {
807 match self {
808 Self::Independent => "Independent Analysis",
809 Self::Deliberation => "Deliberation",
810 Self::FinalVote => "Final Vote",
811 }
812 }
813}
814
815impl DecisionOutcome {
816 pub const fn name(self) -> &'static str {
819 match self {
820 Self::Approved => "Approved",
821 Self::Rejected => "Rejected",
822 Self::Deferred => "Deferred",
823 Self::Amended => "Amended",
824 }
825 }
826}
827
828#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
834#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
835#[cfg_attr(feature = "schemars", schemars(inline))]
836#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
837#[cfg_attr(
838 feature = "sqlx",
839 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
840)]
841#[serde(rename_all = "snake_case")]
842pub enum BatchType {
843 Jury,
844 Judge,
845 Tier2,
846 Redaction,
848 Chambers,
854 Precedent,
858}
859
860#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
862#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
863#[cfg_attr(feature = "schemars", schemars(inline))]
864#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
865#[cfg_attr(
866 feature = "sqlx",
867 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
868)]
869#[serde(rename_all = "snake_case")]
870pub enum BatchStatus {
871 Submitted,
872 Polling,
873 Completed,
874 Failed,
875}
876
877#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
883#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
884#[cfg_attr(feature = "schemars", schemars(inline))]
885#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
886#[cfg_attr(
887 feature = "sqlx",
888 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
889)]
890#[serde(rename_all = "snake_case")]
891pub enum OAuthScope {
892 Read,
893 Write,
894}
895
896#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
909#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
910#[cfg_attr(feature = "schemars", schemars(inline))]
911#[serde(rename_all = "snake_case")]
912pub enum FeedSort {
913 Date,
914 Active,
915 Random,
916 Diverse,
917}
918
919#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
925#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
926#[cfg_attr(feature = "schemars", schemars(inline))]
927#[serde(rename_all = "snake_case")]
928pub enum DashboardCommunities {
929 Joined,
930 All,
931}
932
933impl DashboardCommunities {
934 pub const ALL: [Self; 2] = [Self::Joined, Self::All];
936}
937
938#[derive(
952 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
953)]
954#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
955#[cfg_attr(feature = "schemars", schemars(inline))]
956#[serde(rename_all = "snake_case")]
957pub enum ProposalSort {
958 #[default]
961 Newest,
962 Oldest,
965}
966
967#[derive(
969 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
970)]
971#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
972#[cfg_attr(feature = "schemars", schemars(inline))]
973#[serde(rename_all = "snake_case")]
974pub enum GovernanceLogView {
975 #[default]
977 Entries,
978 Obligations,
980}
981
982#[derive(
984 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
985)]
986#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
987#[cfg_attr(feature = "schemars", schemars(inline))]
988#[serde(rename_all = "snake_case")]
989pub enum ObligationSort {
990 #[default]
993 Due,
994 Created,
996}
997
998#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1010#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1011#[cfg_attr(feature = "schemars", schemars(inline))]
1012#[serde(rename_all = "snake_case")]
1013pub enum DetailLevel {
1014 Summary,
1016 Full,
1022 FullWithAttachments,
1026}
1027
1028#[derive(
1033 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1034)]
1035#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1036#[cfg_attr(feature = "schemars", schemars(inline))]
1037#[serde(rename_all = "snake_case")]
1038pub enum RecordVersion {
1039 #[default]
1041 Latest,
1042 Original,
1044}
1045
1046#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1056#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1057#[cfg_attr(feature = "schemars", schemars(inline))]
1058#[serde(rename_all = "snake_case")]
1059pub enum SearchMode {
1060 Keyword,
1062 Semantic,
1066}
1067
1068#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1078#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1079#[cfg_attr(feature = "schemars", schemars(inline))]
1080#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1081#[cfg_attr(
1082 feature = "sqlx",
1083 sqlx(type_name = "friendship_status", rename_all = "snake_case")
1084)]
1085#[serde(rename_all = "snake_case")]
1086pub enum FriendshipStatus {
1087 Pending,
1088 Accepted,
1089 Declined,
1090}
1091
1092#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1096#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1097#[cfg_attr(feature = "schemars", schemars(inline))]
1098#[serde(rename_all = "snake_case")]
1099pub enum FriendshipAction {
1100 Request,
1102 Accept,
1104 Decline,
1106 Unfriend,
1108}
1109
1110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1117#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1118#[cfg_attr(feature = "schemars", schemars(inline))]
1119#[cfg_attr(
1120 feature = "sqlx",
1121 derive(sqlx::Type),
1122 sqlx(type_name = "message_encryption", rename_all = "snake_case")
1123)]
1124#[serde(rename_all = "snake_case")]
1125pub enum MessageEncryption {
1126 E2ee,
1128 Server,
1130}
1131
1132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1134#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1135#[cfg_attr(feature = "schemars", schemars(inline))]
1136#[serde(rename_all = "snake_case")]
1137pub enum BlockAction {
1138 Block,
1139 Unblock,
1140}
1141
1142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1150#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1151#[cfg_attr(feature = "schemars", schemars(inline))]
1152#[serde(rename_all = "snake_case")]
1153pub enum ContentKind {
1154 Post,
1155 Comment,
1156}
1157
1158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1160#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1161#[cfg_attr(feature = "schemars", schemars(inline))]
1162#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1163#[cfg_attr(
1164 feature = "sqlx",
1165 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1166)]
1167#[serde(rename_all = "snake_case")]
1168pub enum PollMode {
1169 Ranked,
1173 Approval,
1176 Single,
1178 #[serde(other)]
1181 #[cfg_attr(feature = "schemars", schemars(skip))]
1182 Unknown,
1183}
1184
1185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1188#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1189#[cfg_attr(feature = "schemars", schemars(inline))]
1190#[serde(rename_all = "snake_case")]
1191pub enum PollStatus {
1192 Scheduled,
1194 Open,
1197 Closed,
1199 #[serde(other)]
1201 #[cfg_attr(feature = "schemars", schemars(skip))]
1202 Unknown,
1203}
1204
1205impl PollStatus {
1206 pub fn at(
1208 opens_at: chrono::DateTime<chrono::Utc>,
1209 closes_at: chrono::DateTime<chrono::Utc>,
1210 now: chrono::DateTime<chrono::Utc>,
1211 ) -> Self {
1212 if now < opens_at {
1213 Self::Scheduled
1214 } else if now < closes_at {
1215 Self::Open
1216 } else {
1217 Self::Closed
1218 }
1219 }
1220}
1221
1222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1226#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1227#[cfg_attr(feature = "schemars", schemars(inline))]
1228#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1229#[cfg_attr(
1230 feature = "sqlx",
1231 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1232)]
1233#[serde(rename_all = "snake_case")]
1234pub enum DeletedBy {
1235 Author,
1239 Operator,
1242 #[serde(other)]
1247 #[cfg_attr(feature = "schemars", schemars(skip))]
1248 Unknown,
1249}
1250
1251#[derive(
1253 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1254)]
1255#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1256#[cfg_attr(feature = "schemars", schemars(inline))]
1257#[serde(rename_all = "snake_case")]
1258pub enum TrashMode {
1259 #[default]
1261 List,
1262 Restore,
1264 DeletePermanently,
1266}
1267
1268#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1270#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1271#[cfg_attr(feature = "schemars", schemars(inline))]
1272#[serde(rename_all = "snake_case")]
1273pub enum RemovedBy {
1274 Author,
1276 Operator,
1278 Moderation,
1280 #[serde(other)]
1283 #[cfg_attr(feature = "schemars", schemars(skip))]
1284 Unknown,
1285}
1286
1287impl RemovedBy {
1288 pub const fn placeholder(self) -> &'static str {
1291 match self {
1292 Self::Author => "[deleted by its author]",
1293 Self::Operator => {
1294 "[removed by the platform's operators in a cleanup]"
1295 }
1296 Self::Moderation => "[removed by moderation]",
1297 Self::Unknown => "[removed]",
1298 }
1299 }
1300}
1301
1302impl From<DeletedBy> for RemovedBy {
1303 fn from(by: DeletedBy) -> Self {
1304 match by {
1305 DeletedBy::Author => Self::Author,
1306 DeletedBy::Operator => Self::Operator,
1307 DeletedBy::Unknown => Self::Unknown,
1308 }
1309 }
1310}
1311
1312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1327#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1328#[cfg_attr(
1329 feature = "sqlx",
1330 sqlx(type_name = "operator_auth_method_enum", rename_all = "snake_case")
1331)]
1332#[serde(rename_all = "snake_case")]
1333pub enum OperatorAuthMethod {
1334 Password,
1336 PasswordReset,
1338 PasswordAndPasskey,
1341 Passkey,
1343}
1344
1345impl fmt::Display for OperatorAuthMethod {
1346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1347 f.write_str(match self {
1348 Self::Password => "password",
1349 Self::PasswordReset => "password reset link",
1350 Self::PasswordAndPasskey => "password and passkey",
1351 Self::Passkey => "passkey",
1352 })
1353 }
1354}
1355
1356#[derive(
1358 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1359)]
1360#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1361#[cfg_attr(
1362 feature = "sqlx",
1363 sqlx(type_name = "operator_login_mode_enum", rename_all = "snake_case")
1364)]
1365#[serde(rename_all = "snake_case")]
1366pub enum OperatorLoginMode {
1367 #[default]
1369 Password,
1370 PasswordAndPasskey,
1372 Passkey,
1375}
1376
1377impl OperatorLoginMode {
1378 pub fn needs_passkey(self) -> bool {
1380 !matches!(self, Self::Password)
1381 }
1382
1383 pub fn as_str(self) -> &'static str {
1385 match self {
1386 Self::Password => "password",
1387 Self::PasswordAndPasskey => "password_and_passkey",
1388 Self::Passkey => "passkey",
1389 }
1390 }
1391}
1392
1393impl fmt::Display for OperatorLoginMode {
1394 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1395 f.write_str(match self {
1396 Self::Password => "password only",
1397 Self::PasswordAndPasskey => "password, then a passkey",
1398 Self::Passkey => "passkey only",
1399 })
1400 }
1401}
1402
1403#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1407#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1408#[cfg_attr(
1409 feature = "sqlx",
1410 sqlx(type_name = "remedy_outcome_enum", rename_all = "snake_case")
1411)]
1412#[serde(rename_all = "snake_case")]
1413pub enum RemedyOutcome {
1414 Restored,
1416 SuspensionLifted,
1418 RecordOnly,
1420 Unrestorable,
1422 HumanRequired,
1424}
1425
1426#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1429#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1430#[cfg_attr(
1431 feature = "sqlx",
1432 sqlx(type_name = "agenda_thread_role_enum", rename_all = "snake_case")
1433)]
1434#[serde(rename_all = "snake_case")]
1435pub enum ThreadRole {
1436 Primary,
1438 Related,
1440}
1441
1442#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1445#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1446#[cfg_attr(
1447 feature = "sqlx",
1448 sqlx(type_name = "agenda_thread_added_by_enum", rename_all = "snake_case")
1449)]
1450#[serde(rename_all = "snake_case")]
1451pub enum ThreadAddedBy {
1452 Steward,
1454}
1455
1456impl fmt::Display for ThreadAddedBy {
1457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1458 f.write_str(match self {
1459 Self::Steward => "the Steward",
1460 })
1461 }
1462}
1463
1464#[derive(
1467 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1468)]
1469#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1470#[cfg_attr(
1471 feature = "sqlx",
1472 sqlx(type_name = "trust_tier_enum", rename_all = "snake_case")
1473)]
1474#[serde(rename_all = "snake_case")]
1475pub enum TrustTier {
1476 #[default]
1477 Standard,
1478 Trusted,
1479}
1480
1481impl_display_fromstr!(TargetType);
1486impl_display_fromstr!(PollMode);
1487impl_display_fromstr!(PollStatus);
1488impl_display_fromstr!(ClientPlatform);
1489impl_display_fromstr!(ModerationTargetType);
1490impl_display_fromstr!(ModerationActionType);
1491impl_display_fromstr!(ModerationTier);
1492impl_display_fromstr!(AppealStatus);
1493impl_display_fromstr!(AppealOutcome);
1494impl_display_fromstr!(ModelRole);
1495impl_display_fromstr!(ArchiveContextKind);
1496impl_display_fromstr!(ProposalCategory);
1497impl_display_fromstr!(DesignationKind);
1498impl_display_fromstr!(GovernanceLogEntryType);
1499impl_display_fromstr!(AmendmentKind);
1500impl_display_fromstr!(ObligationKind);
1501impl_display_fromstr!(OwedBy);
1502impl_display_fromstr!(ObligationStatus);
1503impl_display_fromstr!(CalendarEventKind);
1504impl_display_fromstr!(CalendarEventStatus);
1505impl_display_fromstr!(Standing);
1506impl_display_fromstr!(CitationRelation);
1507impl_display_fromstr!(KeyStatus);
1508impl_display_fromstr!(MeetingStatus);
1509impl_display_fromstr!(AgendaItemStatus);
1510impl_display_fromstr!(AgendaSourceType);
1511impl_display_fromstr!(RoundType);
1512impl_display_fromstr!(DecisionOutcome);
1513impl_display_fromstr!(BatchType);
1514impl_display_fromstr!(BatchStatus);
1515impl_display_fromstr!(OAuthScope);
1516impl_display_fromstr!(FeedSort);
1517impl_display_fromstr!(DashboardCommunities);
1518impl_display_fromstr!(ProposalSort);
1519impl_display_fromstr!(GovernanceLogView);
1520impl_display_fromstr!(ObligationSort);
1521impl_display_fromstr!(DetailLevel);
1522impl_display_fromstr!(RecordVersion);
1523impl_display_fromstr!(SearchMode);
1524impl_display_fromstr!(FriendshipStatus);
1525impl_display_fromstr!(FriendshipAction);
1526impl_display_fromstr!(BlockAction);
1527impl_display_fromstr!(MessageEncryption);
1528impl_display_fromstr!(ContentKind);
1529impl_display_fromstr!(DeletedBy);
1530impl_display_fromstr!(TrashMode);
1531impl_display_fromstr!(RemovedBy);
1532impl_display_fromstr!(RemedyOutcome);
1533impl_display_fromstr!(ThreadRole);
1534impl_display_fromstr!(TrustTier);
1535
1536macro_rules! closed_variants {
1551 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1552 impl $name {
1553 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1555 [$(Self::$v),+];
1556 }
1557 const _: () = {
1558 #[allow(dead_code)]
1559 fn every_variant_is_listed(v: $name) {
1560 match v {
1561 $($name::$v)|+ $($(| $name::$x)+)? => {}
1562 }
1563 }
1564 };
1565 };
1566}
1567
1568closed_variants!(TargetType [Post, Comment, Message]);
1569closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1570closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1571closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1572closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1573closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1574closed_variants!(ModelRole [
1577 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1578 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor,
1579 Witness
1580]);
1581closed_variants!(ArchiveContextKind [AgendaItem, Meeting, Appeal, Flag, InterviewRun]);
1582closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1583closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1584closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1585closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation, DataSet, Test] never_written [Unknown]);
1586closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1587closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1588closed_variants!(KeyStatus [Active, Retired, Compromised]);
1589closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1590closed_variants!(OwedBy [Steward, Staff, Council]);
1591closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1592closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1593closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1594closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1595closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1596closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1597closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1598closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1599closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1600closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1601closed_variants!(OAuthScope [Read, Write]);
1602closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1603closed_variants!(MessageEncryption [E2ee, Server]);
1604closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1605closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1606closed_variants!(OperatorAuthMethod [Password, PasswordReset, PasswordAndPasskey, Passkey]);
1607closed_variants!(OperatorLoginMode [Password, PasswordAndPasskey, Passkey]);
1608closed_variants!(RemedyOutcome [Restored, SuspensionLifted, RecordOnly, Unrestorable, HumanRequired]);
1609closed_variants!(ThreadRole [Primary, Related]);
1610closed_variants!(ThreadAddedBy[Steward]);
1611closed_variants!(TrustTier [Standard, Trusted]);
1612
1613#[cfg(test)]
1614mod tests {
1615 use super::*;
1616
1617 #[test]
1618 fn target_type_serde_round_trip() {
1619 let val = TargetType::Post;
1620 let json = serde_json::to_string(&val).unwrap();
1621 assert_eq!(json, "\"post\"");
1622 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1623 assert_eq!(val, deserialized);
1624 }
1625
1626 #[test]
1627 fn target_type_display() {
1628 assert_eq!(TargetType::Post.to_string(), "post");
1629 assert_eq!(TargetType::Comment.to_string(), "comment");
1630 }
1631
1632 #[test]
1633 fn target_type_from_str() {
1634 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1635 assert_eq!(
1636 TargetType::from_str("comment").unwrap(),
1637 TargetType::Comment
1638 );
1639 }
1640
1641 #[test]
1642 fn moderation_tier_serde() {
1643 let tier = ModerationTier::Tier2;
1644 let json = serde_json::to_string(&tier).unwrap();
1645 assert_eq!(json, "\"2\"");
1646 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1647 assert_eq!(tier, deserialized);
1648 }
1649
1650 #[test]
1653 fn message_encryption_wire_values() {
1654 assert_eq!(
1655 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1656 "\"e2ee\""
1657 );
1658 assert_eq!(
1659 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1660 "\"server\""
1661 );
1662 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1663 assert_eq!(
1664 MessageEncryption::from_str("server").unwrap(),
1665 MessageEncryption::Server
1666 );
1667 }
1668
1669 #[test]
1670 fn search_mode_wire_values() {
1671 assert_eq!(
1672 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1673 "\"keyword\""
1674 );
1675 assert_eq!(
1676 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1677 "\"semantic\""
1678 );
1679 assert_eq!(
1680 SearchMode::from_str("semantic").unwrap(),
1681 SearchMode::Semantic
1682 );
1683 }
1684
1685 #[test]
1688 fn no_sort_orders_by_votes() {
1689 for gone in ["score", "controversial", "unpopular"] {
1690 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1691 assert!(
1692 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1693 .is_err(),
1694 "{gone}"
1695 );
1696 }
1697 assert!(ProposalSort::from_str("score").is_err());
1698 }
1699
1700 #[cfg(feature = "schemars")]
1701 #[test]
1702 fn feed_sort_schema_is_ref_free() {
1703 use schemars::JsonSchema;
1704
1705 assert!(<FeedSort as JsonSchema>::inline_schema());
1706
1707 let schema = schemars::schema_for!(FeedSort);
1708 let value = serde_json::to_value(&schema).unwrap();
1709 let blob = value.to_string();
1710 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1711 assert!(!blob.contains("$ref"), "no $ref: {value}");
1712 let values: Vec<&str> = value["enum"]
1713 .as_array()
1714 .expect("FeedSort should render as a flat `enum`")
1715 .iter()
1716 .filter_map(|v| v.as_str())
1717 .collect();
1718 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1719 }
1720
1721 #[cfg(feature = "schemars")]
1724 #[test]
1725 fn trash_enum_schemas_are_plain_enums() {
1726 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1727 assert!(T::inline_schema());
1728 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1729 let blob = value.to_string();
1730 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1731 assert!(value.get("oneOf").is_none(), "{value}");
1732 value["enum"]
1733 .as_array()
1734 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1735 .iter()
1736 .map(|v| v.as_str().unwrap().to_owned())
1737 .collect()
1738 }
1739 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1740 assert_eq!(
1741 values::<TrashMode>(),
1742 ["list", "restore", "delete_permanently"]
1743 );
1744 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1745 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1746 }
1747
1748 #[cfg(feature = "schemars")]
1751 #[test]
1752 fn obligation_enum_schemas_are_strict_safe() {
1753 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1754 assert!(T::inline_schema());
1755 let value = crate::responses::inline_schema_for::<T>();
1756 assert_eq!(
1757 crate::tools::strict_schema_violations(&value),
1758 Vec::<String>::new()
1759 );
1760 value["enum"]
1761 .as_array()
1762 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1763 .iter()
1764 .map(|v| v.as_str().unwrap().to_owned())
1765 .collect()
1766 }
1767 assert_eq!(
1768 values::<ObligationKind>(),
1769 [
1770 "carry_forward",
1771 "ratification",
1772 "review",
1773 "referral",
1774 "reveal",
1775 "task"
1776 ]
1777 );
1778 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1779 assert_eq!(
1780 values::<ObligationStatus>(),
1781 ["open", "lapsed", "discharged", "withdrawn"]
1782 );
1783 assert_eq!(
1784 values::<CalendarEventKind>(),
1785 ["sitting", "special_sitting"]
1786 );
1787 }
1788
1789 #[test]
1790 fn trash_enum_wire_values() {
1791 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1792 assert_eq!(
1793 "delete_permanently".parse::<TrashMode>().unwrap(),
1794 TrashMode::DeletePermanently
1795 );
1796 assert_eq!(TrashMode::default(), TrashMode::List);
1797 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1798 assert_eq!(ContentKind::Comment.to_string(), "comment");
1799 }
1800
1801 #[test]
1804 fn unknown_trash_causes_parse() {
1805 assert_eq!(
1806 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1807 RemovedBy::Unknown
1808 );
1809 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1810 assert_eq!(
1811 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1812 DeletedBy::Unknown
1813 );
1814 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1815 }
1816
1817 #[test]
1818 fn proposal_category_round_trip() {
1819 for cat in [
1820 ProposalCategory::Routine,
1821 ProposalCategory::Policy,
1822 ProposalCategory::Constitutional,
1823 ProposalCategory::Emergency,
1824 ] {
1825 let json = serde_json::to_string(&cat).unwrap();
1826 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1827 assert_eq!(cat, back);
1828 }
1829 }
1830
1831 #[test]
1834 fn governance_amendment_and_key_wire_values() {
1835 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1836 assert_eq!(
1837 GovernanceLogEntryType::KeyRotation.to_string(),
1838 "key_rotation"
1839 );
1840 assert_eq!(
1841 AmendmentKind::NonPrecedential.to_string(),
1842 "non_precedential"
1843 );
1844 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1845 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1846 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1847 assert_eq!(RecordVersion::Original.to_string(), "original");
1848 assert_eq!(
1849 "latest".parse::<RecordVersion>().unwrap(),
1850 RecordVersion::Latest
1851 );
1852 assert_eq!(
1853 "superseded".parse::<AmendmentKind>().unwrap(),
1854 AmendmentKind::Superseded
1855 );
1856 assert_eq!(Standing::default(), Standing::InForce);
1857 assert_eq!(Standing::InForce.to_string(), "in_force");
1858 assert_eq!(
1859 "compromised".parse::<KeyStatus>().unwrap(),
1860 KeyStatus::Compromised
1861 );
1862 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1863 }
1864
1865 #[test]
1868 fn obligation_and_calendar_wire_values() {
1869 assert_eq!(
1870 GovernanceLogEntryType::Obligation.to_string(),
1871 "obligation"
1872 );
1873 assert_eq!(GovernanceLogEntryType::DataSet.to_string(), "data_set");
1874 assert_eq!(
1875 "data_set".parse::<GovernanceLogEntryType>().unwrap(),
1876 GovernanceLogEntryType::DataSet
1877 );
1878 assert_eq!(GovernanceLogEntryType::Test.to_string(), "test");
1879 assert_eq!(
1880 "test".parse::<GovernanceLogEntryType>().unwrap(),
1881 GovernanceLogEntryType::Test
1882 );
1883 assert_eq!(
1885 serde_json::from_value::<GovernanceLogEntryType>(
1886 serde_json::json!("something_newer")
1887 )
1888 .unwrap(),
1889 GovernanceLogEntryType::Unknown
1890 );
1891 assert!(
1892 !GovernanceLogEntryType::ALL
1893 .contains(&GovernanceLogEntryType::Unknown)
1894 );
1895 let kinds: Vec<String> =
1896 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1897 assert_eq!(
1898 kinds,
1899 [
1900 "planned",
1901 "discharged",
1902 "lapsed",
1903 "rescheduled",
1904 "withdrawn"
1905 ]
1906 );
1907 assert!(AmendmentKind::Lapsed.is_obligation());
1908 assert!(!AmendmentKind::Revision.is_obligation());
1909 for (kind, wire) in [
1910 (ObligationKind::CarryForward, "carry_forward"),
1911 (ObligationKind::Ratification, "ratification"),
1912 (ObligationKind::Review, "review"),
1913 (ObligationKind::Referral, "referral"),
1914 (ObligationKind::Reveal, "reveal"),
1915 (ObligationKind::Task, "task"),
1916 ] {
1917 assert_eq!(kind.to_string(), wire);
1918 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1919 }
1920 for (who, wire) in [
1921 (OwedBy::Steward, "steward"),
1922 (OwedBy::Staff, "staff"),
1923 (OwedBy::Council, "council"),
1924 ] {
1925 assert_eq!(who.to_string(), wire);
1926 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1927 }
1928 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1929 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1930 assert!(ObligationStatus::Withdrawn.is_closed());
1931 assert!(!ObligationStatus::Lapsed.is_closed());
1932 assert_eq!(
1933 CalendarEventKind::SpecialSitting.to_string(),
1934 "special_sitting"
1935 );
1936 assert!("festival".parse::<CalendarEventKind>().is_err());
1939 assert_eq!(
1940 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1941 CalendarEventStatus::Cancelled
1942 );
1943 }
1944
1945 #[test]
1947 fn detail_level_wire_names() {
1948 for (level, wire) in [
1949 (DetailLevel::Summary, "summary"),
1950 (DetailLevel::Full, "full"),
1951 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1952 ] {
1953 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1954 assert_eq!(level.to_string(), wire);
1955 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1956 }
1957 }
1958
1959 #[cfg(feature = "schemars")]
1964 #[test]
1965 fn enum_json_schema_is_inlined() {
1966 use schemars::JsonSchema;
1967
1968 assert!(<TargetType as JsonSchema>::inline_schema());
1969 assert!(<FeedSort as JsonSchema>::inline_schema());
1970 assert!(<DashboardCommunities as JsonSchema>::inline_schema());
1971 assert!(<ProposalSort as JsonSchema>::inline_schema());
1972 assert!(<DetailLevel as JsonSchema>::inline_schema());
1973 assert!(<RecordVersion as JsonSchema>::inline_schema());
1974 assert!(<SearchMode as JsonSchema>::inline_schema());
1975 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1976 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1977 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1978 assert!(<Standing as JsonSchema>::inline_schema());
1979 assert!(<CitationRelation as JsonSchema>::inline_schema());
1980 assert!(<KeyStatus as JsonSchema>::inline_schema());
1981 assert!(<OAuthScope as JsonSchema>::inline_schema());
1982 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1983 assert!(<ModerationTier as JsonSchema>::inline_schema());
1984 assert!(<ObligationKind as JsonSchema>::inline_schema());
1985 assert!(<OwedBy as JsonSchema>::inline_schema());
1986 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1987 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1988 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1989
1990 #[derive(schemars::JsonSchema)]
1991 #[allow(dead_code)]
1992 struct Container {
1993 target_type: TargetType,
1994 sort: Option<FeedSort>,
1995 proposal_sort: Option<ProposalSort>,
1996 category: Option<ProposalCategory>,
1997 detail: Option<DetailLevel>,
1998 version: Option<RecordVersion>,
1999 search_mode: Option<SearchMode>,
2000 }
2001
2002 let schema = schemars::schema_for!(Container);
2003 let value = serde_json::to_value(&schema).unwrap();
2004 let blob = value.to_string();
2005
2006 assert!(
2007 value.get("$defs").is_none(),
2008 "no $defs should be emitted for enum-only container; got schema: {value}"
2009 );
2010 assert!(
2011 !blob.contains("$ref"),
2012 "enum container schema must contain no $ref anywhere; got: {value}"
2013 );
2014
2015 let target_type_enum = value["properties"]["target_type"]["enum"]
2017 .as_array()
2018 .expect("target_type should have inline `enum` array");
2019 assert!(
2020 target_type_enum
2021 .contains(&serde_json::Value::String("post".into()))
2022 );
2023 assert!(
2024 target_type_enum
2025 .contains(&serde_json::Value::String("comment".into()))
2026 );
2027 }
2028
2029 #[cfg(feature = "schemars")]
2035 #[test]
2036 fn search_mode_schema_is_ref_free() {
2037 use schemars::JsonSchema;
2038
2039 assert!(<SearchMode as JsonSchema>::inline_schema());
2040
2041 let schema = schemars::schema_for!(SearchMode);
2042 let value = serde_json::to_value(&schema).unwrap();
2043 let blob = value.to_string();
2044 assert!(value.get("$defs").is_none(), "no $defs: {value}");
2045 assert!(!blob.contains("$ref"), "no $ref: {value}");
2046
2047 let variants = value["oneOf"]
2053 .as_array()
2054 .expect("SearchMode should have an inline `oneOf` array");
2055 let consts: Vec<&str> = variants
2056 .iter()
2057 .filter_map(|v| v["const"].as_str())
2058 .collect();
2059 assert!(consts.contains(&"keyword"), "{value}");
2060 assert!(consts.contains(&"semantic"), "{value}");
2061 }
2062}