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