Skip to main content

mqdb_core/
entity.rs

1// Copyright 2025-2026 LabOverWire. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::error::{Error, Result};
5use crate::keys;
6use serde_json::Value;
7
8#[derive(Clone)]
9pub struct Entity {
10    pub name: String,
11    pub id: String,
12    pub data: Value,
13}
14
15impl Entity {
16    #[allow(clippy::must_use_candidate)]
17    pub fn new(name: String, id: String, data: Value) -> Self {
18        Self { name, id, data }
19    }
20
21    /// # Errors
22    /// Returns an error if the data doesn't have an 'id' field.
23    pub fn from_json(name: String, mut data: Value) -> Result<Self> {
24        let id = data
25            .get("id")
26            .and_then(Value::as_str)
27            .map(std::string::ToString::to_string)
28            .or_else(|| {
29                data.get("id")
30                    .and_then(Value::as_i64)
31                    .map(|n| n.to_string())
32            })
33            .ok_or_else(|| Error::Validation("missing 'id' field".into()))?;
34
35        if let Value::Object(ref mut obj) = data {
36            obj.remove("id");
37        }
38
39        Ok(Self { name, id, data })
40    }
41
42    #[must_use]
43    pub fn to_json(&self) -> Value {
44        let mut data = self.data.clone();
45        if let Value::Object(ref mut obj) = data {
46            obj.insert("id".to_string(), Value::String(self.id.clone()));
47        }
48        data
49    }
50
51    #[must_use]
52    pub fn key(&self) -> Vec<u8> {
53        keys::encode_data_key(&self.name, &self.id)
54    }
55
56    /// # Errors
57    /// Returns an error if serialization fails.
58    pub fn serialize(&self) -> Result<Vec<u8>> {
59        let json_data = serde_json::to_vec(&self.data)?;
60        Ok(crate::checksum::encode_with_checksum(&json_data))
61    }
62
63    /// # Errors
64    /// Returns an error if checksum verification fails or data is invalid.
65    pub fn deserialize(name: String, id: String, data: &[u8]) -> Result<Self> {
66        let json_data = crate::checksum::decode_and_verify(data).map_err(|err| {
67            tracing::warn!("checksum verification failed for {name}/{id}: {err}");
68            Error::Corruption {
69                entity: name.clone(),
70                id: id.clone(),
71            }
72        })?;
73        let value: Value = serde_json::from_slice(json_data)?;
74        Ok(Self {
75            name,
76            id,
77            data: value,
78        })
79    }
80
81    #[must_use]
82    pub fn get_field(&self, field: &str) -> Option<&Value> {
83        self.data.get(field)
84    }
85
86    #[must_use]
87    pub fn extract_index_values(&self, fields: &[String]) -> Vec<(String, Vec<u8>)> {
88        let mut values = Vec::new();
89
90        for field in fields {
91            if let Some(value) = self.get_field(field)
92                && let Ok(encoded) = keys::encode_value_for_index(value)
93            {
94                values.push((field.clone(), encoded));
95            }
96        }
97
98        values
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use serde_json::json;
106
107    #[test]
108    fn test_entity_from_json() {
109        let data = json!({
110            "id": "123",
111            "name": "John",
112            "email": "john@example.com"
113        });
114
115        let entity = Entity::from_json("users".into(), data).unwrap();
116        assert_eq!(entity.id, "123");
117        assert_eq!(entity.name, "users");
118        assert_eq!(entity.get_field("name").unwrap(), "John");
119    }
120
121    #[test]
122    fn test_entity_to_json() {
123        let entity = Entity::new("users".into(), "123".into(), json!({"name": "John"}));
124
125        let json = entity.to_json();
126        assert_eq!(json["id"], "123");
127        assert_eq!(json["name"], "John");
128    }
129}