rialo_api_types/
validation.rs1use std::str::FromStr;
7
8use rialo_s_sdk::pubkey::Pubkey;
9use thiserror::Error;
10use validator::ValidationErrors;
11
12use crate::constants::*;
13
14#[derive(Debug, Error, Clone)]
16pub enum ValidationError {
17 #[error("Invalid format: {0}")]
18 InvalidFormat(String),
19
20 #[error("Value out of range: {0}")]
21 OutOfRange(String),
22
23 #[error("Missing required field: {0}")]
24 MissingField(String),
25
26 #[error("Invalid signature: {0}")]
27 InvalidSignature(String),
28
29 #[error("Invalid encoding: {0}. Supported encodings: base64, base58")]
30 InvalidEncoding(String),
31
32 #[error("Invalid public key: {0}")]
33 InvalidPublicKey(String),
34
35 #[error("Invalid transaction: {0}")]
36 InvalidTransaction(String),
37
38 #[error("Multiple validation errors: {0}")]
39 Multiple(String),
40}
41
42impl From<ValidationErrors> for ValidationError {
43 fn from(errors: ValidationErrors) -> Self {
44 let error_messages: Vec<String> = errors
45 .field_errors()
46 .iter()
47 .flat_map(|(field, errors)| {
48 errors.iter().map(move |error| {
49 format!(
50 "{}: {}",
51 field,
52 error
53 .message
54 .as_ref()
55 .unwrap_or(&"validation failed".into())
56 )
57 })
58 })
59 .collect();
60
61 if error_messages.len() == 1 {
62 ValidationError::InvalidFormat(error_messages[0].clone())
63 } else {
64 ValidationError::Multiple(error_messages.join(", "))
65 }
66 }
67}
68
69pub type ValidationResult<T> = Result<T, ValidationError>;
71
72pub fn validate_pubkey(pubkey: &str) -> Result<(), validator::ValidationError> {
74 Pubkey::from_str(pubkey).map_err(|_| validator::ValidationError::new("invalid_pubkey"))?;
75 Ok(())
76}
77
78pub fn validate_base64(data: &str) -> Result<(), validator::ValidationError> {
80 use fastcrypto::encoding::{Base64, Encoding};
81 Base64::decode(data).map_err(|_| validator::ValidationError::new("invalid_base64"))?;
82 Ok(())
83}
84
85pub fn validate_base58(data: &str) -> Result<(), validator::ValidationError> {
87 use fastcrypto::encoding::{Base58, Encoding};
88 Base58::decode(data).map_err(|_| validator::ValidationError::new("invalid_base58"))?;
89 Ok(())
90}
91
92pub fn validate_signature(signature: &str) -> Result<(), validator::ValidationError> {
94 if signature.len() < MIN_SIGNATURE_LENGTH || signature.len() > MAX_SIGNATURE_LENGTH {
97 return Err(validator::ValidationError::new("invalid_signature_length"));
98 }
99 validate_base58(signature)
100}
101
102pub fn validate_nonce(nonce: &str) -> Result<(), validator::ValidationError> {
104 if nonce.is_empty() {
105 return Err(validator::ValidationError::new("empty_nonce"));
106 }
107 if nonce.len() > MAX_NONCE_LENGTH {
108 return Err(validator::ValidationError::new("nonce_too_long"));
109 }
110 Ok(())
111}
112
113pub fn validate_lamports(lamports: u64) -> Result<(), validator::ValidationError> {
115 if lamports > MAX_LAMPORTS {
117 return Err(validator::ValidationError::new("lamports_too_large"));
118 }
119 Ok(())
120}
121
122pub fn validate_limit(limit: &u64) -> Result<(), validator::ValidationError> {
124 if *limit == 0 {
125 return Err(validator::ValidationError::new("limit_zero"));
126 }
127 if *limit > MAX_PAGINATION_LIMIT {
128 return Err(validator::ValidationError::new("limit_too_large"));
129 }
130 Ok(())
131}
132
133pub fn validate_pubkey_array(pubkeys: &[String]) -> Result<(), validator::ValidationError> {
135 for pubkey in pubkeys {
136 validate_pubkey(pubkey)?;
137 }
138 Ok(())
139}
140
141pub fn validate_signatures_array(signatures: &[String]) -> Result<(), validator::ValidationError> {
143 for signature in signatures {
144 validate_signature(signature)?;
145 }
146 Ok(())
147}
148
149pub fn validate_airdrop_amount(lamports: u64) -> Result<(), validator::ValidationError> {
151 validate_lamports(lamports)?;
152
153 if lamports > MAX_AIRDROP_AMOUNT {
155 return Err(validator::ValidationError::new("airdrop_amount_too_large"));
156 }
157
158 if lamports == 0 {
159 return Err(validator::ValidationError::new("airdrop_amount_zero"));
160 }
161
162 Ok(())
163}
164
165pub fn validate_airdrop_amount_i64(lamports: i64) -> Result<(), validator::ValidationError> {
167 if lamports < 0 {
169 return Err(validator::ValidationError::new("airdrop_amount_negative"));
170 }
171
172 if lamports == 0 {
174 return Err(validator::ValidationError::new("airdrop_amount_zero"));
175 }
176
177 let lamports_u64 = lamports as u64;
179 validate_lamports(lamports_u64)?;
180
181 if lamports_u64 > MAX_AIRDROP_AMOUNT {
183 return Err(validator::ValidationError::new("airdrop_amount_too_large"));
184 }
185
186 Ok(())
187}
188
189pub fn validate_signature_limit(limit: &u16) -> Result<(), validator::ValidationError> {
191 if *limit == 0 {
192 return Err(validator::ValidationError::new("limit_must_be_positive"));
193 }
194 if *limit > MAX_PAGINATION_LIMIT as u16 {
195 return Err(validator::ValidationError::new("limit_exceeds_maximum"));
196 }
197 Ok(())
198}
199
200pub fn validate_transaction_data(transaction: &str) -> Result<(), validator::ValidationError> {
202 if transaction.is_empty() {
204 return Ok(());
205 }
206
207 if validate_base64(transaction).is_ok() {
209 return validate_transaction_structure_base64(transaction);
211 }
212
213 if validate_base58(transaction).is_ok() {
215 return validate_transaction_structure_base58(transaction);
216 }
217
218 Err(validator::ValidationError::new(
220 "invalid_transaction_encoding",
221 ))
222}
223
224fn validate_transaction_structure_base64(
226 transaction: &str,
227) -> Result<(), validator::ValidationError> {
228 use fastcrypto::encoding::{Base64, Encoding};
229
230 let decoded = Base64::decode(transaction)
232 .map_err(|_| validator::ValidationError::new("invalid_base64_transaction"))?;
233
234 validate_transaction_bytes(&decoded)
235}
236
237fn validate_transaction_structure_base58(
239 transaction: &str,
240) -> Result<(), validator::ValidationError> {
241 use fastcrypto::encoding::{Base58, Encoding};
242
243 let decoded = Base58::decode(transaction)
245 .map_err(|_| validator::ValidationError::new("invalid_base58_transaction"))?;
246
247 validate_transaction_bytes(&decoded)
248}
249
250fn validate_transaction_bytes(transaction_bytes: &[u8]) -> Result<(), validator::ValidationError> {
252 if transaction_bytes.len() < MIN_TRANSACTION_SIZE {
254 return Err(validator::ValidationError::new("transaction_too_small"));
255 }
256
257 if transaction_bytes.len() > MAX_TRANSACTION_SIZE {
259 return Err(validator::ValidationError::new("transaction_too_large"));
260 }
261
262 match bincode::deserialize::<rialo_s_sdk::transaction::VersionedTransaction>(transaction_bytes)
264 {
265 Ok(_) => Ok(()),
266 Err(_) => {
267 match bincode::deserialize::<rialo_s_sdk::transaction::Transaction>(transaction_bytes) {
269 Ok(_) => Ok(()),
270 Err(_) => Err(validator::ValidationError::new(
271 "invalid_transaction_structure",
272 )),
273 }
274 }
275 }
276}
277
278pub fn validate_limit_string(limit: &str) -> Result<(), validator::ValidationError> {
280 let limit_val: u64 = limit
281 .parse()
282 .map_err(|_| validator::ValidationError::new("invalid_limit_format"))?;
283 validate_limit(&limit_val)?;
284 Ok(())
285}
286
287pub fn validate_blockhash(blockhash: &str) -> Result<(), validator::ValidationError> {
289 if blockhash.len() < MIN_BLOCKHASH_LENGTH || blockhash.len() > MAX_BLOCKHASH_LENGTH {
291 return Err(validator::ValidationError::new("invalid_blockhash_length"));
292 }
293 validate_base58(blockhash)
294}
295
296pub fn validate_addresses(addresses: &[String]) -> Result<(), validator::ValidationError> {
298 for address in addresses {
299 validate_pubkey(address)?;
300 }
301 Ok(())
302}
303
304pub fn validate_signatures(signatures: &[String]) -> Result<(), validator::ValidationError> {
306 for signature in signatures {
307 validate_signature(signature)?;
308 }
309 Ok(())
310}
311
312pub fn validate_encoding(encoding: &str) -> Result<(), validator::ValidationError> {
314 match encoding {
315 "json" | "jsonParsed" | "base58" | "base64" => Ok(()),
316 _ => Err(validator::ValidationError::new("invalid_encoding_format")),
317 }
318}
319
320pub fn validate_max_transaction_version(version: &u8) -> Result<(), validator::ValidationError> {
322 if *version <= 1 {
323 Ok(())
324 } else {
325 Err(validator::ValidationError::new(
326 "invalid_max_transaction_version",
327 ))
328 }
329}
330
331pub fn validate_request<T>(request: T) -> ValidationResult<T>
333where
334 T: validator::Validate,
335{
336 request.validate().map_err(ValidationError::from)?;
337 Ok(request)
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_validate_limit() {
346 assert!(validate_limit(&1).is_ok());
347 assert!(validate_limit(&MAX_PAGINATION_LIMIT).is_ok());
348 assert!(validate_limit(&0).is_err());
349 assert!(validate_limit(&(MAX_PAGINATION_LIMIT + 1)).is_err());
350 }
351
352 #[test]
353 fn test_validate_nonce() {
354 assert!(validate_nonce("valid_nonce").is_ok());
355 assert!(validate_nonce("").is_err());
356 let long_nonce = "x".repeat(65);
357 assert!(validate_nonce(&long_nonce).is_err());
358 }
359
360 #[test]
361 fn test_validate_encoding() {
362 assert!(validate_encoding("json").is_ok());
363 assert!(validate_encoding("jsonParsed").is_ok());
364 assert!(validate_encoding("base58").is_ok());
365 assert!(validate_encoding("base64").is_ok());
366 assert!(validate_encoding("invalid").is_err());
367 }
368
369 #[test]
370 fn test_validate_max_transaction_version() {
371 assert!(validate_max_transaction_version(&0).is_ok());
372 assert!(validate_max_transaction_version(&1).is_ok());
373 assert!(validate_max_transaction_version(&2).is_err());
374 }
375}