use std::fmt;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize};
use crate::error::{KeepsakeError, Result};
use crate::policy::ExpiryPolicy;
use super::{Keepsake, RelationId, validate_not_empty};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct RelationKey {
pub kind: RelationKind,
pub name: RelationName,
}
impl RelationKey {
pub fn new(kind: impl Into<String>, name: impl Into<String>) -> Result<Self> {
let relation = Self {
kind: RelationKind::new(kind)?,
name: RelationName::new(name)?,
};
Ok(relation)
}
pub fn validate(&self) -> Result<()> {
self.kind.validate()?;
self.name.validate()
}
#[must_use]
pub fn kind(&self) -> &str {
self.kind.as_str()
}
#[must_use]
pub fn name(&self) -> &str {
self.name.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RelationKind(String);
impl RelationKind {
pub fn new(value: impl Into<String>) -> Result<Self> {
let value = value.into();
validate_not_empty("relation.kind", &value)?;
Ok(Self(value))
}
pub fn validate(&self) -> Result<()> {
validate_not_empty("relation.kind", &self.0)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for RelationKind {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for RelationKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RelationName(String);
impl RelationName {
pub fn new(value: impl Into<String>) -> Result<Self> {
let value = value.into();
validate_not_empty("relation.name", &value)?;
Ok(Self(value))
}
pub fn validate(&self) -> Result<()> {
validate_not_empty("relation.name", &self.0)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for RelationName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for RelationName {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StaticRelationKey {
pub kind: &'static str,
pub name: &'static str,
}
impl StaticRelationKey {
#[must_use]
pub const fn new(kind: &'static str, name: &'static str) -> Self {
assert_valid_static_relation_component(kind);
assert_valid_static_relation_component(name);
Self { kind, name }
}
pub fn to_relation_key(self) -> Result<RelationKey> {
RelationKey::new(self.kind, self.name)
}
}
const fn assert_valid_static_relation_component(value: &str) {
let bytes = value.as_bytes();
assert!(
!bytes.is_empty(),
"static relation component must not be empty"
);
let mut index = 0;
let mut has_non_whitespace = false;
while index < bytes.len() {
let byte = bytes[index];
if !(byte == b' ' || byte == b'\n' || byte == b'\r' || byte == b'\t') {
has_non_whitespace = true;
}
index += 1;
}
assert!(
has_non_whitespace,
"static relation component must not be whitespace"
);
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RelationDefinition {
pub id: RelationId,
pub key: RelationKey,
pub enabled: bool,
pub expiry: ExpiryPolicy,
}
impl RelationDefinition {
pub fn new(
id: RelationId,
key: RelationKey,
enabled: bool,
expiry: ExpiryPolicy,
) -> Result<Self> {
expiry.validate()?;
Ok(Self {
id,
key,
enabled,
expiry,
})
}
pub fn enabled(id: RelationId, key: RelationKey, expiry: ExpiryPolicy) -> Result<Self> {
Self::new(id, key, true, expiry)
}
pub fn disabled(id: RelationId, key: RelationKey, expiry: ExpiryPolicy) -> Result<Self> {
Self::new(id, key, false, expiry)
}
pub fn from_spec<Spec>(at: DateTime<Utc>) -> Result<Self>
where
Spec: RelationSpec,
{
Self::new(
Spec::ID,
Spec::KEY.to_relation_key()?,
Spec::ENABLED,
Spec::expiry(at),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ActiveRelation {
keepsake: Keepsake,
relation: RelationDefinition,
}
impl ActiveRelation {
pub fn new(keepsake: Keepsake, relation: RelationDefinition) -> Result<Self> {
if keepsake.relation_id() != relation.id {
return Err(KeepsakeError::ActiveRelationMismatch {
keepsake_relation_id: keepsake.relation_id(),
relation_id: relation.id,
});
}
if !keepsake.is_active() {
return Err(KeepsakeError::InactiveActiveRelation {
keepsake_id: keepsake.id(),
});
}
Ok(Self { keepsake, relation })
}
#[must_use]
pub const fn keepsake(&self) -> &Keepsake {
&self.keepsake
}
#[must_use]
pub const fn relation(&self) -> &RelationDefinition {
&self.relation
}
#[must_use]
pub fn into_parts(self) -> (Keepsake, RelationDefinition) {
(self.keepsake, self.relation)
}
}
impl<'de> Deserialize<'de> for ActiveRelation {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct ActiveRelationRecord {
keepsake: Keepsake,
relation: RelationDefinition,
}
let record = ActiveRelationRecord::deserialize(deserializer)?;
Self::new(record.keepsake, record.relation).map_err(serde::de::Error::custom)
}
}
pub trait RelationSpec {
const ID: RelationId;
const KEY: StaticRelationKey;
const ENABLED: bool = true;
fn expiry(at: DateTime<Utc>) -> ExpiryPolicy;
}