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 #[serde(other)]
469 #[cfg_attr(feature = "schemars", schemars(skip))]
470 Unknown,
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
480#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
481#[cfg_attr(feature = "schemars", schemars(inline))]
482#[serde(rename_all = "snake_case")]
483pub enum AmendmentKind {
484 NonPrecedential,
486 Overruled,
488 Superseded,
490 Reinstated,
492 Correction,
494 Redaction,
496 Reattested,
499 Revision,
502 Planned,
506 Discharged,
508 Lapsed,
511 Rescheduled,
513 Withdrawn,
515}
516
517impl AmendmentKind {
518 pub const OBLIGATION: [Self; 5] = [
521 Self::Planned,
522 Self::Discharged,
523 Self::Lapsed,
524 Self::Rescheduled,
525 Self::Withdrawn,
526 ];
527
528 pub fn is_obligation(self) -> bool {
530 Self::OBLIGATION.contains(&self)
531 }
532}
533
534#[derive(
539 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
540)]
541#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
542#[cfg_attr(feature = "schemars", schemars(inline))]
543#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
544#[cfg_attr(
545 feature = "sqlx",
546 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
547)]
548#[serde(rename_all = "snake_case")]
549pub enum Standing {
550 #[default]
551 InForce,
552 NonPrecedential,
553 Overruled,
554 Superseded,
555}
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
562#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
563#[cfg_attr(feature = "schemars", schemars(inline))]
564#[serde(rename_all = "snake_case")]
565pub enum CitationRelation {
566 Concerns,
568 Authority,
570 Overrules,
572}
573
574#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
577#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
578#[cfg_attr(feature = "schemars", schemars(inline))]
579#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
580#[cfg_attr(
581 feature = "sqlx",
582 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
583)]
584#[serde(rename_all = "snake_case")]
585pub enum KeyStatus {
586 Active,
588 Retired,
590 Compromised,
593}
594
595#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
602#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
603#[cfg_attr(feature = "schemars", schemars(inline))]
604#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
605#[cfg_attr(
606 feature = "sqlx",
607 sqlx(type_name = "obligation_kind_enum", rename_all = "snake_case")
608)]
609#[serde(rename_all = "snake_case")]
610pub enum ObligationKind {
611 CarryForward,
613 Ratification,
615 Review,
617 Referral,
619 Reveal,
621 Task,
623}
624
625#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
627#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
628#[cfg_attr(feature = "schemars", schemars(inline))]
629#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
630#[cfg_attr(
631 feature = "sqlx",
632 sqlx(type_name = "obligation_owed_by_enum", rename_all = "snake_case")
633)]
634#[serde(rename_all = "snake_case")]
635pub enum OwedBy {
636 Steward,
637 Staff,
639 Council,
640}
641
642#[derive(
645 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize,
646)]
647#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
648#[cfg_attr(feature = "schemars", schemars(inline))]
649#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
650#[cfg_attr(
651 feature = "sqlx",
652 sqlx(type_name = "obligation_status_enum", rename_all = "snake_case")
653)]
654#[serde(rename_all = "snake_case")]
655pub enum ObligationStatus {
656 #[default]
658 Open,
659 Lapsed,
661 Discharged,
663 Withdrawn,
665}
666
667impl ObligationStatus {
668 pub fn is_closed(self) -> bool {
670 matches!(self, Self::Discharged | Self::Withdrawn)
671 }
672}
673
674#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
676#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
677#[cfg_attr(feature = "schemars", schemars(inline))]
678#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
679#[cfg_attr(
680 feature = "sqlx",
681 sqlx(type_name = "calendar_event_kind_enum", rename_all = "snake_case")
682)]
683#[serde(rename_all = "snake_case")]
684pub enum CalendarEventKind {
685 Sitting,
687 SpecialSitting,
689}
690
691#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
694#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
695#[cfg_attr(feature = "schemars", schemars(inline))]
696#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
697#[cfg_attr(
698 feature = "sqlx",
699 sqlx(type_name = "calendar_event_status_enum", rename_all = "snake_case")
700)]
701#[serde(rename_all = "snake_case")]
702pub enum CalendarEventStatus {
703 Scheduled,
704 Held,
705 Cancelled,
706}
707
708#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
714#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
715#[cfg_attr(feature = "schemars", schemars(inline))]
716#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
717#[cfg_attr(
718 feature = "sqlx",
719 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
720)]
721#[serde(rename_all = "snake_case")]
722pub enum MeetingStatus {
723 Active,
724 Adjourned,
725 Cancelled,
726}
727
728#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
730#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
731#[cfg_attr(feature = "schemars", schemars(inline))]
732#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
733#[cfg_attr(
734 feature = "sqlx",
735 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
736)]
737#[serde(rename_all = "snake_case")]
738pub enum AgendaItemStatus {
739 Pending,
740 Deliberating,
741 Decided,
742 Deferred,
743 CarriedOver,
744}
745
746#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
748#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
749#[cfg_attr(feature = "schemars", schemars(inline))]
750#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
751#[cfg_attr(
752 feature = "sqlx",
753 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
754)]
755#[serde(rename_all = "snake_case")]
756pub enum AgendaSourceType {
757 Proposal,
758 AppealReferral,
759 StewardSubmission,
760 Internal,
761}
762
763#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
765#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
766#[cfg_attr(feature = "schemars", schemars(inline))]
767#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
768#[cfg_attr(
769 feature = "sqlx",
770 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
771)]
772#[serde(rename_all = "snake_case")]
773pub enum RoundType {
774 Independent,
775 Deliberation,
776 FinalVote,
777}
778
779#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
781#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
782#[cfg_attr(feature = "schemars", schemars(inline))]
783#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
784#[cfg_attr(
785 feature = "sqlx",
786 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
787)]
788#[serde(rename_all = "snake_case")]
789pub enum DecisionOutcome {
790 Approved,
791 Rejected,
792 Deferred,
793 Amended,
794}
795
796impl RoundType {
797 pub const fn name(self) -> &'static str {
801 match self {
802 Self::Independent => "Independent Analysis",
803 Self::Deliberation => "Deliberation",
804 Self::FinalVote => "Final Vote",
805 }
806 }
807}
808
809impl DecisionOutcome {
810 pub const fn name(self) -> &'static str {
813 match self {
814 Self::Approved => "Approved",
815 Self::Rejected => "Rejected",
816 Self::Deferred => "Deferred",
817 Self::Amended => "Amended",
818 }
819 }
820}
821
822#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
828#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
829#[cfg_attr(feature = "schemars", schemars(inline))]
830#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
831#[cfg_attr(
832 feature = "sqlx",
833 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
834)]
835#[serde(rename_all = "snake_case")]
836pub enum BatchType {
837 Jury,
838 Judge,
839 Tier2,
840 Redaction,
842 Chambers,
848 Precedent,
852}
853
854#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
856#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
857#[cfg_attr(feature = "schemars", schemars(inline))]
858#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
859#[cfg_attr(
860 feature = "sqlx",
861 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
862)]
863#[serde(rename_all = "snake_case")]
864pub enum BatchStatus {
865 Submitted,
866 Polling,
867 Completed,
868 Failed,
869}
870
871#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
877#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
878#[cfg_attr(feature = "schemars", schemars(inline))]
879#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
880#[cfg_attr(
881 feature = "sqlx",
882 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
883)]
884#[serde(rename_all = "snake_case")]
885pub enum OAuthScope {
886 Read,
887 Write,
888}
889
890#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
903#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
904#[cfg_attr(feature = "schemars", schemars(inline))]
905#[serde(rename_all = "snake_case")]
906pub enum FeedSort {
907 Date,
908 Active,
909 Random,
910 Diverse,
911}
912
913#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
919#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
920#[cfg_attr(feature = "schemars", schemars(inline))]
921#[serde(rename_all = "snake_case")]
922pub enum DashboardCommunities {
923 Joined,
924 All,
925}
926
927impl DashboardCommunities {
928 pub const ALL: [Self; 2] = [Self::Joined, Self::All];
930}
931
932#[derive(
946 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
947)]
948#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
949#[cfg_attr(feature = "schemars", schemars(inline))]
950#[serde(rename_all = "snake_case")]
951pub enum ProposalSort {
952 #[default]
955 Newest,
956 Oldest,
959}
960
961#[derive(
963 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
964)]
965#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
966#[cfg_attr(feature = "schemars", schemars(inline))]
967#[serde(rename_all = "snake_case")]
968pub enum GovernanceLogView {
969 #[default]
971 Entries,
972 Obligations,
974}
975
976#[derive(
978 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
979)]
980#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
981#[cfg_attr(feature = "schemars", schemars(inline))]
982#[serde(rename_all = "snake_case")]
983pub enum ObligationSort {
984 #[default]
987 Due,
988 Created,
990}
991
992#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1004#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1005#[cfg_attr(feature = "schemars", schemars(inline))]
1006#[serde(rename_all = "snake_case")]
1007pub enum DetailLevel {
1008 Summary,
1010 Full,
1016 FullWithAttachments,
1020}
1021
1022#[derive(
1027 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1028)]
1029#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1030#[cfg_attr(feature = "schemars", schemars(inline))]
1031#[serde(rename_all = "snake_case")]
1032pub enum RecordVersion {
1033 #[default]
1035 Latest,
1036 Original,
1038}
1039
1040#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1051#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1052#[cfg_attr(feature = "schemars", schemars(inline))]
1053#[serde(rename_all = "snake_case")]
1054pub enum SearchMode {
1055 Keyword,
1057 Semantic,
1062}
1063
1064#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1074#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1075#[cfg_attr(feature = "schemars", schemars(inline))]
1076#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1077#[cfg_attr(
1078 feature = "sqlx",
1079 sqlx(type_name = "friendship_status", rename_all = "snake_case")
1080)]
1081#[serde(rename_all = "snake_case")]
1082pub enum FriendshipStatus {
1083 Pending,
1084 Accepted,
1085 Declined,
1086}
1087
1088#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1092#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1093#[cfg_attr(feature = "schemars", schemars(inline))]
1094#[serde(rename_all = "snake_case")]
1095pub enum FriendshipAction {
1096 Request,
1098 Accept,
1100 Decline,
1102 Unfriend,
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1113#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1114#[cfg_attr(feature = "schemars", schemars(inline))]
1115#[cfg_attr(
1116 feature = "sqlx",
1117 derive(sqlx::Type),
1118 sqlx(type_name = "message_encryption", rename_all = "snake_case")
1119)]
1120#[serde(rename_all = "snake_case")]
1121pub enum MessageEncryption {
1122 E2ee,
1124 Server,
1126}
1127
1128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1130#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1131#[cfg_attr(feature = "schemars", schemars(inline))]
1132#[serde(rename_all = "snake_case")]
1133pub enum BlockAction {
1134 Block,
1135 Unblock,
1136}
1137
1138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1146#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1147#[cfg_attr(feature = "schemars", schemars(inline))]
1148#[serde(rename_all = "snake_case")]
1149pub enum ContentKind {
1150 Post,
1151 Comment,
1152}
1153
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1156#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1157#[cfg_attr(feature = "schemars", schemars(inline))]
1158#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1159#[cfg_attr(
1160 feature = "sqlx",
1161 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1162)]
1163#[serde(rename_all = "snake_case")]
1164pub enum PollMode {
1165 Ranked,
1169 Approval,
1172 Single,
1174 #[serde(other)]
1177 #[cfg_attr(feature = "schemars", schemars(skip))]
1178 Unknown,
1179}
1180
1181#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1184#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1185#[cfg_attr(feature = "schemars", schemars(inline))]
1186#[serde(rename_all = "snake_case")]
1187pub enum PollStatus {
1188 Scheduled,
1190 Open,
1193 Closed,
1195 #[serde(other)]
1197 #[cfg_attr(feature = "schemars", schemars(skip))]
1198 Unknown,
1199}
1200
1201impl PollStatus {
1202 pub fn at(
1204 opens_at: chrono::DateTime<chrono::Utc>,
1205 closes_at: chrono::DateTime<chrono::Utc>,
1206 now: chrono::DateTime<chrono::Utc>,
1207 ) -> Self {
1208 if now < opens_at {
1209 Self::Scheduled
1210 } else if now < closes_at {
1211 Self::Open
1212 } else {
1213 Self::Closed
1214 }
1215 }
1216}
1217
1218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1222#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1223#[cfg_attr(feature = "schemars", schemars(inline))]
1224#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1225#[cfg_attr(
1226 feature = "sqlx",
1227 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1228)]
1229#[serde(rename_all = "snake_case")]
1230pub enum DeletedBy {
1231 Author,
1235 Operator,
1238 #[serde(other)]
1243 #[cfg_attr(feature = "schemars", schemars(skip))]
1244 Unknown,
1245}
1246
1247#[derive(
1249 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1250)]
1251#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1252#[cfg_attr(feature = "schemars", schemars(inline))]
1253#[serde(rename_all = "snake_case")]
1254pub enum TrashMode {
1255 #[default]
1257 List,
1258 Restore,
1260 DeletePermanently,
1262}
1263
1264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1266#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1267#[cfg_attr(feature = "schemars", schemars(inline))]
1268#[serde(rename_all = "snake_case")]
1269pub enum RemovedBy {
1270 Author,
1272 Operator,
1274 Moderation,
1276 #[serde(other)]
1279 #[cfg_attr(feature = "schemars", schemars(skip))]
1280 Unknown,
1281}
1282
1283impl RemovedBy {
1284 pub const fn placeholder(self) -> &'static str {
1287 match self {
1288 Self::Author => "[deleted by its author]",
1289 Self::Operator => {
1290 "[removed by the platform's operators in a cleanup]"
1291 }
1292 Self::Moderation => "[removed by moderation]",
1293 Self::Unknown => "[removed]",
1294 }
1295 }
1296}
1297
1298impl From<DeletedBy> for RemovedBy {
1299 fn from(by: DeletedBy) -> Self {
1300 match by {
1301 DeletedBy::Author => Self::Author,
1302 DeletedBy::Operator => Self::Operator,
1303 DeletedBy::Unknown => Self::Unknown,
1304 }
1305 }
1306}
1307
1308#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1323#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1324#[cfg_attr(
1325 feature = "sqlx",
1326 sqlx(type_name = "operator_auth_method_enum", rename_all = "snake_case")
1327)]
1328#[serde(rename_all = "snake_case")]
1329pub enum OperatorAuthMethod {
1330 Password,
1332 PasswordReset,
1334 PasswordAndPasskey,
1337 Passkey,
1339}
1340
1341impl fmt::Display for OperatorAuthMethod {
1342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1343 f.write_str(match self {
1344 Self::Password => "password",
1345 Self::PasswordReset => "password reset link",
1346 Self::PasswordAndPasskey => "password and passkey",
1347 Self::Passkey => "passkey",
1348 })
1349 }
1350}
1351
1352#[derive(
1354 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1355)]
1356#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1357#[cfg_attr(
1358 feature = "sqlx",
1359 sqlx(type_name = "operator_login_mode_enum", rename_all = "snake_case")
1360)]
1361#[serde(rename_all = "snake_case")]
1362pub enum OperatorLoginMode {
1363 #[default]
1365 Password,
1366 PasswordAndPasskey,
1368 Passkey,
1371}
1372
1373impl OperatorLoginMode {
1374 pub fn needs_passkey(self) -> bool {
1376 !matches!(self, Self::Password)
1377 }
1378
1379 pub fn as_str(self) -> &'static str {
1381 match self {
1382 Self::Password => "password",
1383 Self::PasswordAndPasskey => "password_and_passkey",
1384 Self::Passkey => "passkey",
1385 }
1386 }
1387}
1388
1389impl fmt::Display for OperatorLoginMode {
1390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1391 f.write_str(match self {
1392 Self::Password => "password only",
1393 Self::PasswordAndPasskey => "password, then a passkey",
1394 Self::Passkey => "passkey only",
1395 })
1396 }
1397}
1398
1399#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1403#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1404#[cfg_attr(
1405 feature = "sqlx",
1406 sqlx(type_name = "remedy_outcome_enum", rename_all = "snake_case")
1407)]
1408#[serde(rename_all = "snake_case")]
1409pub enum RemedyOutcome {
1410 Restored,
1412 SuspensionLifted,
1414 RecordOnly,
1416 Unrestorable,
1418 HumanRequired,
1420}
1421
1422#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1425#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1426#[cfg_attr(
1427 feature = "sqlx",
1428 sqlx(type_name = "agenda_thread_role_enum", rename_all = "snake_case")
1429)]
1430#[serde(rename_all = "snake_case")]
1431pub enum ThreadRole {
1432 Primary,
1434 Related,
1436}
1437
1438#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1441#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1442#[cfg_attr(
1443 feature = "sqlx",
1444 sqlx(type_name = "agenda_thread_added_by_enum", rename_all = "snake_case")
1445)]
1446#[serde(rename_all = "snake_case")]
1447pub enum ThreadAddedBy {
1448 Steward,
1450}
1451
1452impl fmt::Display for ThreadAddedBy {
1453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1454 f.write_str(match self {
1455 Self::Steward => "the Steward",
1456 })
1457 }
1458}
1459
1460#[derive(
1463 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1464)]
1465#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1466#[cfg_attr(
1467 feature = "sqlx",
1468 sqlx(type_name = "trust_tier_enum", rename_all = "snake_case")
1469)]
1470#[serde(rename_all = "snake_case")]
1471pub enum TrustTier {
1472 #[default]
1473 Standard,
1474 Trusted,
1475}
1476
1477impl_display_fromstr!(TargetType);
1482impl_display_fromstr!(PollMode);
1483impl_display_fromstr!(PollStatus);
1484impl_display_fromstr!(ClientPlatform);
1485impl_display_fromstr!(ModerationTargetType);
1486impl_display_fromstr!(ModerationActionType);
1487impl_display_fromstr!(ModerationTier);
1488impl_display_fromstr!(AppealStatus);
1489impl_display_fromstr!(AppealOutcome);
1490impl_display_fromstr!(ModelRole);
1491impl_display_fromstr!(ArchiveContextKind);
1492impl_display_fromstr!(ProposalCategory);
1493impl_display_fromstr!(DesignationKind);
1494impl_display_fromstr!(GovernanceLogEntryType);
1495impl_display_fromstr!(AmendmentKind);
1496impl_display_fromstr!(ObligationKind);
1497impl_display_fromstr!(OwedBy);
1498impl_display_fromstr!(ObligationStatus);
1499impl_display_fromstr!(CalendarEventKind);
1500impl_display_fromstr!(CalendarEventStatus);
1501impl_display_fromstr!(Standing);
1502impl_display_fromstr!(CitationRelation);
1503impl_display_fromstr!(KeyStatus);
1504impl_display_fromstr!(MeetingStatus);
1505impl_display_fromstr!(AgendaItemStatus);
1506impl_display_fromstr!(AgendaSourceType);
1507impl_display_fromstr!(RoundType);
1508impl_display_fromstr!(DecisionOutcome);
1509impl_display_fromstr!(BatchType);
1510impl_display_fromstr!(BatchStatus);
1511impl_display_fromstr!(OAuthScope);
1512impl_display_fromstr!(FeedSort);
1513impl_display_fromstr!(DashboardCommunities);
1514impl_display_fromstr!(ProposalSort);
1515impl_display_fromstr!(GovernanceLogView);
1516impl_display_fromstr!(ObligationSort);
1517impl_display_fromstr!(DetailLevel);
1518impl_display_fromstr!(RecordVersion);
1519impl_display_fromstr!(SearchMode);
1520impl_display_fromstr!(FriendshipStatus);
1521impl_display_fromstr!(FriendshipAction);
1522impl_display_fromstr!(BlockAction);
1523impl_display_fromstr!(MessageEncryption);
1524impl_display_fromstr!(ContentKind);
1525impl_display_fromstr!(DeletedBy);
1526impl_display_fromstr!(TrashMode);
1527impl_display_fromstr!(RemovedBy);
1528impl_display_fromstr!(RemedyOutcome);
1529impl_display_fromstr!(ThreadRole);
1530impl_display_fromstr!(TrustTier);
1531
1532macro_rules! closed_variants {
1547 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1548 impl $name {
1549 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1551 [$(Self::$v),+];
1552 }
1553 const _: () = {
1554 #[allow(dead_code)]
1555 fn every_variant_is_listed(v: $name) {
1556 match v {
1557 $($name::$v)|+ $($(| $name::$x)+)? => {}
1558 }
1559 }
1560 };
1561 };
1562}
1563
1564closed_variants!(TargetType [Post, Comment, Message]);
1565closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1566closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1567closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1568closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1569closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1570closed_variants!(ModelRole [
1573 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1574 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor,
1575 Witness
1576]);
1577closed_variants!(ArchiveContextKind [AgendaItem, Meeting, Appeal, Flag, InterviewRun]);
1578closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1579closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1580closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1581closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation, DataSet] never_written [Unknown]);
1582closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1583closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1584closed_variants!(KeyStatus [Active, Retired, Compromised]);
1585closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1586closed_variants!(OwedBy [Steward, Staff, Council]);
1587closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1588closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1589closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1590closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1591closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1592closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1593closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1594closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1595closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1596closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1597closed_variants!(OAuthScope [Read, Write]);
1598closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1599closed_variants!(MessageEncryption [E2ee, Server]);
1600closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1601closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1602closed_variants!(OperatorAuthMethod [Password, PasswordReset, PasswordAndPasskey, Passkey]);
1603closed_variants!(OperatorLoginMode [Password, PasswordAndPasskey, Passkey]);
1604closed_variants!(RemedyOutcome [Restored, SuspensionLifted, RecordOnly, Unrestorable, HumanRequired]);
1605closed_variants!(ThreadRole [Primary, Related]);
1606closed_variants!(ThreadAddedBy[Steward]);
1607closed_variants!(TrustTier [Standard, Trusted]);
1608
1609#[cfg(test)]
1610mod tests {
1611 use super::*;
1612
1613 #[test]
1614 fn target_type_serde_round_trip() {
1615 let val = TargetType::Post;
1616 let json = serde_json::to_string(&val).unwrap();
1617 assert_eq!(json, "\"post\"");
1618 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1619 assert_eq!(val, deserialized);
1620 }
1621
1622 #[test]
1623 fn target_type_display() {
1624 assert_eq!(TargetType::Post.to_string(), "post");
1625 assert_eq!(TargetType::Comment.to_string(), "comment");
1626 }
1627
1628 #[test]
1629 fn target_type_from_str() {
1630 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1631 assert_eq!(
1632 TargetType::from_str("comment").unwrap(),
1633 TargetType::Comment
1634 );
1635 }
1636
1637 #[test]
1638 fn moderation_tier_serde() {
1639 let tier = ModerationTier::Tier2;
1640 let json = serde_json::to_string(&tier).unwrap();
1641 assert_eq!(json, "\"2\"");
1642 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1643 assert_eq!(tier, deserialized);
1644 }
1645
1646 #[test]
1649 fn message_encryption_wire_values() {
1650 assert_eq!(
1651 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1652 "\"e2ee\""
1653 );
1654 assert_eq!(
1655 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1656 "\"server\""
1657 );
1658 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1659 assert_eq!(
1660 MessageEncryption::from_str("server").unwrap(),
1661 MessageEncryption::Server
1662 );
1663 }
1664
1665 #[test]
1666 fn search_mode_wire_values() {
1667 assert_eq!(
1668 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1669 "\"keyword\""
1670 );
1671 assert_eq!(
1672 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1673 "\"semantic\""
1674 );
1675 assert_eq!(
1676 SearchMode::from_str("semantic").unwrap(),
1677 SearchMode::Semantic
1678 );
1679 }
1680
1681 #[test]
1684 fn no_sort_orders_by_votes() {
1685 for gone in ["score", "controversial", "unpopular"] {
1686 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1687 assert!(
1688 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1689 .is_err(),
1690 "{gone}"
1691 );
1692 }
1693 assert!(ProposalSort::from_str("score").is_err());
1694 }
1695
1696 #[cfg(feature = "schemars")]
1697 #[test]
1698 fn feed_sort_schema_is_ref_free() {
1699 use schemars::JsonSchema;
1700
1701 assert!(<FeedSort as JsonSchema>::inline_schema());
1702
1703 let schema = schemars::schema_for!(FeedSort);
1704 let value = serde_json::to_value(&schema).unwrap();
1705 let blob = value.to_string();
1706 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1707 assert!(!blob.contains("$ref"), "no $ref: {value}");
1708 let values: Vec<&str> = value["enum"]
1709 .as_array()
1710 .expect("FeedSort should render as a flat `enum`")
1711 .iter()
1712 .filter_map(|v| v.as_str())
1713 .collect();
1714 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1715 }
1716
1717 #[cfg(feature = "schemars")]
1720 #[test]
1721 fn trash_enum_schemas_are_plain_enums() {
1722 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1723 assert!(T::inline_schema());
1724 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1725 let blob = value.to_string();
1726 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1727 assert!(value.get("oneOf").is_none(), "{value}");
1728 value["enum"]
1729 .as_array()
1730 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1731 .iter()
1732 .map(|v| v.as_str().unwrap().to_owned())
1733 .collect()
1734 }
1735 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1736 assert_eq!(
1737 values::<TrashMode>(),
1738 ["list", "restore", "delete_permanently"]
1739 );
1740 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1741 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1742 }
1743
1744 #[cfg(feature = "schemars")]
1747 #[test]
1748 fn obligation_enum_schemas_are_strict_safe() {
1749 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1750 assert!(T::inline_schema());
1751 let value = crate::responses::inline_schema_for::<T>();
1752 assert_eq!(
1753 crate::tools::strict_schema_violations(&value),
1754 Vec::<String>::new()
1755 );
1756 value["enum"]
1757 .as_array()
1758 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1759 .iter()
1760 .map(|v| v.as_str().unwrap().to_owned())
1761 .collect()
1762 }
1763 assert_eq!(
1764 values::<ObligationKind>(),
1765 [
1766 "carry_forward",
1767 "ratification",
1768 "review",
1769 "referral",
1770 "reveal",
1771 "task"
1772 ]
1773 );
1774 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1775 assert_eq!(
1776 values::<ObligationStatus>(),
1777 ["open", "lapsed", "discharged", "withdrawn"]
1778 );
1779 assert_eq!(
1780 values::<CalendarEventKind>(),
1781 ["sitting", "special_sitting"]
1782 );
1783 }
1784
1785 #[test]
1786 fn trash_enum_wire_values() {
1787 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1788 assert_eq!(
1789 "delete_permanently".parse::<TrashMode>().unwrap(),
1790 TrashMode::DeletePermanently
1791 );
1792 assert_eq!(TrashMode::default(), TrashMode::List);
1793 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1794 assert_eq!(ContentKind::Comment.to_string(), "comment");
1795 }
1796
1797 #[test]
1800 fn unknown_trash_causes_parse() {
1801 assert_eq!(
1802 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1803 RemovedBy::Unknown
1804 );
1805 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1806 assert_eq!(
1807 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1808 DeletedBy::Unknown
1809 );
1810 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1811 }
1812
1813 #[test]
1814 fn proposal_category_round_trip() {
1815 for cat in [
1816 ProposalCategory::Routine,
1817 ProposalCategory::Policy,
1818 ProposalCategory::Constitutional,
1819 ProposalCategory::Emergency,
1820 ] {
1821 let json = serde_json::to_string(&cat).unwrap();
1822 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1823 assert_eq!(cat, back);
1824 }
1825 }
1826
1827 #[test]
1830 fn governance_amendment_and_key_wire_values() {
1831 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1832 assert_eq!(
1833 GovernanceLogEntryType::KeyRotation.to_string(),
1834 "key_rotation"
1835 );
1836 assert_eq!(
1837 AmendmentKind::NonPrecedential.to_string(),
1838 "non_precedential"
1839 );
1840 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1841 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1842 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1843 assert_eq!(RecordVersion::Original.to_string(), "original");
1844 assert_eq!(
1845 "latest".parse::<RecordVersion>().unwrap(),
1846 RecordVersion::Latest
1847 );
1848 assert_eq!(
1849 "superseded".parse::<AmendmentKind>().unwrap(),
1850 AmendmentKind::Superseded
1851 );
1852 assert_eq!(Standing::default(), Standing::InForce);
1853 assert_eq!(Standing::InForce.to_string(), "in_force");
1854 assert_eq!(
1855 "compromised".parse::<KeyStatus>().unwrap(),
1856 KeyStatus::Compromised
1857 );
1858 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1859 }
1860
1861 #[test]
1864 fn obligation_and_calendar_wire_values() {
1865 assert_eq!(
1866 GovernanceLogEntryType::Obligation.to_string(),
1867 "obligation"
1868 );
1869 assert_eq!(GovernanceLogEntryType::DataSet.to_string(), "data_set");
1870 assert_eq!(
1871 "data_set".parse::<GovernanceLogEntryType>().unwrap(),
1872 GovernanceLogEntryType::DataSet
1873 );
1874 assert_eq!(
1876 serde_json::from_value::<GovernanceLogEntryType>(
1877 serde_json::json!("something_newer")
1878 )
1879 .unwrap(),
1880 GovernanceLogEntryType::Unknown
1881 );
1882 assert!(
1883 !GovernanceLogEntryType::ALL
1884 .contains(&GovernanceLogEntryType::Unknown)
1885 );
1886 let kinds: Vec<String> =
1887 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1888 assert_eq!(
1889 kinds,
1890 [
1891 "planned",
1892 "discharged",
1893 "lapsed",
1894 "rescheduled",
1895 "withdrawn"
1896 ]
1897 );
1898 assert!(AmendmentKind::Lapsed.is_obligation());
1899 assert!(!AmendmentKind::Revision.is_obligation());
1900 for (kind, wire) in [
1901 (ObligationKind::CarryForward, "carry_forward"),
1902 (ObligationKind::Ratification, "ratification"),
1903 (ObligationKind::Review, "review"),
1904 (ObligationKind::Referral, "referral"),
1905 (ObligationKind::Reveal, "reveal"),
1906 (ObligationKind::Task, "task"),
1907 ] {
1908 assert_eq!(kind.to_string(), wire);
1909 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1910 }
1911 for (who, wire) in [
1912 (OwedBy::Steward, "steward"),
1913 (OwedBy::Staff, "staff"),
1914 (OwedBy::Council, "council"),
1915 ] {
1916 assert_eq!(who.to_string(), wire);
1917 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1918 }
1919 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1920 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1921 assert!(ObligationStatus::Withdrawn.is_closed());
1922 assert!(!ObligationStatus::Lapsed.is_closed());
1923 assert_eq!(
1924 CalendarEventKind::SpecialSitting.to_string(),
1925 "special_sitting"
1926 );
1927 assert!("festival".parse::<CalendarEventKind>().is_err());
1930 assert_eq!(
1931 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1932 CalendarEventStatus::Cancelled
1933 );
1934 }
1935
1936 #[test]
1938 fn detail_level_wire_names() {
1939 for (level, wire) in [
1940 (DetailLevel::Summary, "summary"),
1941 (DetailLevel::Full, "full"),
1942 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1943 ] {
1944 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1945 assert_eq!(level.to_string(), wire);
1946 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1947 }
1948 }
1949
1950 #[cfg(feature = "schemars")]
1955 #[test]
1956 fn enum_json_schema_is_inlined() {
1957 use schemars::JsonSchema;
1958
1959 assert!(<TargetType as JsonSchema>::inline_schema());
1960 assert!(<FeedSort as JsonSchema>::inline_schema());
1961 assert!(<DashboardCommunities as JsonSchema>::inline_schema());
1962 assert!(<ProposalSort as JsonSchema>::inline_schema());
1963 assert!(<DetailLevel as JsonSchema>::inline_schema());
1964 assert!(<RecordVersion as JsonSchema>::inline_schema());
1965 assert!(<SearchMode as JsonSchema>::inline_schema());
1966 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1967 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1968 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1969 assert!(<Standing as JsonSchema>::inline_schema());
1970 assert!(<CitationRelation as JsonSchema>::inline_schema());
1971 assert!(<KeyStatus as JsonSchema>::inline_schema());
1972 assert!(<OAuthScope as JsonSchema>::inline_schema());
1973 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1974 assert!(<ModerationTier as JsonSchema>::inline_schema());
1975 assert!(<ObligationKind as JsonSchema>::inline_schema());
1976 assert!(<OwedBy as JsonSchema>::inline_schema());
1977 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1978 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1979 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1980
1981 #[derive(schemars::JsonSchema)]
1982 #[allow(dead_code)]
1983 struct Container {
1984 target_type: TargetType,
1985 sort: Option<FeedSort>,
1986 proposal_sort: Option<ProposalSort>,
1987 category: Option<ProposalCategory>,
1988 detail: Option<DetailLevel>,
1989 version: Option<RecordVersion>,
1990 search_mode: Option<SearchMode>,
1991 }
1992
1993 let schema = schemars::schema_for!(Container);
1994 let value = serde_json::to_value(&schema).unwrap();
1995 let blob = value.to_string();
1996
1997 assert!(
1998 value.get("$defs").is_none(),
1999 "no $defs should be emitted for enum-only container; got schema: {value}"
2000 );
2001 assert!(
2002 !blob.contains("$ref"),
2003 "enum container schema must contain no $ref anywhere; got: {value}"
2004 );
2005
2006 let target_type_enum = value["properties"]["target_type"]["enum"]
2008 .as_array()
2009 .expect("target_type should have inline `enum` array");
2010 assert!(
2011 target_type_enum
2012 .contains(&serde_json::Value::String("post".into()))
2013 );
2014 assert!(
2015 target_type_enum
2016 .contains(&serde_json::Value::String("comment".into()))
2017 );
2018 }
2019
2020 #[cfg(feature = "schemars")]
2026 #[test]
2027 fn search_mode_schema_is_ref_free() {
2028 use schemars::JsonSchema;
2029
2030 assert!(<SearchMode as JsonSchema>::inline_schema());
2031
2032 let schema = schemars::schema_for!(SearchMode);
2033 let value = serde_json::to_value(&schema).unwrap();
2034 let blob = value.to_string();
2035 assert!(value.get("$defs").is_none(), "no $defs: {value}");
2036 assert!(!blob.contains("$ref"), "no $ref: {value}");
2037
2038 let variants = value["oneOf"]
2044 .as_array()
2045 .expect("SearchMode should have an inline `oneOf` array");
2046 let consts: Vec<&str> = variants
2047 .iter()
2048 .filter_map(|v| v["const"].as_str())
2049 .collect();
2050 assert!(consts.contains(&"keyword"), "{value}");
2051 assert!(consts.contains(&"semantic"), "{value}");
2052 }
2053}