use std::str::FromStr;
use serde::{Deserialize, Serialize};
use sqlx::Sqlite;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ReportStatus {
Pending,
Resolved,
}
impl ReportStatus {
pub fn as_str(self) -> &'static str {
match self {
ReportStatus::Pending => "pending",
ReportStatus::Resolved => "resolved",
}
}
pub fn from_db_str(s: &str) -> Option<ReportStatus> {
match s {
"pending" => Some(ReportStatus::Pending),
"resolved" => Some(ReportStatus::Resolved),
_ => None,
}
}
}
impl std::fmt::Display for ReportStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for ReportStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
ReportStatus::from_db_str(s).ok_or(())
}
}
impl sqlx::Type<Sqlite> for ReportStatus {
fn type_info() -> <Sqlite as sqlx::Database>::TypeInfo {
<str as sqlx::Type<Sqlite>>::type_info()
}
fn compatible(ty: &<Sqlite as sqlx::Database>::TypeInfo) -> bool {
<str as sqlx::Type<Sqlite>>::compatible(ty)
}
}
impl<'r> sqlx::Decode<'r, Sqlite> for ReportStatus {
fn decode(value: <Sqlite as sqlx::Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
let s = <&str as sqlx::Decode<Sqlite>>::decode(value)?;
ReportStatus::from_db_str(s)
.ok_or_else(|| format!("reports.status value {s:?} violates CHECK constraint").into())
}
}
impl<'q> sqlx::Encode<'q, Sqlite> for ReportStatus {
fn encode_by_ref(
&self,
buf: &mut <Sqlite as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
<&str as sqlx::Encode<Sqlite>>::encode_by_ref(&self.as_str(), buf)
}
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct Report {
pub id: i64,
pub created_at: String,
pub reported_by: String,
pub reason_type: String,
pub reason: Option<String>,
pub subject_type: String,
pub subject_did: String,
pub subject_uri: Option<String>,
pub subject_cid: Option<String>,
pub status: ReportStatus,
pub resolved_at: Option<String>,
pub resolved_by: Option<String>,
pub resolution_label: Option<String>,
pub resolution_reason: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_db_str_round_trips_known_values() {
for s in ["pending", "resolved"] {
let st = ReportStatus::from_db_str(s).expect("known value");
assert_eq!(st.as_str(), s);
}
}
#[test]
fn from_db_str_rejects_unknown() {
assert!(ReportStatus::from_db_str("Pending").is_none());
assert!(ReportStatus::from_db_str("").is_none());
assert!(ReportStatus::from_db_str("dismissed").is_none());
}
#[test]
fn serializes_as_lowercase_string() {
let v = serde_json::to_value(ReportStatus::Pending).unwrap();
assert_eq!(v, serde_json::Value::String("pending".into()));
let v = serde_json::to_value(ReportStatus::Resolved).unwrap();
assert_eq!(v, serde_json::Value::String("resolved".into()));
}
#[test]
fn deserializes_from_lexicon_known_values() {
let p: ReportStatus = serde_json::from_str("\"pending\"").unwrap();
assert_eq!(p, ReportStatus::Pending);
let r: ReportStatus = serde_json::from_str("\"resolved\"").unwrap();
assert_eq!(r, ReportStatus::Resolved);
}
}