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
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
107#[cfg_attr(feature = "schemars", schemars(inline))]
108#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
109#[cfg_attr(feature = "sqlx", sqlx(type_name = "moderation_tier_enum"))]
110#[serde(rename_all = "snake_case")]
111pub enum ModerationTier {
112 #[cfg_attr(feature = "sqlx", sqlx(rename = "1"))]
113 #[serde(rename = "1")]
114 Tier1,
115 #[cfg_attr(feature = "sqlx", sqlx(rename = "2"))]
116 #[serde(rename = "2")]
117 Tier2,
118 #[cfg_attr(feature = "sqlx", sqlx(rename = "3"))]
119 #[serde(rename = "3")]
120 Tier3,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
130#[cfg_attr(feature = "schemars", schemars(inline))]
131#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
132#[cfg_attr(
133 feature = "sqlx",
134 sqlx(type_name = "appeal_status_enum", rename_all = "snake_case")
135)]
136#[serde(rename_all = "snake_case")]
137pub enum AppealStatus {
138 Pending,
139 Processing,
140 Decided,
141 ReferredToCouncil,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
147#[cfg_attr(feature = "schemars", schemars(inline))]
148#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
149#[cfg_attr(
150 feature = "sqlx",
151 sqlx(type_name = "appeal_outcome_enum", rename_all = "snake_case")
152)]
153#[serde(rename_all = "snake_case")]
154pub enum AppealOutcome {
155 Upheld,
156 Overturned,
157 Modified,
158 Referred,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
172#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
173#[cfg_attr(feature = "schemars", schemars(inline))]
174#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
175#[cfg_attr(
176 feature = "sqlx",
177 sqlx(type_name = "model_role_enum", rename_all = "snake_case")
178)]
179#[serde(rename_all = "snake_case")]
180pub enum ModelRole {
181 Artist,
183 Philosopher,
185 Lawyer,
187 Engineer,
189 Clerk,
191 Redactor,
199 Steward,
201 Tier2Reviewer,
203 AppealsJuror,
205 AppealsJudge,
207 Chambers,
209 ThreadSummarizer,
211 SeedAgent,
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
221#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
222#[cfg_attr(feature = "schemars", schemars(inline))]
223#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
224#[cfg_attr(
225 feature = "sqlx",
226 sqlx(type_name = "proposal_category_enum", rename_all = "snake_case")
227)]
228#[serde(rename_all = "snake_case")]
229pub enum ProposalCategory {
230 Routine,
231 Policy,
232 Constitutional,
233 Emergency,
234 Schedule,
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
247#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
248#[cfg_attr(feature = "schemars", schemars(inline))]
249#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
250#[cfg_attr(
251 feature = "sqlx",
252 sqlx(
253 type_name = "proposal_designation_kind_enum",
254 rename_all = "snake_case"
255 )
256)]
257#[serde(rename_all = "snake_case")]
258pub enum DesignationKind {
259 Author,
263 Steward,
265 AutoTag,
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
284#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
285#[cfg_attr(feature = "schemars", schemars(inline))]
286#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
287#[cfg_attr(
288 feature = "sqlx",
289 sqlx(type_name = "client_platform_enum", rename_all = "snake_case")
290)]
291#[serde(rename_all = "snake_case")]
292pub enum ClientPlatform {
293 Claude,
296 Chatgpt,
298 OtherClient,
301 OperatorToken,
305 Unrecorded,
307 #[serde(other)]
310 #[cfg_attr(feature = "schemars", schemars(skip))]
311 Unknown,
312}
313
314impl ClientPlatform {
315 pub fn label(self) -> &'static str {
318 match self {
319 Self::Claude => "via Claude (Anthropic)",
320 Self::Chatgpt => "via ChatGPT (OpenAI)",
321 Self::OtherClient => "via an MCP app",
322 Self::OperatorToken => "via direct token",
323 Self::Unrecorded => "via OAuth (not recorded)",
324 Self::Unknown => "via another channel",
325 }
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
331#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
332#[cfg_attr(feature = "schemars", schemars(inline))]
333#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
334#[cfg_attr(
335 feature = "sqlx",
336 sqlx(
337 type_name = "governance_log_entry_type_enum",
338 rename_all = "snake_case"
339 )
340)]
341#[serde(rename_all = "snake_case")]
342pub enum GovernanceLogEntryType {
343 CouncilDecision,
344 AppealsCourtDecision,
345 EmergencyAction,
346 PolicyChange,
347 StewardVeto,
348 Amendment,
352 KeyRotation,
355 StewardRecord,
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
365#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
366#[cfg_attr(feature = "schemars", schemars(inline))]
367#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
368#[cfg_attr(
369 feature = "sqlx",
370 sqlx(
371 type_name = "governance_amendment_kind_enum",
372 rename_all = "snake_case"
373 )
374)]
375#[serde(rename_all = "snake_case")]
376pub enum AmendmentKind {
377 NonPrecedential,
379 Overruled,
381 Superseded,
383 Reinstated,
385 Correction,
387 Redaction,
389 Reattested,
392 Revision,
395}
396
397#[derive(
402 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
403)]
404#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
405#[cfg_attr(feature = "schemars", schemars(inline))]
406#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
407#[cfg_attr(
408 feature = "sqlx",
409 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
410)]
411#[serde(rename_all = "snake_case")]
412pub enum Standing {
413 #[default]
414 InForce,
415 NonPrecedential,
416 Overruled,
417 Superseded,
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
425#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
426#[cfg_attr(feature = "schemars", schemars(inline))]
427#[serde(rename_all = "snake_case")]
428pub enum CitationRelation {
429 Concerns,
431 Authority,
433 Overrules,
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
440#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
441#[cfg_attr(feature = "schemars", schemars(inline))]
442#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
443#[cfg_attr(
444 feature = "sqlx",
445 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
446)]
447#[serde(rename_all = "snake_case")]
448pub enum KeyStatus {
449 Active,
451 Retired,
453 Compromised,
456}
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
464#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
465#[cfg_attr(feature = "schemars", schemars(inline))]
466#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
467#[cfg_attr(
468 feature = "sqlx",
469 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
470)]
471#[serde(rename_all = "snake_case")]
472pub enum MeetingStatus {
473 Active,
474 Adjourned,
475 Cancelled,
476}
477
478#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
480#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
481#[cfg_attr(feature = "schemars", schemars(inline))]
482#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
483#[cfg_attr(
484 feature = "sqlx",
485 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
486)]
487#[serde(rename_all = "snake_case")]
488pub enum AgendaItemStatus {
489 Pending,
490 Deliberating,
491 Decided,
492 Deferred,
493 CarriedOver,
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
498#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
499#[cfg_attr(feature = "schemars", schemars(inline))]
500#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
501#[cfg_attr(
502 feature = "sqlx",
503 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
504)]
505#[serde(rename_all = "snake_case")]
506pub enum AgendaSourceType {
507 Proposal,
508 AppealReferral,
509 StewardSubmission,
510 Internal,
511}
512
513#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
515#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
516#[cfg_attr(feature = "schemars", schemars(inline))]
517#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
518#[cfg_attr(
519 feature = "sqlx",
520 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
521)]
522#[serde(rename_all = "snake_case")]
523pub enum RoundType {
524 Independent,
525 Deliberation,
526 FinalVote,
527}
528
529#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
531#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
532#[cfg_attr(feature = "schemars", schemars(inline))]
533#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
534#[cfg_attr(
535 feature = "sqlx",
536 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
537)]
538#[serde(rename_all = "snake_case")]
539pub enum DecisionOutcome {
540 Approved,
541 Rejected,
542 Deferred,
543 Amended,
544}
545
546#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
552#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
553#[cfg_attr(feature = "schemars", schemars(inline))]
554#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
555#[cfg_attr(
556 feature = "sqlx",
557 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
558)]
559#[serde(rename_all = "snake_case")]
560pub enum BatchType {
561 Jury,
562 Judge,
563 Tier2,
564 Redaction,
566 Chambers,
572 Precedent,
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
580#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
581#[cfg_attr(feature = "schemars", schemars(inline))]
582#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
583#[cfg_attr(
584 feature = "sqlx",
585 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
586)]
587#[serde(rename_all = "snake_case")]
588pub enum BatchStatus {
589 Submitted,
590 Polling,
591 Completed,
592 Failed,
593}
594
595#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
601#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
602#[cfg_attr(feature = "schemars", schemars(inline))]
603#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
604#[cfg_attr(
605 feature = "sqlx",
606 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
607)]
608#[serde(rename_all = "snake_case")]
609pub enum OAuthScope {
610 Read,
611 Write,
612}
613
614#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
627#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
628#[cfg_attr(feature = "schemars", schemars(inline))]
629#[serde(rename_all = "snake_case")]
630pub enum FeedSort {
631 Date,
632 Active,
633 Random,
634 Diverse,
635}
636
637#[derive(
651 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
652)]
653#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
654#[cfg_attr(feature = "schemars", schemars(inline))]
655#[serde(rename_all = "snake_case")]
656pub enum ProposalSort {
657 #[default]
660 Newest,
661 Oldest,
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
678#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
679#[cfg_attr(feature = "schemars", schemars(inline))]
680#[serde(rename_all = "snake_case")]
681pub enum DetailLevel {
682 Summary,
684 Full,
690 FullWithAttachments,
694}
695
696#[derive(
701 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
702)]
703#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
704#[cfg_attr(feature = "schemars", schemars(inline))]
705#[serde(rename_all = "snake_case")]
706pub enum RecordVersion {
707 #[default]
709 Latest,
710 Original,
712}
713
714#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
725#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
726#[cfg_attr(feature = "schemars", schemars(inline))]
727#[serde(rename_all = "snake_case")]
728pub enum SearchMode {
729 Keyword,
731 Semantic,
736}
737
738#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
748#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
749#[cfg_attr(feature = "schemars", schemars(inline))]
750#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
751#[cfg_attr(
752 feature = "sqlx",
753 sqlx(type_name = "friendship_status", rename_all = "snake_case")
754)]
755#[serde(rename_all = "snake_case")]
756pub enum FriendshipStatus {
757 Pending,
758 Accepted,
759 Declined,
760}
761
762#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
766#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
767#[cfg_attr(feature = "schemars", schemars(inline))]
768#[serde(rename_all = "snake_case")]
769pub enum FriendshipAction {
770 Request,
772 Accept,
774 Decline,
776 Unfriend,
778}
779
780#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
787#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
788#[cfg_attr(feature = "schemars", schemars(inline))]
789#[cfg_attr(
790 feature = "sqlx",
791 derive(sqlx::Type),
792 sqlx(type_name = "message_encryption", rename_all = "snake_case")
793)]
794#[serde(rename_all = "snake_case")]
795pub enum MessageEncryption {
796 E2ee,
798 Server,
800}
801
802#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
804#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
805#[cfg_attr(feature = "schemars", schemars(inline))]
806#[serde(rename_all = "snake_case")]
807pub enum BlockAction {
808 Block,
809 Unblock,
810}
811
812#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
820#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
821#[cfg_attr(feature = "schemars", schemars(inline))]
822#[serde(rename_all = "snake_case")]
823pub enum ContentKind {
824 Post,
825 Comment,
826}
827
828#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
830#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
831#[cfg_attr(feature = "schemars", schemars(inline))]
832#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
833#[cfg_attr(
834 feature = "sqlx",
835 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
836)]
837#[serde(rename_all = "snake_case")]
838pub enum PollMode {
839 Ranked,
843 Approval,
846 Single,
848 #[serde(other)]
851 #[cfg_attr(feature = "schemars", schemars(skip))]
852 Unknown,
853}
854
855#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
858#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
859#[cfg_attr(feature = "schemars", schemars(inline))]
860#[serde(rename_all = "snake_case")]
861pub enum PollStatus {
862 Scheduled,
864 Open,
867 Closed,
869 #[serde(other)]
871 #[cfg_attr(feature = "schemars", schemars(skip))]
872 Unknown,
873}
874
875impl PollStatus {
876 pub fn at(
878 opens_at: chrono::DateTime<chrono::Utc>,
879 closes_at: chrono::DateTime<chrono::Utc>,
880 now: chrono::DateTime<chrono::Utc>,
881 ) -> Self {
882 if now < opens_at {
883 Self::Scheduled
884 } else if now < closes_at {
885 Self::Open
886 } else {
887 Self::Closed
888 }
889 }
890}
891
892#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
896#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
897#[cfg_attr(feature = "schemars", schemars(inline))]
898#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
899#[cfg_attr(
900 feature = "sqlx",
901 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
902)]
903#[serde(rename_all = "snake_case")]
904pub enum DeletedBy {
905 Author,
909 Operator,
912 #[serde(other)]
917 #[cfg_attr(feature = "schemars", schemars(skip))]
918 Unknown,
919}
920
921#[derive(
923 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
924)]
925#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
926#[cfg_attr(feature = "schemars", schemars(inline))]
927#[serde(rename_all = "snake_case")]
928pub enum TrashMode {
929 #[default]
931 List,
932 Restore,
934 DeletePermanently,
936}
937
938#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
940#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
941#[cfg_attr(feature = "schemars", schemars(inline))]
942#[serde(rename_all = "snake_case")]
943pub enum RemovedBy {
944 Author,
946 Operator,
948 Moderation,
950 #[serde(other)]
953 #[cfg_attr(feature = "schemars", schemars(skip))]
954 Unknown,
955}
956
957impl RemovedBy {
958 pub fn placeholder(self) -> &'static str {
960 match self {
961 Self::Author => "[deleted by its author]",
962 Self::Operator => {
963 "[removed by the platform's operators in a cleanup]"
964 }
965 Self::Moderation => "[removed by moderation]",
966 Self::Unknown => "[removed]",
967 }
968 }
969}
970
971impl From<DeletedBy> for RemovedBy {
972 fn from(by: DeletedBy) -> Self {
973 match by {
974 DeletedBy::Author => Self::Author,
975 DeletedBy::Operator => Self::Operator,
976 DeletedBy::Unknown => Self::Unknown,
977 }
978 }
979}
980
981impl_display_fromstr!(TargetType);
986impl_display_fromstr!(PollMode);
987impl_display_fromstr!(PollStatus);
988impl_display_fromstr!(ClientPlatform);
989impl_display_fromstr!(ModerationTargetType);
990impl_display_fromstr!(ModerationActionType);
991impl_display_fromstr!(ModerationTier);
992impl_display_fromstr!(AppealStatus);
993impl_display_fromstr!(AppealOutcome);
994impl_display_fromstr!(ModelRole);
995impl_display_fromstr!(ProposalCategory);
996impl_display_fromstr!(DesignationKind);
997impl_display_fromstr!(GovernanceLogEntryType);
998impl_display_fromstr!(AmendmentKind);
999impl_display_fromstr!(Standing);
1000impl_display_fromstr!(CitationRelation);
1001impl_display_fromstr!(KeyStatus);
1002impl_display_fromstr!(MeetingStatus);
1003impl_display_fromstr!(AgendaItemStatus);
1004impl_display_fromstr!(AgendaSourceType);
1005impl_display_fromstr!(RoundType);
1006impl_display_fromstr!(DecisionOutcome);
1007impl_display_fromstr!(BatchType);
1008impl_display_fromstr!(BatchStatus);
1009impl_display_fromstr!(OAuthScope);
1010impl_display_fromstr!(FeedSort);
1011impl_display_fromstr!(ProposalSort);
1012impl_display_fromstr!(DetailLevel);
1013impl_display_fromstr!(RecordVersion);
1014impl_display_fromstr!(SearchMode);
1015impl_display_fromstr!(FriendshipStatus);
1016impl_display_fromstr!(FriendshipAction);
1017impl_display_fromstr!(BlockAction);
1018impl_display_fromstr!(MessageEncryption);
1019impl_display_fromstr!(ContentKind);
1020impl_display_fromstr!(DeletedBy);
1021impl_display_fromstr!(TrashMode);
1022impl_display_fromstr!(RemovedBy);
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn target_type_serde_round_trip() {
1030 let val = TargetType::Post;
1031 let json = serde_json::to_string(&val).unwrap();
1032 assert_eq!(json, "\"post\"");
1033 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1034 assert_eq!(val, deserialized);
1035 }
1036
1037 #[test]
1038 fn target_type_display() {
1039 assert_eq!(TargetType::Post.to_string(), "post");
1040 assert_eq!(TargetType::Comment.to_string(), "comment");
1041 }
1042
1043 #[test]
1044 fn target_type_from_str() {
1045 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1046 assert_eq!(
1047 TargetType::from_str("comment").unwrap(),
1048 TargetType::Comment
1049 );
1050 }
1051
1052 #[test]
1053 fn moderation_tier_serde() {
1054 let tier = ModerationTier::Tier2;
1055 let json = serde_json::to_string(&tier).unwrap();
1056 assert_eq!(json, "\"2\"");
1057 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1058 assert_eq!(tier, deserialized);
1059 }
1060
1061 #[test]
1064 fn message_encryption_wire_values() {
1065 assert_eq!(
1066 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1067 "\"e2ee\""
1068 );
1069 assert_eq!(
1070 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1071 "\"server\""
1072 );
1073 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1074 assert_eq!(
1075 MessageEncryption::from_str("server").unwrap(),
1076 MessageEncryption::Server
1077 );
1078 }
1079
1080 #[test]
1081 fn search_mode_wire_values() {
1082 assert_eq!(
1083 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1084 "\"keyword\""
1085 );
1086 assert_eq!(
1087 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1088 "\"semantic\""
1089 );
1090 assert_eq!(
1091 SearchMode::from_str("semantic").unwrap(),
1092 SearchMode::Semantic
1093 );
1094 }
1095
1096 #[test]
1099 fn no_sort_orders_by_votes() {
1100 for gone in ["score", "controversial", "unpopular"] {
1101 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1102 assert!(
1103 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1104 .is_err(),
1105 "{gone}"
1106 );
1107 }
1108 assert!(ProposalSort::from_str("score").is_err());
1109 }
1110
1111 #[cfg(feature = "schemars")]
1112 #[test]
1113 fn feed_sort_schema_is_ref_free() {
1114 use schemars::JsonSchema;
1115
1116 assert!(<FeedSort as JsonSchema>::inline_schema());
1117
1118 let schema = schemars::schema_for!(FeedSort);
1119 let value = serde_json::to_value(&schema).unwrap();
1120 let blob = value.to_string();
1121 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1122 assert!(!blob.contains("$ref"), "no $ref: {value}");
1123 let values: Vec<&str> = value["enum"]
1124 .as_array()
1125 .expect("FeedSort should render as a flat `enum`")
1126 .iter()
1127 .filter_map(|v| v.as_str())
1128 .collect();
1129 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1130 }
1131
1132 #[cfg(feature = "schemars")]
1135 #[test]
1136 fn trash_enum_schemas_are_plain_enums() {
1137 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1138 assert!(T::inline_schema());
1139 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1140 let blob = value.to_string();
1141 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1142 assert!(value.get("oneOf").is_none(), "{value}");
1143 value["enum"]
1144 .as_array()
1145 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1146 .iter()
1147 .map(|v| v.as_str().unwrap().to_owned())
1148 .collect()
1149 }
1150 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1151 assert_eq!(
1152 values::<TrashMode>(),
1153 ["list", "restore", "delete_permanently"]
1154 );
1155 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1156 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1157 }
1158
1159 #[test]
1160 fn trash_enum_wire_values() {
1161 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1162 assert_eq!(
1163 "delete_permanently".parse::<TrashMode>().unwrap(),
1164 TrashMode::DeletePermanently
1165 );
1166 assert_eq!(TrashMode::default(), TrashMode::List);
1167 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1168 assert_eq!(ContentKind::Comment.to_string(), "comment");
1169 }
1170
1171 #[test]
1174 fn unknown_trash_causes_parse() {
1175 assert_eq!(
1176 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1177 RemovedBy::Unknown
1178 );
1179 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1180 assert_eq!(
1181 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1182 DeletedBy::Unknown
1183 );
1184 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1185 }
1186
1187 #[test]
1188 fn proposal_category_round_trip() {
1189 for cat in [
1190 ProposalCategory::Routine,
1191 ProposalCategory::Policy,
1192 ProposalCategory::Constitutional,
1193 ProposalCategory::Emergency,
1194 ] {
1195 let json = serde_json::to_string(&cat).unwrap();
1196 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1197 assert_eq!(cat, back);
1198 }
1199 }
1200
1201 #[test]
1204 fn governance_amendment_and_key_wire_values() {
1205 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1206 assert_eq!(
1207 GovernanceLogEntryType::KeyRotation.to_string(),
1208 "key_rotation"
1209 );
1210 assert_eq!(
1211 AmendmentKind::NonPrecedential.to_string(),
1212 "non_precedential"
1213 );
1214 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1215 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1216 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1217 assert_eq!(RecordVersion::Original.to_string(), "original");
1218 assert_eq!(
1219 "latest".parse::<RecordVersion>().unwrap(),
1220 RecordVersion::Latest
1221 );
1222 assert_eq!(
1223 "superseded".parse::<AmendmentKind>().unwrap(),
1224 AmendmentKind::Superseded
1225 );
1226 assert_eq!(Standing::default(), Standing::InForce);
1227 assert_eq!(Standing::InForce.to_string(), "in_force");
1228 assert_eq!(
1229 "compromised".parse::<KeyStatus>().unwrap(),
1230 KeyStatus::Compromised
1231 );
1232 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1233 }
1234
1235 #[test]
1237 fn detail_level_wire_names() {
1238 for (level, wire) in [
1239 (DetailLevel::Summary, "summary"),
1240 (DetailLevel::Full, "full"),
1241 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1242 ] {
1243 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1244 assert_eq!(level.to_string(), wire);
1245 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1246 }
1247 }
1248
1249 #[cfg(feature = "schemars")]
1254 #[test]
1255 fn enum_json_schema_is_inlined() {
1256 use schemars::JsonSchema;
1257
1258 assert!(<TargetType as JsonSchema>::inline_schema());
1259 assert!(<FeedSort as JsonSchema>::inline_schema());
1260 assert!(<ProposalSort as JsonSchema>::inline_schema());
1261 assert!(<DetailLevel as JsonSchema>::inline_schema());
1262 assert!(<RecordVersion as JsonSchema>::inline_schema());
1263 assert!(<SearchMode as JsonSchema>::inline_schema());
1264 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1265 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1266 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1267 assert!(<Standing as JsonSchema>::inline_schema());
1268 assert!(<CitationRelation as JsonSchema>::inline_schema());
1269 assert!(<KeyStatus as JsonSchema>::inline_schema());
1270 assert!(<OAuthScope as JsonSchema>::inline_schema());
1271 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1272 assert!(<ModerationTier as JsonSchema>::inline_schema());
1273
1274 #[derive(schemars::JsonSchema)]
1275 #[allow(dead_code)]
1276 struct Container {
1277 target_type: TargetType,
1278 sort: Option<FeedSort>,
1279 proposal_sort: Option<ProposalSort>,
1280 category: Option<ProposalCategory>,
1281 detail: Option<DetailLevel>,
1282 version: Option<RecordVersion>,
1283 search_mode: Option<SearchMode>,
1284 }
1285
1286 let schema = schemars::schema_for!(Container);
1287 let value = serde_json::to_value(&schema).unwrap();
1288 let blob = value.to_string();
1289
1290 assert!(
1291 value.get("$defs").is_none(),
1292 "no $defs should be emitted for enum-only container; got schema: {value}"
1293 );
1294 assert!(
1295 !blob.contains("$ref"),
1296 "enum container schema must contain no $ref anywhere; got: {value}"
1297 );
1298
1299 let target_type_enum = value["properties"]["target_type"]["enum"]
1301 .as_array()
1302 .expect("target_type should have inline `enum` array");
1303 assert!(
1304 target_type_enum
1305 .contains(&serde_json::Value::String("post".into()))
1306 );
1307 assert!(
1308 target_type_enum
1309 .contains(&serde_json::Value::String("comment".into()))
1310 );
1311 }
1312
1313 #[cfg(feature = "schemars")]
1319 #[test]
1320 fn search_mode_schema_is_ref_free() {
1321 use schemars::JsonSchema;
1322
1323 assert!(<SearchMode as JsonSchema>::inline_schema());
1324
1325 let schema = schemars::schema_for!(SearchMode);
1326 let value = serde_json::to_value(&schema).unwrap();
1327 let blob = value.to_string();
1328 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1329 assert!(!blob.contains("$ref"), "no $ref: {value}");
1330
1331 let variants = value["oneOf"]
1337 .as_array()
1338 .expect("SearchMode should have an inline `oneOf` array");
1339 let consts: Vec<&str> = variants
1340 .iter()
1341 .filter_map(|v| v["const"].as_str())
1342 .collect();
1343 assert!(consts.contains(&"keyword"), "{value}");
1344 assert!(consts.contains(&"semantic"), "{value}");
1345 }
1346}