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)]
245#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
246#[cfg_attr(feature = "schemars", schemars(inline))]
247#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
248#[cfg_attr(
249 feature = "sqlx",
250 sqlx(
251 type_name = "governance_log_entry_type_enum",
252 rename_all = "snake_case"
253 )
254)]
255#[serde(rename_all = "snake_case")]
256pub enum GovernanceLogEntryType {
257 CouncilDecision,
258 AppealsCourtDecision,
259 EmergencyAction,
260 PolicyChange,
261 StewardVeto,
262 Amendment,
266 KeyRotation,
269 StewardRecord,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
279#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
280#[cfg_attr(feature = "schemars", schemars(inline))]
281#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
282#[cfg_attr(
283 feature = "sqlx",
284 sqlx(
285 type_name = "governance_amendment_kind_enum",
286 rename_all = "snake_case"
287 )
288)]
289#[serde(rename_all = "snake_case")]
290pub enum AmendmentKind {
291 NonPrecedential,
293 Overruled,
295 Superseded,
297 Reinstated,
299 Correction,
301 Redaction,
303 Reattested,
306}
307
308#[derive(
313 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
314)]
315#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
316#[cfg_attr(feature = "schemars", schemars(inline))]
317#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
318#[cfg_attr(
319 feature = "sqlx",
320 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
321)]
322#[serde(rename_all = "snake_case")]
323pub enum Standing {
324 #[default]
325 InForce,
326 NonPrecedential,
327 Overruled,
328 Superseded,
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
334#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
335#[cfg_attr(feature = "schemars", schemars(inline))]
336#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
337#[cfg_attr(
338 feature = "sqlx",
339 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
340)]
341#[serde(rename_all = "snake_case")]
342pub enum KeyStatus {
343 Active,
345 Retired,
347 Compromised,
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
358#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
359#[cfg_attr(feature = "schemars", schemars(inline))]
360#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
361#[cfg_attr(
362 feature = "sqlx",
363 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
364)]
365#[serde(rename_all = "snake_case")]
366pub enum MeetingStatus {
367 Active,
368 Adjourned,
369 Cancelled,
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
374#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
375#[cfg_attr(feature = "schemars", schemars(inline))]
376#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
377#[cfg_attr(
378 feature = "sqlx",
379 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
380)]
381#[serde(rename_all = "snake_case")]
382pub enum AgendaItemStatus {
383 Pending,
384 Deliberating,
385 Decided,
386 Deferred,
387 CarriedOver,
388}
389
390#[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 = "agenda_source_type_enum", rename_all = "snake_case")
398)]
399#[serde(rename_all = "snake_case")]
400pub enum AgendaSourceType {
401 Proposal,
402 AppealReferral,
403 StewardSubmission,
404 Internal,
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
409#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
410#[cfg_attr(feature = "schemars", schemars(inline))]
411#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
412#[cfg_attr(
413 feature = "sqlx",
414 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
415)]
416#[serde(rename_all = "snake_case")]
417pub enum RoundType {
418 Independent,
419 Deliberation,
420 FinalVote,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
425#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
426#[cfg_attr(feature = "schemars", schemars(inline))]
427#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
428#[cfg_attr(
429 feature = "sqlx",
430 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
431)]
432#[serde(rename_all = "snake_case")]
433pub enum DecisionOutcome {
434 Approved,
435 Rejected,
436 Deferred,
437 Amended,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
446#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
447#[cfg_attr(feature = "schemars", schemars(inline))]
448#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
449#[cfg_attr(
450 feature = "sqlx",
451 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
452)]
453#[serde(rename_all = "snake_case")]
454pub enum BatchType {
455 Jury,
456 Judge,
457 Tier2,
458 Redaction,
460 Chambers,
466 Precedent,
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
474#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
475#[cfg_attr(feature = "schemars", schemars(inline))]
476#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
477#[cfg_attr(
478 feature = "sqlx",
479 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
480)]
481#[serde(rename_all = "snake_case")]
482pub enum BatchStatus {
483 Submitted,
484 Polling,
485 Completed,
486 Failed,
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
495#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
496#[cfg_attr(feature = "schemars", schemars(inline))]
497#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
498#[cfg_attr(
499 feature = "sqlx",
500 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
501)]
502#[serde(rename_all = "snake_case")]
503pub enum OAuthScope {
504 Read,
505 Write,
506}
507
508#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
514#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
515#[cfg_attr(feature = "schemars", schemars(inline))]
516#[serde(rename_all = "snake_case")]
517pub enum FeedSort {
518 Date,
519 Score,
520 Active,
521 Random,
522 Controversial,
523 Diverse,
524 Unpopular,
530}
531
532#[derive(
546 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
547)]
548#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
549#[cfg_attr(feature = "schemars", schemars(inline))]
550#[serde(rename_all = "snake_case")]
551pub enum ProposalSort {
552 #[default]
555 Newest,
556 Oldest,
559 Score,
561}
562
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
578#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
579#[cfg_attr(feature = "schemars", schemars(inline))]
580#[serde(rename_all = "snake_case")]
581pub enum DetailLevel {
582 Summary,
584 Full,
587}
588
589#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
600#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
601#[cfg_attr(feature = "schemars", schemars(inline))]
602#[serde(rename_all = "snake_case")]
603pub enum SearchMode {
604 Keyword,
606 Semantic,
611}
612
613#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
623#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
624#[cfg_attr(feature = "schemars", schemars(inline))]
625#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
626#[cfg_attr(
627 feature = "sqlx",
628 sqlx(type_name = "friendship_status", rename_all = "snake_case")
629)]
630#[serde(rename_all = "snake_case")]
631pub enum FriendshipStatus {
632 Pending,
633 Accepted,
634 Declined,
635}
636
637#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
641#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
642#[cfg_attr(feature = "schemars", schemars(inline))]
643#[serde(rename_all = "snake_case")]
644pub enum FriendshipAction {
645 Request,
647 Accept,
649 Decline,
651 Unfriend,
653}
654
655#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
662#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
663#[cfg_attr(feature = "schemars", schemars(inline))]
664#[cfg_attr(
665 feature = "sqlx",
666 derive(sqlx::Type),
667 sqlx(type_name = "message_encryption", rename_all = "snake_case")
668)]
669#[serde(rename_all = "snake_case")]
670pub enum MessageEncryption {
671 E2ee,
673 Server,
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
679#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
680#[cfg_attr(feature = "schemars", schemars(inline))]
681#[serde(rename_all = "snake_case")]
682pub enum BlockAction {
683 Block,
684 Unblock,
685}
686
687impl_display_fromstr!(TargetType);
692impl_display_fromstr!(ModerationTargetType);
693impl_display_fromstr!(ModerationActionType);
694impl_display_fromstr!(ModerationTier);
695impl_display_fromstr!(AppealStatus);
696impl_display_fromstr!(AppealOutcome);
697impl_display_fromstr!(ModelRole);
698impl_display_fromstr!(ProposalCategory);
699impl_display_fromstr!(GovernanceLogEntryType);
700impl_display_fromstr!(AmendmentKind);
701impl_display_fromstr!(Standing);
702impl_display_fromstr!(KeyStatus);
703impl_display_fromstr!(MeetingStatus);
704impl_display_fromstr!(AgendaItemStatus);
705impl_display_fromstr!(AgendaSourceType);
706impl_display_fromstr!(RoundType);
707impl_display_fromstr!(DecisionOutcome);
708impl_display_fromstr!(BatchType);
709impl_display_fromstr!(BatchStatus);
710impl_display_fromstr!(OAuthScope);
711impl_display_fromstr!(FeedSort);
712impl_display_fromstr!(ProposalSort);
713impl_display_fromstr!(DetailLevel);
714impl_display_fromstr!(SearchMode);
715impl_display_fromstr!(FriendshipStatus);
716impl_display_fromstr!(FriendshipAction);
717impl_display_fromstr!(BlockAction);
718impl_display_fromstr!(MessageEncryption);
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 fn target_type_serde_round_trip() {
726 let val = TargetType::Post;
727 let json = serde_json::to_string(&val).unwrap();
728 assert_eq!(json, "\"post\"");
729 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
730 assert_eq!(val, deserialized);
731 }
732
733 #[test]
734 fn target_type_display() {
735 assert_eq!(TargetType::Post.to_string(), "post");
736 assert_eq!(TargetType::Comment.to_string(), "comment");
737 }
738
739 #[test]
740 fn target_type_from_str() {
741 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
742 assert_eq!(
743 TargetType::from_str("comment").unwrap(),
744 TargetType::Comment
745 );
746 }
747
748 #[test]
749 fn moderation_tier_serde() {
750 let tier = ModerationTier::Tier2;
751 let json = serde_json::to_string(&tier).unwrap();
752 assert_eq!(json, "\"2\"");
753 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
754 assert_eq!(tier, deserialized);
755 }
756
757 #[test]
760 fn message_encryption_wire_values() {
761 assert_eq!(
762 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
763 "\"e2ee\""
764 );
765 assert_eq!(
766 serde_json::to_string(&MessageEncryption::Server).unwrap(),
767 "\"server\""
768 );
769 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
770 assert_eq!(
771 MessageEncryption::from_str("server").unwrap(),
772 MessageEncryption::Server
773 );
774 }
775
776 #[test]
777 fn search_mode_wire_values() {
778 assert_eq!(
779 serde_json::to_string(&SearchMode::Keyword).unwrap(),
780 "\"keyword\""
781 );
782 assert_eq!(
783 serde_json::to_string(&SearchMode::Semantic).unwrap(),
784 "\"semantic\""
785 );
786 assert_eq!(
787 SearchMode::from_str("semantic").unwrap(),
788 SearchMode::Semantic
789 );
790 }
791
792 #[test]
793 fn feed_sort_unpopular_round_trip() {
794 let json = serde_json::to_string(&FeedSort::Unpopular).unwrap();
795 assert_eq!(json, "\"unpopular\"");
796 let back: FeedSort = serde_json::from_str(&json).unwrap();
797 assert_eq!(back, FeedSort::Unpopular);
798 assert_eq!(FeedSort::Unpopular.to_string(), "unpopular");
799 assert_eq!(
800 FeedSort::from_str("unpopular").unwrap(),
801 FeedSort::Unpopular
802 );
803 }
804
805 #[cfg(feature = "schemars")]
809 #[test]
810 fn feed_sort_schema_is_ref_free() {
811 use schemars::JsonSchema;
812
813 assert!(<FeedSort as JsonSchema>::inline_schema());
814
815 let schema = schemars::schema_for!(FeedSort);
816 let value = serde_json::to_value(&schema).unwrap();
817 let blob = value.to_string();
818 assert!(value.get("$defs").is_none(), "no $defs: {value}");
819 assert!(!blob.contains("$ref"), "no $ref: {value}");
820
821 let variants = value["oneOf"]
827 .as_array()
828 .expect("FeedSort should have an inline `oneOf` array");
829 let mut found: Vec<&str> = variants
830 .iter()
831 .filter_map(|v| v["const"].as_str())
832 .collect();
833 for branch in variants {
834 if let Some(plain) = branch["enum"].as_array() {
835 found.extend(plain.iter().filter_map(|v| v.as_str()));
836 }
837 }
838 for expected in [
839 "date",
840 "score",
841 "active",
842 "random",
843 "controversial",
844 "diverse",
845 "unpopular",
846 ] {
847 assert!(found.contains(&expected), "{value}");
848 }
849 }
850
851 #[test]
852 fn proposal_category_round_trip() {
853 for cat in [
854 ProposalCategory::Routine,
855 ProposalCategory::Policy,
856 ProposalCategory::Constitutional,
857 ProposalCategory::Emergency,
858 ] {
859 let json = serde_json::to_string(&cat).unwrap();
860 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
861 assert_eq!(cat, back);
862 }
863 }
864
865 #[test]
868 fn governance_amendment_and_key_wire_values() {
869 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
870 assert_eq!(
871 GovernanceLogEntryType::KeyRotation.to_string(),
872 "key_rotation"
873 );
874 assert_eq!(
875 AmendmentKind::NonPrecedential.to_string(),
876 "non_precedential"
877 );
878 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
879 assert_eq!(
880 "superseded".parse::<AmendmentKind>().unwrap(),
881 AmendmentKind::Superseded
882 );
883 assert_eq!(Standing::default(), Standing::InForce);
884 assert_eq!(Standing::InForce.to_string(), "in_force");
885 assert_eq!(
886 "compromised".parse::<KeyStatus>().unwrap(),
887 KeyStatus::Compromised
888 );
889 assert_eq!(KeyStatus::Retired.to_string(), "retired");
890 }
891
892 #[cfg(feature = "schemars")]
897 #[test]
898 fn enum_json_schema_is_inlined() {
899 use schemars::JsonSchema;
900
901 assert!(<TargetType as JsonSchema>::inline_schema());
902 assert!(<FeedSort as JsonSchema>::inline_schema());
903 assert!(<ProposalSort as JsonSchema>::inline_schema());
904 assert!(<DetailLevel as JsonSchema>::inline_schema());
905 assert!(<SearchMode as JsonSchema>::inline_schema());
906 assert!(<ProposalCategory as JsonSchema>::inline_schema());
907 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
908 assert!(<AmendmentKind as JsonSchema>::inline_schema());
909 assert!(<Standing as JsonSchema>::inline_schema());
910 assert!(<KeyStatus as JsonSchema>::inline_schema());
911 assert!(<OAuthScope as JsonSchema>::inline_schema());
912 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
913 assert!(<ModerationTier as JsonSchema>::inline_schema());
914
915 #[derive(schemars::JsonSchema)]
916 #[allow(dead_code)]
917 struct Container {
918 target_type: TargetType,
919 sort: Option<FeedSort>,
920 proposal_sort: Option<ProposalSort>,
921 category: Option<ProposalCategory>,
922 detail: Option<DetailLevel>,
923 search_mode: Option<SearchMode>,
924 }
925
926 let schema = schemars::schema_for!(Container);
927 let value = serde_json::to_value(&schema).unwrap();
928 let blob = value.to_string();
929
930 assert!(
931 value.get("$defs").is_none(),
932 "no $defs should be emitted for enum-only container; got schema: {value}"
933 );
934 assert!(
935 !blob.contains("$ref"),
936 "enum container schema must contain no $ref anywhere; got: {value}"
937 );
938
939 let target_type_enum = value["properties"]["target_type"]["enum"]
941 .as_array()
942 .expect("target_type should have inline `enum` array");
943 assert!(
944 target_type_enum
945 .contains(&serde_json::Value::String("post".into()))
946 );
947 assert!(
948 target_type_enum
949 .contains(&serde_json::Value::String("comment".into()))
950 );
951 }
952
953 #[cfg(feature = "schemars")]
959 #[test]
960 fn search_mode_schema_is_ref_free() {
961 use schemars::JsonSchema;
962
963 assert!(<SearchMode as JsonSchema>::inline_schema());
964
965 let schema = schemars::schema_for!(SearchMode);
966 let value = serde_json::to_value(&schema).unwrap();
967 let blob = value.to_string();
968 assert!(value.get("$defs").is_none(), "no $defs: {value}");
969 assert!(!blob.contains("$ref"), "no $ref: {value}");
970
971 let variants = value["oneOf"]
977 .as_array()
978 .expect("SearchMode should have an inline `oneOf` array");
979 let consts: Vec<&str> = variants
980 .iter()
981 .filter_map(|v| v["const"].as_str())
982 .collect();
983 assert!(consts.contains(&"keyword"), "{value}");
984 assert!(consts.contains(&"semantic"), "{value}");
985 }
986}