1#[cfg(feature = "decimal")]
2use std::str::FromStr;
3
4use base64::Engine;
5
6use crate::{ParamError, ValueType};
7
8#[derive(Debug, Clone, PartialEq)]
10#[non_exhaustive]
11pub enum ScalarValue {
12 Bool(bool),
13 I32(i32),
14 I64(i64),
15 F32(f32),
16 F64(f64),
17 #[cfg(feature = "decimal")]
18 Decimal(rust_decimal::Decimal),
19 Text(String),
20 Bytes(Vec<u8>),
21 #[cfg(feature = "chrono")]
22 Date(chrono::NaiveDate),
23 #[cfg(feature = "chrono")]
24 Time(chrono::NaiveTime),
25 #[cfg(feature = "chrono")]
26 DateTime(chrono::NaiveDateTime),
27 #[cfg(feature = "chrono")]
28 Timestamp(chrono::DateTime<chrono::Utc>),
29 #[cfg(feature = "uuid")]
30 Uuid(uuid::Uuid),
31 #[cfg(feature = "json")]
32 Json(Box<serde_json::Value>),
33 #[cfg(feature = "postgres-types")]
34 ArrayI64(Vec<Option<i64>>),
35 #[cfg(feature = "postgres-types")]
36 ArrayText(Vec<Option<String>>),
37 #[cfg(feature = "postgres-types")]
38 Int8Range(Box<crate::Int8Range>),
39 #[cfg(feature = "postgres-types")]
40 Network(Box<ipnet::IpNet>),
41}
42
43impl ScalarValue {
44 pub fn logical_weight(&self) -> usize {
46 match self {
47 #[cfg(feature = "postgres-types")]
48 Self::ArrayI64(values) => values.len().max(1),
49 #[cfg(feature = "postgres-types")]
50 Self::ArrayText(values) => values.len().max(1),
51 #[cfg(feature = "json")]
52 Self::Json(value) => json_weight(value),
53 _ => 1,
54 }
55 }
56
57 pub(crate) fn validate(&self) -> Result<(), ParamError> {
58 #[cfg(feature = "postgres-types")]
59 if let Self::Int8Range(value) = self {
60 value
61 .validate()
62 .map_err(|reason| ParamError::InvalidValue {
63 field: "<range>".to_owned(),
64 reason,
65 })?;
66 }
67 if matches!(self, Self::F32(value) if !value.is_finite())
68 || matches!(self, Self::F64(value) if !value.is_finite())
69 {
70 return Err(ParamError::InvalidValue {
71 field: "<value>".to_owned(),
72 reason: "expected a finite float".to_owned(),
73 });
74 }
75 Ok(())
76 }
77 #[must_use]
78 pub fn to_external_text(&self) -> String {
79 RowValue::from(self.clone()).to_external_text()
80 }
81 #[must_use]
82 pub fn value_type(&self) -> ValueType {
83 match self {
84 Self::Bool(_) => ValueType::Bool,
85 Self::I32(_) => ValueType::I32,
86 Self::I64(_) => ValueType::I64,
87 Self::F32(_) => ValueType::F32,
88 Self::F64(_) => ValueType::F64,
89 #[cfg(feature = "decimal")]
90 Self::Decimal(_) => ValueType::Decimal,
91 Self::Text(_) => ValueType::Text,
92 Self::Bytes(_) => ValueType::Bytes,
93 #[cfg(feature = "chrono")]
94 Self::Date(_) => ValueType::Date,
95 #[cfg(feature = "chrono")]
96 Self::Time(_) => ValueType::Time,
97 #[cfg(feature = "chrono")]
98 Self::DateTime(_) => ValueType::DateTime,
99 #[cfg(feature = "chrono")]
100 Self::Timestamp(_) => ValueType::Timestamp,
101 #[cfg(feature = "uuid")]
102 Self::Uuid(_) => ValueType::Uuid,
103 #[cfg(feature = "json")]
104 Self::Json(_) => ValueType::Json,
105 #[cfg(feature = "postgres-types")]
106 Self::ArrayI64(_) => ValueType::ArrayI64,
107 #[cfg(feature = "postgres-types")]
108 Self::ArrayText(_) => ValueType::ArrayText,
109 #[cfg(feature = "postgres-types")]
110 Self::Int8Range(_) => ValueType::Int8Range,
111 #[cfg(feature = "postgres-types")]
112 Self::Network(_) => ValueType::Network,
113 }
114 }
115
116 pub(crate) fn parse(
117 value_type: ValueType,
118 input: &str,
119 field: &str,
120 ) -> Result<Self, ParamError> {
121 let invalid = |reason: &str| ParamError::InvalidValue {
122 field: field.to_owned(),
123 reason: reason.to_owned(),
124 };
125 match value_type {
126 ValueType::Bool => match input {
127 "true" => Ok(Self::Bool(true)),
128 "false" => Ok(Self::Bool(false)),
129 _ => Err(invalid("expected true or false")),
130 },
131 ValueType::I32 => input
132 .parse()
133 .map(Self::I32)
134 .map_err(|_| invalid("expected a 32-bit decimal integer")),
135 ValueType::I64 => input
136 .parse()
137 .map(Self::I64)
138 .map_err(|_| invalid("expected a 64-bit decimal integer")),
139 ValueType::F32 => {
140 let value: f32 = input
141 .parse()
142 .map_err(|_| invalid("expected a finite 32-bit float"))?;
143 value
144 .is_finite()
145 .then_some(Self::F32(value))
146 .ok_or_else(|| invalid("expected a finite 32-bit float"))
147 }
148 ValueType::F64 => {
149 let value: f64 = input
150 .parse()
151 .map_err(|_| invalid("expected a finite 64-bit float"))?;
152 value
153 .is_finite()
154 .then_some(Self::F64(value))
155 .ok_or_else(|| invalid("expected a finite 64-bit float"))
156 }
157 #[cfg(feature = "decimal")]
158 ValueType::Decimal => rust_decimal::Decimal::from_str(input)
159 .map(Self::Decimal)
160 .map_err(|_| invalid("expected a decimal")),
161 ValueType::Text => Ok(Self::Text(input.to_owned())),
162 ValueType::Bytes => base64::engine::general_purpose::URL_SAFE_NO_PAD
163 .decode(input)
164 .map(Self::Bytes)
165 .map_err(|_| invalid("expected unpadded base64url")),
166 #[cfg(feature = "chrono")]
167 ValueType::Date => chrono::NaiveDate::parse_from_str(input, "%Y-%m-%d")
168 .map(Self::Date)
169 .map_err(|_| invalid("expected YYYY-MM-DD")),
170 #[cfg(feature = "chrono")]
171 ValueType::Time => chrono::NaiveTime::parse_from_str(input, "%H:%M:%S%.f")
172 .map(Self::Time)
173 .map_err(|_| invalid("expected HH:MM:SS[.fraction]")),
174 #[cfg(feature = "chrono")]
175 ValueType::DateTime => {
176 chrono::NaiveDateTime::parse_from_str(input, "%Y-%m-%dT%H:%M:%S%.f")
177 .map(Self::DateTime)
178 .map_err(|_| invalid("expected YYYY-MM-DDTHH:MM:SS[.fraction]"))
179 }
180 #[cfg(feature = "chrono")]
181 ValueType::Timestamp => chrono::DateTime::parse_from_rfc3339(input)
182 .map(|value| Self::Timestamp(value.to_utc()))
183 .map_err(|_| invalid("expected an RFC 3339 timestamp")),
184 #[cfg(feature = "uuid")]
185 ValueType::Uuid => uuid::Uuid::parse_str(input)
186 .map(Self::Uuid)
187 .map_err(|_| invalid("expected a UUID")),
188 #[cfg(feature = "json")]
189 ValueType::Json => serde_json::from_str(input)
190 .map(|value| Self::Json(Box::new(value)))
191 .map_err(|_| invalid("expected structured JSON")),
192 #[cfg(feature = "postgres-types")]
193 ValueType::ArrayI64 => serde_json::from_str(input)
194 .map(Self::ArrayI64)
195 .map_err(|_| invalid("expected JSON array of int64 values or null")),
196 #[cfg(feature = "postgres-types")]
197 ValueType::ArrayText => serde_json::from_str(input)
198 .map(Self::ArrayText)
199 .map_err(|_| invalid("expected JSON array of strings or null")),
200 #[cfg(feature = "postgres-types")]
201 ValueType::Int8Range => input
202 .parse()
203 .map(|value| Self::Int8Range(Box::new(value)))
204 .map_err(|reason: String| invalid(&reason)),
205 #[cfg(feature = "postgres-types")]
206 ValueType::Network => input
207 .parse()
208 .or_else(|_| input.parse::<std::net::IpAddr>().map(ipnet::IpNet::from))
209 .map(|value| Self::Network(Box::new(value)))
210 .map_err(|_| invalid("expected IPv4/IPv6 address or network")),
211 }
212 }
213}
214
215#[cfg(feature = "json")]
216fn json_weight(value: &serde_json::Value) -> usize {
217 match value {
218 serde_json::Value::Array(values) => values
219 .iter()
220 .map(json_weight)
221 .sum::<usize>()
222 .saturating_add(1),
223 serde_json::Value::Object(values) => values
224 .values()
225 .map(json_weight)
226 .sum::<usize>()
227 .saturating_add(1),
228 _ => 1,
229 }
230}
231
232#[derive(Debug, Clone, PartialEq)]
234pub struct TypedList {
235 pub element_type: ValueType,
236 pub values: Vec<ScalarValue>,
237}
238
239impl TypedList {
240 pub fn new(element_type: ValueType, values: Vec<ScalarValue>) -> Result<Self, ParamError> {
241 if values.is_empty() {
242 return Err(ParamError::InvalidValue {
243 field: "<list>".to_owned(),
244 reason: "list must not be empty".to_owned(),
245 });
246 }
247 if values
248 .iter()
249 .any(|value| value.value_type() != element_type)
250 {
251 return Err(ParamError::InvalidValue {
252 field: "<list>".to_owned(),
253 reason: "list elements must have one type".to_owned(),
254 });
255 }
256 Ok(Self {
257 element_type,
258 values,
259 })
260 }
261}
262
263#[derive(Debug, Clone, PartialEq)]
265#[non_exhaustive]
266pub enum RowValue {
267 #[cfg(feature = "postgres-types")]
268 ArrayI64(Vec<Option<i64>>),
269 #[cfg(feature = "postgres-types")]
270 ArrayText(Vec<Option<String>>),
271 #[cfg(feature = "postgres-types")]
272 Int8Range(crate::Int8Range),
273 #[cfg(feature = "postgres-types")]
274 Network(ipnet::IpNet),
275 List(Vec<RowValue>),
277 Null,
278 Bool(bool),
279 I32(i32),
280 I64(i64),
281 F32(f32),
282 F64(f64),
283 #[cfg(feature = "decimal")]
284 Decimal(rust_decimal::Decimal),
285 Text(String),
286 Bytes(Vec<u8>),
287 #[cfg(feature = "chrono")]
288 Date(chrono::NaiveDate),
289 #[cfg(feature = "chrono")]
290 Time(chrono::NaiveTime),
291 #[cfg(feature = "chrono")]
292 DateTime(chrono::NaiveDateTime),
293 #[cfg(feature = "chrono")]
294 Timestamp(chrono::DateTime<chrono::Utc>),
295 #[cfg(feature = "uuid")]
296 Uuid(uuid::Uuid),
297 #[cfg(feature = "json")]
298 Json(serde_json::Value),
299}
300
301impl RowValue {
302 #[must_use]
303 pub fn value_type(&self) -> Option<ValueType> {
304 match self {
305 Self::Null | Self::List(_) => None,
306 Self::Bool(_) => Some(ValueType::Bool),
307 Self::I32(_) => Some(ValueType::I32),
308 Self::I64(_) => Some(ValueType::I64),
309 Self::F32(_) => Some(ValueType::F32),
310 Self::F64(_) => Some(ValueType::F64),
311 #[cfg(feature = "decimal")]
312 Self::Decimal(_) => Some(ValueType::Decimal),
313 Self::Text(_) => Some(ValueType::Text),
314 Self::Bytes(_) => Some(ValueType::Bytes),
315 #[cfg(feature = "chrono")]
316 Self::Date(_) => Some(ValueType::Date),
317 #[cfg(feature = "chrono")]
318 Self::Time(_) => Some(ValueType::Time),
319 #[cfg(feature = "chrono")]
320 Self::DateTime(_) => Some(ValueType::DateTime),
321 #[cfg(feature = "chrono")]
322 Self::Timestamp(_) => Some(ValueType::Timestamp),
323 #[cfg(feature = "uuid")]
324 Self::Uuid(_) => Some(ValueType::Uuid),
325 #[cfg(feature = "json")]
326 Self::Json(_) => Some(ValueType::Json),
327 #[cfg(feature = "postgres-types")]
328 Self::ArrayI64(_) => Some(ValueType::ArrayI64),
329 #[cfg(feature = "postgres-types")]
330 Self::ArrayText(_) => Some(ValueType::ArrayText),
331 #[cfg(feature = "postgres-types")]
332 Self::Int8Range(_) => Some(ValueType::Int8Range),
333 #[cfg(feature = "postgres-types")]
334 Self::Network(_) => Some(ValueType::Network),
335 }
336 }
337 #[must_use]
340 pub fn to_external_text(&self) -> String {
341 match self {
342 #[cfg(feature = "postgres-types")]
343 Self::ArrayI64(value) => serde_json::to_string(value).expect("integer array"),
344 #[cfg(feature = "postgres-types")]
345 Self::ArrayText(value) => serde_json::to_string(value).expect("text array"),
346 #[cfg(feature = "postgres-types")]
347 Self::Int8Range(value) => value.to_string(),
348 #[cfg(feature = "postgres-types")]
349 Self::Network(value) => value.to_string(),
350 Self::List(values) => format!(
351 "[{}]",
352 values.iter().map(json_text).collect::<Vec<_>>().join(",")
353 ),
354 Self::Null => String::new(),
355 Self::Bool(value) => value.to_string(),
356 Self::I32(value) => value.to_string(),
357 Self::I64(value) => value.to_string(),
358 Self::F32(value) => value.to_string(),
359 Self::F64(value) => value.to_string(),
360 Self::Text(value) => value.clone(),
361 Self::Bytes(value) => base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(value),
362 #[cfg(feature = "decimal")]
363 Self::Decimal(value) => value.to_string(),
364 #[cfg(feature = "chrono")]
365 Self::Date(value) => value.format("%Y-%m-%d").to_string(),
366 #[cfg(feature = "chrono")]
367 Self::Time(value) => value.format("%H:%M:%S%.f").to_string(),
368 #[cfg(feature = "chrono")]
369 Self::DateTime(value) => value.format("%Y-%m-%dT%H:%M:%S%.f").to_string(),
370 #[cfg(feature = "chrono")]
371 Self::Timestamp(value) => value.to_rfc3339(),
372 #[cfg(feature = "uuid")]
373 Self::Uuid(value) => value.to_string(),
374 #[cfg(feature = "json")]
375 Self::Json(value) => value.to_string(),
376 }
377 }
378}
379
380impl From<ScalarValue> for RowValue {
381 fn from(value: ScalarValue) -> Self {
382 match value {
383 #[cfg(feature = "postgres-types")]
384 ScalarValue::ArrayI64(value) => Self::ArrayI64(value),
385 #[cfg(feature = "postgres-types")]
386 ScalarValue::ArrayText(value) => Self::ArrayText(value),
387 #[cfg(feature = "postgres-types")]
388 ScalarValue::Int8Range(value) => Self::Int8Range(*value),
389 #[cfg(feature = "postgres-types")]
390 ScalarValue::Network(value) => Self::Network(*value),
391 ScalarValue::Bool(value) => Self::Bool(value),
392 ScalarValue::I32(value) => Self::I32(value),
393 ScalarValue::I64(value) => Self::I64(value),
394 ScalarValue::F32(value) => Self::F32(value),
395 ScalarValue::F64(value) => Self::F64(value),
396 ScalarValue::Text(value) => Self::Text(value),
397 ScalarValue::Bytes(value) => Self::Bytes(value),
398 #[cfg(feature = "decimal")]
399 ScalarValue::Decimal(value) => Self::Decimal(value),
400 #[cfg(feature = "chrono")]
401 ScalarValue::Date(value) => Self::Date(value),
402 #[cfg(feature = "chrono")]
403 ScalarValue::Time(value) => Self::Time(value),
404 #[cfg(feature = "chrono")]
405 ScalarValue::DateTime(value) => Self::DateTime(value),
406 #[cfg(feature = "chrono")]
407 ScalarValue::Timestamp(value) => Self::Timestamp(value),
408 #[cfg(feature = "uuid")]
409 ScalarValue::Uuid(value) => Self::Uuid(value),
410 #[cfg(feature = "json")]
411 ScalarValue::Json(value) => Self::Json(*value),
412 }
413 }
414}
415
416fn json_text(value: &RowValue) -> String {
417 match value {
418 RowValue::Null => "null".to_owned(),
419 RowValue::Bool(_) | RowValue::I32(_) => value.to_external_text(),
420 RowValue::F32(number) if number.is_finite() => number.to_string(),
421 RowValue::F64(number) if number.is_finite() => number.to_string(),
422 RowValue::F32(_) | RowValue::F64(_) => "null".to_owned(),
423 RowValue::List(_) => value.to_external_text(),
424 #[cfg(feature = "json")]
425 RowValue::Json(json) => json.to_string(),
426 _ => {
427 use std::fmt::Write;
428 let mut output = String::from("\"");
429 for character in value.to_external_text().chars() {
430 match character {
431 '"' => output.push_str("\\\""),
432 '\\' => output.push_str("\\\\"),
433 character if character <= '\u{1f}' => {
434 write!(&mut output, "\\u{:04x}", u32::from(character))
435 .expect("writing to String cannot fail");
436 }
437 character => output.push(character),
438 }
439 }
440 output.push('"');
441 output
442 }
443 }
444}
445
446#[cfg(feature = "serde")]
447impl serde::Serialize for RowValue {
448 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
449 where
450 S: serde::Serializer,
451 {
452 use serde::ser::Error as _;
453 match self {
454 Self::List(values) => serde::Serialize::serialize(values, serializer),
455 #[cfg(feature = "postgres-types")]
456 Self::ArrayI64(values) => serde::Serialize::serialize(
457 &values
458 .iter()
459 .map(|value| value.map(|value| value.to_string()))
460 .collect::<Vec<_>>(),
461 serializer,
462 ),
463 #[cfg(feature = "postgres-types")]
464 Self::ArrayText(values) => serde::Serialize::serialize(values, serializer),
465 #[cfg(feature = "postgres-types")]
466 Self::Int8Range(value) => serializer.serialize_str(&value.to_string()),
467 #[cfg(feature = "postgres-types")]
468 Self::Network(value) => serializer.serialize_str(&value.to_string()),
469 Self::Null => serializer.serialize_none(),
470 Self::Bool(value) => serializer.serialize_bool(*value),
471 Self::I32(value) => serializer.serialize_i32(*value),
472 Self::I64(value) => serializer.serialize_str(&value.to_string()),
474 Self::F32(value) if value.is_finite() => serializer.serialize_f32(*value),
475 Self::F64(value) if value.is_finite() => serializer.serialize_f64(*value),
476 Self::F32(_) | Self::F64(_) => {
477 Err(S::Error::custom("non-finite floats cannot be serialized"))
478 }
479 #[cfg(feature = "decimal")]
480 Self::Decimal(value) => serializer.serialize_str(&value.to_string()),
481 Self::Text(value) => serializer.serialize_str(value),
482 Self::Bytes(value) => serializer
483 .serialize_str(&base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(value)),
484 #[cfg(feature = "chrono")]
485 Self::Date(value) => serializer.serialize_str(&value.format("%Y-%m-%d").to_string()),
486 #[cfg(feature = "chrono")]
487 Self::Time(value) => serializer.serialize_str(&value.format("%H:%M:%S%.f").to_string()),
488 #[cfg(feature = "chrono")]
489 Self::DateTime(value) => {
490 serializer.serialize_str(&value.format("%Y-%m-%dT%H:%M:%S%.f").to_string())
491 }
492 #[cfg(feature = "chrono")]
493 Self::Timestamp(value) => serializer.serialize_str(&value.to_rfc3339()),
494 #[cfg(feature = "uuid")]
495 Self::Uuid(value) => serializer.serialize_str(&value.to_string()),
496 #[cfg(feature = "json")]
497 Self::Json(value) => value.serialize(serializer),
498 }
499 }
500}
501
502pub trait IntoScalarValue {
503 fn into_scalar_value(self) -> ScalarValue;
504}
505
506macro_rules! impl_scalar {
507 ($type:ty, $variant:ident) => {
508 impl IntoScalarValue for $type {
509 fn into_scalar_value(self) -> ScalarValue {
510 ScalarValue::$variant(self)
511 }
512 }
513 };
514}
515
516impl_scalar!(bool, Bool);
517impl_scalar!(i32, I32);
518impl_scalar!(i64, I64);
519impl_scalar!(f32, F32);
520impl_scalar!(f64, F64);
521impl_scalar!(String, Text);
522impl_scalar!(Vec<u8>, Bytes);
523
524impl IntoScalarValue for &str {
525 fn into_scalar_value(self) -> ScalarValue {
526 ScalarValue::Text(self.to_owned())
527 }
528}
529
530#[cfg(feature = "decimal")]
531impl_scalar!(rust_decimal::Decimal, Decimal);
532#[cfg(feature = "chrono")]
533impl_scalar!(chrono::NaiveDate, Date);
534#[cfg(feature = "chrono")]
535impl_scalar!(chrono::NaiveTime, Time);
536#[cfg(feature = "chrono")]
537impl_scalar!(chrono::NaiveDateTime, DateTime);
538#[cfg(feature = "chrono")]
539impl_scalar!(chrono::DateTime<chrono::Utc>, Timestamp);
540#[cfg(feature = "uuid")]
541impl_scalar!(uuid::Uuid, Uuid);
542#[cfg(feature = "json")]
543impl IntoScalarValue for serde_json::Value {
544 fn into_scalar_value(self) -> ScalarValue {
545 ScalarValue::Json(Box::new(self))
546 }
547}
548#[cfg(feature = "postgres-types")]
549impl_scalar!(Vec<Option<i64>>, ArrayI64);
550#[cfg(feature = "postgres-types")]
551impl_scalar!(Vec<Option<String>>, ArrayText);
552#[cfg(feature = "postgres-types")]
553impl IntoScalarValue for crate::Int8Range {
554 fn into_scalar_value(self) -> ScalarValue {
555 ScalarValue::Int8Range(Box::new(self))
556 }
557}
558#[cfg(feature = "postgres-types")]
559impl IntoScalarValue for ipnet::IpNet {
560 fn into_scalar_value(self) -> ScalarValue {
561 ScalarValue::Network(Box::new(self))
562 }
563}
564#[cfg(feature = "postgres-types")]
565impl IntoScalarValue for Vec<i64> {
566 fn into_scalar_value(self) -> ScalarValue {
567 ScalarValue::ArrayI64(self.into_iter().map(Some).collect())
568 }
569}
570#[cfg(feature = "postgres-types")]
571impl IntoScalarValue for Vec<String> {
572 fn into_scalar_value(self) -> ScalarValue {
573 ScalarValue::ArrayText(self.into_iter().map(Some).collect())
574 }
575}