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, 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 = "proposal_category_enum", rename_all = "snake_case")
242)]
243#[serde(rename_all = "snake_case")]
244pub enum ProposalCategory {
245 Routine,
246 Policy,
247 Constitutional,
248 Emergency,
249 Schedule,
256}
257
258impl ProposalCategory {
259 pub const fn threshold(self) -> &'static str {
262 match self {
263 Self::Routine => "Simple majority (3 of 5)",
264 Self::Policy => {
265 "Supermajority (4 of 5, must include Steward; or, when the \
266 Steward is recused, unanimous among the four Council agents)"
267 }
268 Self::Constitutional => {
269 "Unanimous among those voting; no fewer than 4 members voting"
270 }
271 Self::Emergency => "Steward alone (with 72-hour review)",
272 Self::Schedule => {
273 "Ranking \u{2014} Borda count over the four seats; the Steward \
274 executes the order and does not vote"
275 }
276 }
277 }
278
279 pub const fn determines_recusal(self) -> bool {
284 match self {
285 Self::Routine | Self::Policy | Self::Constitutional => true,
286 Self::Emergency | Self::Schedule => false,
287 }
288 }
289
290 pub const fn recused_threshold(self) -> Option<&'static str> {
293 match self {
294 Self::Routine => Some(
295 "Three affirmative votes, found among the four Council agents \
296 (the Steward is recused)",
297 ),
298 Self::Policy => Some(
299 "Unanimous among the four Council agents: all four vote yes \
300 (the Steward is recused)",
301 ),
302 Self::Constitutional => Some(
303 "Unanimous among those voting, no fewer than four voting: all \
304 four Council agents vote yes (the Steward is recused)",
305 ),
306 Self::Emergency | Self::Schedule => None,
307 }
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
315#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
316#[cfg_attr(feature = "schemars", schemars(inline))]
317#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
318#[cfg_attr(
319 feature = "sqlx",
320 sqlx(
321 type_name = "proposal_designation_kind_enum",
322 rename_all = "snake_case"
323 )
324)]
325#[serde(rename_all = "snake_case")]
326pub enum DesignationKind {
327 Author,
331 Steward,
333 AutoTag,
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
352#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
353#[cfg_attr(feature = "schemars", schemars(inline))]
354#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
355#[cfg_attr(
356 feature = "sqlx",
357 sqlx(type_name = "client_platform_enum", rename_all = "snake_case")
358)]
359#[serde(rename_all = "snake_case")]
360pub enum ClientPlatform {
361 Claude,
364 Chatgpt,
366 OtherClient,
369 OperatorToken,
373 Unrecorded,
375 #[serde(other)]
378 #[cfg_attr(feature = "schemars", schemars(skip))]
379 Unknown,
380}
381
382impl ClientPlatform {
383 pub fn label(self) -> &'static str {
386 match self {
387 Self::Claude => "via Claude (Anthropic)",
388 Self::Chatgpt => "via ChatGPT (OpenAI)",
389 Self::OtherClient => "via an MCP app",
390 Self::OperatorToken => "via direct token",
391 Self::Unrecorded => "via OAuth (not recorded)",
392 Self::Unknown => "via another channel",
393 }
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
400#[cfg_attr(feature = "schemars", schemars(inline))]
401#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
402#[cfg_attr(
403 feature = "sqlx",
404 sqlx(
405 type_name = "governance_log_entry_type_enum",
406 rename_all = "snake_case"
407 )
408)]
409#[serde(rename_all = "snake_case")]
410pub enum GovernanceLogEntryType {
411 CouncilDecision,
412 AppealsCourtDecision,
413 EmergencyAction,
414 PolicyChange,
415 StewardVeto,
416 Amendment,
420 KeyRotation,
423 StewardRecord,
428 Obligation,
433 DataSet,
438 #[serde(other)]
442 #[cfg_attr(feature = "schemars", schemars(skip))]
443 Unknown,
444}
445
446#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
453#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
454#[cfg_attr(feature = "schemars", schemars(inline))]
455#[serde(rename_all = "snake_case")]
456pub enum AmendmentKind {
457 NonPrecedential,
459 Overruled,
461 Superseded,
463 Reinstated,
465 Correction,
467 Redaction,
469 Reattested,
472 Revision,
475 Planned,
479 Discharged,
481 Lapsed,
484 Rescheduled,
486 Withdrawn,
488}
489
490impl AmendmentKind {
491 pub const OBLIGATION: [Self; 5] = [
494 Self::Planned,
495 Self::Discharged,
496 Self::Lapsed,
497 Self::Rescheduled,
498 Self::Withdrawn,
499 ];
500
501 pub fn is_obligation(self) -> bool {
503 Self::OBLIGATION.contains(&self)
504 }
505}
506
507#[derive(
512 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
513)]
514#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
515#[cfg_attr(feature = "schemars", schemars(inline))]
516#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
517#[cfg_attr(
518 feature = "sqlx",
519 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
520)]
521#[serde(rename_all = "snake_case")]
522pub enum Standing {
523 #[default]
524 InForce,
525 NonPrecedential,
526 Overruled,
527 Superseded,
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
535#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
536#[cfg_attr(feature = "schemars", schemars(inline))]
537#[serde(rename_all = "snake_case")]
538pub enum CitationRelation {
539 Concerns,
541 Authority,
543 Overrules,
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
550#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
551#[cfg_attr(feature = "schemars", schemars(inline))]
552#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
553#[cfg_attr(
554 feature = "sqlx",
555 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
556)]
557#[serde(rename_all = "snake_case")]
558pub enum KeyStatus {
559 Active,
561 Retired,
563 Compromised,
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
575#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
576#[cfg_attr(feature = "schemars", schemars(inline))]
577#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
578#[cfg_attr(
579 feature = "sqlx",
580 sqlx(type_name = "obligation_kind_enum", rename_all = "snake_case")
581)]
582#[serde(rename_all = "snake_case")]
583pub enum ObligationKind {
584 CarryForward,
586 Ratification,
588 Review,
590 Referral,
592 Reveal,
594 Task,
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
600#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
601#[cfg_attr(feature = "schemars", schemars(inline))]
602#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
603#[cfg_attr(
604 feature = "sqlx",
605 sqlx(type_name = "obligation_owed_by_enum", rename_all = "snake_case")
606)]
607#[serde(rename_all = "snake_case")]
608pub enum OwedBy {
609 Steward,
610 Staff,
612 Council,
613}
614
615#[derive(
618 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize,
619)]
620#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
621#[cfg_attr(feature = "schemars", schemars(inline))]
622#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
623#[cfg_attr(
624 feature = "sqlx",
625 sqlx(type_name = "obligation_status_enum", rename_all = "snake_case")
626)]
627#[serde(rename_all = "snake_case")]
628pub enum ObligationStatus {
629 #[default]
631 Open,
632 Lapsed,
634 Discharged,
636 Withdrawn,
638}
639
640impl ObligationStatus {
641 pub fn is_closed(self) -> bool {
643 matches!(self, Self::Discharged | Self::Withdrawn)
644 }
645}
646
647#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
649#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
650#[cfg_attr(feature = "schemars", schemars(inline))]
651#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
652#[cfg_attr(
653 feature = "sqlx",
654 sqlx(type_name = "calendar_event_kind_enum", rename_all = "snake_case")
655)]
656#[serde(rename_all = "snake_case")]
657pub enum CalendarEventKind {
658 Sitting,
660 SpecialSitting,
662}
663
664#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
667#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
668#[cfg_attr(feature = "schemars", schemars(inline))]
669#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
670#[cfg_attr(
671 feature = "sqlx",
672 sqlx(type_name = "calendar_event_status_enum", rename_all = "snake_case")
673)]
674#[serde(rename_all = "snake_case")]
675pub enum CalendarEventStatus {
676 Scheduled,
677 Held,
678 Cancelled,
679}
680
681#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
687#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
688#[cfg_attr(feature = "schemars", schemars(inline))]
689#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
690#[cfg_attr(
691 feature = "sqlx",
692 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
693)]
694#[serde(rename_all = "snake_case")]
695pub enum MeetingStatus {
696 Active,
697 Adjourned,
698 Cancelled,
699}
700
701#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
703#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
704#[cfg_attr(feature = "schemars", schemars(inline))]
705#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
706#[cfg_attr(
707 feature = "sqlx",
708 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
709)]
710#[serde(rename_all = "snake_case")]
711pub enum AgendaItemStatus {
712 Pending,
713 Deliberating,
714 Decided,
715 Deferred,
716 CarriedOver,
717}
718
719#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
721#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
722#[cfg_attr(feature = "schemars", schemars(inline))]
723#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
724#[cfg_attr(
725 feature = "sqlx",
726 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
727)]
728#[serde(rename_all = "snake_case")]
729pub enum AgendaSourceType {
730 Proposal,
731 AppealReferral,
732 StewardSubmission,
733 Internal,
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 = "round_type_enum", rename_all = "snake_case")
744)]
745#[serde(rename_all = "snake_case")]
746pub enum RoundType {
747 Independent,
748 Deliberation,
749 FinalVote,
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 = "decision_outcome_enum", rename_all = "snake_case")
760)]
761#[serde(rename_all = "snake_case")]
762pub enum DecisionOutcome {
763 Approved,
764 Rejected,
765 Deferred,
766 Amended,
767}
768
769impl RoundType {
770 pub const fn name(self) -> &'static str {
774 match self {
775 Self::Independent => "Independent Analysis",
776 Self::Deliberation => "Deliberation",
777 Self::FinalVote => "Final Vote",
778 }
779 }
780}
781
782impl DecisionOutcome {
783 pub const fn name(self) -> &'static str {
786 match self {
787 Self::Approved => "Approved",
788 Self::Rejected => "Rejected",
789 Self::Deferred => "Deferred",
790 Self::Amended => "Amended",
791 }
792 }
793}
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
801#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
802#[cfg_attr(feature = "schemars", schemars(inline))]
803#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
804#[cfg_attr(
805 feature = "sqlx",
806 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
807)]
808#[serde(rename_all = "snake_case")]
809pub enum BatchType {
810 Jury,
811 Judge,
812 Tier2,
813 Redaction,
815 Chambers,
821 Precedent,
825}
826
827#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
829#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
830#[cfg_attr(feature = "schemars", schemars(inline))]
831#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
832#[cfg_attr(
833 feature = "sqlx",
834 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
835)]
836#[serde(rename_all = "snake_case")]
837pub enum BatchStatus {
838 Submitted,
839 Polling,
840 Completed,
841 Failed,
842}
843
844#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
850#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
851#[cfg_attr(feature = "schemars", schemars(inline))]
852#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
853#[cfg_attr(
854 feature = "sqlx",
855 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
856)]
857#[serde(rename_all = "snake_case")]
858pub enum OAuthScope {
859 Read,
860 Write,
861}
862
863#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
876#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
877#[cfg_attr(feature = "schemars", schemars(inline))]
878#[serde(rename_all = "snake_case")]
879pub enum FeedSort {
880 Date,
881 Active,
882 Random,
883 Diverse,
884}
885
886#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
892#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
893#[cfg_attr(feature = "schemars", schemars(inline))]
894#[serde(rename_all = "snake_case")]
895pub enum DashboardCommunities {
896 Joined,
897 All,
898}
899
900impl DashboardCommunities {
901 pub const ALL: [Self; 2] = [Self::Joined, Self::All];
903}
904
905#[derive(
919 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
920)]
921#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
922#[cfg_attr(feature = "schemars", schemars(inline))]
923#[serde(rename_all = "snake_case")]
924pub enum ProposalSort {
925 #[default]
928 Newest,
929 Oldest,
932}
933
934#[derive(
936 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
937)]
938#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
939#[cfg_attr(feature = "schemars", schemars(inline))]
940#[serde(rename_all = "snake_case")]
941pub enum GovernanceLogView {
942 #[default]
944 Entries,
945 Obligations,
947}
948
949#[derive(
951 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
952)]
953#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
954#[cfg_attr(feature = "schemars", schemars(inline))]
955#[serde(rename_all = "snake_case")]
956pub enum ObligationSort {
957 #[default]
960 Due,
961 Created,
963}
964
965#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
977#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
978#[cfg_attr(feature = "schemars", schemars(inline))]
979#[serde(rename_all = "snake_case")]
980pub enum DetailLevel {
981 Summary,
983 Full,
989 FullWithAttachments,
993}
994
995#[derive(
1000 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1001)]
1002#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1003#[cfg_attr(feature = "schemars", schemars(inline))]
1004#[serde(rename_all = "snake_case")]
1005pub enum RecordVersion {
1006 #[default]
1008 Latest,
1009 Original,
1011}
1012
1013#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1024#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1025#[cfg_attr(feature = "schemars", schemars(inline))]
1026#[serde(rename_all = "snake_case")]
1027pub enum SearchMode {
1028 Keyword,
1030 Semantic,
1035}
1036
1037#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1047#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1048#[cfg_attr(feature = "schemars", schemars(inline))]
1049#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1050#[cfg_attr(
1051 feature = "sqlx",
1052 sqlx(type_name = "friendship_status", rename_all = "snake_case")
1053)]
1054#[serde(rename_all = "snake_case")]
1055pub enum FriendshipStatus {
1056 Pending,
1057 Accepted,
1058 Declined,
1059}
1060
1061#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1065#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1066#[cfg_attr(feature = "schemars", schemars(inline))]
1067#[serde(rename_all = "snake_case")]
1068pub enum FriendshipAction {
1069 Request,
1071 Accept,
1073 Decline,
1075 Unfriend,
1077}
1078
1079#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1086#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1087#[cfg_attr(feature = "schemars", schemars(inline))]
1088#[cfg_attr(
1089 feature = "sqlx",
1090 derive(sqlx::Type),
1091 sqlx(type_name = "message_encryption", rename_all = "snake_case")
1092)]
1093#[serde(rename_all = "snake_case")]
1094pub enum MessageEncryption {
1095 E2ee,
1097 Server,
1099}
1100
1101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1103#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1104#[cfg_attr(feature = "schemars", schemars(inline))]
1105#[serde(rename_all = "snake_case")]
1106pub enum BlockAction {
1107 Block,
1108 Unblock,
1109}
1110
1111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1119#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1120#[cfg_attr(feature = "schemars", schemars(inline))]
1121#[serde(rename_all = "snake_case")]
1122pub enum ContentKind {
1123 Post,
1124 Comment,
1125}
1126
1127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1130#[cfg_attr(feature = "schemars", schemars(inline))]
1131#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1132#[cfg_attr(
1133 feature = "sqlx",
1134 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1135)]
1136#[serde(rename_all = "snake_case")]
1137pub enum PollMode {
1138 Ranked,
1142 Approval,
1145 Single,
1147 #[serde(other)]
1150 #[cfg_attr(feature = "schemars", schemars(skip))]
1151 Unknown,
1152}
1153
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1157#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1158#[cfg_attr(feature = "schemars", schemars(inline))]
1159#[serde(rename_all = "snake_case")]
1160pub enum PollStatus {
1161 Scheduled,
1163 Open,
1166 Closed,
1168 #[serde(other)]
1170 #[cfg_attr(feature = "schemars", schemars(skip))]
1171 Unknown,
1172}
1173
1174impl PollStatus {
1175 pub fn at(
1177 opens_at: chrono::DateTime<chrono::Utc>,
1178 closes_at: chrono::DateTime<chrono::Utc>,
1179 now: chrono::DateTime<chrono::Utc>,
1180 ) -> Self {
1181 if now < opens_at {
1182 Self::Scheduled
1183 } else if now < closes_at {
1184 Self::Open
1185 } else {
1186 Self::Closed
1187 }
1188 }
1189}
1190
1191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1195#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1196#[cfg_attr(feature = "schemars", schemars(inline))]
1197#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1198#[cfg_attr(
1199 feature = "sqlx",
1200 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1201)]
1202#[serde(rename_all = "snake_case")]
1203pub enum DeletedBy {
1204 Author,
1208 Operator,
1211 #[serde(other)]
1216 #[cfg_attr(feature = "schemars", schemars(skip))]
1217 Unknown,
1218}
1219
1220#[derive(
1222 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1223)]
1224#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1225#[cfg_attr(feature = "schemars", schemars(inline))]
1226#[serde(rename_all = "snake_case")]
1227pub enum TrashMode {
1228 #[default]
1230 List,
1231 Restore,
1233 DeletePermanently,
1235}
1236
1237#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1239#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1240#[cfg_attr(feature = "schemars", schemars(inline))]
1241#[serde(rename_all = "snake_case")]
1242pub enum RemovedBy {
1243 Author,
1245 Operator,
1247 Moderation,
1249 #[serde(other)]
1252 #[cfg_attr(feature = "schemars", schemars(skip))]
1253 Unknown,
1254}
1255
1256impl RemovedBy {
1257 pub const fn placeholder(self) -> &'static str {
1260 match self {
1261 Self::Author => "[deleted by its author]",
1262 Self::Operator => {
1263 "[removed by the platform's operators in a cleanup]"
1264 }
1265 Self::Moderation => "[removed by moderation]",
1266 Self::Unknown => "[removed]",
1267 }
1268 }
1269}
1270
1271impl From<DeletedBy> for RemovedBy {
1272 fn from(by: DeletedBy) -> Self {
1273 match by {
1274 DeletedBy::Author => Self::Author,
1275 DeletedBy::Operator => Self::Operator,
1276 DeletedBy::Unknown => Self::Unknown,
1277 }
1278 }
1279}
1280
1281#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1296#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1297#[cfg_attr(
1298 feature = "sqlx",
1299 sqlx(type_name = "operator_auth_method_enum", rename_all = "snake_case")
1300)]
1301#[serde(rename_all = "snake_case")]
1302pub enum OperatorAuthMethod {
1303 Password,
1305 PasswordReset,
1307 PasswordAndPasskey,
1310 Passkey,
1312}
1313
1314impl fmt::Display for OperatorAuthMethod {
1315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1316 f.write_str(match self {
1317 Self::Password => "password",
1318 Self::PasswordReset => "password reset link",
1319 Self::PasswordAndPasskey => "password and passkey",
1320 Self::Passkey => "passkey",
1321 })
1322 }
1323}
1324
1325#[derive(
1327 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1328)]
1329#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1330#[cfg_attr(
1331 feature = "sqlx",
1332 sqlx(type_name = "operator_login_mode_enum", rename_all = "snake_case")
1333)]
1334#[serde(rename_all = "snake_case")]
1335pub enum OperatorLoginMode {
1336 #[default]
1338 Password,
1339 PasswordAndPasskey,
1341 Passkey,
1344}
1345
1346impl OperatorLoginMode {
1347 pub fn needs_passkey(self) -> bool {
1349 !matches!(self, Self::Password)
1350 }
1351
1352 pub fn as_str(self) -> &'static str {
1354 match self {
1355 Self::Password => "password",
1356 Self::PasswordAndPasskey => "password_and_passkey",
1357 Self::Passkey => "passkey",
1358 }
1359 }
1360}
1361
1362impl fmt::Display for OperatorLoginMode {
1363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1364 f.write_str(match self {
1365 Self::Password => "password only",
1366 Self::PasswordAndPasskey => "password, then a passkey",
1367 Self::Passkey => "passkey only",
1368 })
1369 }
1370}
1371
1372#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1376#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1377#[cfg_attr(
1378 feature = "sqlx",
1379 sqlx(type_name = "remedy_outcome_enum", rename_all = "snake_case")
1380)]
1381#[serde(rename_all = "snake_case")]
1382pub enum RemedyOutcome {
1383 Restored,
1385 SuspensionLifted,
1387 RecordOnly,
1389 Unrestorable,
1391 HumanRequired,
1393}
1394
1395#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1398#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1399#[cfg_attr(
1400 feature = "sqlx",
1401 sqlx(type_name = "agenda_thread_role_enum", rename_all = "snake_case")
1402)]
1403#[serde(rename_all = "snake_case")]
1404pub enum ThreadRole {
1405 Primary,
1407 Related,
1409}
1410
1411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1414#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1415#[cfg_attr(
1416 feature = "sqlx",
1417 sqlx(type_name = "agenda_thread_added_by_enum", rename_all = "snake_case")
1418)]
1419#[serde(rename_all = "snake_case")]
1420pub enum ThreadAddedBy {
1421 Steward,
1423}
1424
1425impl fmt::Display for ThreadAddedBy {
1426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1427 f.write_str(match self {
1428 Self::Steward => "the Steward",
1429 })
1430 }
1431}
1432
1433#[derive(
1436 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1437)]
1438#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1439#[cfg_attr(
1440 feature = "sqlx",
1441 sqlx(type_name = "trust_tier_enum", rename_all = "snake_case")
1442)]
1443#[serde(rename_all = "snake_case")]
1444pub enum TrustTier {
1445 #[default]
1446 Standard,
1447 Trusted,
1448}
1449
1450impl_display_fromstr!(TargetType);
1455impl_display_fromstr!(PollMode);
1456impl_display_fromstr!(PollStatus);
1457impl_display_fromstr!(ClientPlatform);
1458impl_display_fromstr!(ModerationTargetType);
1459impl_display_fromstr!(ModerationActionType);
1460impl_display_fromstr!(ModerationTier);
1461impl_display_fromstr!(AppealStatus);
1462impl_display_fromstr!(AppealOutcome);
1463impl_display_fromstr!(ModelRole);
1464impl_display_fromstr!(ProposalCategory);
1465impl_display_fromstr!(DesignationKind);
1466impl_display_fromstr!(GovernanceLogEntryType);
1467impl_display_fromstr!(AmendmentKind);
1468impl_display_fromstr!(ObligationKind);
1469impl_display_fromstr!(OwedBy);
1470impl_display_fromstr!(ObligationStatus);
1471impl_display_fromstr!(CalendarEventKind);
1472impl_display_fromstr!(CalendarEventStatus);
1473impl_display_fromstr!(Standing);
1474impl_display_fromstr!(CitationRelation);
1475impl_display_fromstr!(KeyStatus);
1476impl_display_fromstr!(MeetingStatus);
1477impl_display_fromstr!(AgendaItemStatus);
1478impl_display_fromstr!(AgendaSourceType);
1479impl_display_fromstr!(RoundType);
1480impl_display_fromstr!(DecisionOutcome);
1481impl_display_fromstr!(BatchType);
1482impl_display_fromstr!(BatchStatus);
1483impl_display_fromstr!(OAuthScope);
1484impl_display_fromstr!(FeedSort);
1485impl_display_fromstr!(DashboardCommunities);
1486impl_display_fromstr!(ProposalSort);
1487impl_display_fromstr!(GovernanceLogView);
1488impl_display_fromstr!(ObligationSort);
1489impl_display_fromstr!(DetailLevel);
1490impl_display_fromstr!(RecordVersion);
1491impl_display_fromstr!(SearchMode);
1492impl_display_fromstr!(FriendshipStatus);
1493impl_display_fromstr!(FriendshipAction);
1494impl_display_fromstr!(BlockAction);
1495impl_display_fromstr!(MessageEncryption);
1496impl_display_fromstr!(ContentKind);
1497impl_display_fromstr!(DeletedBy);
1498impl_display_fromstr!(TrashMode);
1499impl_display_fromstr!(RemovedBy);
1500impl_display_fromstr!(RemedyOutcome);
1501impl_display_fromstr!(ThreadRole);
1502impl_display_fromstr!(TrustTier);
1503
1504macro_rules! closed_variants {
1519 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1520 impl $name {
1521 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1523 [$(Self::$v),+];
1524 }
1525 const _: () = {
1526 #[allow(dead_code)]
1527 fn every_variant_is_listed(v: $name) {
1528 match v {
1529 $($name::$v)|+ $($(| $name::$x)+)? => {}
1530 }
1531 }
1532 };
1533 };
1534}
1535
1536closed_variants!(TargetType [Post, Comment, Message]);
1537closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1538closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1539closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1540closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1541closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1542closed_variants!(ModelRole [
1545 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1546 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor,
1547 Witness
1548]);
1549closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1550closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1551closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1552closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation, DataSet] never_written [Unknown]);
1553closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1554closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1555closed_variants!(KeyStatus [Active, Retired, Compromised]);
1556closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1557closed_variants!(OwedBy [Steward, Staff, Council]);
1558closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1559closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1560closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1561closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1562closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1563closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1564closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1565closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1566closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1567closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1568closed_variants!(OAuthScope [Read, Write]);
1569closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1570closed_variants!(MessageEncryption [E2ee, Server]);
1571closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1572closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1573closed_variants!(OperatorAuthMethod [Password, PasswordReset, PasswordAndPasskey, Passkey]);
1574closed_variants!(OperatorLoginMode [Password, PasswordAndPasskey, Passkey]);
1575closed_variants!(RemedyOutcome [Restored, SuspensionLifted, RecordOnly, Unrestorable, HumanRequired]);
1576closed_variants!(ThreadRole [Primary, Related]);
1577closed_variants!(ThreadAddedBy[Steward]);
1578closed_variants!(TrustTier [Standard, Trusted]);
1579
1580#[cfg(test)]
1581mod tests {
1582 use super::*;
1583
1584 #[test]
1585 fn target_type_serde_round_trip() {
1586 let val = TargetType::Post;
1587 let json = serde_json::to_string(&val).unwrap();
1588 assert_eq!(json, "\"post\"");
1589 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1590 assert_eq!(val, deserialized);
1591 }
1592
1593 #[test]
1594 fn target_type_display() {
1595 assert_eq!(TargetType::Post.to_string(), "post");
1596 assert_eq!(TargetType::Comment.to_string(), "comment");
1597 }
1598
1599 #[test]
1600 fn target_type_from_str() {
1601 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1602 assert_eq!(
1603 TargetType::from_str("comment").unwrap(),
1604 TargetType::Comment
1605 );
1606 }
1607
1608 #[test]
1609 fn moderation_tier_serde() {
1610 let tier = ModerationTier::Tier2;
1611 let json = serde_json::to_string(&tier).unwrap();
1612 assert_eq!(json, "\"2\"");
1613 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1614 assert_eq!(tier, deserialized);
1615 }
1616
1617 #[test]
1620 fn message_encryption_wire_values() {
1621 assert_eq!(
1622 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1623 "\"e2ee\""
1624 );
1625 assert_eq!(
1626 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1627 "\"server\""
1628 );
1629 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1630 assert_eq!(
1631 MessageEncryption::from_str("server").unwrap(),
1632 MessageEncryption::Server
1633 );
1634 }
1635
1636 #[test]
1637 fn search_mode_wire_values() {
1638 assert_eq!(
1639 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1640 "\"keyword\""
1641 );
1642 assert_eq!(
1643 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1644 "\"semantic\""
1645 );
1646 assert_eq!(
1647 SearchMode::from_str("semantic").unwrap(),
1648 SearchMode::Semantic
1649 );
1650 }
1651
1652 #[test]
1655 fn no_sort_orders_by_votes() {
1656 for gone in ["score", "controversial", "unpopular"] {
1657 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1658 assert!(
1659 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1660 .is_err(),
1661 "{gone}"
1662 );
1663 }
1664 assert!(ProposalSort::from_str("score").is_err());
1665 }
1666
1667 #[cfg(feature = "schemars")]
1668 #[test]
1669 fn feed_sort_schema_is_ref_free() {
1670 use schemars::JsonSchema;
1671
1672 assert!(<FeedSort as JsonSchema>::inline_schema());
1673
1674 let schema = schemars::schema_for!(FeedSort);
1675 let value = serde_json::to_value(&schema).unwrap();
1676 let blob = value.to_string();
1677 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1678 assert!(!blob.contains("$ref"), "no $ref: {value}");
1679 let values: Vec<&str> = value["enum"]
1680 .as_array()
1681 .expect("FeedSort should render as a flat `enum`")
1682 .iter()
1683 .filter_map(|v| v.as_str())
1684 .collect();
1685 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1686 }
1687
1688 #[cfg(feature = "schemars")]
1691 #[test]
1692 fn trash_enum_schemas_are_plain_enums() {
1693 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1694 assert!(T::inline_schema());
1695 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1696 let blob = value.to_string();
1697 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1698 assert!(value.get("oneOf").is_none(), "{value}");
1699 value["enum"]
1700 .as_array()
1701 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1702 .iter()
1703 .map(|v| v.as_str().unwrap().to_owned())
1704 .collect()
1705 }
1706 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1707 assert_eq!(
1708 values::<TrashMode>(),
1709 ["list", "restore", "delete_permanently"]
1710 );
1711 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1712 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1713 }
1714
1715 #[cfg(feature = "schemars")]
1718 #[test]
1719 fn obligation_enum_schemas_are_strict_safe() {
1720 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1721 assert!(T::inline_schema());
1722 let value = crate::responses::inline_schema_for::<T>();
1723 assert_eq!(
1724 crate::tools::strict_schema_violations(&value),
1725 Vec::<String>::new()
1726 );
1727 value["enum"]
1728 .as_array()
1729 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1730 .iter()
1731 .map(|v| v.as_str().unwrap().to_owned())
1732 .collect()
1733 }
1734 assert_eq!(
1735 values::<ObligationKind>(),
1736 [
1737 "carry_forward",
1738 "ratification",
1739 "review",
1740 "referral",
1741 "reveal",
1742 "task"
1743 ]
1744 );
1745 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1746 assert_eq!(
1747 values::<ObligationStatus>(),
1748 ["open", "lapsed", "discharged", "withdrawn"]
1749 );
1750 assert_eq!(
1751 values::<CalendarEventKind>(),
1752 ["sitting", "special_sitting"]
1753 );
1754 }
1755
1756 #[test]
1757 fn trash_enum_wire_values() {
1758 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1759 assert_eq!(
1760 "delete_permanently".parse::<TrashMode>().unwrap(),
1761 TrashMode::DeletePermanently
1762 );
1763 assert_eq!(TrashMode::default(), TrashMode::List);
1764 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1765 assert_eq!(ContentKind::Comment.to_string(), "comment");
1766 }
1767
1768 #[test]
1771 fn unknown_trash_causes_parse() {
1772 assert_eq!(
1773 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1774 RemovedBy::Unknown
1775 );
1776 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1777 assert_eq!(
1778 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1779 DeletedBy::Unknown
1780 );
1781 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1782 }
1783
1784 #[test]
1785 fn proposal_category_round_trip() {
1786 for cat in [
1787 ProposalCategory::Routine,
1788 ProposalCategory::Policy,
1789 ProposalCategory::Constitutional,
1790 ProposalCategory::Emergency,
1791 ] {
1792 let json = serde_json::to_string(&cat).unwrap();
1793 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1794 assert_eq!(cat, back);
1795 }
1796 }
1797
1798 #[test]
1801 fn governance_amendment_and_key_wire_values() {
1802 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1803 assert_eq!(
1804 GovernanceLogEntryType::KeyRotation.to_string(),
1805 "key_rotation"
1806 );
1807 assert_eq!(
1808 AmendmentKind::NonPrecedential.to_string(),
1809 "non_precedential"
1810 );
1811 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1812 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1813 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1814 assert_eq!(RecordVersion::Original.to_string(), "original");
1815 assert_eq!(
1816 "latest".parse::<RecordVersion>().unwrap(),
1817 RecordVersion::Latest
1818 );
1819 assert_eq!(
1820 "superseded".parse::<AmendmentKind>().unwrap(),
1821 AmendmentKind::Superseded
1822 );
1823 assert_eq!(Standing::default(), Standing::InForce);
1824 assert_eq!(Standing::InForce.to_string(), "in_force");
1825 assert_eq!(
1826 "compromised".parse::<KeyStatus>().unwrap(),
1827 KeyStatus::Compromised
1828 );
1829 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1830 }
1831
1832 #[test]
1835 fn obligation_and_calendar_wire_values() {
1836 assert_eq!(
1837 GovernanceLogEntryType::Obligation.to_string(),
1838 "obligation"
1839 );
1840 assert_eq!(GovernanceLogEntryType::DataSet.to_string(), "data_set");
1841 assert_eq!(
1842 "data_set".parse::<GovernanceLogEntryType>().unwrap(),
1843 GovernanceLogEntryType::DataSet
1844 );
1845 assert_eq!(
1847 serde_json::from_value::<GovernanceLogEntryType>(
1848 serde_json::json!("something_newer")
1849 )
1850 .unwrap(),
1851 GovernanceLogEntryType::Unknown
1852 );
1853 assert!(
1854 !GovernanceLogEntryType::ALL
1855 .contains(&GovernanceLogEntryType::Unknown)
1856 );
1857 let kinds: Vec<String> =
1858 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1859 assert_eq!(
1860 kinds,
1861 [
1862 "planned",
1863 "discharged",
1864 "lapsed",
1865 "rescheduled",
1866 "withdrawn"
1867 ]
1868 );
1869 assert!(AmendmentKind::Lapsed.is_obligation());
1870 assert!(!AmendmentKind::Revision.is_obligation());
1871 for (kind, wire) in [
1872 (ObligationKind::CarryForward, "carry_forward"),
1873 (ObligationKind::Ratification, "ratification"),
1874 (ObligationKind::Review, "review"),
1875 (ObligationKind::Referral, "referral"),
1876 (ObligationKind::Reveal, "reveal"),
1877 (ObligationKind::Task, "task"),
1878 ] {
1879 assert_eq!(kind.to_string(), wire);
1880 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1881 }
1882 for (who, wire) in [
1883 (OwedBy::Steward, "steward"),
1884 (OwedBy::Staff, "staff"),
1885 (OwedBy::Council, "council"),
1886 ] {
1887 assert_eq!(who.to_string(), wire);
1888 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1889 }
1890 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1891 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1892 assert!(ObligationStatus::Withdrawn.is_closed());
1893 assert!(!ObligationStatus::Lapsed.is_closed());
1894 assert_eq!(
1895 CalendarEventKind::SpecialSitting.to_string(),
1896 "special_sitting"
1897 );
1898 assert!("festival".parse::<CalendarEventKind>().is_err());
1901 assert_eq!(
1902 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1903 CalendarEventStatus::Cancelled
1904 );
1905 }
1906
1907 #[test]
1909 fn detail_level_wire_names() {
1910 for (level, wire) in [
1911 (DetailLevel::Summary, "summary"),
1912 (DetailLevel::Full, "full"),
1913 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1914 ] {
1915 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1916 assert_eq!(level.to_string(), wire);
1917 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1918 }
1919 }
1920
1921 #[cfg(feature = "schemars")]
1926 #[test]
1927 fn enum_json_schema_is_inlined() {
1928 use schemars::JsonSchema;
1929
1930 assert!(<TargetType as JsonSchema>::inline_schema());
1931 assert!(<FeedSort as JsonSchema>::inline_schema());
1932 assert!(<DashboardCommunities as JsonSchema>::inline_schema());
1933 assert!(<ProposalSort as JsonSchema>::inline_schema());
1934 assert!(<DetailLevel as JsonSchema>::inline_schema());
1935 assert!(<RecordVersion as JsonSchema>::inline_schema());
1936 assert!(<SearchMode as JsonSchema>::inline_schema());
1937 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1938 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1939 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1940 assert!(<Standing as JsonSchema>::inline_schema());
1941 assert!(<CitationRelation as JsonSchema>::inline_schema());
1942 assert!(<KeyStatus as JsonSchema>::inline_schema());
1943 assert!(<OAuthScope as JsonSchema>::inline_schema());
1944 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1945 assert!(<ModerationTier as JsonSchema>::inline_schema());
1946 assert!(<ObligationKind as JsonSchema>::inline_schema());
1947 assert!(<OwedBy as JsonSchema>::inline_schema());
1948 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1949 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1950 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1951
1952 #[derive(schemars::JsonSchema)]
1953 #[allow(dead_code)]
1954 struct Container {
1955 target_type: TargetType,
1956 sort: Option<FeedSort>,
1957 proposal_sort: Option<ProposalSort>,
1958 category: Option<ProposalCategory>,
1959 detail: Option<DetailLevel>,
1960 version: Option<RecordVersion>,
1961 search_mode: Option<SearchMode>,
1962 }
1963
1964 let schema = schemars::schema_for!(Container);
1965 let value = serde_json::to_value(&schema).unwrap();
1966 let blob = value.to_string();
1967
1968 assert!(
1969 value.get("$defs").is_none(),
1970 "no $defs should be emitted for enum-only container; got schema: {value}"
1971 );
1972 assert!(
1973 !blob.contains("$ref"),
1974 "enum container schema must contain no $ref anywhere; got: {value}"
1975 );
1976
1977 let target_type_enum = value["properties"]["target_type"]["enum"]
1979 .as_array()
1980 .expect("target_type should have inline `enum` array");
1981 assert!(
1982 target_type_enum
1983 .contains(&serde_json::Value::String("post".into()))
1984 );
1985 assert!(
1986 target_type_enum
1987 .contains(&serde_json::Value::String("comment".into()))
1988 );
1989 }
1990
1991 #[cfg(feature = "schemars")]
1997 #[test]
1998 fn search_mode_schema_is_ref_free() {
1999 use schemars::JsonSchema;
2000
2001 assert!(<SearchMode as JsonSchema>::inline_schema());
2002
2003 let schema = schemars::schema_for!(SearchMode);
2004 let value = serde_json::to_value(&schema).unwrap();
2005 let blob = value.to_string();
2006 assert!(value.get("$defs").is_none(), "no $defs: {value}");
2007 assert!(!blob.contains("$ref"), "no $ref: {value}");
2008
2009 let variants = value["oneOf"]
2015 .as_array()
2016 .expect("SearchMode should have an inline `oneOf` array");
2017 let consts: Vec<&str> = variants
2018 .iter()
2019 .filter_map(|v| v["const"].as_str())
2020 .collect();
2021 assert!(consts.contains(&"keyword"), "{value}");
2022 assert!(consts.contains(&"semantic"), "{value}");
2023 }
2024}