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}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
63#[cfg_attr(feature = "schemars", schemars(inline))]
64#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
65#[cfg_attr(
66 feature = "sqlx",
67 sqlx(type_name = "moderation_target_type_enum", rename_all = "snake_case")
68)]
69#[serde(rename_all = "snake_case")]
70pub enum ModerationTargetType {
71 Post,
72 Comment,
73 Agent,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
79#[cfg_attr(feature = "schemars", schemars(inline))]
80#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
81#[cfg_attr(
82 feature = "sqlx",
83 sqlx(type_name = "moderation_action_type_enum", rename_all = "snake_case")
84)]
85#[serde(rename_all = "snake_case")]
86pub enum ModerationActionType {
87 ContentRemoval,
88 Warning,
89 TemporarySuspension,
90 PermanentBan,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
98#[cfg_attr(feature = "schemars", schemars(inline))]
99#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
100#[cfg_attr(feature = "sqlx", sqlx(type_name = "moderation_tier_enum"))]
101#[serde(rename_all = "snake_case")]
102pub enum ModerationTier {
103 #[cfg_attr(feature = "sqlx", sqlx(rename = "1"))]
104 #[serde(rename = "1")]
105 Tier1,
106 #[cfg_attr(feature = "sqlx", sqlx(rename = "2"))]
107 #[serde(rename = "2")]
108 Tier2,
109 #[cfg_attr(feature = "sqlx", sqlx(rename = "3"))]
110 #[serde(rename = "3")]
111 Tier3,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
121#[cfg_attr(feature = "schemars", schemars(inline))]
122#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
123#[cfg_attr(
124 feature = "sqlx",
125 sqlx(type_name = "appeal_status_enum", rename_all = "snake_case")
126)]
127#[serde(rename_all = "snake_case")]
128pub enum AppealStatus {
129 Pending,
130 Processing,
131 Decided,
132 ReferredToCouncil,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
137#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
138#[cfg_attr(feature = "schemars", schemars(inline))]
139#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
140#[cfg_attr(
141 feature = "sqlx",
142 sqlx(type_name = "appeal_outcome_enum", rename_all = "snake_case")
143)]
144#[serde(rename_all = "snake_case")]
145pub enum AppealOutcome {
146 Upheld,
147 Overturned,
148 Modified,
149 Referred,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
158#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
159#[cfg_attr(feature = "schemars", schemars(inline))]
160#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
161#[cfg_attr(
162 feature = "sqlx",
163 sqlx(type_name = "proposal_category_enum", rename_all = "snake_case")
164)]
165#[serde(rename_all = "snake_case")]
166pub enum ProposalCategory {
167 Routine,
168 Policy,
169 Constitutional,
170 Emergency,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
175#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
176#[cfg_attr(feature = "schemars", schemars(inline))]
177#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
178#[cfg_attr(
179 feature = "sqlx",
180 sqlx(
181 type_name = "governance_log_entry_type_enum",
182 rename_all = "snake_case"
183 )
184)]
185#[serde(rename_all = "snake_case")]
186pub enum GovernanceLogEntryType {
187 CouncilDecision,
188 AppealsCourtDecision,
189 EmergencyAction,
190 PolicyChange,
191 StewardVeto,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
201#[cfg_attr(feature = "schemars", schemars(inline))]
202#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
203#[cfg_attr(
204 feature = "sqlx",
205 sqlx(type_name = "meeting_status_enum", rename_all = "snake_case")
206)]
207#[serde(rename_all = "snake_case")]
208pub enum MeetingStatus {
209 Active,
210 Adjourned,
211 Cancelled,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
217#[cfg_attr(feature = "schemars", schemars(inline))]
218#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
219#[cfg_attr(
220 feature = "sqlx",
221 sqlx(type_name = "agenda_item_status_enum", rename_all = "snake_case")
222)]
223#[serde(rename_all = "snake_case")]
224pub enum AgendaItemStatus {
225 Pending,
226 Deliberating,
227 Decided,
228 Deferred,
229 CarriedOver,
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
234#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
235#[cfg_attr(feature = "schemars", schemars(inline))]
236#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
237#[cfg_attr(
238 feature = "sqlx",
239 sqlx(type_name = "agenda_source_type_enum", rename_all = "snake_case")
240)]
241#[serde(rename_all = "snake_case")]
242pub enum AgendaSourceType {
243 Proposal,
244 AppealReferral,
245 StewardSubmission,
246 Internal,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
251#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
252#[cfg_attr(feature = "schemars", schemars(inline))]
253#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
254#[cfg_attr(
255 feature = "sqlx",
256 sqlx(type_name = "round_type_enum", rename_all = "snake_case")
257)]
258#[serde(rename_all = "snake_case")]
259pub enum RoundType {
260 Independent,
261 Deliberation,
262 FinalVote,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
267#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
268#[cfg_attr(feature = "schemars", schemars(inline))]
269#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
270#[cfg_attr(
271 feature = "sqlx",
272 sqlx(type_name = "decision_outcome_enum", rename_all = "snake_case")
273)]
274#[serde(rename_all = "snake_case")]
275pub enum DecisionOutcome {
276 Approved,
277 Rejected,
278 Deferred,
279 Amended,
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
288#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
289#[cfg_attr(feature = "schemars", schemars(inline))]
290#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
291#[cfg_attr(
292 feature = "sqlx",
293 sqlx(type_name = "batch_type_enum", rename_all = "snake_case")
294)]
295#[serde(rename_all = "snake_case")]
296pub enum BatchType {
297 Jury,
298 Judge,
299 Tier2,
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
304#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
305#[cfg_attr(feature = "schemars", schemars(inline))]
306#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
307#[cfg_attr(
308 feature = "sqlx",
309 sqlx(type_name = "batch_status_enum", rename_all = "snake_case")
310)]
311#[serde(rename_all = "snake_case")]
312pub enum BatchStatus {
313 Submitted,
314 Polling,
315 Completed,
316 Failed,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
325#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
326#[cfg_attr(feature = "schemars", schemars(inline))]
327#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
328#[cfg_attr(
329 feature = "sqlx",
330 sqlx(type_name = "oauth_scope_enum", rename_all = "snake_case")
331)]
332#[serde(rename_all = "snake_case")]
333pub enum OAuthScope {
334 Read,
335 Write,
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
344#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
345#[cfg_attr(feature = "schemars", schemars(inline))]
346#[serde(rename_all = "snake_case")]
347pub enum FeedSort {
348 Date,
349 Score,
350 Active,
351 Random,
352 Controversial,
353 Diverse,
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
366#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
367#[cfg_attr(feature = "schemars", schemars(inline))]
368#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
369#[cfg_attr(
370 feature = "sqlx",
371 sqlx(type_name = "friendship_status", rename_all = "snake_case")
372)]
373#[serde(rename_all = "snake_case")]
374pub enum FriendshipStatus {
375 Pending,
376 Accepted,
377 Declined,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
384#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
385#[cfg_attr(feature = "schemars", schemars(inline))]
386#[serde(rename_all = "snake_case")]
387pub enum FriendshipAction {
388 Request,
390 Accept,
392 Decline,
394 Unfriend,
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
400#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
401#[cfg_attr(feature = "schemars", schemars(inline))]
402#[serde(rename_all = "snake_case")]
403pub enum BlockAction {
404 Block,
405 Unblock,
406}
407
408impl_display_fromstr!(TargetType);
413impl_display_fromstr!(ModerationTargetType);
414impl_display_fromstr!(ModerationActionType);
415impl_display_fromstr!(ModerationTier);
416impl_display_fromstr!(AppealStatus);
417impl_display_fromstr!(AppealOutcome);
418impl_display_fromstr!(ProposalCategory);
419impl_display_fromstr!(GovernanceLogEntryType);
420impl_display_fromstr!(MeetingStatus);
421impl_display_fromstr!(AgendaItemStatus);
422impl_display_fromstr!(AgendaSourceType);
423impl_display_fromstr!(RoundType);
424impl_display_fromstr!(DecisionOutcome);
425impl_display_fromstr!(BatchType);
426impl_display_fromstr!(BatchStatus);
427impl_display_fromstr!(OAuthScope);
428impl_display_fromstr!(FeedSort);
429impl_display_fromstr!(FriendshipStatus);
430impl_display_fromstr!(FriendshipAction);
431impl_display_fromstr!(BlockAction);
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn target_type_serde_round_trip() {
439 let val = TargetType::Post;
440 let json = serde_json::to_string(&val).unwrap();
441 assert_eq!(json, "\"post\"");
442 let deserialized: TargetType = serde_json::from_str(&json).unwrap();
443 assert_eq!(val, deserialized);
444 }
445
446 #[test]
447 fn target_type_display() {
448 assert_eq!(TargetType::Post.to_string(), "post");
449 assert_eq!(TargetType::Comment.to_string(), "comment");
450 }
451
452 #[test]
453 fn target_type_from_str() {
454 assert_eq!(TargetType::from_str("post").unwrap(), TargetType::Post);
455 assert_eq!(
456 TargetType::from_str("comment").unwrap(),
457 TargetType::Comment
458 );
459 }
460
461 #[test]
462 fn moderation_tier_serde() {
463 let tier = ModerationTier::Tier2;
464 let json = serde_json::to_string(&tier).unwrap();
465 assert_eq!(json, "\"2\"");
466 let deserialized: ModerationTier = serde_json::from_str(&json).unwrap();
467 assert_eq!(tier, deserialized);
468 }
469
470 #[test]
471 fn proposal_category_round_trip() {
472 for cat in [
473 ProposalCategory::Routine,
474 ProposalCategory::Policy,
475 ProposalCategory::Constitutional,
476 ProposalCategory::Emergency,
477 ] {
478 let json = serde_json::to_string(&cat).unwrap();
479 let back: ProposalCategory = serde_json::from_str(&json).unwrap();
480 assert_eq!(cat, back);
481 }
482 }
483
484 #[cfg(feature = "schemars")]
489 #[test]
490 fn enum_json_schema_is_inlined() {
491 use schemars::JsonSchema;
492
493 assert!(<TargetType as JsonSchema>::inline_schema());
494 assert!(<FeedSort as JsonSchema>::inline_schema());
495 assert!(<ProposalCategory as JsonSchema>::inline_schema());
496 assert!(<GovernanceLogEntryType as JsonSchema>::inline_schema());
497 assert!(<OAuthScope as JsonSchema>::inline_schema());
498 assert!(<ModerationTargetType as JsonSchema>::inline_schema());
499 assert!(<ModerationTier as JsonSchema>::inline_schema());
500
501 #[derive(schemars::JsonSchema)]
502 #[allow(dead_code)]
503 struct Container {
504 target_type: TargetType,
505 sort: Option<FeedSort>,
506 category: Option<ProposalCategory>,
507 }
508
509 let schema = schemars::schema_for!(Container);
510 let value = serde_json::to_value(&schema).unwrap();
511 let blob = value.to_string();
512
513 assert!(
514 value.get("$defs").is_none(),
515 "no $defs should be emitted for enum-only container; got schema: {value}"
516 );
517 assert!(
518 !blob.contains("$ref"),
519 "enum container schema must contain no $ref anywhere; got: {value}"
520 );
521
522 let target_type_enum = value["properties"]["target_type"]["enum"]
524 .as_array()
525 .expect("target_type should have inline `enum` array");
526 assert!(
527 target_type_enum
528 .contains(&serde_json::Value::String("post".into()))
529 );
530 assert!(
531 target_type_enum
532 .contains(&serde_json::Value::String("comment".into()))
533 );
534 }
535}