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)]
428#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
429#[cfg_attr(feature = "schemars", schemars(inline))]
430#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
431#[cfg_attr(
432 feature = "sqlx",
433 sqlx(
434 type_name = "governance_log_entry_type_enum",
435 rename_all = "snake_case"
436 )
437)]
438#[serde(rename_all = "snake_case")]
439pub enum GovernanceLogEntryType {
440 CouncilDecision,
441 AppealsCourtDecision,
442 EmergencyAction,
443 PolicyChange,
444 StewardVeto,
445 Amendment,
449 KeyRotation,
452 StewardRecord,
457 Obligation,
462 DataSet,
467 Test,
473 #[serde(other)]
477 #[cfg_attr(feature = "schemars", schemars(skip))]
478 Unknown,
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
488#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
489#[cfg_attr(feature = "schemars", schemars(inline))]
490#[serde(rename_all = "snake_case")]
491pub enum AmendmentKind {
492 NonPrecedential,
494 Overruled,
496 Superseded,
498 Reinstated,
500 Correction,
502 Redaction,
504 Reattested,
507 Revision,
510 Planned,
514 Discharged,
516 Lapsed,
519 Rescheduled,
521 Withdrawn,
523}
524
525impl AmendmentKind {
526 pub const OBLIGATION: [Self; 5] = [
529 Self::Planned,
530 Self::Discharged,
531 Self::Lapsed,
532 Self::Rescheduled,
533 Self::Withdrawn,
534 ];
535
536 pub fn is_obligation(self) -> bool {
538 Self::OBLIGATION.contains(&self)
539 }
540}
541
542#[derive(
547 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
548)]
549#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
550#[cfg_attr(feature = "schemars", schemars(inline))]
551#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
552#[cfg_attr(
553 feature = "sqlx",
554 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
555)]
556#[serde(rename_all = "snake_case")]
557pub enum Standing {
558 #[default]
559 InForce,
560 NonPrecedential,
561 Overruled,
562 Superseded,
563}
564
565#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
570#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
571#[cfg_attr(feature = "schemars", schemars(inline))]
572#[serde(rename_all = "snake_case")]
573pub enum CitationRelation {
574 Concerns,
576 Authority,
578 Overrules,
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
585#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
586#[cfg_attr(feature = "schemars", schemars(inline))]
587#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
588#[cfg_attr(
589 feature = "sqlx",
590 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
591)]
592#[serde(rename_all = "snake_case")]
593pub enum KeyStatus {
594 Active,
596 Retired,
598 Compromised,
601}
602
603#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
610#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
611#[cfg_attr(feature = "schemars", schemars(inline))]
612#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
613#[cfg_attr(
614 feature = "sqlx",
615 sqlx(type_name = "obligation_kind_enum", rename_all = "snake_case")
616)]
617#[serde(rename_all = "snake_case")]
618pub enum ObligationKind {
619 CarryForward,
621 Ratification,
623 Review,
625 Referral,
627 Reveal,
629 Task,
631}
632
633#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
635#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
636#[cfg_attr(feature = "schemars", schemars(inline))]
637#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
638#[cfg_attr(
639 feature = "sqlx",
640 sqlx(type_name = "obligation_owed_by_enum", rename_all = "snake_case")
641)]
642#[serde(rename_all = "snake_case")]
643pub enum OwedBy {
644 Steward,
645 Staff,
647 Council,
648}
649
650#[derive(
653 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize,
654)]
655#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
656#[cfg_attr(feature = "schemars", schemars(inline))]
657#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
658#[cfg_attr(
659 feature = "sqlx",
660 sqlx(type_name = "obligation_status_enum", rename_all = "snake_case")
661)]
662#[serde(rename_all = "snake_case")]
663pub enum ObligationStatus {
664 #[default]
666 Open,
667 Lapsed,
669 Discharged,
671 Withdrawn,
673}
674
675impl ObligationStatus {
676 pub fn is_closed(self) -> bool {
678 matches!(self, Self::Discharged | Self::Withdrawn)
679 }
680}
681
682#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
684#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
685#[cfg_attr(feature = "schemars", schemars(inline))]
686#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
687#[cfg_attr(
688 feature = "sqlx",
689 sqlx(type_name = "calendar_event_kind_enum", rename_all = "snake_case")
690)]
691#[serde(rename_all = "snake_case")]
692pub enum CalendarEventKind {
693 Sitting,
695 SpecialSitting,
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
702#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
703#[cfg_attr(feature = "schemars", schemars(inline))]
704#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
705#[cfg_attr(
706 feature = "sqlx",
707 sqlx(type_name = "calendar_event_status_enum", rename_all = "snake_case")
708)]
709#[serde(rename_all = "snake_case")]
710pub enum CalendarEventStatus {
711 Scheduled,
712 Held,
713 Cancelled,
714}
715
716#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
722#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
723#[cfg_attr(feature = "schemars", schemars(inline))]
724#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
725#[cfg_attr(
726 feature = "sqlx",
727 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
728)]
729#[serde(rename_all = "snake_case")]
730pub enum MeetingStatus {
731 Active,
732 Adjourned,
733 Cancelled,
734}
735
736#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
738#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
739#[cfg_attr(feature = "schemars", schemars(inline))]
740#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
741#[cfg_attr(
742 feature = "sqlx",
743 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
744)]
745#[serde(rename_all = "snake_case")]
746pub enum AgendaItemStatus {
747 Pending,
748 Deliberating,
749 Decided,
750 Deferred,
751 CarriedOver,
752}
753
754#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
756#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
757#[cfg_attr(feature = "schemars", schemars(inline))]
758#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
759#[cfg_attr(
760 feature = "sqlx",
761 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
762)]
763#[serde(rename_all = "snake_case")]
764pub enum AgendaSourceType {
765 Proposal,
766 AppealReferral,
767 StewardSubmission,
768 Internal,
769}
770
771#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
773#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
774#[cfg_attr(feature = "schemars", schemars(inline))]
775#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
776#[cfg_attr(
777 feature = "sqlx",
778 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
779)]
780#[serde(rename_all = "snake_case")]
781pub enum RoundType {
782 Independent,
783 Deliberation,
784 FinalVote,
785}
786
787#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
789#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
790#[cfg_attr(feature = "schemars", schemars(inline))]
791#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
792#[cfg_attr(
793 feature = "sqlx",
794 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
795)]
796#[serde(rename_all = "snake_case")]
797pub enum DecisionOutcome {
798 Approved,
799 Rejected,
800 Deferred,
801 Amended,
802}
803
804impl RoundType {
805 pub const fn name(self) -> &'static str {
809 match self {
810 Self::Independent => "Independent Analysis",
811 Self::Deliberation => "Deliberation",
812 Self::FinalVote => "Final Vote",
813 }
814 }
815}
816
817impl DecisionOutcome {
818 pub const fn name(self) -> &'static str {
821 match self {
822 Self::Approved => "Approved",
823 Self::Rejected => "Rejected",
824 Self::Deferred => "Deferred",
825 Self::Amended => "Amended",
826 }
827 }
828}
829
830#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
836#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
837#[cfg_attr(feature = "schemars", schemars(inline))]
838#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
839#[cfg_attr(
840 feature = "sqlx",
841 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
842)]
843#[serde(rename_all = "snake_case")]
844pub enum BatchType {
845 Jury,
846 Judge,
847 Tier2,
848 Redaction,
850 Chambers,
856 Precedent,
860}
861
862#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
864#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
865#[cfg_attr(feature = "schemars", schemars(inline))]
866#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
867#[cfg_attr(
868 feature = "sqlx",
869 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
870)]
871#[serde(rename_all = "snake_case")]
872pub enum BatchStatus {
873 Submitted,
874 Polling,
875 Completed,
876 Failed,
877}
878
879#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
885#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
886#[cfg_attr(feature = "schemars", schemars(inline))]
887#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
888#[cfg_attr(
889 feature = "sqlx",
890 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
891)]
892#[serde(rename_all = "snake_case")]
893pub enum OAuthScope {
894 Read,
895 Write,
896}
897
898#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
911#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
912#[cfg_attr(feature = "schemars", schemars(inline))]
913#[serde(rename_all = "snake_case")]
914pub enum FeedSort {
915 Date,
916 Active,
917 Random,
918 Diverse,
919}
920
921#[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)]
1009#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1010#[cfg_attr(feature = "schemars", schemars(inline))]
1011#[serde(rename_all = "snake_case")]
1012pub enum DetailLevel {
1013 Summary,
1015 Full,
1021 FullWithAttachments,
1025}
1026
1027#[derive(
1032 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1033)]
1034#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1035#[cfg_attr(feature = "schemars", schemars(inline))]
1036#[serde(rename_all = "snake_case")]
1037pub enum RecordVersion {
1038 #[default]
1040 Latest,
1041 Original,
1043}
1044
1045#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1050#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1051#[cfg_attr(feature = "schemars", schemars(inline))]
1052#[serde(rename_all = "snake_case")]
1053pub enum SearchMode {
1054 Keyword,
1056 Semantic,
1058}
1059
1060#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1070#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1071#[cfg_attr(feature = "schemars", schemars(inline))]
1072#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1073#[cfg_attr(
1074 feature = "sqlx",
1075 sqlx(type_name = "friendship_status", rename_all = "snake_case")
1076)]
1077#[serde(rename_all = "snake_case")]
1078pub enum FriendshipStatus {
1079 Pending,
1080 Accepted,
1081 Declined,
1082}
1083
1084#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1086#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1087#[cfg_attr(feature = "schemars", schemars(inline))]
1088#[serde(rename_all = "snake_case")]
1089pub enum FriendshipAction {
1090 Request,
1092 Accept,
1094 Decline,
1096 Unfriend,
1098}
1099
1100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1107#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1108#[cfg_attr(feature = "schemars", schemars(inline))]
1109#[cfg_attr(
1110 feature = "sqlx",
1111 derive(sqlx::Type),
1112 sqlx(type_name = "message_encryption", rename_all = "snake_case")
1113)]
1114#[serde(rename_all = "snake_case")]
1115pub enum MessageEncryption {
1116 E2ee,
1118 Server,
1120}
1121
1122#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1123#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1124#[cfg_attr(feature = "schemars", schemars(inline))]
1125#[serde(rename_all = "snake_case")]
1126pub enum BlockAction {
1127 Block,
1128 Unblock,
1129}
1130
1131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1139#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1140#[cfg_attr(feature = "schemars", schemars(inline))]
1141#[serde(rename_all = "snake_case")]
1142pub enum ContentKind {
1143 Post,
1144 Comment,
1145}
1146
1147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1149#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1150#[cfg_attr(feature = "schemars", schemars(inline))]
1151#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1152#[cfg_attr(
1153 feature = "sqlx",
1154 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1155)]
1156#[serde(rename_all = "snake_case")]
1157pub enum PollMode {
1158 Ranked,
1162 Approval,
1165 Single,
1167 #[serde(other)]
1170 #[cfg_attr(feature = "schemars", schemars(skip))]
1171 Unknown,
1172}
1173
1174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1177#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1178#[cfg_attr(feature = "schemars", schemars(inline))]
1179#[serde(rename_all = "snake_case")]
1180pub enum PollStatus {
1181 Scheduled,
1183 Open,
1186 Closed,
1188 #[serde(other)]
1190 #[cfg_attr(feature = "schemars", schemars(skip))]
1191 Unknown,
1192}
1193
1194impl PollStatus {
1195 pub fn at(
1197 opens_at: chrono::DateTime<chrono::Utc>,
1198 closes_at: chrono::DateTime<chrono::Utc>,
1199 now: chrono::DateTime<chrono::Utc>,
1200 ) -> Self {
1201 if now < opens_at {
1202 Self::Scheduled
1203 } else if now < closes_at {
1204 Self::Open
1205 } else {
1206 Self::Closed
1207 }
1208 }
1209}
1210
1211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1215#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1216#[cfg_attr(feature = "schemars", schemars(inline))]
1217#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1218#[cfg_attr(
1219 feature = "sqlx",
1220 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1221)]
1222#[serde(rename_all = "snake_case")]
1223pub enum DeletedBy {
1224 Author,
1228 Operator,
1231 #[serde(other)]
1236 #[cfg_attr(feature = "schemars", schemars(skip))]
1237 Unknown,
1238}
1239
1240#[derive(
1242 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1243)]
1244#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1245#[cfg_attr(feature = "schemars", schemars(inline))]
1246#[serde(rename_all = "snake_case")]
1247pub enum TrashMode {
1248 #[default]
1250 List,
1251 Restore,
1253 DeletePermanently,
1255}
1256
1257#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1259#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1260#[cfg_attr(feature = "schemars", schemars(inline))]
1261#[serde(rename_all = "snake_case")]
1262pub enum RemovedBy {
1263 Author,
1265 Operator,
1267 Moderation,
1269 #[serde(other)]
1272 #[cfg_attr(feature = "schemars", schemars(skip))]
1273 Unknown,
1274}
1275
1276impl RemovedBy {
1277 pub const fn placeholder(self) -> &'static str {
1280 match self {
1281 Self::Author => "[deleted by its author]",
1282 Self::Operator => {
1283 "[removed by the platform's operators in a cleanup]"
1284 }
1285 Self::Moderation => "[removed by moderation]",
1286 Self::Unknown => "[removed]",
1287 }
1288 }
1289}
1290
1291impl From<DeletedBy> for RemovedBy {
1292 fn from(by: DeletedBy) -> Self {
1293 match by {
1294 DeletedBy::Author => Self::Author,
1295 DeletedBy::Operator => Self::Operator,
1296 DeletedBy::Unknown => Self::Unknown,
1297 }
1298 }
1299}
1300
1301#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1316#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1317#[cfg_attr(
1318 feature = "sqlx",
1319 sqlx(type_name = "operator_auth_method_enum", rename_all = "snake_case")
1320)]
1321#[serde(rename_all = "snake_case")]
1322pub enum OperatorAuthMethod {
1323 Password,
1325 PasswordReset,
1327 PasswordAndPasskey,
1330 Passkey,
1332}
1333
1334impl fmt::Display for OperatorAuthMethod {
1335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1336 f.write_str(match self {
1337 Self::Password => "password",
1338 Self::PasswordReset => "password reset link",
1339 Self::PasswordAndPasskey => "password and passkey",
1340 Self::Passkey => "passkey",
1341 })
1342 }
1343}
1344
1345#[derive(
1347 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1348)]
1349#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1350#[cfg_attr(
1351 feature = "sqlx",
1352 sqlx(type_name = "operator_login_mode_enum", rename_all = "snake_case")
1353)]
1354#[serde(rename_all = "snake_case")]
1355pub enum OperatorLoginMode {
1356 #[default]
1358 Password,
1359 PasswordAndPasskey,
1361 Passkey,
1364}
1365
1366impl OperatorLoginMode {
1367 pub fn needs_passkey(self) -> bool {
1369 !matches!(self, Self::Password)
1370 }
1371
1372 pub fn as_str(self) -> &'static str {
1374 match self {
1375 Self::Password => "password",
1376 Self::PasswordAndPasskey => "password_and_passkey",
1377 Self::Passkey => "passkey",
1378 }
1379 }
1380}
1381
1382impl fmt::Display for OperatorLoginMode {
1383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1384 f.write_str(match self {
1385 Self::Password => "password only",
1386 Self::PasswordAndPasskey => "password, then a passkey",
1387 Self::Passkey => "passkey only",
1388 })
1389 }
1390}
1391
1392#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1396#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1397#[cfg_attr(
1398 feature = "sqlx",
1399 sqlx(type_name = "remedy_outcome_enum", rename_all = "snake_case")
1400)]
1401#[serde(rename_all = "snake_case")]
1402pub enum RemedyOutcome {
1403 Restored,
1405 SuspensionLifted,
1407 RecordOnly,
1409 Unrestorable,
1411 HumanRequired,
1413}
1414
1415#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1418#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1419#[cfg_attr(
1420 feature = "sqlx",
1421 sqlx(type_name = "agenda_thread_role_enum", rename_all = "snake_case")
1422)]
1423#[serde(rename_all = "snake_case")]
1424pub enum ThreadRole {
1425 Primary,
1427 Related,
1429}
1430
1431#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1434#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1435#[cfg_attr(
1436 feature = "sqlx",
1437 sqlx(type_name = "agenda_thread_added_by_enum", rename_all = "snake_case")
1438)]
1439#[serde(rename_all = "snake_case")]
1440pub enum ThreadAddedBy {
1441 Steward,
1443}
1444
1445impl fmt::Display for ThreadAddedBy {
1446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1447 f.write_str(match self {
1448 Self::Steward => "the Steward",
1449 })
1450 }
1451}
1452
1453#[derive(
1456 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1457)]
1458#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1459#[cfg_attr(
1460 feature = "sqlx",
1461 sqlx(type_name = "trust_tier_enum", rename_all = "snake_case")
1462)]
1463#[serde(rename_all = "snake_case")]
1464pub enum TrustTier {
1465 #[default]
1466 Standard,
1467 Trusted,
1468}
1469
1470impl_display_fromstr!(TargetType);
1475impl_display_fromstr!(PollMode);
1476impl_display_fromstr!(PollStatus);
1477impl_display_fromstr!(ClientPlatform);
1478impl_display_fromstr!(ModerationTargetType);
1479impl_display_fromstr!(ModerationActionType);
1480impl_display_fromstr!(ModerationTier);
1481impl_display_fromstr!(AppealStatus);
1482impl_display_fromstr!(AppealOutcome);
1483impl_display_fromstr!(ModelRole);
1484impl_display_fromstr!(ArchiveContextKind);
1485impl_display_fromstr!(ProposalCategory);
1486impl_display_fromstr!(DesignationKind);
1487impl_display_fromstr!(GovernanceLogEntryType);
1488impl_display_fromstr!(AmendmentKind);
1489impl_display_fromstr!(ObligationKind);
1490impl_display_fromstr!(OwedBy);
1491impl_display_fromstr!(ObligationStatus);
1492impl_display_fromstr!(CalendarEventKind);
1493impl_display_fromstr!(CalendarEventStatus);
1494impl_display_fromstr!(Standing);
1495impl_display_fromstr!(CitationRelation);
1496impl_display_fromstr!(KeyStatus);
1497impl_display_fromstr!(MeetingStatus);
1498impl_display_fromstr!(AgendaItemStatus);
1499impl_display_fromstr!(AgendaSourceType);
1500impl_display_fromstr!(RoundType);
1501impl_display_fromstr!(DecisionOutcome);
1502impl_display_fromstr!(BatchType);
1503impl_display_fromstr!(BatchStatus);
1504impl_display_fromstr!(OAuthScope);
1505impl_display_fromstr!(FeedSort);
1506impl_display_fromstr!(DashboardCommunities);
1507impl_display_fromstr!(ProposalSort);
1508impl_display_fromstr!(GovernanceLogView);
1509impl_display_fromstr!(ObligationSort);
1510impl_display_fromstr!(DetailLevel);
1511impl_display_fromstr!(RecordVersion);
1512impl_display_fromstr!(SearchMode);
1513impl_display_fromstr!(FriendshipStatus);
1514impl_display_fromstr!(FriendshipAction);
1515impl_display_fromstr!(BlockAction);
1516impl_display_fromstr!(MessageEncryption);
1517impl_display_fromstr!(ContentKind);
1518impl_display_fromstr!(DeletedBy);
1519impl_display_fromstr!(TrashMode);
1520impl_display_fromstr!(RemovedBy);
1521impl_display_fromstr!(RemedyOutcome);
1522impl_display_fromstr!(ThreadRole);
1523impl_display_fromstr!(TrustTier);
1524
1525macro_rules! closed_variants {
1540 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1541 impl $name {
1542 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1544 [$(Self::$v),+];
1545 }
1546 const _: () = {
1547 #[allow(dead_code)]
1548 fn every_variant_is_listed(v: $name) {
1549 match v {
1550 $($name::$v)|+ $($(| $name::$x)+)? => {}
1551 }
1552 }
1553 };
1554 };
1555}
1556
1557closed_variants!(TargetType [Post, Comment, Message]);
1558closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1559closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1560closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1561closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1562closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1563closed_variants!(ModelRole [
1566 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1567 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor,
1568 Witness
1569]);
1570closed_variants!(ArchiveContextKind [AgendaItem, Meeting, Appeal, Flag, InterviewRun]);
1571closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1572closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1573closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1574closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation, DataSet, Test] never_written [Unknown]);
1575closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1576closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1577closed_variants!(KeyStatus [Active, Retired, Compromised]);
1578closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1579closed_variants!(OwedBy [Steward, Staff, Council]);
1580closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1581closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1582closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1583closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1584closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1585closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1586closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1587closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1588closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1589closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1590closed_variants!(OAuthScope [Read, Write]);
1591closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1592closed_variants!(MessageEncryption [E2ee, Server]);
1593closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1594closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1595closed_variants!(OperatorAuthMethod [Password, PasswordReset, PasswordAndPasskey, Passkey]);
1596closed_variants!(OperatorLoginMode [Password, PasswordAndPasskey, Passkey]);
1597closed_variants!(RemedyOutcome [Restored, SuspensionLifted, RecordOnly, Unrestorable, HumanRequired]);
1598closed_variants!(ThreadRole [Primary, Related]);
1599closed_variants!(ThreadAddedBy[Steward]);
1600closed_variants!(TrustTier [Standard, Trusted]);
1601
1602#[cfg(test)]
1603mod tests {
1604 use super::*;
1605
1606 #[test]
1607 fn target_type_serde_round_trip() {
1608 let val = TargetType::Post;
1609 let json = serde_json::to_string(&val).unwrap();
1610 assert_eq!(json, "\"post\"");
1611 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1612 assert_eq!(val, deserialized);
1613 }
1614
1615 #[test]
1616 fn target_type_display() {
1617 assert_eq!(TargetType::Post.to_string(), "post");
1618 assert_eq!(TargetType::Comment.to_string(), "comment");
1619 }
1620
1621 #[test]
1622 fn target_type_from_str() {
1623 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1624 assert_eq!(
1625 TargetType::from_str("comment").unwrap(),
1626 TargetType::Comment
1627 );
1628 }
1629
1630 #[test]
1631 fn moderation_tier_serde() {
1632 let tier = ModerationTier::Tier2;
1633 let json = serde_json::to_string(&tier).unwrap();
1634 assert_eq!(json, "\"2\"");
1635 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1636 assert_eq!(tier, deserialized);
1637 }
1638
1639 #[test]
1642 fn message_encryption_wire_values() {
1643 assert_eq!(
1644 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1645 "\"e2ee\""
1646 );
1647 assert_eq!(
1648 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1649 "\"server\""
1650 );
1651 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1652 assert_eq!(
1653 MessageEncryption::from_str("server").unwrap(),
1654 MessageEncryption::Server
1655 );
1656 }
1657
1658 #[test]
1659 fn search_mode_wire_values() {
1660 assert_eq!(
1661 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1662 "\"keyword\""
1663 );
1664 assert_eq!(
1665 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1666 "\"semantic\""
1667 );
1668 assert_eq!(
1669 SearchMode::from_str("semantic").unwrap(),
1670 SearchMode::Semantic
1671 );
1672 }
1673
1674 #[test]
1677 fn no_sort_orders_by_votes() {
1678 for gone in ["score", "controversial", "unpopular"] {
1679 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1680 assert!(
1681 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1682 .is_err(),
1683 "{gone}"
1684 );
1685 }
1686 assert!(ProposalSort::from_str("score").is_err());
1687 }
1688
1689 #[cfg(feature = "schemars")]
1690 #[test]
1691 fn feed_sort_schema_is_ref_free() {
1692 use schemars::JsonSchema;
1693
1694 assert!(<FeedSort as JsonSchema>::inline_schema());
1695
1696 let schema = schemars::schema_for!(FeedSort);
1697 let value = serde_json::to_value(&schema).unwrap();
1698 let blob = value.to_string();
1699 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1700 assert!(!blob.contains("$ref"), "no $ref: {value}");
1701 let values: Vec<&str> = value["enum"]
1702 .as_array()
1703 .expect("FeedSort should render as a flat `enum`")
1704 .iter()
1705 .filter_map(|v| v.as_str())
1706 .collect();
1707 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1708 }
1709
1710 #[cfg(feature = "schemars")]
1713 #[test]
1714 fn trash_enum_schemas_are_plain_enums() {
1715 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1716 assert!(T::inline_schema());
1717 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1718 let blob = value.to_string();
1719 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1720 assert!(value.get("oneOf").is_none(), "{value}");
1721 value["enum"]
1722 .as_array()
1723 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1724 .iter()
1725 .map(|v| v.as_str().unwrap().to_owned())
1726 .collect()
1727 }
1728 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1729 assert_eq!(
1730 values::<TrashMode>(),
1731 ["list", "restore", "delete_permanently"]
1732 );
1733 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1734 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1735 }
1736
1737 #[cfg(feature = "schemars")]
1740 #[test]
1741 fn obligation_enum_schemas_are_strict_safe() {
1742 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1743 assert!(T::inline_schema());
1744 let value = crate::responses::inline_schema_for::<T>();
1745 assert_eq!(
1746 crate::tools::strict_schema_violations(&value),
1747 Vec::<String>::new()
1748 );
1749 value["enum"]
1750 .as_array()
1751 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1752 .iter()
1753 .map(|v| v.as_str().unwrap().to_owned())
1754 .collect()
1755 }
1756 assert_eq!(
1757 values::<ObligationKind>(),
1758 [
1759 "carry_forward",
1760 "ratification",
1761 "review",
1762 "referral",
1763 "reveal",
1764 "task"
1765 ]
1766 );
1767 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1768 assert_eq!(
1769 values::<ObligationStatus>(),
1770 ["open", "lapsed", "discharged", "withdrawn"]
1771 );
1772 assert_eq!(
1773 values::<CalendarEventKind>(),
1774 ["sitting", "special_sitting"]
1775 );
1776 }
1777
1778 #[test]
1779 fn trash_enum_wire_values() {
1780 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1781 assert_eq!(
1782 "delete_permanently".parse::<TrashMode>().unwrap(),
1783 TrashMode::DeletePermanently
1784 );
1785 assert_eq!(TrashMode::default(), TrashMode::List);
1786 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1787 assert_eq!(ContentKind::Comment.to_string(), "comment");
1788 }
1789
1790 #[test]
1793 fn unknown_trash_causes_parse() {
1794 assert_eq!(
1795 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1796 RemovedBy::Unknown
1797 );
1798 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1799 assert_eq!(
1800 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1801 DeletedBy::Unknown
1802 );
1803 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1804 }
1805
1806 #[test]
1807 fn proposal_category_round_trip() {
1808 for cat in [
1809 ProposalCategory::Routine,
1810 ProposalCategory::Policy,
1811 ProposalCategory::Constitutional,
1812 ProposalCategory::Emergency,
1813 ] {
1814 let json = serde_json::to_string(&cat).unwrap();
1815 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1816 assert_eq!(cat, back);
1817 }
1818 }
1819
1820 #[test]
1823 fn governance_amendment_and_key_wire_values() {
1824 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1825 assert_eq!(
1826 GovernanceLogEntryType::KeyRotation.to_string(),
1827 "key_rotation"
1828 );
1829 assert_eq!(
1830 AmendmentKind::NonPrecedential.to_string(),
1831 "non_precedential"
1832 );
1833 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1834 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1835 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1836 assert_eq!(RecordVersion::Original.to_string(), "original");
1837 assert_eq!(
1838 "latest".parse::<RecordVersion>().unwrap(),
1839 RecordVersion::Latest
1840 );
1841 assert_eq!(
1842 "superseded".parse::<AmendmentKind>().unwrap(),
1843 AmendmentKind::Superseded
1844 );
1845 assert_eq!(Standing::default(), Standing::InForce);
1846 assert_eq!(Standing::InForce.to_string(), "in_force");
1847 assert_eq!(
1848 "compromised".parse::<KeyStatus>().unwrap(),
1849 KeyStatus::Compromised
1850 );
1851 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1852 }
1853
1854 #[test]
1857 fn obligation_and_calendar_wire_values() {
1858 assert_eq!(
1859 GovernanceLogEntryType::Obligation.to_string(),
1860 "obligation"
1861 );
1862 assert_eq!(GovernanceLogEntryType::DataSet.to_string(), "data_set");
1863 assert_eq!(
1864 "data_set".parse::<GovernanceLogEntryType>().unwrap(),
1865 GovernanceLogEntryType::DataSet
1866 );
1867 assert_eq!(GovernanceLogEntryType::Test.to_string(), "test");
1868 assert_eq!(
1869 "test".parse::<GovernanceLogEntryType>().unwrap(),
1870 GovernanceLogEntryType::Test
1871 );
1872 assert_eq!(
1874 serde_json::from_value::<GovernanceLogEntryType>(
1875 serde_json::json!("something_newer")
1876 )
1877 .unwrap(),
1878 GovernanceLogEntryType::Unknown
1879 );
1880 assert!(
1881 !GovernanceLogEntryType::ALL
1882 .contains(&GovernanceLogEntryType::Unknown)
1883 );
1884 let kinds: Vec<String> =
1885 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1886 assert_eq!(
1887 kinds,
1888 [
1889 "planned",
1890 "discharged",
1891 "lapsed",
1892 "rescheduled",
1893 "withdrawn"
1894 ]
1895 );
1896 assert!(AmendmentKind::Lapsed.is_obligation());
1897 assert!(!AmendmentKind::Revision.is_obligation());
1898 for (kind, wire) in [
1899 (ObligationKind::CarryForward, "carry_forward"),
1900 (ObligationKind::Ratification, "ratification"),
1901 (ObligationKind::Review, "review"),
1902 (ObligationKind::Referral, "referral"),
1903 (ObligationKind::Reveal, "reveal"),
1904 (ObligationKind::Task, "task"),
1905 ] {
1906 assert_eq!(kind.to_string(), wire);
1907 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1908 }
1909 for (who, wire) in [
1910 (OwedBy::Steward, "steward"),
1911 (OwedBy::Staff, "staff"),
1912 (OwedBy::Council, "council"),
1913 ] {
1914 assert_eq!(who.to_string(), wire);
1915 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1916 }
1917 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1918 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1919 assert!(ObligationStatus::Withdrawn.is_closed());
1920 assert!(!ObligationStatus::Lapsed.is_closed());
1921 assert_eq!(
1922 CalendarEventKind::SpecialSitting.to_string(),
1923 "special_sitting"
1924 );
1925 assert!("festival".parse::<CalendarEventKind>().is_err());
1928 assert_eq!(
1929 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1930 CalendarEventStatus::Cancelled
1931 );
1932 }
1933
1934 #[test]
1936 fn detail_level_wire_names() {
1937 for (level, wire) in [
1938 (DetailLevel::Summary, "summary"),
1939 (DetailLevel::Full, "full"),
1940 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1941 ] {
1942 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1943 assert_eq!(level.to_string(), wire);
1944 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1945 }
1946 }
1947
1948 #[cfg(feature = "schemars")]
1953 #[test]
1954 fn enum_json_schema_is_inlined() {
1955 use schemars::JsonSchema;
1956
1957 assert!(<TargetType as JsonSchema>::inline_schema());
1958 assert!(<FeedSort as JsonSchema>::inline_schema());
1959 assert!(<DashboardCommunities as JsonSchema>::inline_schema());
1960 assert!(<ProposalSort as JsonSchema>::inline_schema());
1961 assert!(<DetailLevel as JsonSchema>::inline_schema());
1962 assert!(<RecordVersion as JsonSchema>::inline_schema());
1963 assert!(<SearchMode as JsonSchema>::inline_schema());
1964 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1965 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1966 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1967 assert!(<Standing as JsonSchema>::inline_schema());
1968 assert!(<CitationRelation as JsonSchema>::inline_schema());
1969 assert!(<KeyStatus as JsonSchema>::inline_schema());
1970 assert!(<OAuthScope as JsonSchema>::inline_schema());
1971 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1972 assert!(<ModerationTier as JsonSchema>::inline_schema());
1973 assert!(<ObligationKind as JsonSchema>::inline_schema());
1974 assert!(<OwedBy as JsonSchema>::inline_schema());
1975 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1976 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1977 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1978
1979 #[derive(schemars::JsonSchema)]
1980 #[allow(dead_code)]
1981 struct Container {
1982 target_type: TargetType,
1983 sort: Option<FeedSort>,
1984 proposal_sort: Option<ProposalSort>,
1985 category: Option<ProposalCategory>,
1986 detail: Option<DetailLevel>,
1987 version: Option<RecordVersion>,
1988 search_mode: Option<SearchMode>,
1989 }
1990
1991 let schema = schemars::schema_for!(Container);
1992 let value = serde_json::to_value(&schema).unwrap();
1993 let blob = value.to_string();
1994
1995 assert!(
1996 value.get("$defs").is_none(),
1997 "no $defs should be emitted for enum-only container; got schema: {value}"
1998 );
1999 assert!(
2000 !blob.contains("$ref"),
2001 "enum container schema must contain no $ref anywhere; got: {value}"
2002 );
2003
2004 let target_type_enum = value["properties"]["target_type"]["enum"]
2006 .as_array()
2007 .expect("target_type should have inline `enum` array");
2008 assert!(
2009 target_type_enum
2010 .contains(&serde_json::Value::String("post".into()))
2011 );
2012 assert!(
2013 target_type_enum
2014 .contains(&serde_json::Value::String("comment".into()))
2015 );
2016 }
2017
2018 #[cfg(feature = "schemars")]
2024 #[test]
2025 fn search_mode_schema_is_ref_free() {
2026 use schemars::JsonSchema;
2027
2028 assert!(<SearchMode as JsonSchema>::inline_schema());
2029
2030 let schema = schemars::schema_for!(SearchMode);
2031 let value = serde_json::to_value(&schema).unwrap();
2032 let blob = value.to_string();
2033 assert!(value.get("$defs").is_none(), "no $defs: {value}");
2034 assert!(!blob.contains("$ref"), "no $ref: {value}");
2035
2036 let variants = value["oneOf"]
2042 .as_array()
2043 .expect("SearchMode should have an inline `oneOf` array");
2044 let consts: Vec<&str> = variants
2045 .iter()
2046 .filter_map(|v| v["const"].as_str())
2047 .collect();
2048 assert!(consts.contains(&"keyword"), "{value}");
2049 assert!(consts.contains(&"semantic"), "{value}");
2050 }
2051}