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 Steward,
193 Tier2Reviewer,
195 AppealsJuror,
197 AppealsJudge,
199 Chambers,
201 ThreadSummarizer,
203 SeedAgent,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
213#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
214#[cfg_attr(feature = "schemars", schemars(inline))]
215#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
216#[cfg_attr(
217 feature = "sqlx",
218 sqlx(type_name = "proposal_category_enum", rename_all = "snake_case")
219)]
220#[serde(rename_all = "snake_case")]
221pub enum ProposalCategory {
222 Routine,
223 Policy,
224 Constitutional,
225 Emergency,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
231#[cfg_attr(feature = "schemars", schemars(inline))]
232#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
233#[cfg_attr(
234 feature = "sqlx",
235 sqlx(
236 type_name = "governance_log_entry_type_enum",
237 rename_all = "snake_case"
238 )
239)]
240#[serde(rename_all = "snake_case")]
241pub enum GovernanceLogEntryType {
242 CouncilDecision,
243 AppealsCourtDecision,
244 EmergencyAction,
245 PolicyChange,
246 StewardVeto,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
255#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
256#[cfg_attr(feature = "schemars", schemars(inline))]
257#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
258#[cfg_attr(
259 feature = "sqlx",
260 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
261)]
262#[serde(rename_all = "snake_case")]
263pub enum MeetingStatus {
264 Active,
265 Adjourned,
266 Cancelled,
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
271#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
272#[cfg_attr(feature = "schemars", schemars(inline))]
273#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
274#[cfg_attr(
275 feature = "sqlx",
276 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
277)]
278#[serde(rename_all = "snake_case")]
279pub enum AgendaItemStatus {
280 Pending,
281 Deliberating,
282 Decided,
283 Deferred,
284 CarriedOver,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
289#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
290#[cfg_attr(feature = "schemars", schemars(inline))]
291#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
292#[cfg_attr(
293 feature = "sqlx",
294 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
295)]
296#[serde(rename_all = "snake_case")]
297pub enum AgendaSourceType {
298 Proposal,
299 AppealReferral,
300 StewardSubmission,
301 Internal,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
307#[cfg_attr(feature = "schemars", schemars(inline))]
308#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
309#[cfg_attr(
310 feature = "sqlx",
311 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
312)]
313#[serde(rename_all = "snake_case")]
314pub enum RoundType {
315 Independent,
316 Deliberation,
317 FinalVote,
318}
319
320#[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 = "decision_outcome_enum", rename_all = "snake_case")
328)]
329#[serde(rename_all = "snake_case")]
330pub enum DecisionOutcome {
331 Approved,
332 Rejected,
333 Deferred,
334 Amended,
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
343#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
344#[cfg_attr(feature = "schemars", schemars(inline))]
345#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
346#[cfg_attr(
347 feature = "sqlx",
348 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
349)]
350#[serde(rename_all = "snake_case")]
351pub enum BatchType {
352 Jury,
353 Judge,
354 Tier2,
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
359#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
360#[cfg_attr(feature = "schemars", schemars(inline))]
361#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
362#[cfg_attr(
363 feature = "sqlx",
364 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
365)]
366#[serde(rename_all = "snake_case")]
367pub enum BatchStatus {
368 Submitted,
369 Polling,
370 Completed,
371 Failed,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, 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 = "oauth_scope_enum", rename_all = "snake_case")
386)]
387#[serde(rename_all = "snake_case")]
388pub enum OAuthScope {
389 Read,
390 Write,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
400#[cfg_attr(feature = "schemars", schemars(inline))]
401#[serde(rename_all = "snake_case")]
402pub enum FeedSort {
403 Date,
404 Score,
405 Active,
406 Random,
407 Controversial,
408 Diverse,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
421#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
422#[cfg_attr(feature = "schemars", schemars(inline))]
423#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
424#[cfg_attr(
425 feature = "sqlx",
426 sqlx(type_name = "friendship_status", rename_all = "snake_case")
427)]
428#[serde(rename_all = "snake_case")]
429pub enum FriendshipStatus {
430 Pending,
431 Accepted,
432 Declined,
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
439#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
440#[cfg_attr(feature = "schemars", schemars(inline))]
441#[serde(rename_all = "snake_case")]
442pub enum FriendshipAction {
443 Request,
445 Accept,
447 Decline,
449 Unfriend,
451}
452
453#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
460#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
461#[cfg_attr(feature = "schemars", schemars(inline))]
462#[cfg_attr(
463 feature = "sqlx",
464 derive(sqlx::Type),
465 sqlx(type_name = "message_encryption", rename_all = "snake_case")
466)]
467#[serde(rename_all = "snake_case")]
468pub enum MessageEncryption {
469 E2ee,
471 Server,
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
477#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
478#[cfg_attr(feature = "schemars", schemars(inline))]
479#[serde(rename_all = "snake_case")]
480pub enum BlockAction {
481 Block,
482 Unblock,
483}
484
485impl_display_fromstr!(TargetType);
490impl_display_fromstr!(ModerationTargetType);
491impl_display_fromstr!(ModerationActionType);
492impl_display_fromstr!(ModerationTier);
493impl_display_fromstr!(AppealStatus);
494impl_display_fromstr!(AppealOutcome);
495impl_display_fromstr!(ModelRole);
496impl_display_fromstr!(ProposalCategory);
497impl_display_fromstr!(GovernanceLogEntryType);
498impl_display_fromstr!(MeetingStatus);
499impl_display_fromstr!(AgendaItemStatus);
500impl_display_fromstr!(AgendaSourceType);
501impl_display_fromstr!(RoundType);
502impl_display_fromstr!(DecisionOutcome);
503impl_display_fromstr!(BatchType);
504impl_display_fromstr!(BatchStatus);
505impl_display_fromstr!(OAuthScope);
506impl_display_fromstr!(FeedSort);
507impl_display_fromstr!(FriendshipStatus);
508impl_display_fromstr!(FriendshipAction);
509impl_display_fromstr!(BlockAction);
510impl_display_fromstr!(MessageEncryption);
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn target_type_serde_round_trip() {
518 let val = TargetType::Post;
519 let json = serde_json::to_string(&val).unwrap();
520 assert_eq!(json, "\"post\"");
521 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
522 assert_eq!(val, deserialized);
523 }
524
525 #[test]
526 fn target_type_display() {
527 assert_eq!(TargetType::Post.to_string(), "post");
528 assert_eq!(TargetType::Comment.to_string(), "comment");
529 }
530
531 #[test]
532 fn target_type_from_str() {
533 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
534 assert_eq!(
535 TargetType::from_str("comment").unwrap(),
536 TargetType::Comment
537 );
538 }
539
540 #[test]
541 fn moderation_tier_serde() {
542 let tier = ModerationTier::Tier2;
543 let json = serde_json::to_string(&tier).unwrap();
544 assert_eq!(json, "\"2\"");
545 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
546 assert_eq!(tier, deserialized);
547 }
548
549 #[test]
552 fn message_encryption_wire_values() {
553 assert_eq!(
554 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
555 "\"e2ee\""
556 );
557 assert_eq!(
558 serde_json::to_string(&MessageEncryption::Server).unwrap(),
559 "\"server\""
560 );
561 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
562 assert_eq!(
563 MessageEncryption::from_str("server").unwrap(),
564 MessageEncryption::Server
565 );
566 }
567
568 #[test]
569 fn proposal_category_round_trip() {
570 for cat in [
571 ProposalCategory::Routine,
572 ProposalCategory::Policy,
573 ProposalCategory::Constitutional,
574 ProposalCategory::Emergency,
575 ] {
576 let json = serde_json::to_string(&cat).unwrap();
577 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
578 assert_eq!(cat, back);
579 }
580 }
581
582 #[cfg(feature = "schemars")]
587 #[test]
588 fn enum_json_schema_is_inlined() {
589 use schemars::JsonSchema;
590
591 assert!(<TargetType as JsonSchema>::inline_schema());
592 assert!(<FeedSort as JsonSchema>::inline_schema());
593 assert!(<ProposalCategory as JsonSchema>::inline_schema());
594 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
595 assert!(<OAuthScope as JsonSchema>::inline_schema());
596 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
597 assert!(<ModerationTier as JsonSchema>::inline_schema());
598
599 #[derive(schemars::JsonSchema)]
600 #[allow(dead_code)]
601 struct Container {
602 target_type: TargetType,
603 sort: Option<FeedSort>,
604 category: Option<ProposalCategory>,
605 }
606
607 let schema = schemars::schema_for!(Container);
608 let value = serde_json::to_value(&schema).unwrap();
609 let blob = value.to_string();
610
611 assert!(
612 value.get("$defs").is_none(),
613 "no $defs should be emitted for enum-only container; got schema: {value}"
614 );
615 assert!(
616 !blob.contains("$ref"),
617 "enum container schema must contain no $ref anywhere; got: {value}"
618 );
619
620 let target_type_enum = value["properties"]["target_type"]["enum"]
622 .as_array()
623 .expect("target_type should have inline `enum` array");
624 assert!(
625 target_type_enum
626 .contains(&serde_json::Value::String("post".into()))
627 );
628 assert!(
629 target_type_enum
630 .contains(&serde_json::Value::String("comment".into()))
631 );
632 }
633}