multisql/data/value/
cast.rs

1use {
2	super::{Convert, Value, ValueError, ValueType},
3	crate::{Error, Result},
4	chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError},
5	std::convert::TryInto,
6	thousands::Separable,
7};
8
9pub trait Cast<Output> {
10	fn cast(self) -> Result<Output>;
11}
12pub trait CastWithRules<Output> {
13	fn cast_with_rule(self, rule: Self) -> Result<Output>;
14}
15
16fn failed_cast(value: &Value, value_type: ValueType) -> Error {
17	Error::Value(ValueError::FailedCast(value.clone(), value_type))
18}
19fn unimplemented_cast(value: &Value, value_type: ValueType) -> Error {
20	Error::Value(ValueError::FailedCast(value.clone().into(), value_type))
21}
22
23// Cores
24impl Cast<bool> for Value {
25	fn cast(self) -> Result<bool> {
26		Ok(match self {
27			Value::Bool(value) => value,
28			Value::I64(value) => match value {
29				1 => true,
30				0 => false,
31				_ => return Err(failed_cast(&self, ValueType::Bool)),
32			},
33			Value::F64(value) => {
34				if value.eq(&1.0) {
35					true
36				} else if value.eq(&0.0) {
37					false
38				} else {
39					return Err(failed_cast(&self, ValueType::Bool));
40				}
41			}
42			Value::Str(value) => match value.to_lowercase().as_str() {
43				"true" => true,
44				"false" => false,
45				_ => return Err(failed_cast(&Value::Str(value), ValueType::Bool)),
46			},
47			Value::Null => return Err(failed_cast(&self, ValueType::Bool)),
48			_ => return Err(unimplemented_cast(&self, ValueType::Bool)),
49		})
50	}
51}
52
53impl Cast<u64> for Value {
54	fn cast(self) -> Result<u64> {
55		Ok(match self {
56			Value::Bool(value) => {
57				if value {
58					1
59				} else {
60					0
61				}
62			}
63			Value::U64(value) => value,
64			Value::I64(value) => value
65				.try_into()
66				.map_err(|_| failed_cast(&self, ValueType::U64))?,
67			Value::F64(value) => (value.trunc() as i64)
68				.try_into()
69				.map_err(|_| failed_cast(&self, ValueType::U64))?,
70			Value::Str(value) => lexical::parse(&value)
71				.map_err(|_| failed_cast(&Value::Str(value), ValueType::U64))?,
72			Value::Null => return Err(failed_cast(&self, ValueType::U64)),
73			_ => return Err(unimplemented_cast(&self, ValueType::U64)),
74		})
75	}
76}
77
78impl Cast<i64> for Value {
79	fn cast(self) -> Result<i64> {
80		Ok(match self {
81			Value::Bool(value) => {
82				if value {
83					1
84				} else {
85					0
86				}
87			}
88			Value::U64(value) => value
89				.try_into()
90				.map_err(|_| failed_cast(&self, ValueType::I64))?,
91			Value::I64(value) => value,
92			Value::F64(value) => value.trunc() as i64,
93			Value::Str(value) => lexical::parse(&value)
94				.map_err(|_| failed_cast(&Value::Str(value), ValueType::I64))?,
95			Value::Null => return Err(failed_cast(&self, ValueType::I64)),
96			_ => return Err(unimplemented_cast(&self, ValueType::I64)),
97		})
98	}
99}
100
101impl Cast<f64> for Value {
102	fn cast(self) -> Result<f64> {
103		Ok(match self {
104			Value::Bool(value) => {
105				if value {
106					1.0
107				} else {
108					0.0
109				}
110			}
111			Value::U64(value) => (value as f64).trunc(),
112			Value::I64(value) => (value as f64).trunc(),
113			Value::F64(value) => value,
114			Value::Str(value) => fast_float::parse(&value)
115				.map_err(|_| failed_cast(&Value::Str(value), ValueType::F64))?,
116			Value::Null => return Err(failed_cast(&self, ValueType::F64)),
117			_ => return Err(unimplemented_cast(&self, ValueType::F64)),
118		})
119	}
120}
121impl Cast<String> for Value {
122	fn cast(self) -> Result<String> {
123		Ok(match self {
124			Value::Bool(value) => (if value { "true" } else { "false" }).to_string(),
125			Value::U64(value) => lexical::to_string(value),
126			Value::I64(value) => lexical::to_string(value),
127			Value::F64(value) => lexical::to_string(value),
128			Value::Str(value) => value,
129			Value::Timestamp(value) => NaiveDateTime::from_timestamp(value, 0).to_string(),
130			Value::Null => String::from("NULL"),
131			_ => return Err(unimplemented_cast(&self, ValueType::Str)),
132		})
133	}
134}
135
136// Utilities
137impl Cast<usize> for Value {
138	fn cast(self) -> Result<usize> {
139		let int: u64 = self.cast()?;
140		int.try_into()
141			.map_err(|_| ValueError::ImpossibleCast.into())
142	}
143}
144
145// Non-Core
146impl CastWithRules<bool> for Value {
147	fn cast_with_rule(self, rule: Self) -> Result<bool> {
148		match rule {
149			Value::I64(000) | Value::Bool(true) => self.cast(),
150			_ => Err(ValueError::InvalidConversionRule.into()),
151		}
152	}
153}
154impl CastWithRules<i64> for Value {
155	fn cast_with_rule(self, rule: Self) -> Result<i64> {
156		match rule {
157			Value::I64(000) | Value::Bool(true) => self.cast(),
158			_ => Err(ValueError::InvalidConversionRule.into()),
159		}
160	}
161}
162impl CastWithRules<f64> for Value {
163	fn cast_with_rule(self, rule: Self) -> Result<f64> {
164		match rule {
165			Value::I64(000) | Value::Bool(true) => self.cast(),
166			_ => Err(ValueError::InvalidConversionRule.into()),
167		}
168	}
169}
170impl CastWithRules<String> for Value {
171	fn cast_with_rule(self, rule: Self) -> Result<String> {
172		match rule {
173			Value::I64(000) | Value::Bool(true) => self.cast(),
174			Value::Str(specified) if specified == *"DATETIME" => {
175				Ok(NaiveDateTime::from_timestamp(self.convert()?, 0)
176					.format("%F %T")
177					.to_string())
178			}
179			Value::Str(specified) if specified == *"MONEY" => {
180				let value: f64 = self.convert()?;
181				let value = (value * 100.0).round() / 100.0;
182				let value = value.separate_with_commas();
183				Ok(format!("${}", value))
184			}
185			Value::Str(specified) if specified == *"SEPARATED" => {
186				let value: f64 = self.convert()?;
187				let value = (value * 100.0).round() / 100.0;
188				let value = value.separate_with_commas();
189				Ok(value)
190			}
191			Value::Str(format) if matches!(self, Value::I64(..)) => {
192				// TODO: TIMESTAMP type
193				Ok(NaiveDateTime::from_timestamp(self.convert()?, 0)
194					.format(&format)
195					.to_string())
196			}
197			_ => Err(ValueError::InvalidConversionRule.into()),
198		}
199	}
200}
201
202// Non-SQL
203// - DateTime
204fn parse_error_into(error: ParseError) -> Error {
205	ValueError::DateTimeParseError(format!("{:?}", error)).into()
206}
207impl Cast<NaiveDateTime> for Value {
208	// Default (from Timestamp)
209	fn cast(self) -> Result<NaiveDateTime> {
210		let timestamp: i64 = self.cast()?;
211		NaiveDateTime::from_timestamp_opt(timestamp, 0)
212			.ok_or_else(|| ValueError::ImpossibleCast.into())
213	}
214}
215#[allow(clippy::zero_prefixed_literal)]
216impl CastWithRules<NaiveDateTime> for Value {
217	fn cast_with_rule(self, rule: Self) -> Result<NaiveDateTime> {
218		fn for_format_datetime(string: Value, format: &str) -> Result<NaiveDateTime> {
219			let string: String = string.cast()?;
220			let string: &str = string.as_str();
221			NaiveDateTime::parse_from_str(string, format).map_err(parse_error_into)
222		}
223		fn for_format_date(string: Value, format: &str) -> Result<NaiveDateTime> {
224			let string: String = string.cast()?;
225			let string: &str = string.as_str();
226			Ok(NaiveDate::parse_from_str(string, format)
227				.map_err(parse_error_into)?
228				.and_hms(0, 0, 0))
229		}
230		fn for_format_time(string: Value, format: &str) -> Result<NaiveDateTime> {
231			let string: String = string.cast()?;
232			let string: &str = string.as_str();
233			Ok(NaiveDateTime::from_timestamp(0, 0)
234				.date()
235				.and_time(NaiveTime::parse_from_str(string, format).map_err(parse_error_into)?))
236		}
237		fn try_rules(try_value: &Value, rules: &[i64]) -> Result<NaiveDateTime> {
238			rules
239				.iter()
240				.find_map(|try_rule| try_value.clone().cast_with_rule((*try_rule).into()).ok())
241				.ok_or_else(|| ValueError::ParseError(try_value.clone(), "TIMESTAMP").into())
242		}
243		const TRY_RULES_TIMESTAMP: [i64; 1] = [000];
244		const TRY_RULES_DATETIME: [i64; 9] = [010, 011, 020, 021, 030, 031, 060, 062, 063];
245		const TRY_RULES_DATE: [i64; 6] = [022, 033, 032, 061, 064, 040]; // 033 should go before 032
246		const TRY_RULES_TIME: [i64; 2] = [100, 101];
247
248		match rule {
249			Value::Null => try_rules(&self, &TRY_RULES_TIMESTAMP)
250				.or_else(|_| try_rules(&self, &TRY_RULES_DATETIME))
251				.or_else(|_| try_rules(&self, &TRY_RULES_DATE))
252				.or_else(|_| try_rules(&self, &TRY_RULES_TIME)),
253			Value::Bool(true) => try_rules(&self, &TRY_RULES_TIMESTAMP),
254			Value::Str(custom) => match custom.as_str() {
255				"TIMESTAMP" => try_rules(&self, &TRY_RULES_TIMESTAMP),
256				"DATETIME" => try_rules(&self, &TRY_RULES_DATETIME),
257				"DATE" => try_rules(&self, &TRY_RULES_DATE),
258				"TIME" => try_rules(&self, &TRY_RULES_TIME),
259				custom_format => for_format_datetime(self.clone(), custom_format)
260					.or_else(|_| for_format_date(self.clone(), custom_format))
261					.or_else(|_| for_format_time(self, custom_format)),
262			},
263			Value::I64(000) => {
264				// From Timestamp (Default)
265				self.cast()
266			}
267			// 02* - Conventional
268			// - From Database format (YYYY-MM-DD HH:MM:SS)
269			Value::I64(020) => for_format_datetime(self, "%F %T"),
270			// - From Database format, no seconds (YYYY-MM-DD HH:MM)
271			Value::I64(021) => for_format_datetime(self, "%F %R"),
272			// - From Database format, no time (YYYY-MM-DD)
273			Value::I64(022) => for_format_date(self, "%F"),
274
275			// 0(3-4)* - Normal
276			// - From Database format, grossified time (YYYY-MM-DD HH:MM:SS (AM/PM))
277			Value::I64(030) => for_format_datetime(self, "%F %r"),
278			// - From Database format, grossified time, no seconds (YYYY-MM-DD HH:MM (AM/PM))
279			Value::I64(031) => for_format_datetime(self, "%I:%M %p"),
280			// - From dd-Mon-YYYY
281			Value::I64(032) => for_format_date(self, "%v"),
282			// - From dd-Mon-YY
283			Value::I64(033) => for_format_date(self, "%e-%b-%y"),
284
285			Value::I64(040) => for_format_date(self, "%Y%m%d"),
286
287			// 0(5-8)* - Locales
288			// 06* - Australia
289			Value::I64(060) => for_format_datetime(self, "%d/%m/%Y %H:%M"),
290			Value::I64(061) => for_format_date(self, "%d/%m/%Y"),
291			Value::I64(062) => for_format_datetime(self, "%d/%m/%Y %H:%M:%S"),
292			Value::I64(063) => for_format_datetime(self, "%d%m%Y %H:%M:%S"),
293			Value::I64(064) => for_format_date(self, "%d%m%Y"),
294
295			// 10* - Time
296			// - (HH:MM:SS)
297			Value::I64(100) => for_format_time(self, "%T"),
298			// - No seconds (HH:MM)
299			Value::I64(101) => for_format_time(self, "%R"),
300			_ => Err(ValueError::InvalidConversionRule.into()),
301		}
302	}
303}