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 Obligation,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
370#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
371#[cfg_attr(feature = "schemars", schemars(inline))]
372#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
373#[cfg_attr(
374 feature = "sqlx",
375 sqlx(
376 type_name = "governance_amendment_kind_enum",
377 rename_all = "snake_case"
378 )
379)]
380#[serde(rename_all = "snake_case")]
381pub enum AmendmentKind {
382 NonPrecedential,
384 Overruled,
386 Superseded,
388 Reinstated,
390 Correction,
392 Redaction,
394 Reattested,
397 Revision,
400 Planned,
404 Discharged,
406 Lapsed,
409 Rescheduled,
411 Withdrawn,
413}
414
415impl AmendmentKind {
416 pub const OBLIGATION: [Self; 5] = [
419 Self::Planned,
420 Self::Discharged,
421 Self::Lapsed,
422 Self::Rescheduled,
423 Self::Withdrawn,
424 ];
425
426 pub fn is_obligation(self) -> bool {
428 Self::OBLIGATION.contains(&self)
429 }
430}
431
432#[derive(
437 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
438)]
439#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
440#[cfg_attr(feature = "schemars", schemars(inline))]
441#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
442#[cfg_attr(
443 feature = "sqlx",
444 sqlx(type_name = "governance_standing_enum", rename_all = "snake_case")
445)]
446#[serde(rename_all = "snake_case")]
447pub enum Standing {
448 #[default]
449 InForce,
450 NonPrecedential,
451 Overruled,
452 Superseded,
453}
454
455#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
460#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
461#[cfg_attr(feature = "schemars", schemars(inline))]
462#[serde(rename_all = "snake_case")]
463pub enum CitationRelation {
464 Concerns,
466 Authority,
468 Overrules,
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
475#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
476#[cfg_attr(feature = "schemars", schemars(inline))]
477#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
478#[cfg_attr(
479 feature = "sqlx",
480 sqlx(type_name = "governance_key_status_enum", rename_all = "snake_case")
481)]
482#[serde(rename_all = "snake_case")]
483pub enum KeyStatus {
484 Active,
486 Retired,
488 Compromised,
491}
492
493#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
500#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
501#[cfg_attr(feature = "schemars", schemars(inline))]
502#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
503#[cfg_attr(
504 feature = "sqlx",
505 sqlx(type_name = "obligation_kind_enum", rename_all = "snake_case")
506)]
507#[serde(rename_all = "snake_case")]
508pub enum ObligationKind {
509 CarryForward,
511 Ratification,
513 Review,
515 Referral,
517 Reveal,
519 Task,
521}
522
523#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
525#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
526#[cfg_attr(feature = "schemars", schemars(inline))]
527#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
528#[cfg_attr(
529 feature = "sqlx",
530 sqlx(type_name = "obligation_owed_by_enum", rename_all = "snake_case")
531)]
532#[serde(rename_all = "snake_case")]
533pub enum OwedBy {
534 Steward,
535 Staff,
537 Council,
538}
539
540#[derive(
543 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize,
544)]
545#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
546#[cfg_attr(feature = "schemars", schemars(inline))]
547#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
548#[cfg_attr(
549 feature = "sqlx",
550 sqlx(type_name = "obligation_status_enum", rename_all = "snake_case")
551)]
552#[serde(rename_all = "snake_case")]
553pub enum ObligationStatus {
554 #[default]
556 Open,
557 Lapsed,
559 Discharged,
561 Withdrawn,
563}
564
565impl ObligationStatus {
566 pub fn is_closed(self) -> bool {
568 matches!(self, Self::Discharged | Self::Withdrawn)
569 }
570}
571
572#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
574#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
575#[cfg_attr(feature = "schemars", schemars(inline))]
576#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
577#[cfg_attr(
578 feature = "sqlx",
579 sqlx(type_name = "calendar_event_kind_enum", rename_all = "snake_case")
580)]
581#[serde(rename_all = "snake_case")]
582pub enum CalendarEventKind {
583 Sitting,
585 SpecialSitting,
587}
588
589#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
592#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
593#[cfg_attr(feature = "schemars", schemars(inline))]
594#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
595#[cfg_attr(
596 feature = "sqlx",
597 sqlx(type_name = "calendar_event_status_enum", rename_all = "snake_case")
598)]
599#[serde(rename_all = "snake_case")]
600pub enum CalendarEventStatus {
601 Scheduled,
602 Held,
603 Cancelled,
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
612#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
613#[cfg_attr(feature = "schemars", schemars(inline))]
614#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
615#[cfg_attr(
616 feature = "sqlx",
617 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
618)]
619#[serde(rename_all = "snake_case")]
620pub enum MeetingStatus {
621 Active,
622 Adjourned,
623 Cancelled,
624}
625
626#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
628#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
629#[cfg_attr(feature = "schemars", schemars(inline))]
630#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
631#[cfg_attr(
632 feature = "sqlx",
633 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
634)]
635#[serde(rename_all = "snake_case")]
636pub enum AgendaItemStatus {
637 Pending,
638 Deliberating,
639 Decided,
640 Deferred,
641 CarriedOver,
642}
643
644#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
646#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
647#[cfg_attr(feature = "schemars", schemars(inline))]
648#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
649#[cfg_attr(
650 feature = "sqlx",
651 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
652)]
653#[serde(rename_all = "snake_case")]
654pub enum AgendaSourceType {
655 Proposal,
656 AppealReferral,
657 StewardSubmission,
658 Internal,
659}
660
661#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
663#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
664#[cfg_attr(feature = "schemars", schemars(inline))]
665#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
666#[cfg_attr(
667 feature = "sqlx",
668 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
669)]
670#[serde(rename_all = "snake_case")]
671pub enum RoundType {
672 Independent,
673 Deliberation,
674 FinalVote,
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#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
682#[cfg_attr(
683 feature = "sqlx",
684 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
685)]
686#[serde(rename_all = "snake_case")]
687pub enum DecisionOutcome {
688 Approved,
689 Rejected,
690 Deferred,
691 Amended,
692}
693
694#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
700#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
701#[cfg_attr(feature = "schemars", schemars(inline))]
702#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
703#[cfg_attr(
704 feature = "sqlx",
705 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
706)]
707#[serde(rename_all = "snake_case")]
708pub enum BatchType {
709 Jury,
710 Judge,
711 Tier2,
712 Redaction,
714 Chambers,
720 Precedent,
724}
725
726#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
728#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
729#[cfg_attr(feature = "schemars", schemars(inline))]
730#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
731#[cfg_attr(
732 feature = "sqlx",
733 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
734)]
735#[serde(rename_all = "snake_case")]
736pub enum BatchStatus {
737 Submitted,
738 Polling,
739 Completed,
740 Failed,
741}
742
743#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
749#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
750#[cfg_attr(feature = "schemars", schemars(inline))]
751#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
752#[cfg_attr(
753 feature = "sqlx",
754 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
755)]
756#[serde(rename_all = "snake_case")]
757pub enum OAuthScope {
758 Read,
759 Write,
760}
761
762#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
775#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
776#[cfg_attr(feature = "schemars", schemars(inline))]
777#[serde(rename_all = "snake_case")]
778pub enum FeedSort {
779 Date,
780 Active,
781 Random,
782 Diverse,
783}
784
785#[derive(
799 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
800)]
801#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
802#[cfg_attr(feature = "schemars", schemars(inline))]
803#[serde(rename_all = "snake_case")]
804pub enum ProposalSort {
805 #[default]
808 Newest,
809 Oldest,
812}
813
814#[derive(
816 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
817)]
818#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
819#[cfg_attr(feature = "schemars", schemars(inline))]
820#[serde(rename_all = "snake_case")]
821pub enum GovernanceLogView {
822 #[default]
824 Entries,
825 Obligations,
827}
828
829#[derive(
831 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
832)]
833#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
834#[cfg_attr(feature = "schemars", schemars(inline))]
835#[serde(rename_all = "snake_case")]
836pub enum ObligationSort {
837 #[default]
840 Due,
841 Created,
843}
844
845#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
857#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
858#[cfg_attr(feature = "schemars", schemars(inline))]
859#[serde(rename_all = "snake_case")]
860pub enum DetailLevel {
861 Summary,
863 Full,
869 FullWithAttachments,
873}
874
875#[derive(
880 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
881)]
882#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
883#[cfg_attr(feature = "schemars", schemars(inline))]
884#[serde(rename_all = "snake_case")]
885pub enum RecordVersion {
886 #[default]
888 Latest,
889 Original,
891}
892
893#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
904#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
905#[cfg_attr(feature = "schemars", schemars(inline))]
906#[serde(rename_all = "snake_case")]
907pub enum SearchMode {
908 Keyword,
910 Semantic,
915}
916
917#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
927#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
928#[cfg_attr(feature = "schemars", schemars(inline))]
929#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
930#[cfg_attr(
931 feature = "sqlx",
932 sqlx(type_name = "friendship_status", rename_all = "snake_case")
933)]
934#[serde(rename_all = "snake_case")]
935pub enum FriendshipStatus {
936 Pending,
937 Accepted,
938 Declined,
939}
940
941#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
945#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
946#[cfg_attr(feature = "schemars", schemars(inline))]
947#[serde(rename_all = "snake_case")]
948pub enum FriendshipAction {
949 Request,
951 Accept,
953 Decline,
955 Unfriend,
957}
958
959#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
966#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
967#[cfg_attr(feature = "schemars", schemars(inline))]
968#[cfg_attr(
969 feature = "sqlx",
970 derive(sqlx::Type),
971 sqlx(type_name = "message_encryption", rename_all = "snake_case")
972)]
973#[serde(rename_all = "snake_case")]
974pub enum MessageEncryption {
975 E2ee,
977 Server,
979}
980
981#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
983#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
984#[cfg_attr(feature = "schemars", schemars(inline))]
985#[serde(rename_all = "snake_case")]
986pub enum BlockAction {
987 Block,
988 Unblock,
989}
990
991#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
999#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1000#[cfg_attr(feature = "schemars", schemars(inline))]
1001#[serde(rename_all = "snake_case")]
1002pub enum ContentKind {
1003 Post,
1004 Comment,
1005}
1006
1007#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1009#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1010#[cfg_attr(feature = "schemars", schemars(inline))]
1011#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1012#[cfg_attr(
1013 feature = "sqlx",
1014 sqlx(type_name = "poll_mode_enum", rename_all = "snake_case")
1015)]
1016#[serde(rename_all = "snake_case")]
1017pub enum PollMode {
1018 Ranked,
1022 Approval,
1025 Single,
1027 #[serde(other)]
1030 #[cfg_attr(feature = "schemars", schemars(skip))]
1031 Unknown,
1032}
1033
1034#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1037#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1038#[cfg_attr(feature = "schemars", schemars(inline))]
1039#[serde(rename_all = "snake_case")]
1040pub enum PollStatus {
1041 Scheduled,
1043 Open,
1046 Closed,
1048 #[serde(other)]
1050 #[cfg_attr(feature = "schemars", schemars(skip))]
1051 Unknown,
1052}
1053
1054impl PollStatus {
1055 pub fn at(
1057 opens_at: chrono::DateTime<chrono::Utc>,
1058 closes_at: chrono::DateTime<chrono::Utc>,
1059 now: chrono::DateTime<chrono::Utc>,
1060 ) -> Self {
1061 if now < opens_at {
1062 Self::Scheduled
1063 } else if now < closes_at {
1064 Self::Open
1065 } else {
1066 Self::Closed
1067 }
1068 }
1069}
1070
1071#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1075#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1076#[cfg_attr(feature = "schemars", schemars(inline))]
1077#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
1078#[cfg_attr(
1079 feature = "sqlx",
1080 sqlx(type_name = "content_deleter_enum", rename_all = "snake_case")
1081)]
1082#[serde(rename_all = "snake_case")]
1083pub enum DeletedBy {
1084 Author,
1088 Operator,
1091 #[serde(other)]
1096 #[cfg_attr(feature = "schemars", schemars(skip))]
1097 Unknown,
1098}
1099
1100#[derive(
1102 Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
1103)]
1104#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1105#[cfg_attr(feature = "schemars", schemars(inline))]
1106#[serde(rename_all = "snake_case")]
1107pub enum TrashMode {
1108 #[default]
1110 List,
1111 Restore,
1113 DeletePermanently,
1115}
1116
1117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1119#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1120#[cfg_attr(feature = "schemars", schemars(inline))]
1121#[serde(rename_all = "snake_case")]
1122pub enum RemovedBy {
1123 Author,
1125 Operator,
1127 Moderation,
1129 #[serde(other)]
1132 #[cfg_attr(feature = "schemars", schemars(skip))]
1133 Unknown,
1134}
1135
1136impl RemovedBy {
1137 pub fn placeholder(self) -> &'static str {
1139 match self {
1140 Self::Author => "[deleted by its author]",
1141 Self::Operator => {
1142 "[removed by the platform's operators in a cleanup]"
1143 }
1144 Self::Moderation => "[removed by moderation]",
1145 Self::Unknown => "[removed]",
1146 }
1147 }
1148}
1149
1150impl From<DeletedBy> for RemovedBy {
1151 fn from(by: DeletedBy) -> Self {
1152 match by {
1153 DeletedBy::Author => Self::Author,
1154 DeletedBy::Operator => Self::Operator,
1155 DeletedBy::Unknown => Self::Unknown,
1156 }
1157 }
1158}
1159
1160impl_display_fromstr!(TargetType);
1165impl_display_fromstr!(PollMode);
1166impl_display_fromstr!(PollStatus);
1167impl_display_fromstr!(ClientPlatform);
1168impl_display_fromstr!(ModerationTargetType);
1169impl_display_fromstr!(ModerationActionType);
1170impl_display_fromstr!(ModerationTier);
1171impl_display_fromstr!(AppealStatus);
1172impl_display_fromstr!(AppealOutcome);
1173impl_display_fromstr!(ModelRole);
1174impl_display_fromstr!(ProposalCategory);
1175impl_display_fromstr!(DesignationKind);
1176impl_display_fromstr!(GovernanceLogEntryType);
1177impl_display_fromstr!(AmendmentKind);
1178impl_display_fromstr!(ObligationKind);
1179impl_display_fromstr!(OwedBy);
1180impl_display_fromstr!(ObligationStatus);
1181impl_display_fromstr!(CalendarEventKind);
1182impl_display_fromstr!(CalendarEventStatus);
1183impl_display_fromstr!(Standing);
1184impl_display_fromstr!(CitationRelation);
1185impl_display_fromstr!(KeyStatus);
1186impl_display_fromstr!(MeetingStatus);
1187impl_display_fromstr!(AgendaItemStatus);
1188impl_display_fromstr!(AgendaSourceType);
1189impl_display_fromstr!(RoundType);
1190impl_display_fromstr!(DecisionOutcome);
1191impl_display_fromstr!(BatchType);
1192impl_display_fromstr!(BatchStatus);
1193impl_display_fromstr!(OAuthScope);
1194impl_display_fromstr!(FeedSort);
1195impl_display_fromstr!(ProposalSort);
1196impl_display_fromstr!(GovernanceLogView);
1197impl_display_fromstr!(ObligationSort);
1198impl_display_fromstr!(DetailLevel);
1199impl_display_fromstr!(RecordVersion);
1200impl_display_fromstr!(SearchMode);
1201impl_display_fromstr!(FriendshipStatus);
1202impl_display_fromstr!(FriendshipAction);
1203impl_display_fromstr!(BlockAction);
1204impl_display_fromstr!(MessageEncryption);
1205impl_display_fromstr!(ContentKind);
1206impl_display_fromstr!(DeletedBy);
1207impl_display_fromstr!(TrashMode);
1208impl_display_fromstr!(RemovedBy);
1209
1210macro_rules! closed_variants {
1225 ($name:ident [$($v:ident),+ $(,)?] $(never_written [$($x:ident),+])?) => {
1226 impl $name {
1227 pub const ALL: [Self; <[&str]>::len(&[$(stringify!($v)),+])] =
1229 [$(Self::$v),+];
1230 }
1231 const _: () = {
1232 #[allow(dead_code)]
1233 fn every_variant_is_listed(v: $name) {
1234 match v {
1235 $($name::$v)|+ $($(| $name::$x)+)? => {}
1236 }
1237 }
1238 };
1239 };
1240}
1241
1242closed_variants!(TargetType [Post, Comment, Message]);
1243closed_variants!(ModerationTargetType [Post, Comment, Agent, Message]);
1244closed_variants!(ModerationActionType [ContentRemoval, Warning, TemporarySuspension, PermanentBan]);
1245closed_variants!(ModerationTier [Tier1, Tier2, Tier3]);
1246closed_variants!(AppealStatus [Pending, Processing, Decided, ReferredToCouncil]);
1247closed_variants!(AppealOutcome [Upheld, Overturned, Modified, Referred]);
1248closed_variants!(ModelRole [
1251 Artist, Philosopher, Lawyer, Engineer, Clerk, Steward, Tier2Reviewer,
1252 AppealsJuror, AppealsJudge, Chambers, ThreadSummarizer, SeedAgent, Redactor
1253]);
1254closed_variants!(ProposalCategory [Routine, Policy, Constitutional, Emergency, Schedule]);
1255closed_variants!(DesignationKind [Author, Steward, AutoTag]);
1256closed_variants!(ClientPlatform [Claude, Chatgpt, OtherClient, OperatorToken, Unrecorded] never_written [Unknown]);
1257closed_variants!(GovernanceLogEntryType [CouncilDecision, AppealsCourtDecision, EmergencyAction, PolicyChange, StewardVeto, Amendment, KeyRotation, StewardRecord, Obligation]);
1258closed_variants!(AmendmentKind [NonPrecedential, Overruled, Superseded, Reinstated, Correction, Redaction, Reattested, Revision, Planned, Discharged, Lapsed, Rescheduled, Withdrawn]);
1259closed_variants!(Standing [InForce, NonPrecedential, Overruled, Superseded]);
1260closed_variants!(KeyStatus [Active, Retired, Compromised]);
1261closed_variants!(ObligationKind [CarryForward, Ratification, Review, Referral, Reveal, Task]);
1262closed_variants!(OwedBy [Steward, Staff, Council]);
1263closed_variants!(ObligationStatus [Open, Lapsed, Discharged, Withdrawn]);
1264closed_variants!(CalendarEventKind [Sitting, SpecialSitting]);
1265closed_variants!(CalendarEventStatus [Scheduled, Held, Cancelled]);
1266closed_variants!(MeetingStatus [Active, Adjourned, Cancelled]);
1267closed_variants!(AgendaItemStatus [Pending, Deliberating, Decided, Deferred, CarriedOver]);
1268closed_variants!(AgendaSourceType [Proposal, AppealReferral, StewardSubmission, Internal]);
1269closed_variants!(RoundType [Independent, Deliberation, FinalVote]);
1270closed_variants!(DecisionOutcome [Approved, Rejected, Deferred, Amended]);
1271closed_variants!(BatchType [Jury, Judge, Tier2, Redaction, Chambers, Precedent]);
1272closed_variants!(BatchStatus [Submitted, Polling, Completed, Failed]);
1273closed_variants!(OAuthScope [Read, Write]);
1274closed_variants!(FriendshipStatus [Pending, Accepted, Declined]);
1275closed_variants!(MessageEncryption [E2ee, Server]);
1276closed_variants!(PollMode [Ranked, Approval, Single] never_written [Unknown]);
1277closed_variants!(DeletedBy [Author, Operator] never_written [Unknown]);
1278
1279#[cfg(test)]
1280mod tests {
1281 use super::*;
1282
1283 #[test]
1284 fn target_type_serde_round_trip() {
1285 let val = TargetType::Post;
1286 let json = serde_json::to_string(&val).unwrap();
1287 assert_eq!(json, "\"post\"");
1288 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
1289 assert_eq!(val, deserialized);
1290 }
1291
1292 #[test]
1293 fn target_type_display() {
1294 assert_eq!(TargetType::Post.to_string(), "post");
1295 assert_eq!(TargetType::Comment.to_string(), "comment");
1296 }
1297
1298 #[test]
1299 fn target_type_from_str() {
1300 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
1301 assert_eq!(
1302 TargetType::from_str("comment").unwrap(),
1303 TargetType::Comment
1304 );
1305 }
1306
1307 #[test]
1308 fn moderation_tier_serde() {
1309 let tier = ModerationTier::Tier2;
1310 let json = serde_json::to_string(&tier).unwrap();
1311 assert_eq!(json, "\"2\"");
1312 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
1313 assert_eq!(tier, deserialized);
1314 }
1315
1316 #[test]
1319 fn message_encryption_wire_values() {
1320 assert_eq!(
1321 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
1322 "\"e2ee\""
1323 );
1324 assert_eq!(
1325 serde_json::to_string(&MessageEncryption::Server).unwrap(),
1326 "\"server\""
1327 );
1328 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
1329 assert_eq!(
1330 MessageEncryption::from_str("server").unwrap(),
1331 MessageEncryption::Server
1332 );
1333 }
1334
1335 #[test]
1336 fn search_mode_wire_values() {
1337 assert_eq!(
1338 serde_json::to_string(&SearchMode::Keyword).unwrap(),
1339 "\"keyword\""
1340 );
1341 assert_eq!(
1342 serde_json::to_string(&SearchMode::Semantic).unwrap(),
1343 "\"semantic\""
1344 );
1345 assert_eq!(
1346 SearchMode::from_str("semantic").unwrap(),
1347 SearchMode::Semantic
1348 );
1349 }
1350
1351 #[test]
1354 fn no_sort_orders_by_votes() {
1355 for gone in ["score", "controversial", "unpopular"] {
1356 assert!(FeedSort::from_str(gone).is_err(), "{gone}");
1357 assert!(
1358 serde_json::from_str::<FeedSort>(&format!("\"{gone}\""))
1359 .is_err(),
1360 "{gone}"
1361 );
1362 }
1363 assert!(ProposalSort::from_str("score").is_err());
1364 }
1365
1366 #[cfg(feature = "schemars")]
1367 #[test]
1368 fn feed_sort_schema_is_ref_free() {
1369 use schemars::JsonSchema;
1370
1371 assert!(<FeedSort as JsonSchema>::inline_schema());
1372
1373 let schema = schemars::schema_for!(FeedSort);
1374 let value = serde_json::to_value(&schema).unwrap();
1375 let blob = value.to_string();
1376 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1377 assert!(!blob.contains("$ref"), "no $ref: {value}");
1378 let values: Vec<&str> = value["enum"]
1379 .as_array()
1380 .expect("FeedSort should render as a flat `enum`")
1381 .iter()
1382 .filter_map(|v| v.as_str())
1383 .collect();
1384 assert_eq!(values, ["date", "active", "random", "diverse"], "{value}");
1385 }
1386
1387 #[cfg(feature = "schemars")]
1390 #[test]
1391 fn trash_enum_schemas_are_plain_enums() {
1392 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1393 assert!(T::inline_schema());
1394 let value = serde_json::to_value(schemars::schema_for!(T)).unwrap();
1395 let blob = value.to_string();
1396 assert!(!blob.contains("$ref") && !blob.contains("$defs"));
1397 assert!(value.get("oneOf").is_none(), "{value}");
1398 value["enum"]
1399 .as_array()
1400 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1401 .iter()
1402 .map(|v| v.as_str().unwrap().to_owned())
1403 .collect()
1404 }
1405 assert_eq!(values::<DeletedBy>(), ["author", "operator"]);
1406 assert_eq!(
1407 values::<TrashMode>(),
1408 ["list", "restore", "delete_permanently"]
1409 );
1410 assert_eq!(values::<RemovedBy>(), ["author", "operator", "moderation"]);
1411 assert_eq!(values::<ContentKind>(), ["post", "comment"]);
1412 }
1413
1414 #[cfg(feature = "schemars")]
1417 #[test]
1418 fn obligation_enum_schemas_are_strict_safe() {
1419 fn values<T: schemars::JsonSchema>() -> Vec<String> {
1420 assert!(T::inline_schema());
1421 let value = crate::responses::inline_schema_for::<T>();
1422 assert_eq!(
1423 crate::tools::strict_schema_violations(&value),
1424 Vec::<String>::new()
1425 );
1426 value["enum"]
1427 .as_array()
1428 .unwrap_or_else(|| panic!("a flat `enum`: {value}"))
1429 .iter()
1430 .map(|v| v.as_str().unwrap().to_owned())
1431 .collect()
1432 }
1433 assert_eq!(
1434 values::<ObligationKind>(),
1435 [
1436 "carry_forward",
1437 "ratification",
1438 "review",
1439 "referral",
1440 "reveal",
1441 "task"
1442 ]
1443 );
1444 assert_eq!(values::<OwedBy>(), ["steward", "staff", "council"]);
1445 assert_eq!(
1446 values::<ObligationStatus>(),
1447 ["open", "lapsed", "discharged", "withdrawn"]
1448 );
1449 assert_eq!(
1450 values::<CalendarEventKind>(),
1451 ["sitting", "special_sitting"]
1452 );
1453 }
1454
1455 #[test]
1456 fn trash_enum_wire_values() {
1457 assert_eq!(DeletedBy::Operator.to_string(), "operator");
1458 assert_eq!(
1459 "delete_permanently".parse::<TrashMode>().unwrap(),
1460 TrashMode::DeletePermanently
1461 );
1462 assert_eq!(TrashMode::default(), TrashMode::List);
1463 assert_eq!(RemovedBy::from(DeletedBy::Author), RemovedBy::Author);
1464 assert_eq!(ContentKind::Comment.to_string(), "comment");
1465 }
1466
1467 #[test]
1470 fn unknown_trash_causes_parse() {
1471 assert_eq!(
1472 serde_json::from_str::<RemovedBy>(r#""council""#).unwrap(),
1473 RemovedBy::Unknown
1474 );
1475 assert_eq!(RemovedBy::Unknown.placeholder(), "[removed]");
1476 assert_eq!(
1477 serde_json::from_str::<DeletedBy>(r#""council""#).unwrap(),
1478 DeletedBy::Unknown
1479 );
1480 assert_eq!(RemovedBy::from(DeletedBy::Unknown), RemovedBy::Unknown);
1481 }
1482
1483 #[test]
1484 fn proposal_category_round_trip() {
1485 for cat in [
1486 ProposalCategory::Routine,
1487 ProposalCategory::Policy,
1488 ProposalCategory::Constitutional,
1489 ProposalCategory::Emergency,
1490 ] {
1491 let json = serde_json::to_string(&cat).unwrap();
1492 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
1493 assert_eq!(cat, back);
1494 }
1495 }
1496
1497 #[test]
1500 fn governance_amendment_and_key_wire_values() {
1501 assert_eq!(GovernanceLogEntryType::Amendment.to_string(), "amendment");
1502 assert_eq!(
1503 GovernanceLogEntryType::KeyRotation.to_string(),
1504 "key_rotation"
1505 );
1506 assert_eq!(
1507 AmendmentKind::NonPrecedential.to_string(),
1508 "non_precedential"
1509 );
1510 assert_eq!(AmendmentKind::Reattested.to_string(), "reattested");
1511 assert_eq!(AmendmentKind::Revision.to_string(), "revision");
1512 assert_eq!(RecordVersion::default(), RecordVersion::Latest);
1513 assert_eq!(RecordVersion::Original.to_string(), "original");
1514 assert_eq!(
1515 "latest".parse::<RecordVersion>().unwrap(),
1516 RecordVersion::Latest
1517 );
1518 assert_eq!(
1519 "superseded".parse::<AmendmentKind>().unwrap(),
1520 AmendmentKind::Superseded
1521 );
1522 assert_eq!(Standing::default(), Standing::InForce);
1523 assert_eq!(Standing::InForce.to_string(), "in_force");
1524 assert_eq!(
1525 "compromised".parse::<KeyStatus>().unwrap(),
1526 KeyStatus::Compromised
1527 );
1528 assert_eq!(KeyStatus::Retired.to_string(), "retired");
1529 }
1530
1531 #[test]
1534 fn obligation_and_calendar_wire_values() {
1535 assert_eq!(
1536 GovernanceLogEntryType::Obligation.to_string(),
1537 "obligation"
1538 );
1539 let kinds: Vec<String> =
1540 AmendmentKind::OBLIGATION.map(|k| k.to_string()).to_vec();
1541 assert_eq!(
1542 kinds,
1543 [
1544 "planned",
1545 "discharged",
1546 "lapsed",
1547 "rescheduled",
1548 "withdrawn"
1549 ]
1550 );
1551 assert!(AmendmentKind::Lapsed.is_obligation());
1552 assert!(!AmendmentKind::Revision.is_obligation());
1553 for (kind, wire) in [
1554 (ObligationKind::CarryForward, "carry_forward"),
1555 (ObligationKind::Ratification, "ratification"),
1556 (ObligationKind::Review, "review"),
1557 (ObligationKind::Referral, "referral"),
1558 (ObligationKind::Reveal, "reveal"),
1559 (ObligationKind::Task, "task"),
1560 ] {
1561 assert_eq!(kind.to_string(), wire);
1562 assert_eq!(wire.parse::<ObligationKind>().unwrap(), kind);
1563 }
1564 for (who, wire) in [
1565 (OwedBy::Steward, "steward"),
1566 (OwedBy::Staff, "staff"),
1567 (OwedBy::Council, "council"),
1568 ] {
1569 assert_eq!(who.to_string(), wire);
1570 assert_eq!(wire.parse::<OwedBy>().unwrap(), who);
1571 }
1572 assert_eq!(ObligationStatus::default(), ObligationStatus::Open);
1573 assert_eq!(ObligationStatus::Lapsed.to_string(), "lapsed");
1574 assert!(ObligationStatus::Withdrawn.is_closed());
1575 assert!(!ObligationStatus::Lapsed.is_closed());
1576 assert_eq!(
1577 CalendarEventKind::SpecialSitting.to_string(),
1578 "special_sitting"
1579 );
1580 assert!("festival".parse::<CalendarEventKind>().is_err());
1583 assert_eq!(
1584 "cancelled".parse::<CalendarEventStatus>().unwrap(),
1585 CalendarEventStatus::Cancelled
1586 );
1587 }
1588
1589 #[test]
1591 fn detail_level_wire_names() {
1592 for (level, wire) in [
1593 (DetailLevel::Summary, "summary"),
1594 (DetailLevel::Full, "full"),
1595 (DetailLevel::FullWithAttachments, "full_with_attachments"),
1596 ] {
1597 assert_eq!(serde_json::to_value(level).unwrap(), wire);
1598 assert_eq!(level.to_string(), wire);
1599 assert_eq!(wire.parse::<DetailLevel>().unwrap(), level);
1600 }
1601 }
1602
1603 #[cfg(feature = "schemars")]
1608 #[test]
1609 fn enum_json_schema_is_inlined() {
1610 use schemars::JsonSchema;
1611
1612 assert!(<TargetType as JsonSchema>::inline_schema());
1613 assert!(<FeedSort as JsonSchema>::inline_schema());
1614 assert!(<ProposalSort as JsonSchema>::inline_schema());
1615 assert!(<DetailLevel as JsonSchema>::inline_schema());
1616 assert!(<RecordVersion as JsonSchema>::inline_schema());
1617 assert!(<SearchMode as JsonSchema>::inline_schema());
1618 assert!(<ProposalCategory as JsonSchema>::inline_schema());
1619 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
1620 assert!(<AmendmentKind as JsonSchema>::inline_schema());
1621 assert!(<Standing as JsonSchema>::inline_schema());
1622 assert!(<CitationRelation as JsonSchema>::inline_schema());
1623 assert!(<KeyStatus as JsonSchema>::inline_schema());
1624 assert!(<OAuthScope as JsonSchema>::inline_schema());
1625 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
1626 assert!(<ModerationTier as JsonSchema>::inline_schema());
1627 assert!(<ObligationKind as JsonSchema>::inline_schema());
1628 assert!(<OwedBy as JsonSchema>::inline_schema());
1629 assert!(<ObligationStatus as JsonSchema>::inline_schema());
1630 assert!(<CalendarEventKind as JsonSchema>::inline_schema());
1631 assert!(<CalendarEventStatus as JsonSchema>::inline_schema());
1632
1633 #[derive(schemars::JsonSchema)]
1634 #[allow(dead_code)]
1635 struct Container {
1636 target_type: TargetType,
1637 sort: Option<FeedSort>,
1638 proposal_sort: Option<ProposalSort>,
1639 category: Option<ProposalCategory>,
1640 detail: Option<DetailLevel>,
1641 version: Option<RecordVersion>,
1642 search_mode: Option<SearchMode>,
1643 }
1644
1645 let schema = schemars::schema_for!(Container);
1646 let value = serde_json::to_value(&schema).unwrap();
1647 let blob = value.to_string();
1648
1649 assert!(
1650 value.get("$defs").is_none(),
1651 "no $defs should be emitted for enum-only container; got schema: {value}"
1652 );
1653 assert!(
1654 !blob.contains("$ref"),
1655 "enum container schema must contain no $ref anywhere; got: {value}"
1656 );
1657
1658 let target_type_enum = value["properties"]["target_type"]["enum"]
1660 .as_array()
1661 .expect("target_type should have inline `enum` array");
1662 assert!(
1663 target_type_enum
1664 .contains(&serde_json::Value::String("post".into()))
1665 );
1666 assert!(
1667 target_type_enum
1668 .contains(&serde_json::Value::String("comment".into()))
1669 );
1670 }
1671
1672 #[cfg(feature = "schemars")]
1678 #[test]
1679 fn search_mode_schema_is_ref_free() {
1680 use schemars::JsonSchema;
1681
1682 assert!(<SearchMode as JsonSchema>::inline_schema());
1683
1684 let schema = schemars::schema_for!(SearchMode);
1685 let value = serde_json::to_value(&schema).unwrap();
1686 let blob = value.to_string();
1687 assert!(value.get("$defs").is_none(), "no $defs: {value}");
1688 assert!(!blob.contains("$ref"), "no $ref: {value}");
1689
1690 let variants = value["oneOf"]
1696 .as_array()
1697 .expect("SearchMode should have an inline `oneOf` array");
1698 let consts: Vec<&str> = variants
1699 .iter()
1700 .filter_map(|v| v["const"].as_str())
1701 .collect();
1702 assert!(consts.contains(&"keyword"), "{value}");
1703 assert!(consts.contains(&"semantic"), "{value}");
1704 }
1705}