hehe_core/resource/
mod.rs1use bytes::Bytes;
2use camino::Utf8PathBuf;
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::error::Result;
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
9#[serde(tag = "type", rename_all = "snake_case")]
10pub enum ResourceRef {
11 #[serde(skip)]
12 Inline(Bytes),
13 Base64 { data: String },
14 File { path: Utf8PathBuf },
15 Url { url: Url },
16 ContentAddress { hash: String },
17}
18
19impl ResourceRef {
20 pub fn inline(data: impl Into<Bytes>) -> Self {
21 Self::Inline(data.into())
22 }
23
24 pub fn base64(data: impl Into<String>) -> Self {
25 Self::Base64 { data: data.into() }
26 }
27
28 pub fn file(path: impl Into<Utf8PathBuf>) -> Self {
29 Self::File { path: path.into() }
30 }
31
32 pub fn url(url: Url) -> Self {
33 Self::Url { url }
34 }
35
36 pub fn content_address(hash: impl Into<String>) -> Self {
37 Self::ContentAddress { hash: hash.into() }
38 }
39
40 pub fn is_inline(&self) -> bool {
41 matches!(self, Self::Inline(_) | Self::Base64 { .. })
42 }
43
44 pub fn is_remote(&self) -> bool {
45 matches!(self, Self::Url { .. })
46 }
47
48 pub fn is_local(&self) -> bool {
49 matches!(self, Self::File { .. })
50 }
51}
52
53#[derive(Clone, Debug, Default, Serialize, Deserialize)]
54pub struct ResourceMeta {
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub media_type: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub size: Option<u64>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub filename: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub checksum: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub encoding: Option<String>,
65}
66
67impl ResourceMeta {
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn with_media_type(mut self, media_type: impl Into<String>) -> Self {
73 self.media_type = Some(media_type.into());
74 self
75 }
76
77 pub fn with_size(mut self, size: u64) -> Self {
78 self.size = Some(size);
79 self
80 }
81
82 pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
83 self.filename = Some(filename.into());
84 self
85 }
86
87 pub fn with_checksum(mut self, checksum: impl Into<String>) -> Self {
88 self.checksum = Some(checksum.into());
89 self
90 }
91}
92
93#[derive(Clone, Debug)]
94pub struct Resource {
95 pub reference: ResourceRef,
96 pub meta: ResourceMeta,
97}
98
99impl Resource {
100 pub fn new(reference: ResourceRef) -> Self {
101 Self {
102 reference,
103 meta: ResourceMeta::default(),
104 }
105 }
106
107 pub fn with_meta(mut self, meta: ResourceMeta) -> Self {
108 self.meta = meta;
109 self
110 }
111
112 pub fn inline(data: impl Into<Bytes>) -> Self {
113 Self::new(ResourceRef::inline(data))
114 }
115
116 pub fn from_base64(data: impl Into<String>) -> Self {
117 Self::new(ResourceRef::base64(data))
118 }
119
120 pub fn from_file(path: impl Into<Utf8PathBuf>) -> Self {
121 Self::new(ResourceRef::file(path))
122 }
123
124 pub fn from_url(url: Url) -> Self {
125 Self::new(ResourceRef::url(url))
126 }
127}
128
129#[async_trait::async_trait]
130pub trait ResourceResolver: Send + Sync {
131 async fn resolve(&self, resource: &ResourceRef) -> Result<Bytes>;
132 async fn resolve_base64(&self, resource: &ResourceRef) -> Result<String>;
133 async fn metadata(&self, resource: &ResourceRef) -> Result<ResourceMeta>;
134}
135
136#[async_trait::async_trait]
137pub trait ResourceStore: Send + Sync {
138 async fn store(&self, data: Bytes, meta: ResourceMeta) -> Result<String>;
139 async fn get(&self, content_address: &str) -> Result<Option<Bytes>>;
140 async fn exists(&self, content_address: &str) -> Result<bool>;
141 async fn delete(&self, content_address: &str) -> Result<bool>;
142}