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)]
1057#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1058#[cfg_attr(feature = "schemars", schemars(inline))]
1059#[serde(rename_all = "snake_case")]
1060pub enum SearchMode {
1061 Keyword,
1063 Semantic,
1068}
1069
1070#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1080#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1081#[cfg_attr(feature = "schemars", schemars(inline))]
1082#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1083#[cfg_attr(
1084 feature = "sqlx",
1085 sqlx(type_name = "friendship_status", rename_all = "snake_case")
1086)]
1087#[serde(rename_all = "snake_case")]
1088pub enum FriendshipStatus {
1089 Pending,
1090 Accepted,
1091 Declined,
1092}
1093
1094#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1098#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1099#[cfg_attr(feature = "schemars", schemars(inline))]
1100#[serde(rename_all = "snake_case")]
1101pub enum FriendshipAction {
1102 Request,
1104 Accept,
1106 Decline,
1108 Unfriend,
1110}
1111
1112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1119#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1120#[cfg_attr(feature = "schemars", schemars(inline))]
1121#[cfg_attr(
1122 feature = "sqlx",
1123 derive(sqlx::Type),
1124 sqlx(type_name = "message_encryption", rename_all = "snake_case")
1125)]
1126#[serde(rename_all = "snake_case")]
1127pub enum MessageEncryption {
1128 E2ee,
1130 Server,
1132}
1133
1134#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1136#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1137#[cfg_attr(feature = "schemars", schemars(inline))]
1138#[serde(rename_all = "snake_case")]
1139pub enum BlockAction {
1140 Block,
1141 Unblock,
1142}
1143
1144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1152#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1153#[cfg_attr(feature = "schemars", schemars(inline))]
1154#[serde(rename_all = "snake_case")]
1155pub enum ContentKind {
1156 Post,
1157 Comment,
1158}
1159
1160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1162#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1163#[cfg_attr(feature = "schemars", schemars(inline))]
1164#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1165#[cfg_attr(
1166 feature = "sqlx",
1167 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1168)]
1169#[serde(rename_all = "snake_case")]
1170pub enum PollMode {
1171 Ranked,
1175 Approval,
1178 Single,
1180 #[serde(other)]
1183 #[cfg_attr(feature = "schemars", schemars(skip))]
1184 Unknown,
1185}
1186
1187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1190#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1191#[cfg_attr(feature = "schemars", schemars(inline))]
1192#[serde(rename_all = "snake_case")]
1193pub enum PollStatus {
1194 Scheduled,
1196 Open,
1199 Closed,
1201 #[serde(other)]
1203 #[cfg_attr(feature = "schemars", schemars(skip))]
1204 Unknown,
1205}
1206
1207impl PollStatus {
1208 pub fn at(
1210 opens_at: chrono::DateTime<chrono::Utc>,
1211 closes_at: chrono::DateTime<chrono::Utc>,
1212 now: chrono::DateTime<chrono::Utc>,
1213 ) -> Self {
1214 if now < opens_at {
1215 Self::Scheduled
1216 } else if now < closes_at {
1217 Self::Open
1218 } else {
1219 Self::Closed
1220 }
1221 }
1222}
1223
1224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1228#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1229#[cfg_attr(feature = "schemars", schemars(inline))]
1230#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1231#[cfg_attr(
1232 feature = "sqlx",
1233 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1234)]
1235#[serde(rename_all = "snake_case")]
1236pub enum DeletedBy {
1237 Author,
1241 Operator,
1244 #[serde(other)]
1249 #[cfg_attr(feature = "schemars", schemars(skip))]
1250 Unknown,
1251}
1252
1253#[derive(
1255 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1256)]
1257#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1258#[cfg_attr(feature = "schemars", schemars(inline))]
1259#[serde(rename_all = "snake_case")]
1260pub enum TrashMode {
1261 #[default]
1263 List,
1264 Restore,
1266 DeletePermanently,
1268}
1269
1270#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1272#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1273#[cfg_attr(feature = "schemars", schemars(inline))]
1274#[serde(rename_all = "snake_case")]
1275pub enum RemovedBy {
1276 Author,
1278 Operator,
1280 Moderation,
1282 #[serde(other)]
1285 #[cfg_attr(feature = "schemars", schemars(skip))]
1286 Unknown,
1287}
1288
1289impl RemovedBy {
1290 pub const fn placeholder(self) -> &'static str {
1293 match self {
1294 Self::Author => "[deleted by its author]",
1295 Self::Operator => {
1296 "[removed by the platform's operators in a cleanup]"
1297 }
1298 Self::Moderation => "[removed by moderation]",
1299 Self::Unknown => "[removed]",
1300 }
1301 }
1302}
1303
1304impl From<DeletedBy> for RemovedBy {
1305 fn from(by: DeletedBy) -> Self {
1306 match by {
1307 DeletedBy::Author => Self::Author,
1308 DeletedBy::Operator => Self::Operator,
1309 DeletedBy::Unknown => Self::Unknown,
1310 }
1311 }
1312}
1313
1314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1329#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1330#[cfg_attr(
1331 feature = "sqlx",
1332 sqlx(type_name = "operator_auth_method_enum", rename_all = "snake_case")
1333)]
1334#[serde(rename_all = "snake_case")]
1335pub enum OperatorAuthMethod {
1336 Password,
1338 PasswordReset,
1340 PasswordAndPasskey,
1343 Passkey,
1345}
1346
1347impl fmt::Display for OperatorAuthMethod {
1348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1349 f.write_str(match self {
1350 Self::Password => "password",
1351 Self::PasswordReset => "password reset link",
1352 Self::PasswordAndPasskey => "password and passkey",
1353 Self::Passkey => "passkey",
1354 })
1355 }
1356}
1357
1358#[derive(
1360 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1361)]
1362#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1363#[cfg_attr(
1364 feature = "sqlx",
1365 sqlx(type_name = "operator_login_mode_enum", rename_all = "snake_case")
1366)]
1367#[serde(rename_all = "snake_case")]
1368pub enum OperatorLoginMode {
1369 #[default]
1371 Password,
1372 PasswordAndPasskey,
1374 Passkey,
1377}
1378
1379impl OperatorLoginMode {
1380 pub fn needs_passkey(self) -> bool {
1382 !matches!(self, Self::Password)
1383 }
1384
1385 pub fn as_str(self) -> &'static str {
1387 match self {
1388 Self::Password => "password",
1389 Self::PasswordAndPasskey => "password_and_passkey",
1390 Self::Passkey => "passkey",
1391 }
1392 }
1393}
1394
1395impl fmt::Display for OperatorLoginMode {
1396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1397 f.write_str(match self {
1398 Self::Password => "password only",
1399 Self::PasswordAndPasskey => "password, then a passkey",
1400 Self::Passkey => "passkey only",
1401 })
1402 }
1403}
1404
1405#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1409#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1410#[cfg_attr(
1411 feature = "sqlx",
1412 sqlx(type_name = "remedy_outcome_enum", rename_all = "snake_case")
1413)]
1414#[serde(rename_all = "snake_case")]
1415pub enum RemedyOutcome {
1416 Restored,
1418 SuspensionLifted,
1420 RecordOnly,
1422 Unrestorable,
1424 HumanRequired,
1426}
1427
1428#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1431#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1432#[cfg_attr(
1433 feature = "sqlx",
1434 sqlx(type_name = "agenda_thread_role_enum", rename_all = "snake_case")
1435)]
1436#[serde(rename_all = "snake_case")]
1437pub enum ThreadRole {
1438 Primary,
1440 Related,
1442}
1443
1444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1447#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1448#[cfg_attr(
1449 feature = "sqlx",
1450 sqlx(type_name = "agenda_thread_added_by_enum", rename_all = "snake_case")
1451)]
1452#[serde(rename_all = "snake_case")]
1453pub enum ThreadAddedBy {
1454 Steward,
1456}
1457
1458impl fmt::Display for ThreadAddedBy {
1459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1460 f.write_str(match self {
1461 Self::Steward => "the Steward",
1462 })
1463 }
1464}
1465
1466#[derive(
1469 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1470)]
1471#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1472#[cfg_attr(
1473 feature = "sqlx",
1474 sqlx(type_name = "trust_tier_enum", rename_all = "snake_case")
1475)]
1476#[serde(rename_all = "snake_case")]
1477pub enum TrustTier {
1478 #[default]
1479 Standard,
1480 Trusted,
1481}
1482
1483impl_display_fromstr!(TargetType);
1488impl_display_fromstr!(PollMode);
1489impl_display_fromstr!(PollStatus);
1490impl_display_fromstr!(ClientPlatform);
1491impl_display_fromstr!(ModerationTargetType);
1492impl_display_fromstr!(ModerationActionType);
1493impl_display_fromstr!(ModerationTier);
1494impl_display_fromstr!(AppealStatus);
1495impl_display_fromstr!(AppealOutcome);
1496impl_display_fromstr!(ModelRole);
1497impl_display_fromstr!(ArchiveContextKind);
1498impl_display_fromstr!(ProposalCategory);
1499impl_display_fromstr!(DesignationKind);
1500impl_display_fromstr!(GovernanceLogEntryType);
1501impl_display_fromstr!(AmendmentKind);
1502impl_display_fromstr!(ObligationKind);
1503impl_display_fromstr!(OwedBy);
1504impl_display_fromstr!(ObligationStatus);
1505impl_display_fromstr!(CalendarEventKind);
1506impl_display_fromstr!(CalendarEventStatus);
1507impl_display_fromstr!(Standing);
1508impl_display_fromstr!(CitationRelation);
1509impl_display_fromstr!(KeyStatus);
1510impl_display_fromstr!(MeetingStatus);
1511impl_display_fromstr!(AgendaItemStatus);
1512impl_display_fromstr!(AgendaSourceType);
1513impl_display_fromstr!(RoundType);
1514impl_display_fromstr!(DecisionOutcome);
1515impl_display_fromstr!(BatchType);
1516impl_display_fromstr!(BatchStatus);
1517impl_display_fromstr!(OAuthScope);
1518impl_display_fromstr!(FeedSort);
1519impl_display_fromstr!(DashboardCommunities);
1520impl_display_fromstr!(ProposalSort);
1521impl_display_fromstr!(GovernanceLogView);
1522impl_display_fromstr!(ObligationSort);
1523impl_display_fromstr!(DetailLevel);
1524impl_display_fromstr!(RecordVersion);
1525impl_display_fromstr!(SearchMode);
1526impl_display_fromstr!(FriendshipStatus);
1527impl_display_fromstr!(FriendshipAction);
1528impl_display_fromstr!(BlockAction);
1529impl_display_fromstr!(MessageEncryption);
1530impl_display_fromstr!(ContentKind);
1531impl_display_fromstr!(DeletedBy);
1532impl_display_fromstr!(TrashMode);
1533impl_display_fromstr!(RemovedBy);
1534impl_display_fromstr!(RemedyOutcome);
1535impl_display_fromstr!(ThreadRole);
1536impl_display_fromstr!(TrustTier);
1537
1538macro_rules! closed_variants {
1553 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1554 impl $name {
1555 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1557 [$(Self::$v),+];
1558 }
1559 const _: () = {
1560 #[allow(dead_code)]
1561 fn every_variant_is_listed(v: $name) {
1562 match v {
1563 $($name::$v)|+ $($(| $name::$x)+)? => {}
1564 }
1565 }
1566 };
1567 };
1568}
1569
1570closed_variants!(TargetType [Post, Comment, Message]);
1571closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1572closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1573closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1574closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1575closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1576closed_variants!(ModelRole [
1579 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1580 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor,
1581 Witness
1582]);
1583closed_variants!(ArchiveContextKind [AgendaItem, Meeting, Appeal, Flag, InterviewRun]);
1584closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1585closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1586closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1587closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation, DataSet, Test] never_written [Unknown]);
1588closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1589closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1590closed_variants!(KeyStatus [Active, Retired, Compromised]);
1591closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1592closed_variants!(OwedBy [Steward, Staff, Council]);
1593closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1594closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1595closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1596closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1597closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1598closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1599closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1600closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1601closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1602closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1603closed_variants!(OAuthScope [Read, Write]);
1604closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1605closed_variants!(MessageEncryption [E2ee, Server]);
1606closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1607closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1608closed_variants!(OperatorAuthMethod [Password, PasswordReset, PasswordAndPasskey, Passkey]);
1609closed_variants!(OperatorLoginMode [Password, PasswordAndPasskey, Passkey]);
1610closed_variants!(RemedyOutcome [Restored, SuspensionLifted, RecordOnly, Unrestorable, HumanRequired]);
1611closed_variants!(ThreadRole [Primary, Related]);
1612closed_variants!(ThreadAddedBy[Steward]);
1613closed_variants!(TrustTier [Standard, Trusted]);
1614
1615#[cfg(test)]
1616mod tests {
1617 use super::*;
1618
1619 #[test]
1620 fn target_type_serde_round_trip() {
1621 let val = TargetType::Post;
1622 let json = serde_json::to_string(&val).unwrap();
1623 assert_eq!(json, "\"post\"");
1624 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1625 assert_eq!(val, deserialized);
1626 }
1627
1628 #[test]
1629 fn target_type_display() {
1630 assert_eq!(TargetType::Post.to_string(), "post");
1631 assert_eq!(TargetType::Comment.to_string(), "comment");
1632 }
1633
1634 #[test]
1635 fn target_type_from_str() {
1636 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1637 assert_eq!(
1638 TargetType::from_str("comment").unwrap(),
1639 TargetType::Comment
1640 );
1641 }
1642
1643 #[test]
1644 fn moderation_tier_serde() {
1645 let tier = ModerationTier::Tier2;
1646 let json = serde_json::to_string(&tier).unwrap();
1647 assert_eq!(json, "\"2\"");
1648 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1649 assert_eq!(tier, deserialized);
1650 }
1651
1652 #[test]
1655 fn message_encryption_wire_values() {
1656 assert_eq!(
1657 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1658 "\"e2ee\""
1659 );
1660 assert_eq!(
1661 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1662 "\"server\""
1663 );
1664 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1665 assert_eq!(
1666 MessageEncryption::from_str("server").unwrap(),
1667 MessageEncryption::Server
1668 );
1669 }
1670
1671 #[test]
1672 fn search_mode_wire_values() {
1673 assert_eq!(
1674 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1675 "\"keyword\""
1676 );
1677 assert_eq!(
1678 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1679 "\"semantic\""
1680 );
1681 assert_eq!(
1682 SearchMode::from_str("semantic").unwrap(),
1683 SearchMode::Semantic
1684 );
1685 }
1686
1687 #[test]
1690 fn no_sort_orders_by_votes() {
1691 for gone in ["score", "controversial", "unpopular"] {
1692 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1693 assert!(
1694 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1695 .is_err(),
1696 "{gone}"
1697 );
1698 }
1699 assert!(ProposalSort::from_str("score").is_err());
1700 }
1701
1702 #[cfg(feature = "schemars")]
1703 #[test]
1704 fn feed_sort_schema_is_ref_free() {
1705 use schemars::JsonSchema;
1706
1707 assert!(<FeedSort as JsonSchema>::inline_schema());
1708
1709 let schema = schemars::schema_for!(FeedSort);
1710 let value = serde_json::to_value(&schema).unwrap();
1711 let blob = value.to_string();
1712 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1713 assert!(!blob.contains("$ref"), "no $ref: {value}");
1714 let values: Vec<&str> = value["enum"]
1715 .as_array()
1716 .expect("FeedSort should render as a flat `enum`")
1717 .iter()
1718 .filter_map(|v| v.as_str())
1719 .collect();
1720 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1721 }
1722
1723 #[cfg(feature = "schemars")]
1726 #[test]
1727 fn trash_enum_schemas_are_plain_enums() {
1728 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1729 assert!(T::inline_schema());
1730 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1731 let blob = value.to_string();
1732 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1733 assert!(value.get("oneOf").is_none(), "{value}");
1734 value["enum"]
1735 .as_array()
1736 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1737 .iter()
1738 .map(|v| v.as_str().unwrap().to_owned())
1739 .collect()
1740 }
1741 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1742 assert_eq!(
1743 values::<TrashMode>(),
1744 ["list", "restore", "delete_permanently"]
1745 );
1746 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1747 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1748 }
1749
1750 #[cfg(feature = "schemars")]
1753 #[test]
1754 fn obligation_enum_schemas_are_strict_safe() {
1755 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1756 assert!(T::inline_schema());
1757 let value = crate::responses::inline_schema_for::<T>();
1758 assert_eq!(
1759 crate::tools::strict_schema_violations(&value),
1760 Vec::<String>::new()
1761 );
1762 value["enum"]
1763 .as_array()
1764 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1765 .iter()
1766 .map(|v| v.as_str().unwrap().to_owned())
1767 .collect()
1768 }
1769 assert_eq!(
1770 values::<ObligationKind>(),
1771 [
1772 "carry_forward",
1773 "ratification",
1774 "review",
1775 "referral",
1776 "reveal",
1777 "task"
1778 ]
1779 );
1780 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1781 assert_eq!(
1782 values::<ObligationStatus>(),
1783 ["open", "lapsed", "discharged", "withdrawn"]
1784 );
1785 assert_eq!(
1786 values::<CalendarEventKind>(),
1787 ["sitting", "special_sitting"]
1788 );
1789 }
1790
1791 #[test]
1792 fn trash_enum_wire_values() {
1793 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1794 assert_eq!(
1795 "delete_permanently".parse::<TrashMode>().unwrap(),
1796 TrashMode::DeletePermanently
1797 );
1798 assert_eq!(TrashMode::default(), TrashMode::List);
1799 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1800 assert_eq!(ContentKind::Comment.to_string(), "comment");
1801 }
1802
1803 #[test]
1806 fn unknown_trash_causes_parse() {
1807 assert_eq!(
1808 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1809 RemovedBy::Unknown
1810 );
1811 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1812 assert_eq!(
1813 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1814 DeletedBy::Unknown
1815 );
1816 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1817 }
1818
1819 #[test]
1820 fn proposal_category_round_trip() {
1821 for cat in [
1822 ProposalCategory::Routine,
1823 ProposalCategory::Policy,
1824 ProposalCategory::Constitutional,
1825 ProposalCategory::Emergency,
1826 ] {
1827 let json = serde_json::to_string(&cat).unwrap();
1828 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1829 assert_eq!(cat, back);
1830 }
1831 }
1832
1833 #[test]
1836 fn governance_amendment_and_key_wire_values() {
1837 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1838 assert_eq!(
1839 GovernanceLogEntryType::KeyRotation.to_string(),
1840 "key_rotation"
1841 );
1842 assert_eq!(
1843 AmendmentKind::NonPrecedential.to_string(),
1844 "non_precedential"
1845 );
1846 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1847 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1848 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1849 assert_eq!(RecordVersion::Original.to_string(), "original");
1850 assert_eq!(
1851 "latest".parse::<RecordVersion>().unwrap(),
1852 RecordVersion::Latest
1853 );
1854 assert_eq!(
1855 "superseded".parse::<AmendmentKind>().unwrap(),
1856 AmendmentKind::Superseded
1857 );
1858 assert_eq!(Standing::default(), Standing::InForce);
1859 assert_eq!(Standing::InForce.to_string(), "in_force");
1860 assert_eq!(
1861 "compromised".parse::<KeyStatus>().unwrap(),
1862 KeyStatus::Compromised
1863 );
1864 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1865 }
1866
1867 #[test]
1870 fn obligation_and_calendar_wire_values() {
1871 assert_eq!(
1872 GovernanceLogEntryType::Obligation.to_string(),
1873 "obligation"
1874 );
1875 assert_eq!(GovernanceLogEntryType::DataSet.to_string(), "data_set");
1876 assert_eq!(
1877 "data_set".parse::<GovernanceLogEntryType>().unwrap(),
1878 GovernanceLogEntryType::DataSet
1879 );
1880 assert_eq!(GovernanceLogEntryType::Test.to_string(), "test");
1881 assert_eq!(
1882 "test".parse::<GovernanceLogEntryType>().unwrap(),
1883 GovernanceLogEntryType::Test
1884 );
1885 assert_eq!(
1887 serde_json::from_value::<GovernanceLogEntryType>(
1888 serde_json::json!("something_newer")
1889 )
1890 .unwrap(),
1891 GovernanceLogEntryType::Unknown
1892 );
1893 assert!(
1894 !GovernanceLogEntryType::ALL
1895 .contains(&GovernanceLogEntryType::Unknown)
1896 );
1897 let kinds: Vec<String> =
1898 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1899 assert_eq!(
1900 kinds,
1901 [
1902 "planned",
1903 "discharged",
1904 "lapsed",
1905 "rescheduled",
1906 "withdrawn"
1907 ]
1908 );
1909 assert!(AmendmentKind::Lapsed.is_obligation());
1910 assert!(!AmendmentKind::Revision.is_obligation());
1911 for (kind, wire) in [
1912 (ObligationKind::CarryForward, "carry_forward"),
1913 (ObligationKind::Ratification, "ratification"),
1914 (ObligationKind::Review, "review"),
1915 (ObligationKind::Referral, "referral"),
1916 (ObligationKind::Reveal, "reveal"),
1917 (ObligationKind::Task, "task"),
1918 ] {
1919 assert_eq!(kind.to_string(), wire);
1920 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1921 }
1922 for (who, wire) in [
1923 (OwedBy::Steward, "steward"),
1924 (OwedBy::Staff, "staff"),
1925 (OwedBy::Council, "council"),
1926 ] {
1927 assert_eq!(who.to_string(), wire);
1928 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1929 }
1930 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1931 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1932 assert!(ObligationStatus::Withdrawn.is_closed());
1933 assert!(!ObligationStatus::Lapsed.is_closed());
1934 assert_eq!(
1935 CalendarEventKind::SpecialSitting.to_string(),
1936 "special_sitting"
1937 );
1938 assert!("festival".parse::<CalendarEventKind>().is_err());
1941 assert_eq!(
1942 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1943 CalendarEventStatus::Cancelled
1944 );
1945 }
1946
1947 #[test]
1949 fn detail_level_wire_names() {
1950 for (level, wire) in [
1951 (DetailLevel::Summary, "summary"),
1952 (DetailLevel::Full, "full"),
1953 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1954 ] {
1955 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1956 assert_eq!(level.to_string(), wire);
1957 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1958 }
1959 }
1960
1961 #[cfg(feature = "schemars")]
1966 #[test]
1967 fn enum_json_schema_is_inlined() {
1968 use schemars::JsonSchema;
1969
1970 assert!(<TargetType as JsonSchema>::inline_schema());
1971 assert!(<FeedSort as JsonSchema>::inline_schema());
1972 assert!(<DashboardCommunities as JsonSchema>::inline_schema());
1973 assert!(<ProposalSort as JsonSchema>::inline_schema());
1974 assert!(<DetailLevel as JsonSchema>::inline_schema());
1975 assert!(<RecordVersion as JsonSchema>::inline_schema());
1976 assert!(<SearchMode as JsonSchema>::inline_schema());
1977 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1978 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1979 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1980 assert!(<Standing as JsonSchema>::inline_schema());
1981 assert!(<CitationRelation as JsonSchema>::inline_schema());
1982 assert!(<KeyStatus as JsonSchema>::inline_schema());
1983 assert!(<OAuthScope as JsonSchema>::inline_schema());
1984 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1985 assert!(<ModerationTier as JsonSchema>::inline_schema());
1986 assert!(<ObligationKind as JsonSchema>::inline_schema());
1987 assert!(<OwedBy as JsonSchema>::inline_schema());
1988 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1989 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1990 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1991
1992 #[derive(schemars::JsonSchema)]
1993 #[allow(dead_code)]
1994 struct Container {
1995 target_type: TargetType,
1996 sort: Option<FeedSort>,
1997 proposal_sort: Option<ProposalSort>,
1998 category: Option<ProposalCategory>,
1999 detail: Option<DetailLevel>,
2000 version: Option<RecordVersion>,
2001 search_mode: Option<SearchMode>,
2002 }
2003
2004 let schema = schemars::schema_for!(Container);
2005 let value = serde_json::to_value(&schema).unwrap();
2006 let blob = value.to_string();
2007
2008 assert!(
2009 value.get("$defs").is_none(),
2010 "no $defs should be emitted for enum-only container; got schema: {value}"
2011 );
2012 assert!(
2013 !blob.contains("$ref"),
2014 "enum container schema must contain no $ref anywhere; got: {value}"
2015 );
2016
2017 let target_type_enum = value["properties"]["target_type"]["enum"]
2019 .as_array()
2020 .expect("target_type should have inline `enum` array");
2021 assert!(
2022 target_type_enum
2023 .contains(&serde_json::Value::String("post".into()))
2024 );
2025 assert!(
2026 target_type_enum
2027 .contains(&serde_json::Value::String("comment".into()))
2028 );
2029 }
2030
2031 #[cfg(feature = "schemars")]
2037 #[test]
2038 fn search_mode_schema_is_ref_free() {
2039 use schemars::JsonSchema;
2040
2041 assert!(<SearchMode as JsonSchema>::inline_schema());
2042
2043 let schema = schemars::schema_for!(SearchMode);
2044 let value = serde_json::to_value(&schema).unwrap();
2045 let blob = value.to_string();
2046 assert!(value.get("$defs").is_none(), "no $defs: {value}");
2047 assert!(!blob.contains("$ref"), "no $ref: {value}");
2048
2049 let variants = value["oneOf"]
2055 .as_array()
2056 .expect("SearchMode should have an inline `oneOf` array");
2057 let consts: Vec<&str> = variants
2058 .iter()
2059 .filter_map(|v| v["const"].as_str())
2060 .collect();
2061 assert!(consts.contains(&"keyword"), "{value}");
2062 assert!(consts.contains(&"semantic"), "{value}");
2063 }
2064}