use crate::text::{normalize_optional_text, normalize_optional_text_ref};
use crate::{CoreError, Result};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
const CUSTOM_PREFIX: &str = "custom:";
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SourceKind {
Document,
Log,
Api,
Human,
Ai,
Code,
External,
Custom(String),
}
impl SourceKind {
pub fn custom(extension: impl Into<String>) -> Result<Self> {
let raw = extension.into();
let normalized = raw.trim().to_owned();
if normalized.is_empty() {
return Err(CoreError::invalid_source_kind(
raw,
"custom source kind extension must not be empty after trimming",
));
}
Ok(Self::Custom(normalized))
}
pub fn is_custom(&self) -> bool {
matches!(self, Self::Custom(_))
}
pub fn serialized_value(&self) -> String {
self.try_serialized_value()
.unwrap_or_else(|_| CUSTOM_PREFIX.to_owned())
}
pub fn try_serialized_value(&self) -> Result<String> {
match self {
Self::Document => Ok("document".to_owned()),
Self::Log => Ok("log".to_owned()),
Self::Api => Ok("api".to_owned()),
Self::Human => Ok("human".to_owned()),
Self::Ai => Ok("ai".to_owned()),
Self::Code => Ok("code".to_owned()),
Self::External => Ok("external".to_owned()),
Self::Custom(extension) => {
let custom = Self::custom(extension.clone())?;
let Self::Custom(normalized) = custom else {
unreachable!("SourceKind::custom always returns a custom source kind");
};
Ok(format!("{CUSTOM_PREFIX}{normalized}"))
}
}
}
}
impl FromStr for SourceKind {
type Err = CoreError;
fn from_str(value: &str) -> Result<Self> {
match value {
"document" => Ok(Self::Document),
"log" => Ok(Self::Log),
"api" => Ok(Self::Api),
"human" => Ok(Self::Human),
"ai" => Ok(Self::Ai),
"code" => Ok(Self::Code),
"external" => Ok(Self::External),
custom if custom.starts_with(CUSTOM_PREFIX) => {
Self::custom(&custom[CUSTOM_PREFIX.len()..])
}
unknown => Err(CoreError::invalid_source_kind(
unknown,
"expected document, log, api, human, ai, code, external, or custom:<extension>",
)),
}
}
}
impl Serialize for SourceKind {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = self
.try_serialized_value()
.map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&value)
}
}
impl<'de> Deserialize<'de> for SourceKind {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::from_str(&value).map_err(serde::de::Error::custom)
}
}
impl fmt::Display for SourceKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.serialized_value())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SourceRef {
pub kind: SourceKind,
pub uri: Option<String>,
pub title: Option<String>,
pub captured_at: Option<String>,
pub source_local_id: Option<String>,
}
impl SourceRef {
pub fn new(kind: SourceKind) -> Self {
Self {
kind,
uri: None,
title: None,
captured_at: None,
source_local_id: None,
}
}
pub fn with_uri(mut self, uri: impl Into<String>) -> Result<Self> {
self.uri = normalize_optional_text("uri", Some(uri.into()))?;
Ok(self)
}
pub fn with_title(mut self, title: impl Into<String>) -> Result<Self> {
self.title = normalize_optional_text("title", Some(title.into()))?;
Ok(self)
}
pub fn with_captured_at(mut self, captured_at: impl Into<String>) -> Result<Self> {
self.captured_at = normalize_optional_text("captured_at", Some(captured_at.into()))?;
Ok(self)
}
pub fn with_source_local_id(mut self, source_local_id: impl Into<String>) -> Result<Self> {
self.source_local_id =
normalize_optional_text("source_local_id", Some(source_local_id.into()))?;
Ok(self)
}
pub fn validate(&self) -> Result<()> {
self.to_wire().map(|_| ())
}
fn from_wire(wire: SourceRefWire) -> Result<Self> {
Ok(Self {
kind: wire.kind,
uri: normalize_optional_text("uri", wire.uri)?,
title: normalize_optional_text("title", wire.title)?,
captured_at: normalize_optional_text("captured_at", wire.captured_at)?,
source_local_id: normalize_optional_text("source_local_id", wire.source_local_id)?,
})
}
fn to_wire(&self) -> Result<SourceRefWire> {
self.kind.try_serialized_value()?;
Ok(SourceRefWire {
kind: self.kind.clone(),
uri: normalize_optional_text_ref("uri", self.uri.as_ref())?,
title: normalize_optional_text_ref("title", self.title.as_ref())?,
captured_at: normalize_optional_text_ref("captured_at", self.captured_at.as_ref())?,
source_local_id: normalize_optional_text_ref(
"source_local_id",
self.source_local_id.as_ref(),
)?,
})
}
}
impl Serialize for SourceRef {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_wire()
.map_err(serde::ser::Error::custom)?
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for SourceRef {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire = SourceRefWire::deserialize(deserializer)?;
Self::from_wire(wire).map_err(serde::de::Error::custom)
}
}
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct SourceRefWire {
kind: SourceKind,
#[serde(skip_serializing_if = "Option::is_none")]
uri: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
captured_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
source_local_id: Option<String>,
}