Skip to main content

harmos_domain/model/storage/
keys.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Schema, Object And Payload Keys Shared By Storage Requests And Records.
11
12/* === Modules === */
13
14/* === Re-Exports === */
15
16/* === Imports === */
17
18// Standard Library Imports
19use std::fmt;
20use std::num::{NonZeroU32, NonZeroUsize};
21// Third Party Imports
22use serde::{Deserialize, Serialize};
23// Local Imports
24use crate::{Error, Identifier, MemorySize, Result};
25
26/* === Constants === */
27
28/* === Macros === */
29
30/* === Type Aliases === */
31
32/* === Structs === */
33
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct SchemaKey {
37    pub id: Identifier,
38    pub version: NonZeroU32,
39
40    #[serde(default)]
41    pub payload_encoding: PayloadEncoding,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(deny_unknown_fields)]
46pub struct ObjectValue {
47    pub schema: SchemaKey,
48    pub item: ObjectKey,
49    pub payload: Vec<u8>,
50}
51
52#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
53#[serde(transparent)]
54pub struct ObjectKey(String);
55
56struct KeyWriter {
57    value: String,
58    limit: usize,
59}
60
61/* === Enums === */
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum PayloadEncoding {
66    #[default]
67    ProtobufV1 = 0,
68
69    PostcardV1 = 1,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
73pub enum ObjectRetention {
74    UntilReleased,
75
76    Latest,
77    Versions(NonZeroUsize),
78}
79
80/* === Traits === */
81
82/* === Implementations === */
83
84impl SchemaKey {
85    pub fn require_postcard(&self) -> Result<()> {
86        if self.payload_encoding != PayloadEncoding::PostcardV1 {
87            return Err(invalid("payload requires an explicitly selected legacy decoder or migration"));
88        }
89        Ok(())
90    }
91}
92
93impl MemorySize for SchemaKey {
94    fn memory_size(&self) -> usize {
95        std::mem::size_of::<Self>().saturating_add(self.id.memory_size().saturating_sub(std::mem::size_of::<Identifier>()))
96    }
97}
98
99impl MemorySize for ObjectValue {
100    fn memory_size(&self) -> usize {
101        std::mem::size_of::<Self>()
102            .saturating_add(self.schema.memory_size().saturating_sub(std::mem::size_of::<SchemaKey>()))
103            .saturating_add(self.item.memory_size().saturating_sub(std::mem::size_of::<ObjectKey>()))
104            .saturating_add(self.payload.capacity())
105    }
106}
107
108impl MemorySize for ObjectKey {
109    fn memory_size(&self) -> usize {
110        self.0.memory_size()
111    }
112}
113
114impl ObjectKey {
115    pub fn new<K: fmt::Display + ?Sized>(key: &K, max_bytes: NonZeroUsize) -> Result<Self> {
116        object_key(key, max_bytes)
117    }
118
119    pub fn as_str(&self) -> &str {
120        &self.0
121    }
122
123    pub fn from_owned(value: String) -> Result<Self> {
124        if value.is_empty() {
125            return Err(invalid("object key is empty"));
126        }
127
128        Ok(Self(value))
129    }
130}
131
132impl fmt::Display for ObjectKey {
133    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134        formatter.write_str(&self.0)
135    }
136}
137
138impl<'de> Deserialize<'de> for ObjectKey {
139    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
140        Self::from_owned(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
141    }
142}
143
144impl fmt::Write for KeyWriter {
145    fn write_str(&mut self, value: &str) -> fmt::Result {
146        if value.len() > self.limit - self.value.len() {
147            return Err(fmt::Error);
148        }
149        let remaining = self.value.capacity() - self.value.len();
150        if value.len() > remaining {
151            self.value.reserve_exact(value.len());
152        }
153        self.value.push_str(value);
154        Ok(())
155    }
156}
157
158impl ObjectRetention {
159    pub const fn versions(self) -> NonZeroUsize {
160        match self {
161            Self::UntilReleased | Self::Latest => NonZeroUsize::MIN,
162            Self::Versions(count) => count,
163        }
164    }
165}
166
167/* === Public Functions === */
168
169pub fn object_key<K: fmt::Display + ?Sized>(key: &K, limit: NonZeroUsize) -> Result<ObjectKey> {
170    let mut writer = KeyWriter { value: String::new(), limit: limit.get() };
171    fmt::write(&mut writer, format_args!("{key}")).map_err(|_| invalid("invalid or oversized object key"))?;
172    ObjectKey::from_owned(writer.value)
173}
174
175pub fn check_length(length: usize, max_payload_bytes: NonZeroUsize) -> Result<()> {
176    if length > max_payload_bytes.get() {
177        return Err(invalid("object payload exceeds byte limit"));
178    }
179    Ok(())
180}
181
182pub fn invalid(reason: &'static str) -> Error {
183    Error::InvalidInput { field: "object catalog", reason }
184}
185
186/* === Private Functions === */
187
188/* === Tests === */