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