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(
898 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
899)]
900#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
901#[cfg_attr(feature = "schemars", schemars(inline))]
902#[serde(rename_all = "snake_case")]
903pub enum ProposalSort {
904 #[default]
907 Newest,
908 Oldest,
911}
912
913#[derive(
915 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
916)]
917#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
918#[cfg_attr(feature = "schemars", schemars(inline))]
919#[serde(rename_all = "snake_case")]
920pub enum GovernanceLogView {
921 #[default]
923 Entries,
924 Obligations,
926}
927
928#[derive(
930 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
931)]
932#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
933#[cfg_attr(feature = "schemars", schemars(inline))]
934#[serde(rename_all = "snake_case")]
935pub enum ObligationSort {
936 #[default]
939 Due,
940 Created,
942}
943
944#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
956#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
957#[cfg_attr(feature = "schemars", schemars(inline))]
958#[serde(rename_all = "snake_case")]
959pub enum DetailLevel {
960 Summary,
962 Full,
968 FullWithAttachments,
972}
973
974#[derive(
979 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
980)]
981#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
982#[cfg_attr(feature = "schemars", schemars(inline))]
983#[serde(rename_all = "snake_case")]
984pub enum RecordVersion {
985 #[default]
987 Latest,
988 Original,
990}
991
992#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1003#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1004#[cfg_attr(feature = "schemars", schemars(inline))]
1005#[serde(rename_all = "snake_case")]
1006pub enum SearchMode {
1007 Keyword,
1009 Semantic,
1014}
1015
1016#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1026#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1027#[cfg_attr(feature = "schemars", schemars(inline))]
1028#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1029#[cfg_attr(
1030 feature = "sqlx",
1031 sqlx(type_name = "friendship_status", rename_all = "snake_case")
1032)]
1033#[serde(rename_all = "snake_case")]
1034pub enum FriendshipStatus {
1035 Pending,
1036 Accepted,
1037 Declined,
1038}
1039
1040#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1044#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1045#[cfg_attr(feature = "schemars", schemars(inline))]
1046#[serde(rename_all = "snake_case")]
1047pub enum FriendshipAction {
1048 Request,
1050 Accept,
1052 Decline,
1054 Unfriend,
1056}
1057
1058#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1065#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1066#[cfg_attr(feature = "schemars", schemars(inline))]
1067#[cfg_attr(
1068 feature = "sqlx",
1069 derive(sqlx::Type),
1070 sqlx(type_name = "message_encryption", rename_all = "snake_case")
1071)]
1072#[serde(rename_all = "snake_case")]
1073pub enum MessageEncryption {
1074 E2ee,
1076 Server,
1078}
1079
1080#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1082#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1083#[cfg_attr(feature = "schemars", schemars(inline))]
1084#[serde(rename_all = "snake_case")]
1085pub enum BlockAction {
1086 Block,
1087 Unblock,
1088}
1089
1090#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1098#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1099#[cfg_attr(feature = "schemars", schemars(inline))]
1100#[serde(rename_all = "snake_case")]
1101pub enum ContentKind {
1102 Post,
1103 Comment,
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1108#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1109#[cfg_attr(feature = "schemars", schemars(inline))]
1110#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1111#[cfg_attr(
1112 feature = "sqlx",
1113 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1114)]
1115#[serde(rename_all = "snake_case")]
1116pub enum PollMode {
1117 Ranked,
1121 Approval,
1124 Single,
1126 #[serde(other)]
1129 #[cfg_attr(feature = "schemars", schemars(skip))]
1130 Unknown,
1131}
1132
1133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1136#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1137#[cfg_attr(feature = "schemars", schemars(inline))]
1138#[serde(rename_all = "snake_case")]
1139pub enum PollStatus {
1140 Scheduled,
1142 Open,
1145 Closed,
1147 #[serde(other)]
1149 #[cfg_attr(feature = "schemars", schemars(skip))]
1150 Unknown,
1151}
1152
1153impl PollStatus {
1154 pub fn at(
1156 opens_at: chrono::DateTime<chrono::Utc>,
1157 closes_at: chrono::DateTime<chrono::Utc>,
1158 now: chrono::DateTime<chrono::Utc>,
1159 ) -> Self {
1160 if now < opens_at {
1161 Self::Scheduled
1162 } else if now < closes_at {
1163 Self::Open
1164 } else {
1165 Self::Closed
1166 }
1167 }
1168}
1169
1170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1174#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1175#[cfg_attr(feature = "schemars", schemars(inline))]
1176#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1177#[cfg_attr(
1178 feature = "sqlx",
1179 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1180)]
1181#[serde(rename_all = "snake_case")]
1182pub enum DeletedBy {
1183 Author,
1187 Operator,
1190 #[serde(other)]
1195 #[cfg_attr(feature = "schemars", schemars(skip))]
1196 Unknown,
1197}
1198
1199#[derive(
1201 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1202)]
1203#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1204#[cfg_attr(feature = "schemars", schemars(inline))]
1205#[serde(rename_all = "snake_case")]
1206pub enum TrashMode {
1207 #[default]
1209 List,
1210 Restore,
1212 DeletePermanently,
1214}
1215
1216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1218#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1219#[cfg_attr(feature = "schemars", schemars(inline))]
1220#[serde(rename_all = "snake_case")]
1221pub enum RemovedBy {
1222 Author,
1224 Operator,
1226 Moderation,
1228 #[serde(other)]
1231 #[cfg_attr(feature = "schemars", schemars(skip))]
1232 Unknown,
1233}
1234
1235impl RemovedBy {
1236 pub const fn placeholder(self) -> &'static str {
1239 match self {
1240 Self::Author => "[deleted by its author]",
1241 Self::Operator => {
1242 "[removed by the platform's operators in a cleanup]"
1243 }
1244 Self::Moderation => "[removed by moderation]",
1245 Self::Unknown => "[removed]",
1246 }
1247 }
1248}
1249
1250impl From<DeletedBy> for RemovedBy {
1251 fn from(by: DeletedBy) -> Self {
1252 match by {
1253 DeletedBy::Author => Self::Author,
1254 DeletedBy::Operator => Self::Operator,
1255 DeletedBy::Unknown => Self::Unknown,
1256 }
1257 }
1258}
1259
1260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1275#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1276#[cfg_attr(
1277 feature = "sqlx",
1278 sqlx(type_name = "operator_auth_method_enum", rename_all = "snake_case")
1279)]
1280#[serde(rename_all = "snake_case")]
1281pub enum OperatorAuthMethod {
1282 Password,
1284 PasswordReset,
1286 PasswordAndPasskey,
1289 Passkey,
1291}
1292
1293impl fmt::Display for OperatorAuthMethod {
1294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1295 f.write_str(match self {
1296 Self::Password => "password",
1297 Self::PasswordReset => "password reset link",
1298 Self::PasswordAndPasskey => "password and passkey",
1299 Self::Passkey => "passkey",
1300 })
1301 }
1302}
1303
1304#[derive(
1306 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1307)]
1308#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1309#[cfg_attr(
1310 feature = "sqlx",
1311 sqlx(type_name = "operator_login_mode_enum", rename_all = "snake_case")
1312)]
1313#[serde(rename_all = "snake_case")]
1314pub enum OperatorLoginMode {
1315 #[default]
1317 Password,
1318 PasswordAndPasskey,
1320 Passkey,
1323}
1324
1325impl OperatorLoginMode {
1326 pub fn needs_passkey(self) -> bool {
1328 !matches!(self, Self::Password)
1329 }
1330
1331 pub fn as_str(self) -> &'static str {
1333 match self {
1334 Self::Password => "password",
1335 Self::PasswordAndPasskey => "password_and_passkey",
1336 Self::Passkey => "passkey",
1337 }
1338 }
1339}
1340
1341impl fmt::Display for OperatorLoginMode {
1342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1343 f.write_str(match self {
1344 Self::Password => "password only",
1345 Self::PasswordAndPasskey => "password, then a passkey",
1346 Self::Passkey => "passkey only",
1347 })
1348 }
1349}
1350
1351#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1355#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1356#[cfg_attr(
1357 feature = "sqlx",
1358 sqlx(type_name = "remedy_outcome_enum", rename_all = "snake_case")
1359)]
1360#[serde(rename_all = "snake_case")]
1361pub enum RemedyOutcome {
1362 Restored,
1364 SuspensionLifted,
1366 RecordOnly,
1368 Unrestorable,
1370 HumanRequired,
1372}
1373
1374#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1377#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1378#[cfg_attr(
1379 feature = "sqlx",
1380 sqlx(type_name = "agenda_thread_role_enum", rename_all = "snake_case")
1381)]
1382#[serde(rename_all = "snake_case")]
1383pub enum ThreadRole {
1384 Primary,
1386 Related,
1388}
1389
1390#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1393#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1394#[cfg_attr(
1395 feature = "sqlx",
1396 sqlx(type_name = "agenda_thread_added_by_enum", rename_all = "snake_case")
1397)]
1398#[serde(rename_all = "snake_case")]
1399pub enum ThreadAddedBy {
1400 Steward,
1402}
1403
1404impl fmt::Display for ThreadAddedBy {
1405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1406 f.write_str(match self {
1407 Self::Steward => "the Steward",
1408 })
1409 }
1410}
1411
1412#[derive(
1415 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1416)]
1417#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1418#[cfg_attr(
1419 feature = "sqlx",
1420 sqlx(type_name = "trust_tier_enum", rename_all = "snake_case")
1421)]
1422#[serde(rename_all = "snake_case")]
1423pub enum TrustTier {
1424 #[default]
1425 Standard,
1426 Trusted,
1427}
1428
1429impl_display_fromstr!(TargetType);
1434impl_display_fromstr!(PollMode);
1435impl_display_fromstr!(PollStatus);
1436impl_display_fromstr!(ClientPlatform);
1437impl_display_fromstr!(ModerationTargetType);
1438impl_display_fromstr!(ModerationActionType);
1439impl_display_fromstr!(ModerationTier);
1440impl_display_fromstr!(AppealStatus);
1441impl_display_fromstr!(AppealOutcome);
1442impl_display_fromstr!(ModelRole);
1443impl_display_fromstr!(ProposalCategory);
1444impl_display_fromstr!(DesignationKind);
1445impl_display_fromstr!(GovernanceLogEntryType);
1446impl_display_fromstr!(AmendmentKind);
1447impl_display_fromstr!(ObligationKind);
1448impl_display_fromstr!(OwedBy);
1449impl_display_fromstr!(ObligationStatus);
1450impl_display_fromstr!(CalendarEventKind);
1451impl_display_fromstr!(CalendarEventStatus);
1452impl_display_fromstr!(Standing);
1453impl_display_fromstr!(CitationRelation);
1454impl_display_fromstr!(KeyStatus);
1455impl_display_fromstr!(MeetingStatus);
1456impl_display_fromstr!(AgendaItemStatus);
1457impl_display_fromstr!(AgendaSourceType);
1458impl_display_fromstr!(RoundType);
1459impl_display_fromstr!(DecisionOutcome);
1460impl_display_fromstr!(BatchType);
1461impl_display_fromstr!(BatchStatus);
1462impl_display_fromstr!(OAuthScope);
1463impl_display_fromstr!(FeedSort);
1464impl_display_fromstr!(ProposalSort);
1465impl_display_fromstr!(GovernanceLogView);
1466impl_display_fromstr!(ObligationSort);
1467impl_display_fromstr!(DetailLevel);
1468impl_display_fromstr!(RecordVersion);
1469impl_display_fromstr!(SearchMode);
1470impl_display_fromstr!(FriendshipStatus);
1471impl_display_fromstr!(FriendshipAction);
1472impl_display_fromstr!(BlockAction);
1473impl_display_fromstr!(MessageEncryption);
1474impl_display_fromstr!(ContentKind);
1475impl_display_fromstr!(DeletedBy);
1476impl_display_fromstr!(TrashMode);
1477impl_display_fromstr!(RemovedBy);
1478impl_display_fromstr!(RemedyOutcome);
1479impl_display_fromstr!(ThreadRole);
1480impl_display_fromstr!(TrustTier);
1481
1482macro_rules! closed_variants {
1497 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1498 impl $name {
1499 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1501 [$(Self::$v),+];
1502 }
1503 const _: () = {
1504 #[allow(dead_code)]
1505 fn every_variant_is_listed(v: $name) {
1506 match v {
1507 $($name::$v)|+ $($(| $name::$x)+)? => {}
1508 }
1509 }
1510 };
1511 };
1512}
1513
1514closed_variants!(TargetType [Post, Comment, Message]);
1515closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1516closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1517closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1518closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1519closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1520closed_variants!(ModelRole [
1523 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1524 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor
1525]);
1526closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1527closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1528closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1529closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation, DataSet] never_written [Unknown]);
1530closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1531closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1532closed_variants!(KeyStatus [Active, Retired, Compromised]);
1533closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1534closed_variants!(OwedBy [Steward, Staff, Council]);
1535closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1536closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1537closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1538closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1539closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1540closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1541closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1542closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1543closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1544closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1545closed_variants!(OAuthScope [Read, Write]);
1546closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1547closed_variants!(MessageEncryption [E2ee, Server]);
1548closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1549closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1550closed_variants!(OperatorAuthMethod [Password, PasswordReset, PasswordAndPasskey, Passkey]);
1551closed_variants!(OperatorLoginMode [Password, PasswordAndPasskey, Passkey]);
1552closed_variants!(RemedyOutcome [Restored, SuspensionLifted, RecordOnly, Unrestorable, HumanRequired]);
1553closed_variants!(ThreadRole [Primary, Related]);
1554closed_variants!(ThreadAddedBy[Steward]);
1555closed_variants!(TrustTier [Standard, Trusted]);
1556
1557#[cfg(test)]
1558mod tests {
1559 use super::*;
1560
1561 #[test]
1562 fn target_type_serde_round_trip() {
1563 let val = TargetType::Post;
1564 let json = serde_json::to_string(&val).unwrap();
1565 assert_eq!(json, "\"post\"");
1566 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1567 assert_eq!(val, deserialized);
1568 }
1569
1570 #[test]
1571 fn target_type_display() {
1572 assert_eq!(TargetType::Post.to_string(), "post");
1573 assert_eq!(TargetType::Comment.to_string(), "comment");
1574 }
1575
1576 #[test]
1577 fn target_type_from_str() {
1578 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1579 assert_eq!(
1580 TargetType::from_str("comment").unwrap(),
1581 TargetType::Comment
1582 );
1583 }
1584
1585 #[test]
1586 fn moderation_tier_serde() {
1587 let tier = ModerationTier::Tier2;
1588 let json = serde_json::to_string(&tier).unwrap();
1589 assert_eq!(json, "\"2\"");
1590 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1591 assert_eq!(tier, deserialized);
1592 }
1593
1594 #[test]
1597 fn message_encryption_wire_values() {
1598 assert_eq!(
1599 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1600 "\"e2ee\""
1601 );
1602 assert_eq!(
1603 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1604 "\"server\""
1605 );
1606 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1607 assert_eq!(
1608 MessageEncryption::from_str("server").unwrap(),
1609 MessageEncryption::Server
1610 );
1611 }
1612
1613 #[test]
1614 fn search_mode_wire_values() {
1615 assert_eq!(
1616 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1617 "\"keyword\""
1618 );
1619 assert_eq!(
1620 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1621 "\"semantic\""
1622 );
1623 assert_eq!(
1624 SearchMode::from_str("semantic").unwrap(),
1625 SearchMode::Semantic
1626 );
1627 }
1628
1629 #[test]
1632 fn no_sort_orders_by_votes() {
1633 for gone in ["score", "controversial", "unpopular"] {
1634 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1635 assert!(
1636 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1637 .is_err(),
1638 "{gone}"
1639 );
1640 }
1641 assert!(ProposalSort::from_str("score").is_err());
1642 }
1643
1644 #[cfg(feature = "schemars")]
1645 #[test]
1646 fn feed_sort_schema_is_ref_free() {
1647 use schemars::JsonSchema;
1648
1649 assert!(<FeedSort as JsonSchema>::inline_schema());
1650
1651 let schema = schemars::schema_for!(FeedSort);
1652 let value = serde_json::to_value(&schema).unwrap();
1653 let blob = value.to_string();
1654 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1655 assert!(!blob.contains("$ref"), "no $ref: {value}");
1656 let values: Vec<&str> = value["enum"]
1657 .as_array()
1658 .expect("FeedSort should render as a flat `enum`")
1659 .iter()
1660 .filter_map(|v| v.as_str())
1661 .collect();
1662 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1663 }
1664
1665 #[cfg(feature = "schemars")]
1668 #[test]
1669 fn trash_enum_schemas_are_plain_enums() {
1670 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1671 assert!(T::inline_schema());
1672 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1673 let blob = value.to_string();
1674 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1675 assert!(value.get("oneOf").is_none(), "{value}");
1676 value["enum"]
1677 .as_array()
1678 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1679 .iter()
1680 .map(|v| v.as_str().unwrap().to_owned())
1681 .collect()
1682 }
1683 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1684 assert_eq!(
1685 values::<TrashMode>(),
1686 ["list", "restore", "delete_permanently"]
1687 );
1688 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1689 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1690 }
1691
1692 #[cfg(feature = "schemars")]
1695 #[test]
1696 fn obligation_enum_schemas_are_strict_safe() {
1697 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1698 assert!(T::inline_schema());
1699 let value = crate::responses::inline_schema_for::<T>();
1700 assert_eq!(
1701 crate::tools::strict_schema_violations(&value),
1702 Vec::<String>::new()
1703 );
1704 value["enum"]
1705 .as_array()
1706 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1707 .iter()
1708 .map(|v| v.as_str().unwrap().to_owned())
1709 .collect()
1710 }
1711 assert_eq!(
1712 values::<ObligationKind>(),
1713 [
1714 "carry_forward",
1715 "ratification",
1716 "review",
1717 "referral",
1718 "reveal",
1719 "task"
1720 ]
1721 );
1722 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1723 assert_eq!(
1724 values::<ObligationStatus>(),
1725 ["open", "lapsed", "discharged", "withdrawn"]
1726 );
1727 assert_eq!(
1728 values::<CalendarEventKind>(),
1729 ["sitting", "special_sitting"]
1730 );
1731 }
1732
1733 #[test]
1734 fn trash_enum_wire_values() {
1735 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1736 assert_eq!(
1737 "delete_permanently".parse::<TrashMode>().unwrap(),
1738 TrashMode::DeletePermanently
1739 );
1740 assert_eq!(TrashMode::default(), TrashMode::List);
1741 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1742 assert_eq!(ContentKind::Comment.to_string(), "comment");
1743 }
1744
1745 #[test]
1748 fn unknown_trash_causes_parse() {
1749 assert_eq!(
1750 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1751 RemovedBy::Unknown
1752 );
1753 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1754 assert_eq!(
1755 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1756 DeletedBy::Unknown
1757 );
1758 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1759 }
1760
1761 #[test]
1762 fn proposal_category_round_trip() {
1763 for cat in [
1764 ProposalCategory::Routine,
1765 ProposalCategory::Policy,
1766 ProposalCategory::Constitutional,
1767 ProposalCategory::Emergency,
1768 ] {
1769 let json = serde_json::to_string(&cat).unwrap();
1770 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1771 assert_eq!(cat, back);
1772 }
1773 }
1774
1775 #[test]
1778 fn governance_amendment_and_key_wire_values() {
1779 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1780 assert_eq!(
1781 GovernanceLogEntryType::KeyRotation.to_string(),
1782 "key_rotation"
1783 );
1784 assert_eq!(
1785 AmendmentKind::NonPrecedential.to_string(),
1786 "non_precedential"
1787 );
1788 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1789 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1790 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1791 assert_eq!(RecordVersion::Original.to_string(), "original");
1792 assert_eq!(
1793 "latest".parse::<RecordVersion>().unwrap(),
1794 RecordVersion::Latest
1795 );
1796 assert_eq!(
1797 "superseded".parse::<AmendmentKind>().unwrap(),
1798 AmendmentKind::Superseded
1799 );
1800 assert_eq!(Standing::default(), Standing::InForce);
1801 assert_eq!(Standing::InForce.to_string(), "in_force");
1802 assert_eq!(
1803 "compromised".parse::<KeyStatus>().unwrap(),
1804 KeyStatus::Compromised
1805 );
1806 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1807 }
1808
1809 #[test]
1812 fn obligation_and_calendar_wire_values() {
1813 assert_eq!(
1814 GovernanceLogEntryType::Obligation.to_string(),
1815 "obligation"
1816 );
1817 assert_eq!(GovernanceLogEntryType::DataSet.to_string(), "data_set");
1818 assert_eq!(
1819 "data_set".parse::<GovernanceLogEntryType>().unwrap(),
1820 GovernanceLogEntryType::DataSet
1821 );
1822 assert_eq!(
1824 serde_json::from_value::<GovernanceLogEntryType>(
1825 serde_json::json!("something_newer")
1826 )
1827 .unwrap(),
1828 GovernanceLogEntryType::Unknown
1829 );
1830 assert!(
1831 !GovernanceLogEntryType::ALL
1832 .contains(&GovernanceLogEntryType::Unknown)
1833 );
1834 let kinds: Vec<String> =
1835 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1836 assert_eq!(
1837 kinds,
1838 [
1839 "planned",
1840 "discharged",
1841 "lapsed",
1842 "rescheduled",
1843 "withdrawn"
1844 ]
1845 );
1846 assert!(AmendmentKind::Lapsed.is_obligation());
1847 assert!(!AmendmentKind::Revision.is_obligation());
1848 for (kind, wire) in [
1849 (ObligationKind::CarryForward, "carry_forward"),
1850 (ObligationKind::Ratification, "ratification"),
1851 (ObligationKind::Review, "review"),
1852 (ObligationKind::Referral, "referral"),
1853 (ObligationKind::Reveal, "reveal"),
1854 (ObligationKind::Task, "task"),
1855 ] {
1856 assert_eq!(kind.to_string(), wire);
1857 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1858 }
1859 for (who, wire) in [
1860 (OwedBy::Steward, "steward"),
1861 (OwedBy::Staff, "staff"),
1862 (OwedBy::Council, "council"),
1863 ] {
1864 assert_eq!(who.to_string(), wire);
1865 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1866 }
1867 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1868 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1869 assert!(ObligationStatus::Withdrawn.is_closed());
1870 assert!(!ObligationStatus::Lapsed.is_closed());
1871 assert_eq!(
1872 CalendarEventKind::SpecialSitting.to_string(),
1873 "special_sitting"
1874 );
1875 assert!("festival".parse::<CalendarEventKind>().is_err());
1878 assert_eq!(
1879 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1880 CalendarEventStatus::Cancelled
1881 );
1882 }
1883
1884 #[test]
1886 fn detail_level_wire_names() {
1887 for (level, wire) in [
1888 (DetailLevel::Summary, "summary"),
1889 (DetailLevel::Full, "full"),
1890 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1891 ] {
1892 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1893 assert_eq!(level.to_string(), wire);
1894 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1895 }
1896 }
1897
1898 #[cfg(feature = "schemars")]
1903 #[test]
1904 fn enum_json_schema_is_inlined() {
1905 use schemars::JsonSchema;
1906
1907 assert!(<TargetType as JsonSchema>::inline_schema());
1908 assert!(<FeedSort as JsonSchema>::inline_schema());
1909 assert!(<ProposalSort as JsonSchema>::inline_schema());
1910 assert!(<DetailLevel as JsonSchema>::inline_schema());
1911 assert!(<RecordVersion as JsonSchema>::inline_schema());
1912 assert!(<SearchMode as JsonSchema>::inline_schema());
1913 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1914 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1915 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1916 assert!(<Standing as JsonSchema>::inline_schema());
1917 assert!(<CitationRelation as JsonSchema>::inline_schema());
1918 assert!(<KeyStatus as JsonSchema>::inline_schema());
1919 assert!(<OAuthScope as JsonSchema>::inline_schema());
1920 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1921 assert!(<ModerationTier as JsonSchema>::inline_schema());
1922 assert!(<ObligationKind as JsonSchema>::inline_schema());
1923 assert!(<OwedBy as JsonSchema>::inline_schema());
1924 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1925 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1926 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1927
1928 #[derive(schemars::JsonSchema)]
1929 #[allow(dead_code)]
1930 struct Container {
1931 target_type: TargetType,
1932 sort: Option<FeedSort>,
1933 proposal_sort: Option<ProposalSort>,
1934 category: Option<ProposalCategory>,
1935 detail: Option<DetailLevel>,
1936 version: Option<RecordVersion>,
1937 search_mode: Option<SearchMode>,
1938 }
1939
1940 let schema = schemars::schema_for!(Container);
1941 let value = serde_json::to_value(&schema).unwrap();
1942 let blob = value.to_string();
1943
1944 assert!(
1945 value.get("$defs").is_none(),
1946 "no $defs should be emitted for enum-only container; got schema: {value}"
1947 );
1948 assert!(
1949 !blob.contains("$ref"),
1950 "enum container schema must contain no $ref anywhere; got: {value}"
1951 );
1952
1953 let target_type_enum = value["properties"]["target_type"]["enum"]
1955 .as_array()
1956 .expect("target_type should have inline `enum` array");
1957 assert!(
1958 target_type_enum
1959 .contains(&serde_json::Value::String("post".into()))
1960 );
1961 assert!(
1962 target_type_enum
1963 .contains(&serde_json::Value::String("comment".into()))
1964 );
1965 }
1966
1967 #[cfg(feature = "schemars")]
1973 #[test]
1974 fn search_mode_schema_is_ref_free() {
1975 use schemars::JsonSchema;
1976
1977 assert!(<SearchMode as JsonSchema>::inline_schema());
1978
1979 let schema = schemars::schema_for!(SearchMode);
1980 let value = serde_json::to_value(&schema).unwrap();
1981 let blob = value.to_string();
1982 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1983 assert!(!blob.contains("$ref"), "no $ref: {value}");
1984
1985 let variants = value["oneOf"]
1991 .as_array()
1992 .expect("SearchMode should have an inline `oneOf` array");
1993 let consts: Vec<&str> = variants
1994 .iter()
1995 .filter_map(|v| v["const"].as_str())
1996 .collect();
1997 assert!(consts.contains(&"keyword"), "{value}");
1998 assert!(consts.contains(&"semantic"), "{value}");
1999 }
2000}