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}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
239#[cfg_attr(feature = "schemars", schemars(inline))]
240#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
241#[cfg_attr(
242 feature = "sqlx",
243 sqlx(
244 type_name = "governance_log_entry_type_enum",
245 rename_all = "snake_case"
246 )
247)]
248#[serde(rename_all = "snake_case")]
249pub enum GovernanceLogEntryType {
250 CouncilDecision,
251 AppealsCourtDecision,
252 EmergencyAction,
253 PolicyChange,
254 StewardVeto,
255 Amendment,
259 KeyRotation,
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
267#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
268#[cfg_attr(feature = "schemars", schemars(inline))]
269#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
270#[cfg_attr(
271 feature = "sqlx",
272 sqlx(
273 type_name = "governance_amendment_kind_enum",
274 rename_all = "snake_case"
275 )
276)]
277#[serde(rename_all = "snake_case")]
278pub enum AmendmentKind {
279 NonPrecedential,
281 Overruled,
283 Superseded,
285 Reinstated,
287 Correction,
289 Redaction,
291 Reattested,
294}
295
296#[derive(
301 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
302)]
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(type_name = "governance_standing_enum", rename_all = "snake_case")
309)]
310#[serde(rename_all = "snake_case")]
311pub enum Standing {
312 #[default]
313 InForce,
314 NonPrecedential,
315 Overruled,
316 Superseded,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
322#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
323#[cfg_attr(feature = "schemars", schemars(inline))]
324#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
325#[cfg_attr(
326 feature = "sqlx",
327 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
328)]
329#[serde(rename_all = "snake_case")]
330pub enum KeyStatus {
331 Active,
333 Retired,
335 Compromised,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
346#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
347#[cfg_attr(feature = "schemars", schemars(inline))]
348#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
349#[cfg_attr(
350 feature = "sqlx",
351 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
352)]
353#[serde(rename_all = "snake_case")]
354pub enum MeetingStatus {
355 Active,
356 Adjourned,
357 Cancelled,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
362#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
363#[cfg_attr(feature = "schemars", schemars(inline))]
364#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
365#[cfg_attr(
366 feature = "sqlx",
367 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
368)]
369#[serde(rename_all = "snake_case")]
370pub enum AgendaItemStatus {
371 Pending,
372 Deliberating,
373 Decided,
374 Deferred,
375 CarriedOver,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
380#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
381#[cfg_attr(feature = "schemars", schemars(inline))]
382#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
383#[cfg_attr(
384 feature = "sqlx",
385 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
386)]
387#[serde(rename_all = "snake_case")]
388pub enum AgendaSourceType {
389 Proposal,
390 AppealReferral,
391 StewardSubmission,
392 Internal,
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(type_name = "round_type_enum", rename_all = "snake_case")
403)]
404#[serde(rename_all = "snake_case")]
405pub enum RoundType {
406 Independent,
407 Deliberation,
408 FinalVote,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
413#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
414#[cfg_attr(feature = "schemars", schemars(inline))]
415#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
416#[cfg_attr(
417 feature = "sqlx",
418 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
419)]
420#[serde(rename_all = "snake_case")]
421pub enum DecisionOutcome {
422 Approved,
423 Rejected,
424 Deferred,
425 Amended,
426}
427
428#[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 = "batch_type_enum", rename_all = "snake_case")
440)]
441#[serde(rename_all = "snake_case")]
442pub enum BatchType {
443 Jury,
444 Judge,
445 Tier2,
446 Redaction,
448 Chambers,
454 Precedent,
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
462#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
463#[cfg_attr(feature = "schemars", schemars(inline))]
464#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
465#[cfg_attr(
466 feature = "sqlx",
467 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
468)]
469#[serde(rename_all = "snake_case")]
470pub enum BatchStatus {
471 Submitted,
472 Polling,
473 Completed,
474 Failed,
475}
476
477#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, 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 = "oauth_scope_enum", rename_all = "snake_case")
489)]
490#[serde(rename_all = "snake_case")]
491pub enum OAuthScope {
492 Read,
493 Write,
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
502#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
503#[cfg_attr(feature = "schemars", schemars(inline))]
504#[serde(rename_all = "snake_case")]
505pub enum FeedSort {
506 Date,
507 Score,
508 Active,
509 Random,
510 Controversial,
511 Diverse,
512 Unpopular,
518}
519
520#[derive(
534 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
535)]
536#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
537#[cfg_attr(feature = "schemars", schemars(inline))]
538#[serde(rename_all = "snake_case")]
539pub enum ProposalSort {
540 #[default]
543 Newest,
544 Oldest,
547 Score,
549}
550
551#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
566#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
567#[cfg_attr(feature = "schemars", schemars(inline))]
568#[serde(rename_all = "snake_case")]
569pub enum DetailLevel {
570 Summary,
572 Full,
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
588#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
589#[cfg_attr(feature = "schemars", schemars(inline))]
590#[serde(rename_all = "snake_case")]
591pub enum SearchMode {
592 Keyword,
594 Semantic,
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
611#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
612#[cfg_attr(feature = "schemars", schemars(inline))]
613#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
614#[cfg_attr(
615 feature = "sqlx",
616 sqlx(type_name = "friendship_status", rename_all = "snake_case")
617)]
618#[serde(rename_all = "snake_case")]
619pub enum FriendshipStatus {
620 Pending,
621 Accepted,
622 Declined,
623}
624
625#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
629#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
630#[cfg_attr(feature = "schemars", schemars(inline))]
631#[serde(rename_all = "snake_case")]
632pub enum FriendshipAction {
633 Request,
635 Accept,
637 Decline,
639 Unfriend,
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
650#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
651#[cfg_attr(feature = "schemars", schemars(inline))]
652#[cfg_attr(
653 feature = "sqlx",
654 derive(sqlx::Type),
655 sqlx(type_name = "message_encryption", rename_all = "snake_case")
656)]
657#[serde(rename_all = "snake_case")]
658pub enum MessageEncryption {
659 E2ee,
661 Server,
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
667#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
668#[cfg_attr(feature = "schemars", schemars(inline))]
669#[serde(rename_all = "snake_case")]
670pub enum BlockAction {
671 Block,
672 Unblock,
673}
674
675impl_display_fromstr!(TargetType);
680impl_display_fromstr!(ModerationTargetType);
681impl_display_fromstr!(ModerationActionType);
682impl_display_fromstr!(ModerationTier);
683impl_display_fromstr!(AppealStatus);
684impl_display_fromstr!(AppealOutcome);
685impl_display_fromstr!(ModelRole);
686impl_display_fromstr!(ProposalCategory);
687impl_display_fromstr!(GovernanceLogEntryType);
688impl_display_fromstr!(AmendmentKind);
689impl_display_fromstr!(Standing);
690impl_display_fromstr!(KeyStatus);
691impl_display_fromstr!(MeetingStatus);
692impl_display_fromstr!(AgendaItemStatus);
693impl_display_fromstr!(AgendaSourceType);
694impl_display_fromstr!(RoundType);
695impl_display_fromstr!(DecisionOutcome);
696impl_display_fromstr!(BatchType);
697impl_display_fromstr!(BatchStatus);
698impl_display_fromstr!(OAuthScope);
699impl_display_fromstr!(FeedSort);
700impl_display_fromstr!(ProposalSort);
701impl_display_fromstr!(DetailLevel);
702impl_display_fromstr!(SearchMode);
703impl_display_fromstr!(FriendshipStatus);
704impl_display_fromstr!(FriendshipAction);
705impl_display_fromstr!(BlockAction);
706impl_display_fromstr!(MessageEncryption);
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 #[test]
713 fn target_type_serde_round_trip() {
714 let val = TargetType::Post;
715 let json = serde_json::to_string(&val).unwrap();
716 assert_eq!(json, "\"post\"");
717 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
718 assert_eq!(val, deserialized);
719 }
720
721 #[test]
722 fn target_type_display() {
723 assert_eq!(TargetType::Post.to_string(), "post");
724 assert_eq!(TargetType::Comment.to_string(), "comment");
725 }
726
727 #[test]
728 fn target_type_from_str() {
729 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
730 assert_eq!(
731 TargetType::from_str("comment").unwrap(),
732 TargetType::Comment
733 );
734 }
735
736 #[test]
737 fn moderation_tier_serde() {
738 let tier = ModerationTier::Tier2;
739 let json = serde_json::to_string(&tier).unwrap();
740 assert_eq!(json, "\"2\"");
741 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
742 assert_eq!(tier, deserialized);
743 }
744
745 #[test]
748 fn message_encryption_wire_values() {
749 assert_eq!(
750 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
751 "\"e2ee\""
752 );
753 assert_eq!(
754 serde_json::to_string(&MessageEncryption::Server).unwrap(),
755 "\"server\""
756 );
757 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
758 assert_eq!(
759 MessageEncryption::from_str("server").unwrap(),
760 MessageEncryption::Server
761 );
762 }
763
764 #[test]
765 fn search_mode_wire_values() {
766 assert_eq!(
767 serde_json::to_string(&SearchMode::Keyword).unwrap(),
768 "\"keyword\""
769 );
770 assert_eq!(
771 serde_json::to_string(&SearchMode::Semantic).unwrap(),
772 "\"semantic\""
773 );
774 assert_eq!(
775 SearchMode::from_str("semantic").unwrap(),
776 SearchMode::Semantic
777 );
778 }
779
780 #[test]
781 fn feed_sort_unpopular_round_trip() {
782 let json = serde_json::to_string(&FeedSort::Unpopular).unwrap();
783 assert_eq!(json, "\"unpopular\"");
784 let back: FeedSort = serde_json::from_str(&json).unwrap();
785 assert_eq!(back, FeedSort::Unpopular);
786 assert_eq!(FeedSort::Unpopular.to_string(), "unpopular");
787 assert_eq!(
788 FeedSort::from_str("unpopular").unwrap(),
789 FeedSort::Unpopular
790 );
791 }
792
793 #[cfg(feature = "schemars")]
797 #[test]
798 fn feed_sort_schema_is_ref_free() {
799 use schemars::JsonSchema;
800
801 assert!(<FeedSort as JsonSchema>::inline_schema());
802
803 let schema = schemars::schema_for!(FeedSort);
804 let value = serde_json::to_value(&schema).unwrap();
805 let blob = value.to_string();
806 assert!(value.get("$defs").is_none(), "no $defs: {value}");
807 assert!(!blob.contains("$ref"), "no $ref: {value}");
808
809 let variants = value["oneOf"]
815 .as_array()
816 .expect("FeedSort should have an inline `oneOf` array");
817 let mut found: Vec<&str> = variants
818 .iter()
819 .filter_map(|v| v["const"].as_str())
820 .collect();
821 for branch in variants {
822 if let Some(plain) = branch["enum"].as_array() {
823 found.extend(plain.iter().filter_map(|v| v.as_str()));
824 }
825 }
826 for expected in [
827 "date",
828 "score",
829 "active",
830 "random",
831 "controversial",
832 "diverse",
833 "unpopular",
834 ] {
835 assert!(found.contains(&expected), "{value}");
836 }
837 }
838
839 #[test]
840 fn proposal_category_round_trip() {
841 for cat in [
842 ProposalCategory::Routine,
843 ProposalCategory::Policy,
844 ProposalCategory::Constitutional,
845 ProposalCategory::Emergency,
846 ] {
847 let json = serde_json::to_string(&cat).unwrap();
848 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
849 assert_eq!(cat, back);
850 }
851 }
852
853 #[test]
856 fn governance_amendment_and_key_wire_values() {
857 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
858 assert_eq!(
859 GovernanceLogEntryType::KeyRotation.to_string(),
860 "key_rotation"
861 );
862 assert_eq!(
863 AmendmentKind::NonPrecedential.to_string(),
864 "non_precedential"
865 );
866 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
867 assert_eq!(
868 "superseded".parse::<AmendmentKind>().unwrap(),
869 AmendmentKind::Superseded
870 );
871 assert_eq!(Standing::default(), Standing::InForce);
872 assert_eq!(Standing::InForce.to_string(), "in_force");
873 assert_eq!(
874 "compromised".parse::<KeyStatus>().unwrap(),
875 KeyStatus::Compromised
876 );
877 assert_eq!(KeyStatus::Retired.to_string(), "retired");
878 }
879
880 #[cfg(feature = "schemars")]
885 #[test]
886 fn enum_json_schema_is_inlined() {
887 use schemars::JsonSchema;
888
889 assert!(<TargetType as JsonSchema>::inline_schema());
890 assert!(<FeedSort as JsonSchema>::inline_schema());
891 assert!(<ProposalSort as JsonSchema>::inline_schema());
892 assert!(<DetailLevel as JsonSchema>::inline_schema());
893 assert!(<SearchMode as JsonSchema>::inline_schema());
894 assert!(<ProposalCategory as JsonSchema>::inline_schema());
895 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
896 assert!(<AmendmentKind as JsonSchema>::inline_schema());
897 assert!(<Standing as JsonSchema>::inline_schema());
898 assert!(<KeyStatus as JsonSchema>::inline_schema());
899 assert!(<OAuthScope as JsonSchema>::inline_schema());
900 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
901 assert!(<ModerationTier as JsonSchema>::inline_schema());
902
903 #[derive(schemars::JsonSchema)]
904 #[allow(dead_code)]
905 struct Container {
906 target_type: TargetType,
907 sort: Option<FeedSort>,
908 proposal_sort: Option<ProposalSort>,
909 category: Option<ProposalCategory>,
910 detail: Option<DetailLevel>,
911 search_mode: Option<SearchMode>,
912 }
913
914 let schema = schemars::schema_for!(Container);
915 let value = serde_json::to_value(&schema).unwrap();
916 let blob = value.to_string();
917
918 assert!(
919 value.get("$defs").is_none(),
920 "no $defs should be emitted for enum-only container; got schema: {value}"
921 );
922 assert!(
923 !blob.contains("$ref"),
924 "enum container schema must contain no $ref anywhere; got: {value}"
925 );
926
927 let target_type_enum = value["properties"]["target_type"]["enum"]
929 .as_array()
930 .expect("target_type should have inline `enum` array");
931 assert!(
932 target_type_enum
933 .contains(&serde_json::Value::String("post".into()))
934 );
935 assert!(
936 target_type_enum
937 .contains(&serde_json::Value::String("comment".into()))
938 );
939 }
940
941 #[cfg(feature = "schemars")]
947 #[test]
948 fn search_mode_schema_is_ref_free() {
949 use schemars::JsonSchema;
950
951 assert!(<SearchMode as JsonSchema>::inline_schema());
952
953 let schema = schemars::schema_for!(SearchMode);
954 let value = serde_json::to_value(&schema).unwrap();
955 let blob = value.to_string();
956 assert!(value.get("$defs").is_none(), "no $defs: {value}");
957 assert!(!blob.contains("$ref"), "no $ref: {value}");
958
959 let variants = value["oneOf"]
965 .as_array()
966 .expect("SearchMode should have an inline `oneOf` array");
967 let consts: Vec<&str> = variants
968 .iter()
969 .filter_map(|v| v["const"].as_str())
970 .collect();
971 assert!(consts.contains(&"keyword"), "{value}");
972 assert!(consts.contains(&"semantic"), "{value}");
973 }
974}