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