Skip to main content

hashtree_collection/
manifest.rs

1use std::sync::Arc;
2
3use hashtree_core::{Cid, HashTree, HashTreeConfig, Store};
4use serde::{Deserialize, Serialize};
5
6use crate::helpers::find_manifest_cid;
7use crate::{get_schema_version, CollectionDefinition, CollectionError, CollectionPublishedSchema};
8
9pub const COLLECTION_MANIFEST_METADATA_FILE: &str = ".collection-manifest.json";
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct CollectionManifestMetadata {
13    version: u32,
14    schema_version: u32,
15    published_schema: Option<CollectionPublishedSchema>,
16}
17
18impl CollectionManifestMetadata {
19    pub fn new(schema_version: u32) -> Self {
20        Self {
21            version: 1,
22            schema_version,
23            published_schema: None,
24        }
25    }
26
27    pub fn version(&self) -> u32 {
28        self.version
29    }
30
31    pub fn schema_version(&self) -> u32 {
32        self.schema_version
33    }
34
35    pub fn with_published_schema(mut self, published_schema: CollectionPublishedSchema) -> Self {
36        self.published_schema = Some(published_schema);
37        self
38    }
39
40    pub fn published_schema(&self) -> Option<&CollectionPublishedSchema> {
41        self.published_schema.as_ref()
42    }
43
44    pub(crate) fn from_definition<T>(definition: &CollectionDefinition<T>) -> Option<Self> {
45        let schema_version = get_schema_version(definition);
46        let published_schema = definition.published_schema().cloned();
47        if schema_version == 1 && published_schema.is_none() {
48            return None;
49        }
50
51        Some(Self {
52            version: 1,
53            schema_version,
54            published_schema,
55        })
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61struct SerializedCollectionManifestMetadata {
62    version: u32,
63    schema_version: u32,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    published_schema: Option<SerializedCollectionPublishedSchema>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70struct SerializedCollectionPublishedSchema {
71    #[serde(skip_serializing_if = "Option::is_none")]
72    item_format: Option<String>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    projection_format: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    schema_ref: Option<SerializedCid>,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81struct SerializedCid {
82    hash: String,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    key: Option<String>,
85}
86
87impl From<&CollectionManifestMetadata> for SerializedCollectionManifestMetadata {
88    fn from(value: &CollectionManifestMetadata) -> Self {
89        Self {
90            version: value.version,
91            schema_version: value.schema_version,
92            published_schema: value.published_schema.as_ref().map(Into::into),
93        }
94    }
95}
96
97impl From<&CollectionPublishedSchema> for SerializedCollectionPublishedSchema {
98    fn from(value: &CollectionPublishedSchema) -> Self {
99        Self {
100            item_format: value.item_format().map(ToOwned::to_owned),
101            projection_format: value.projection_format().map(ToOwned::to_owned),
102            schema_ref: value.schema_ref().map(Into::into),
103        }
104    }
105}
106
107impl From<&Cid> for SerializedCid {
108    fn from(value: &Cid) -> Self {
109        Self {
110            hash: hex::encode(value.hash),
111            key: value.key.map(hex::encode),
112        }
113    }
114}
115
116impl TryFrom<SerializedCollectionManifestMetadata> for CollectionManifestMetadata {
117    type Error = CollectionError;
118
119    fn try_from(value: SerializedCollectionManifestMetadata) -> Result<Self, Self::Error> {
120        Ok(Self {
121            version: value.version,
122            schema_version: value.schema_version,
123            published_schema: value.published_schema.map(TryInto::try_into).transpose()?,
124        })
125    }
126}
127
128impl TryFrom<SerializedCollectionPublishedSchema> for CollectionPublishedSchema {
129    type Error = CollectionError;
130
131    fn try_from(value: SerializedCollectionPublishedSchema) -> Result<Self, Self::Error> {
132        let mut schema = CollectionPublishedSchema::new();
133        if let Some(item_format) = value.item_format {
134            schema = schema.with_item_format(item_format);
135        }
136        if let Some(projection_format) = value.projection_format {
137            schema = schema.with_projection_format(projection_format);
138        }
139        if let Some(schema_ref) = value.schema_ref {
140            schema = schema.with_schema_ref(schema_ref.try_into()?);
141        }
142        Ok(schema)
143    }
144}
145
146impl TryFrom<SerializedCid> for Cid {
147    type Error = CollectionError;
148
149    fn try_from(value: SerializedCid) -> Result<Self, Self::Error> {
150        let hash = hex::decode(&value.hash).map_err(|err| {
151            CollectionError::Validation(format!("invalid collection schema_ref hash hex: {err}"))
152        })?;
153        let hash: [u8; 32] = hash.try_into().map_err(|_| {
154            CollectionError::Validation("invalid collection schema_ref hash length".to_string())
155        })?;
156        let key = value
157            .key
158            .map(|key| -> Result<[u8; 32], CollectionError> {
159                let key = hex::decode(&key).map_err(|err| {
160                    CollectionError::Validation(format!(
161                        "invalid collection schema_ref key hex: {err}"
162                    ))
163                })?;
164                let key: [u8; 32] = key.try_into().map_err(|_| {
165                    CollectionError::Validation(
166                        "invalid collection schema_ref key length".to_string(),
167                    )
168                })?;
169                Ok::<[u8; 32], CollectionError>(key)
170            })
171            .transpose()?;
172
173        Ok(Cid { hash, key })
174    }
175}
176
177pub async fn load_collection_manifest_metadata<S: Store>(
178    store: Arc<S>,
179    root: Option<&Cid>,
180) -> Result<Option<CollectionManifestMetadata>, CollectionError> {
181    let Some(root) = root else {
182        return Ok(None);
183    };
184
185    let tree = HashTree::new(HashTreeConfig::new(store));
186    let entries = tree.list_directory(root).await?;
187    let Some(metadata_cid) = find_manifest_cid(&entries, COLLECTION_MANIFEST_METADATA_FILE) else {
188        return Ok(None);
189    };
190    let Some(bytes) = tree.get(&metadata_cid, None).await? else {
191        return Ok(None);
192    };
193    let serialized = serde_json::from_slice::<SerializedCollectionManifestMetadata>(&bytes)
194        .map_err(|err| {
195            CollectionError::Validation(format!("invalid collection manifest metadata: {err}"))
196        })?;
197    Ok(Some(serialized.try_into()?))
198}
199
200pub(crate) async fn write_collection_manifest_metadata<S: Store, T>(
201    tree: &HashTree<S>,
202    definition: &CollectionDefinition<T>,
203) -> Result<Option<(Cid, u64)>, CollectionError> {
204    let Some(metadata) = CollectionManifestMetadata::from_definition(definition) else {
205        return Ok(None);
206    };
207    let bytes = serde_json::to_vec(&SerializedCollectionManifestMetadata::from(&metadata))
208        .map_err(|err| {
209            CollectionError::Validation(format!("serialize collection manifest metadata: {err}"))
210        })?;
211    Ok(Some(tree.put_file(&bytes).await?))
212}