1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use rskit_errors::{AppError, AppResult, ErrorCode};
5
6use crate::{DataPayload, DatasetLimits, Label, MediaType};
7
8pub trait DatasetItem: Send + 'static {
14 fn label(&self) -> Label {
16 Label::Real
17 }
18
19 fn source_offset(&self) -> Option<usize> {
21 None
22 }
23}
24
25#[derive(Debug, Clone)]
27pub struct DataItem {
28 payload: DataPayload,
30 pub label: Label,
32 pub media_type: MediaType,
34 pub source_name: String,
36 pub extension: String,
38 pub metadata: HashMap<String, String>,
40 source_offset: Option<usize>,
42}
43
44impl DataItem {
45 pub fn new(
47 bytes: Vec<u8>,
48 label: Label,
49 media_type: MediaType,
50 source_name: impl Into<String>,
51 ) -> AppResult<Self> {
52 Self::new_bytes(bytes, label, media_type, source_name)
53 }
54
55 pub fn new_bytes(
57 bytes: Vec<u8>,
58 label: Label,
59 media_type: MediaType,
60 source_name: impl Into<String>,
61 ) -> AppResult<Self> {
62 Self::new_bytes_with_limits(
63 bytes,
64 label,
65 media_type,
66 source_name,
67 &DatasetLimits::default(),
68 )
69 }
70
71 pub fn new_bytes_with_limits(
73 bytes: Vec<u8>,
74 label: Label,
75 media_type: MediaType,
76 source_name: impl Into<String>,
77 limits: &DatasetLimits,
78 ) -> AppResult<Self> {
79 Ok(Self {
80 payload: DataPayload::bytes(bytes, limits)?,
81 label,
82 media_type,
83 source_name: source_name.into(),
84 extension: ".jpg".to_string(),
85 metadata: HashMap::new(),
86 source_offset: None,
87 })
88 }
89
90 #[must_use]
92 pub fn new_file(
93 path: impl Into<PathBuf>,
94 label: Label,
95 media_type: MediaType,
96 source_name: impl Into<String>,
97 ) -> Self {
98 Self {
99 payload: DataPayload::file(path),
100 label,
101 media_type,
102 source_name: source_name.into(),
103 extension: ".bin".to_string(),
104 metadata: HashMap::new(),
105 source_offset: None,
106 }
107 }
108
109 #[must_use]
111 pub fn payload(&self) -> &DataPayload {
112 &self.payload
113 }
114
115 pub fn try_with_payload(
117 mut self,
118 payload: DataPayload,
119 limits: &DatasetLimits,
120 ) -> AppResult<Self> {
121 if payload.is_bytes() && payload.len()? > limits.max_in_memory_bytes as u64 {
122 return Err(AppError::new(
123 ErrorCode::InvalidInput,
124 format!(
125 "in-memory dataset payload exceeds max_in_memory_bytes={}",
126 limits.max_in_memory_bytes
127 ),
128 ));
129 }
130 self.payload = payload;
131 Ok(self)
132 }
133
134 #[must_use]
136 pub fn source_offset(&self) -> Option<usize> {
137 self.source_offset
138 }
139
140 #[must_use]
142 pub fn with_source_offset(mut self, offset: usize) -> Self {
143 self.source_offset = Some(offset);
144 self
145 }
146
147 #[must_use]
149 pub fn with_extension(mut self, ext: impl Into<String>) -> Self {
150 self.extension = ext.into();
151 self
152 }
153
154 #[must_use]
156 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
157 self.metadata.insert(key.into(), value.into());
158 self
159 }
160
161 pub fn validate(&self, limits: &DatasetLimits) -> AppResult<()> {
163 validate_extension(&self.extension)?;
164 let len = self.payload.len()?;
165 if self.payload.is_bytes() && len > limits.max_in_memory_bytes as u64 {
166 return Err(AppError::new(
167 ErrorCode::InvalidInput,
168 format!(
169 "in-memory dataset payload is {len} bytes, exceeding max_in_memory_bytes={}",
170 limits.max_in_memory_bytes
171 ),
172 ));
173 }
174 Ok(())
175 }
176
177 pub fn write_to_path(&self, path: &Path, limits: &DatasetLimits) -> AppResult<u64> {
179 self.validate(limits)?;
180 self.payload.write_to_path(path, limits)
181 }
182}
183
184fn validate_extension(extension: &str) -> AppResult<()> {
185 let extension = extension.trim_start_matches('.');
186 if extension.is_empty()
187 || extension.contains('/')
188 || extension.contains('\\')
189 || extension == "."
190 || extension == ".."
191 || extension.contains("..")
192 {
193 return Err(AppError::new(
194 ErrorCode::InvalidInput,
195 format!("invalid dataset item extension: {extension:?}"),
196 ));
197 }
198 rskit_validation::input::validate_safe_path(extension)
199}
200
201impl crate::DatasetItem for DataItem {
202 fn label(&self) -> Label {
203 self.label
204 }
205
206 fn source_offset(&self) -> Option<usize> {
207 self.source_offset
208 }
209}