1use std::fmt;
2
3use crate::CacheModelError;
4use serde::{Deserialize, Serialize};
5
6pub(crate) fn require_non_empty(
7 field: &'static str,
8 value: String,
9) -> Result<String, CacheModelError> {
10 let trimmed = value.trim();
11 if trimmed.is_empty() {
12 Err(CacheModelError::EmptyField { field })
13 } else {
14 Ok(trimmed.to_string())
15 }
16}
17
18pub(crate) fn validate_token(
19 field: &'static str,
20 value: String,
21) -> Result<String, CacheModelError> {
22 let trimmed = require_non_empty(field, value)?;
23 if trimmed
24 .chars()
25 .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | ':' | '/'))
26 {
27 Ok(trimmed)
28 } else {
29 Err(CacheModelError::InvalidToken {
30 field,
31 value: trimmed,
32 })
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
37pub struct VariationKey(String);
38
39impl VariationKey {
40 pub fn new(value: impl Into<String>) -> Self {
41 Self(value.into())
42 }
43
44 pub fn as_str(&self) -> &str {
45 &self.0
46 }
47}
48
49impl fmt::Display for VariationKey {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.write_str(&self.0)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
56pub struct InvalidationTag(String);
57
58impl InvalidationTag {
59 pub fn new(value: impl Into<String>) -> Result<Self, CacheModelError> {
60 Ok(Self(validate_token("invalidation_tag", value.into())?))
61 }
62
63 pub fn as_str(&self) -> &str {
64 &self.0
65 }
66}
67
68impl fmt::Display for InvalidationTag {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.write_str(&self.0)
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
75pub struct CacheNamespace(String);
76
77impl CacheNamespace {
78 pub fn new(value: impl Into<String>) -> Result<Self, CacheModelError> {
79 Ok(Self(validate_token("cache_namespace", value.into())?))
80 }
81
82 pub fn as_str(&self) -> &str {
83 &self.0
84 }
85}
86
87impl fmt::Display for CacheNamespace {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.write_str(&self.0)
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
94pub struct CacheKey {
95 namespace: CacheNamespace,
96 resource: String,
97 variation: Option<VariationKey>,
98}
99
100impl CacheKey {
101 pub fn new(
102 namespace: CacheNamespace,
103 resource: impl Into<String>,
104 variation: Option<VariationKey>,
105 ) -> Result<Self, CacheModelError> {
106 Ok(Self {
107 namespace,
108 resource: require_non_empty("cache_resource", resource.into())?,
109 variation,
110 })
111 }
112
113 pub fn namespace(&self) -> &CacheNamespace {
114 &self.namespace
115 }
116
117 pub fn resource(&self) -> &str {
118 &self.resource
119 }
120
121 pub fn variation(&self) -> Option<&VariationKey> {
122 self.variation.as_ref()
123 }
124}
125
126impl fmt::Display for CacheKey {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match &self.variation {
129 Some(variation) => write!(f, "{}:{}|{}", self.namespace, self.resource, variation),
130 None => write!(f, "{}:{}", self.namespace, self.resource),
131 }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
136pub struct EntityTag(String);
137
138impl EntityTag {
139 pub fn new(value: impl Into<String>) -> Result<Self, CacheModelError> {
140 Ok(Self(validate_token("etag", value.into())?))
141 }
142
143 pub fn as_str(&self) -> &str {
144 &self.0
145 }
146}
147
148impl fmt::Display for EntityTag {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.write_str(&self.0)
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
155pub struct CacheInstant(u64);
156
157impl CacheInstant {
158 pub const fn from_unix_seconds(seconds: u64) -> Self {
159 Self(seconds)
160 }
161
162 pub const fn as_unix_seconds(self) -> u64 {
163 self.0
164 }
165}