hashtree_collection/
manifest.rs1use 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}