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)]
167#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
168#[cfg_attr(feature = "schemars", schemars(inline))]
169#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
170#[cfg_attr(
171 feature = "sqlx",
172 sqlx(type_name = "proposal_category_enum", rename_all = "snake_case")
173)]
174#[serde(rename_all = "snake_case")]
175pub enum ProposalCategory {
176 Routine,
177 Policy,
178 Constitutional,
179 Emergency,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
184#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
185#[cfg_attr(feature = "schemars", schemars(inline))]
186#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
187#[cfg_attr(
188 feature = "sqlx",
189 sqlx(
190 type_name = "governance_log_entry_type_enum",
191 rename_all = "snake_case"
192 )
193)]
194#[serde(rename_all = "snake_case")]
195pub enum GovernanceLogEntryType {
196 CouncilDecision,
197 AppealsCourtDecision,
198 EmergencyAction,
199 PolicyChange,
200 StewardVeto,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
209#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
210#[cfg_attr(feature = "schemars", schemars(inline))]
211#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
212#[cfg_attr(
213 feature = "sqlx",
214 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
215)]
216#[serde(rename_all = "snake_case")]
217pub enum MeetingStatus {
218 Active,
219 Adjourned,
220 Cancelled,
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
225#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
226#[cfg_attr(feature = "schemars", schemars(inline))]
227#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
228#[cfg_attr(
229 feature = "sqlx",
230 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
231)]
232#[serde(rename_all = "snake_case")]
233pub enum AgendaItemStatus {
234 Pending,
235 Deliberating,
236 Decided,
237 Deferred,
238 CarriedOver,
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
243#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
244#[cfg_attr(feature = "schemars", schemars(inline))]
245#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
246#[cfg_attr(
247 feature = "sqlx",
248 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
249)]
250#[serde(rename_all = "snake_case")]
251pub enum AgendaSourceType {
252 Proposal,
253 AppealReferral,
254 StewardSubmission,
255 Internal,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
260#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
261#[cfg_attr(feature = "schemars", schemars(inline))]
262#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
263#[cfg_attr(
264 feature = "sqlx",
265 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
266)]
267#[serde(rename_all = "snake_case")]
268pub enum RoundType {
269 Independent,
270 Deliberation,
271 FinalVote,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
276#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
277#[cfg_attr(feature = "schemars", schemars(inline))]
278#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
279#[cfg_attr(
280 feature = "sqlx",
281 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
282)]
283#[serde(rename_all = "snake_case")]
284pub enum DecisionOutcome {
285 Approved,
286 Rejected,
287 Deferred,
288 Amended,
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
297#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
298#[cfg_attr(feature = "schemars", schemars(inline))]
299#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
300#[cfg_attr(
301 feature = "sqlx",
302 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
303)]
304#[serde(rename_all = "snake_case")]
305pub enum BatchType {
306 Jury,
307 Judge,
308 Tier2,
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
314#[cfg_attr(feature = "schemars", schemars(inline))]
315#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
316#[cfg_attr(
317 feature = "sqlx",
318 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
319)]
320#[serde(rename_all = "snake_case")]
321pub enum BatchStatus {
322 Submitted,
323 Polling,
324 Completed,
325 Failed,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, 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 = "oauth_scope_enum", rename_all = "snake_case")
340)]
341#[serde(rename_all = "snake_case")]
342pub enum OAuthScope {
343 Read,
344 Write,
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
353#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
354#[cfg_attr(feature = "schemars", schemars(inline))]
355#[serde(rename_all = "snake_case")]
356pub enum FeedSort {
357 Date,
358 Score,
359 Active,
360 Random,
361 Controversial,
362 Diverse,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
375#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
376#[cfg_attr(feature = "schemars", schemars(inline))]
377#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
378#[cfg_attr(
379 feature = "sqlx",
380 sqlx(type_name = "friendship_status", rename_all = "snake_case")
381)]
382#[serde(rename_all = "snake_case")]
383pub enum FriendshipStatus {
384 Pending,
385 Accepted,
386 Declined,
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
393#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
394#[cfg_attr(feature = "schemars", schemars(inline))]
395#[serde(rename_all = "snake_case")]
396pub enum FriendshipAction {
397 Request,
399 Accept,
401 Decline,
403 Unfriend,
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
414#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
415#[cfg_attr(feature = "schemars", schemars(inline))]
416#[cfg_attr(
417 feature = "sqlx",
418 derive(sqlx::Type),
419 sqlx(type_name = "message_encryption", rename_all = "snake_case")
420)]
421#[serde(rename_all = "snake_case")]
422pub enum MessageEncryption {
423 E2ee,
425 Server,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
431#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
432#[cfg_attr(feature = "schemars", schemars(inline))]
433#[serde(rename_all = "snake_case")]
434pub enum BlockAction {
435 Block,
436 Unblock,
437}
438
439impl_display_fromstr!(TargetType);
444impl_display_fromstr!(ModerationTargetType);
445impl_display_fromstr!(ModerationActionType);
446impl_display_fromstr!(ModerationTier);
447impl_display_fromstr!(AppealStatus);
448impl_display_fromstr!(AppealOutcome);
449impl_display_fromstr!(ProposalCategory);
450impl_display_fromstr!(GovernanceLogEntryType);
451impl_display_fromstr!(MeetingStatus);
452impl_display_fromstr!(AgendaItemStatus);
453impl_display_fromstr!(AgendaSourceType);
454impl_display_fromstr!(RoundType);
455impl_display_fromstr!(DecisionOutcome);
456impl_display_fromstr!(BatchType);
457impl_display_fromstr!(BatchStatus);
458impl_display_fromstr!(OAuthScope);
459impl_display_fromstr!(FeedSort);
460impl_display_fromstr!(FriendshipStatus);
461impl_display_fromstr!(FriendshipAction);
462impl_display_fromstr!(BlockAction);
463impl_display_fromstr!(MessageEncryption);
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn target_type_serde_round_trip() {
471 let val = TargetType::Post;
472 let json = serde_json::to_string(&val).unwrap();
473 assert_eq!(json, "\"post\"");
474 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
475 assert_eq!(val, deserialized);
476 }
477
478 #[test]
479 fn target_type_display() {
480 assert_eq!(TargetType::Post.to_string(), "post");
481 assert_eq!(TargetType::Comment.to_string(), "comment");
482 }
483
484 #[test]
485 fn target_type_from_str() {
486 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
487 assert_eq!(
488 TargetType::from_str("comment").unwrap(),
489 TargetType::Comment
490 );
491 }
492
493 #[test]
494 fn moderation_tier_serde() {
495 let tier = ModerationTier::Tier2;
496 let json = serde_json::to_string(&tier).unwrap();
497 assert_eq!(json, "\"2\"");
498 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
499 assert_eq!(tier, deserialized);
500 }
501
502 #[test]
505 fn message_encryption_wire_values() {
506 assert_eq!(
507 serde_json::to_string(&MessageEncryption::E2ee).unwrap(),
508 "\"e2ee\""
509 );
510 assert_eq!(
511 serde_json::to_string(&MessageEncryption::Server).unwrap(),
512 "\"server\""
513 );
514 assert_eq!(MessageEncryption::E2ee.to_string(), "e2ee");
515 assert_eq!(
516 MessageEncryption::from_str("server").unwrap(),
517 MessageEncryption::Server
518 );
519 }
520
521 #[test]
522 fn proposal_category_round_trip() {
523 for cat in [
524 ProposalCategory::Routine,
525 ProposalCategory::Policy,
526 ProposalCategory::Constitutional,
527 ProposalCategory::Emergency,
528 ] {
529 let json = serde_json::to_string(&cat).unwrap();
530 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
531 assert_eq!(cat, back);
532 }
533 }
534
535 #[cfg(feature = "schemars")]
540 #[test]
541 fn enum_json_schema_is_inlined() {
542 use schemars::JsonSchema;
543
544 assert!(<TargetType as JsonSchema>::inline_schema());
545 assert!(<FeedSort as JsonSchema>::inline_schema());
546 assert!(<ProposalCategory as JsonSchema>::inline_schema());
547 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
548 assert!(<OAuthScope as JsonSchema>::inline_schema());
549 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
550 assert!(<ModerationTier as JsonSchema>::inline_schema());
551
552 #[derive(schemars::JsonSchema)]
553 #[allow(dead_code)]
554 struct Container {
555 target_type: TargetType,
556 sort: Option<FeedSort>,
557 category: Option<ProposalCategory>,
558 }
559
560 let schema = schemars::schema_for!(Container);
561 let value = serde_json::to_value(&schema).unwrap();
562 let blob = value.to_string();
563
564 assert!(
565 value.get("$defs").is_none(),
566 "no $defs should be emitted for enum-only container; got schema: {value}"
567 );
568 assert!(
569 !blob.contains("$ref"),
570 "enum container schema must contain no $ref anywhere; got: {value}"
571 );
572
573 let target_type_enum = value["properties"]["target_type"]["enum"]
575 .as_array()
576 .expect("target_type should have inline `enum` array");
577 assert!(
578 target_type_enum
579 .contains(&serde_json::Value::String("post".into()))
580 );
581 assert!(
582 target_type_enum
583 .contains(&serde_json::Value::String("comment".into()))
584 );
585 }
586}