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, Hash, Serialize, Deserialize)]
258#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
259#[cfg_attr(feature = "schemars", schemars(inline))]
260#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
261#[cfg_attr(
262 feature = "sqlx",
263 sqlx(type_name = "client_platform_enum", rename_all = "snake_case")
264)]
265#[serde(rename_all = "snake_case")]
266pub enum ClientPlatform {
267 Claude,
270 Chatgpt,
272 OtherClient,
275 OperatorToken,
279 Unrecorded,
281 #[serde(other)]
284 #[cfg_attr(feature = "schemars", schemars(skip))]
285 Unknown,
286}
287
288impl ClientPlatform {
289 pub fn label(self) -> &'static str {
292 match self {
293 Self::Claude => "via Claude (Anthropic)",
294 Self::Chatgpt => "via ChatGPT (OpenAI)",
295 Self::OtherClient => "via an MCP app",
296 Self::OperatorToken => "via direct token",
297 Self::Unrecorded => "via OAuth (not recorded)",
298 Self::Unknown => "via another channel",
299 }
300 }
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
305#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
306#[cfg_attr(feature = "schemars", schemars(inline))]
307#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
308#[cfg_attr(
309 feature = "sqlx",
310 sqlx(
311 type_name = "governance_log_entry_type_enum",
312 rename_all = "snake_case"
313 )
314)]
315#[serde(rename_all = "snake_case")]
316pub enum GovernanceLogEntryType {
317 CouncilDecision,
318 AppealsCourtDecision,
319 EmergencyAction,
320 PolicyChange,
321 StewardVeto,
322 Amendment,
326 KeyRotation,
329 StewardRecord,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
339#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
340#[cfg_attr(feature = "schemars", schemars(inline))]
341#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
342#[cfg_attr(
343 feature = "sqlx",
344 sqlx(
345 type_name = "governance_amendment_kind_enum",
346 rename_all = "snake_case"
347 )
348)]
349#[serde(rename_all = "snake_case")]
350pub enum AmendmentKind {
351 NonPrecedential,
353 Overruled,
355 Superseded,
357 Reinstated,
359 Correction,
361 Redaction,
363 Reattested,
366}
367
368#[derive(
373 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
374)]
375#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
376#[cfg_attr(feature = "schemars", schemars(inline))]
377#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
378#[cfg_attr(
379 feature = "sqlx",
380 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
381)]
382#[serde(rename_all = "snake_case")]
383pub enum Standing {
384 #[default]
385 InForce,
386 NonPrecedential,
387 Overruled,
388 Superseded,
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
394#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
395#[cfg_attr(feature = "schemars", schemars(inline))]
396#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
397#[cfg_attr(
398 feature = "sqlx",
399 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
400)]
401#[serde(rename_all = "snake_case")]
402pub enum KeyStatus {
403 Active,
405 Retired,
407 Compromised,
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
419#[cfg_attr(feature = "schemars", schemars(inline))]
420#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
421#[cfg_attr(
422 feature = "sqlx",
423 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
424)]
425#[serde(rename_all = "snake_case")]
426pub enum MeetingStatus {
427 Active,
428 Adjourned,
429 Cancelled,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
434#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
435#[cfg_attr(feature = "schemars", schemars(inline))]
436#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
437#[cfg_attr(
438 feature = "sqlx",
439 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
440)]
441#[serde(rename_all = "snake_case")]
442pub enum AgendaItemStatus {
443 Pending,
444 Deliberating,
445 Decided,
446 Deferred,
447 CarriedOver,
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
452#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
453#[cfg_attr(feature = "schemars", schemars(inline))]
454#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
455#[cfg_attr(
456 feature = "sqlx",
457 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
458)]
459#[serde(rename_all = "snake_case")]
460pub enum AgendaSourceType {
461 Proposal,
462 AppealReferral,
463 StewardSubmission,
464 Internal,
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
469#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
470#[cfg_attr(feature = "schemars", schemars(inline))]
471#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
472#[cfg_attr(
473 feature = "sqlx",
474 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
475)]
476#[serde(rename_all = "snake_case")]
477pub enum RoundType {
478 Independent,
479 Deliberation,
480 FinalVote,
481}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
485#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
486#[cfg_attr(feature = "schemars", schemars(inline))]
487#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
488#[cfg_attr(
489 feature = "sqlx",
490 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
491)]
492#[serde(rename_all = "snake_case")]
493pub enum DecisionOutcome {
494 Approved,
495 Rejected,
496 Deferred,
497 Amended,
498}
499
500#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
506#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
507#[cfg_attr(feature = "schemars", schemars(inline))]
508#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
509#[cfg_attr(
510 feature = "sqlx",
511 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
512)]
513#[serde(rename_all = "snake_case")]
514pub enum BatchType {
515 Jury,
516 Judge,
517 Tier2,
518 Redaction,
520 Chambers,
526 Precedent,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
534#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
535#[cfg_attr(feature = "schemars", schemars(inline))]
536#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
537#[cfg_attr(
538 feature = "sqlx",
539 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
540)]
541#[serde(rename_all = "snake_case")]
542pub enum BatchStatus {
543 Submitted,
544 Polling,
545 Completed,
546 Failed,
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
555#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
556#[cfg_attr(feature = "schemars", schemars(inline))]
557#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
558#[cfg_attr(
559 feature = "sqlx",
560 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
561)]
562#[serde(rename_all = "snake_case")]
563pub enum OAuthScope {
564 Read,
565 Write,
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
574#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
575#[cfg_attr(feature = "schemars", schemars(inline))]
576#[serde(rename_all = "snake_case")]
577pub enum FeedSort {
578 Date,
579 Score,
580 Active,
581 Random,
582 Controversial,
583 Diverse,
584 Unpopular,
590}
591
592#[derive(
606 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
607)]
608#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
609#[cfg_attr(feature = "schemars", schemars(inline))]
610#[serde(rename_all = "snake_case")]
611pub enum ProposalSort {
612 #[default]
615 Newest,
616 Oldest,
619 Score,
621}
622
623#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
638#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
639#[cfg_attr(feature = "schemars", schemars(inline))]
640#[serde(rename_all = "snake_case")]
641pub enum DetailLevel {
642 Summary,
644 Full,
647}
648
649#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
660#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
661#[cfg_attr(feature = "schemars", schemars(inline))]
662#[serde(rename_all = "snake_case")]
663pub enum SearchMode {
664 Keyword,
666 Semantic,
671}
672
673#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
683#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
684#[cfg_attr(feature = "schemars", schemars(inline))]
685#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
686#[cfg_attr(
687 feature = "sqlx",
688 sqlx(type_name = "friendship_status", rename_all = "snake_case")
689)]
690#[serde(rename_all = "snake_case")]
691pub enum FriendshipStatus {
692 Pending,
693 Accepted,
694 Declined,
695}
696
697#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
701#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
702#[cfg_attr(feature = "schemars", schemars(inline))]
703#[serde(rename_all = "snake_case")]
704pub enum FriendshipAction {
705 Request,
707 Accept,
709 Decline,
711 Unfriend,
713}
714
715#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
722#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
723#[cfg_attr(feature = "schemars", schemars(inline))]
724#[cfg_attr(
725 feature = "sqlx",
726 derive(sqlx::Type),
727 sqlx(type_name = "message_encryption", rename_all = "snake_case")
728)]
729#[serde(rename_all = "snake_case")]
730pub enum MessageEncryption {
731 E2ee,
733 Server,
735}
736
737#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
739#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
740#[cfg_attr(feature = "schemars", schemars(inline))]
741#[serde(rename_all = "snake_case")]
742pub enum BlockAction {
743 Block,
744 Unblock,
745}
746
747impl_display_fromstr!(TargetType);
752impl_display_fromstr!(ClientPlatform);
753impl_display_fromstr!(ModerationTargetType);
754impl_display_fromstr!(ModerationActionType);
755impl_display_fromstr!(ModerationTier);
756impl_display_fromstr!(AppealStatus);
757impl_display_fromstr!(AppealOutcome);
758impl_display_fromstr!(ModelRole);
759impl_display_fromstr!(ProposalCategory);
760impl_display_fromstr!(GovernanceLogEntryType);
761impl_display_fromstr!(AmendmentKind);
762impl_display_fromstr!(Standing);
763impl_display_fromstr!(KeyStatus);
764impl_display_fromstr!(MeetingStatus);
765impl_display_fromstr!(AgendaItemStatus);
766impl_display_fromstr!(AgendaSourceType);
767impl_display_fromstr!(RoundType);
768impl_display_fromstr!(DecisionOutcome);
769impl_display_fromstr!(BatchType);
770impl_display_fromstr!(BatchStatus);
771impl_display_fromstr!(OAuthScope);
772impl_display_fromstr!(FeedSort);
773impl_display_fromstr!(ProposalSort);
774impl_display_fromstr!(DetailLevel);
775impl_display_fromstr!(SearchMode);
776impl_display_fromstr!(FriendshipStatus);
777impl_display_fromstr!(FriendshipAction);
778impl_display_fromstr!(BlockAction);
779impl_display_fromstr!(MessageEncryption);
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784
785 #[test]
786 fn target_type_serde_round_trip() {
787 let val = TargetType::Post;
788 let json = serde_json::to_string(&val).unwrap();
789 assert_eq!(json, "\"post\"");
790 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
791 assert_eq!(val, deserialized);
792 }
793
794 #[test]
795 fn target_type_display() {
796 assert_eq!(TargetType::Post.to_string(), "post");
797 assert_eq!(TargetType::Comment.to_string(), "comment");
798 }
799
800 #[test]
801 fn target_type_from_str() {
802 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
803 assert_eq!(
804 TargetType::from_str("comment").unwrap(),
805 TargetType::Comment
806 );
807 }
808
809 #[test]
810 fn moderation_tier_serde() {
811 let tier = ModerationTier::Tier2;
812 let json = serde_json::to_string(&tier).unwrap();
813 assert_eq!(json, "\"2\"");
814 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
815 assert_eq!(tier, deserialized);
816 }
817
818 #[test]
821 fn message_encryption_wire_values() {
822 assert_eq!(
823 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
824 "\"e2ee\""
825 );
826 assert_eq!(
827 serde_json::to_string(&MessageEncryption::Server).unwrap(),
828 "\"server\""
829 );
830 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
831 assert_eq!(
832 MessageEncryption::from_str("server").unwrap(),
833 MessageEncryption::Server
834 );
835 }
836
837 #[test]
838 fn search_mode_wire_values() {
839 assert_eq!(
840 serde_json::to_string(&SearchMode::Keyword).unwrap(),
841 "\"keyword\""
842 );
843 assert_eq!(
844 serde_json::to_string(&SearchMode::Semantic).unwrap(),
845 "\"semantic\""
846 );
847 assert_eq!(
848 SearchMode::from_str("semantic").unwrap(),
849 SearchMode::Semantic
850 );
851 }
852
853 #[test]
854 fn feed_sort_unpopular_round_trip() {
855 let json = serde_json::to_string(&FeedSort::Unpopular).unwrap();
856 assert_eq!(json, "\"unpopular\"");
857 let back: FeedSort = serde_json::from_str(&json).unwrap();
858 assert_eq!(back, FeedSort::Unpopular);
859 assert_eq!(FeedSort::Unpopular.to_string(), "unpopular");
860 assert_eq!(
861 FeedSort::from_str("unpopular").unwrap(),
862 FeedSort::Unpopular
863 );
864 }
865
866 #[cfg(feature = "schemars")]
870 #[test]
871 fn feed_sort_schema_is_ref_free() {
872 use schemars::JsonSchema;
873
874 assert!(<FeedSort as JsonSchema>::inline_schema());
875
876 let schema = schemars::schema_for!(FeedSort);
877 let value = serde_json::to_value(&schema).unwrap();
878 let blob = value.to_string();
879 assert!(value.get("$defs").is_none(), "no $defs: {value}");
880 assert!(!blob.contains("$ref"), "no $ref: {value}");
881
882 let variants = value["oneOf"]
888 .as_array()
889 .expect("FeedSort should have an inline `oneOf` array");
890 let mut found: Vec<&str> = variants
891 .iter()
892 .filter_map(|v| v["const"].as_str())
893 .collect();
894 for branch in variants {
895 if let Some(plain) = branch["enum"].as_array() {
896 found.extend(plain.iter().filter_map(|v| v.as_str()));
897 }
898 }
899 for expected in [
900 "date",
901 "score",
902 "active",
903 "random",
904 "controversial",
905 "diverse",
906 "unpopular",
907 ] {
908 assert!(found.contains(&expected), "{value}");
909 }
910 }
911
912 #[test]
913 fn proposal_category_round_trip() {
914 for cat in [
915 ProposalCategory::Routine,
916 ProposalCategory::Policy,
917 ProposalCategory::Constitutional,
918 ProposalCategory::Emergency,
919 ] {
920 let json = serde_json::to_string(&cat).unwrap();
921 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
922 assert_eq!(cat, back);
923 }
924 }
925
926 #[test]
929 fn governance_amendment_and_key_wire_values() {
930 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
931 assert_eq!(
932 GovernanceLogEntryType::KeyRotation.to_string(),
933 "key_rotation"
934 );
935 assert_eq!(
936 AmendmentKind::NonPrecedential.to_string(),
937 "non_precedential"
938 );
939 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
940 assert_eq!(
941 "superseded".parse::<AmendmentKind>().unwrap(),
942 AmendmentKind::Superseded
943 );
944 assert_eq!(Standing::default(), Standing::InForce);
945 assert_eq!(Standing::InForce.to_string(), "in_force");
946 assert_eq!(
947 "compromised".parse::<KeyStatus>().unwrap(),
948 KeyStatus::Compromised
949 );
950 assert_eq!(KeyStatus::Retired.to_string(), "retired");
951 }
952
953 #[cfg(feature = "schemars")]
958 #[test]
959 fn enum_json_schema_is_inlined() {
960 use schemars::JsonSchema;
961
962 assert!(<TargetType as JsonSchema>::inline_schema());
963 assert!(<FeedSort as JsonSchema>::inline_schema());
964 assert!(<ProposalSort as JsonSchema>::inline_schema());
965 assert!(<DetailLevel as JsonSchema>::inline_schema());
966 assert!(<SearchMode as JsonSchema>::inline_schema());
967 assert!(<ProposalCategory as JsonSchema>::inline_schema());
968 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
969 assert!(<AmendmentKind as JsonSchema>::inline_schema());
970 assert!(<Standing as JsonSchema>::inline_schema());
971 assert!(<KeyStatus as JsonSchema>::inline_schema());
972 assert!(<OAuthScope as JsonSchema>::inline_schema());
973 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
974 assert!(<ModerationTier as JsonSchema>::inline_schema());
975
976 #[derive(schemars::JsonSchema)]
977 #[allow(dead_code)]
978 struct Container {
979 target_type: TargetType,
980 sort: Option<FeedSort>,
981 proposal_sort: Option<ProposalSort>,
982 category: Option<ProposalCategory>,
983 detail: Option<DetailLevel>,
984 search_mode: Option<SearchMode>,
985 }
986
987 let schema = schemars::schema_for!(Container);
988 let value = serde_json::to_value(&schema).unwrap();
989 let blob = value.to_string();
990
991 assert!(
992 value.get("$defs").is_none(),
993 "no $defs should be emitted for enum-only container; got schema: {value}"
994 );
995 assert!(
996 !blob.contains("$ref"),
997 "enum container schema must contain no $ref anywhere; got: {value}"
998 );
999
1000 let target_type_enum = value["properties"]["target_type"]["enum"]
1002 .as_array()
1003 .expect("target_type should have inline `enum` array");
1004 assert!(
1005 target_type_enum
1006 .contains(&serde_json::Value::String("post".into()))
1007 );
1008 assert!(
1009 target_type_enum
1010 .contains(&serde_json::Value::String("comment".into()))
1011 );
1012 }
1013
1014 #[cfg(feature = "schemars")]
1020 #[test]
1021 fn search_mode_schema_is_ref_free() {
1022 use schemars::JsonSchema;
1023
1024 assert!(<SearchMode as JsonSchema>::inline_schema());
1025
1026 let schema = schemars::schema_for!(SearchMode);
1027 let value = serde_json::to_value(&schema).unwrap();
1028 let blob = value.to_string();
1029 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1030 assert!(!blob.contains("$ref"), "no $ref: {value}");
1031
1032 let variants = value["oneOf"]
1038 .as_array()
1039 .expect("SearchMode should have an inline `oneOf` array");
1040 let consts: Vec<&str> = variants
1041 .iter()
1042 .filter_map(|v| v["const"].as_str())
1043 .collect();
1044 assert!(consts.contains(&"keyword"), "{value}");
1045 assert!(consts.contains(&"semantic"), "{value}");
1046 }
1047}