Skip to content

Commit 492c7e5

Browse files
committed
Handle casting of ExternalValue
Signed-off-by: Tin Švagelj <[email protected]>
1 parent 6d41a53 commit 492c7e5

File tree

2 files changed

+266
-74
lines changed

2 files changed

+266
-74
lines changed

interpreter/src/external.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub trait AsValue: Send + Sync {
2828
}
2929

3030
#[inline]
31-
fn to_value(&self, ty: ValueType) -> Option<Value> {
31+
fn to_value(&self, hint: ValueType) -> Option<Value> {
3232
None
3333
}
3434

interpreter/src/functions.rs

Lines changed: 265 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -140,85 +140,211 @@ pub fn contains(This(this): This<Value>, arg: Value) -> Result<Value> {
140140
.into())
141141
}
142142

143-
// Performs a type conversion on the target. The following conversions are currently
144-
// supported:
145-
// * `string` - Returns a copy of the target string.
146-
// * `timestamp` - Returns the timestamp in RFC3339 format.
147-
// * `duration` - Returns the duration in a string formatted like "72h3m0.5s".
148-
// * `int` - Returns the integer value of the target.
149-
// * `uint` - Returns the unsigned integer value of the target.
150-
// * `float` - Returns the float value of the target.
151-
// * `bytes` - Converts bytes to string using from_utf8_lossy.
143+
macro_rules! return_external_conversion {
144+
($v: ident as $ty: ident) => {{
145+
let $v = unsafe { $v.as_value_unchecked() };
146+
if let Some(it) = $v.to_value(ValueType::$ty) {
147+
if matches!(it, Value::$ty(_)) {
148+
return Ok(it);
149+
}
150+
}
151+
}};
152+
}
153+
154+
/// Converts provided value into a [`string`][Value::String].
155+
///
156+
/// # Supported Conversions
157+
///
158+
/// - [`string`][Value::String] - will be returned as is.
159+
/// - [`timestamp`][Value::Timestamp] - will be formatted into RFC3339 format.
160+
/// - [`duration`][Value::Duration] - will be formatted to string representation
161+
/// (e.g. "72h3m0.5s").
162+
/// - [`int`][Value::Int] - will be formatted into a string.
163+
/// - [`uint`][Value::UInt] - will be formatted into a string.
164+
/// - [`float`][Value::Float] - will be formatted into a string.
165+
/// - [`bytes`][Value::Bytes] - will be converted to string using
166+
/// [`from_utf8_lossy`][String::from_utf8_lossy].
167+
/// - [`external`][Value::External] - will be converted to
168+
/// [`string`][Value::String] if it's not opaque (i.e. implements
169+
/// [`AsValue`][crate::external::AsValue]), and value returned by
170+
/// [`to_value(ValueType::String)`][crate::external::AsValue::to_value] is
171+
/// [`string`][Value::String]. [`AsValue`][crate::external::AsValue]) and the
172+
/// returned value is a [`string`][Value::String].
152173
pub fn string(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
153-
Ok(match this {
154-
Value::String(v) => Value::String(v.clone()),
155-
Value::Timestamp(t) => Value::String(t.to_rfc3339().into()),
156-
Value::Duration(v) => Value::String(format_duration(&v).into()),
157-
Value::Int(v) => Value::String(v.to_string().into()),
158-
Value::UInt(v) => Value::String(v.to_string().into()),
159-
Value::Float(v) => Value::String(v.to_string().into()),
160-
Value::Bytes(v) => Value::String(Arc::new(String::from_utf8_lossy(v.as_slice()).into())),
161-
v => return Err(ftx.error(format!("cannot convert {:?} to string", v))),
162-
})
174+
match &this {
175+
Value::String(v) => return Ok(Value::String(v.clone())),
176+
Value::Timestamp(t) => return Ok(Value::String(t.to_rfc3339().into())),
177+
Value::Duration(v) => return Ok(Value::String(format_duration(&v).into())),
178+
Value::Int(v) => return Ok(Value::String(v.to_string().into())),
179+
Value::UInt(v) => return Ok(Value::String(v.to_string().into())),
180+
Value::Float(v) => return Ok(Value::String(v.to_string().into())),
181+
Value::Bytes(v) => {
182+
return Ok(Value::String(Arc::new(
183+
String::from_utf8_lossy(v.as_slice()).into(),
184+
)))
185+
}
186+
Value::External(v) if !v.is_opaque() => {
187+
return_external_conversion!(v as String);
188+
}
189+
_ => {}
190+
}
191+
192+
Err(ftx.error(format!("cannot convert {:?} to string", this)))
163193
}
164194

165-
pub fn bytes(value: Arc<String>) -> Result<Value> {
166-
Ok(Value::Bytes(value.as_bytes().to_vec().into()))
195+
/// Converts provided value into [`bytes`][Value::Bytes].
196+
///
197+
/// # Supported Conversions
198+
///
199+
/// - [`bytes`][Value::Bytes] - will be returned as is.
200+
/// - [`string`][Value::String] - will return UTF8 bytes.
201+
/// - [`external`][Value::External] - will be converted to
202+
/// [`bytes`][Value::Bytes] if it's not opaque (i.e. implements
203+
/// [`AsValue`][crate::external::AsValue]), and value returned by
204+
/// [`to_value(ValueType::Bytes)`][crate::external::AsValue::to_value] is
205+
/// [`bytes`][Value::Bytes].
206+
pub fn bytes(Arguments(args): Arguments) -> Result<Value> {
207+
if args.len() > 1 {
208+
return Err(ExecutionError::InvalidArgumentCount {
209+
expected: 1,
210+
actual: args.len(),
211+
});
212+
}
213+
let value = match args.get(0) {
214+
Some(it) => it,
215+
None => return Err(ExecutionError::MissingArgumentOrTarget),
216+
};
217+
match value {
218+
Value::Bytes(v) => return Ok(Value::Bytes(v.clone())),
219+
Value::String(v) => return Ok(Value::Bytes(v.as_bytes().to_vec().into())),
220+
Value::External(v) if !v.is_opaque() => {
221+
return_external_conversion!(v as Bytes);
222+
}
223+
_ => {}
224+
}
225+
226+
Err(ExecutionError::UnexpectedType {
227+
got: value.type_of().to_string(),
228+
want: "`string` or `external` convertible to `bytes`".to_string(),
229+
})
167230
}
168231

169-
// Performs a type conversion on the target.
232+
/// Converts provided value into [`float`][Value::Float].
233+
///
234+
/// # Supported Conversions
235+
///
236+
/// - [`string`][Value::String] - will be parsed as `f64`.
237+
/// - [`float`][Value::Float] - will be returned as is.
238+
/// - [`int`][Value::Int] - will be cast into a float.
239+
/// - [`uint`][Value::UInt] - will be cast into a float.
240+
/// - [`external`][Value::External] - will be converted to
241+
/// [`float`][Value::Float] if it's not opaque (i.e. implements
242+
/// [`AsValue`][crate::external::AsValue]), and value returned by
243+
/// [`to_value(ValueType::Float)`][crate::external::AsValue::to_value] is
244+
/// a [`float`][Value::Float].
170245
pub fn double(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
171-
Ok(match this {
172-
Value::String(v) => v
173-
.parse::<f64>()
174-
.map(Value::Float)
175-
.map_err(|e| ftx.error(format!("string parse error: {e}")))?,
176-
Value::Float(v) => Value::Float(v),
177-
Value::Int(v) => Value::Float(v as f64),
178-
Value::UInt(v) => Value::Float(v as f64),
179-
v => return Err(ftx.error(format!("cannot convert {:?} to double", v))),
180-
})
246+
match &this {
247+
Value::Float(v) => return Ok(Value::Float(*v)),
248+
Value::String(v) => {
249+
return v
250+
.parse::<f64>()
251+
.map(Value::Float)
252+
.map_err(|e| ftx.error(format!("string parse error: {e}")))
253+
}
254+
Value::Int(v) => return Ok(Value::Float(*v as f64)),
255+
Value::UInt(v) => return Ok(Value::Float(*v as f64)),
256+
Value::External(v) if !v.is_opaque() => {
257+
return_external_conversion!(v as Float);
258+
}
259+
_ => {}
260+
}
261+
262+
Err(ftx.error(format!("cannot convert {:?} to float", this)))
181263
}
182264

183-
// Performs a type conversion on the target.
265+
/// Converts provided value into [`uint`][Value::UInt].
266+
///
267+
/// # Supported Conversions
268+
///
269+
/// - [`string`][Value::String] - will be parsed as `u64`.
270+
/// - [`float`][Value::Float] - will be cast if there's no overflow.
271+
/// - [`int`][Value::Int] - will be cast if there's no overflow.
272+
/// - [`uint`][Value::UInt] - will be returned as is.
273+
/// - [`external`][Value::External] - will be converted to [`uint`][Value::UInt]
274+
/// if it's not opaque (i.e. implements
275+
/// [`AsValue`][crate::external::AsValue]), and value returned by
276+
/// [`to_value(ValueType::Float)`][crate::external::AsValue::to_value] is
277+
/// an [`uint`][Value::UInt].
184278
pub fn uint(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
185-
Ok(match this {
186-
Value::String(v) => v
187-
.parse::<u64>()
188-
.map(Value::UInt)
189-
.map_err(|e| ftx.error(format!("string parse error: {e}")))?,
279+
match &this {
280+
Value::UInt(v) => return Ok(Value::UInt(*v)),
281+
Value::String(v) => {
282+
return v
283+
.parse::<u64>()
284+
.map(Value::UInt)
285+
.map_err(|e| ftx.error(format!("string parse error: {e}")))
286+
}
190287
Value::Float(v) => {
191-
if v > u64::MAX as f64 || v < u64::MIN as f64 {
288+
if *v > u64::MAX as f64 || *v < u64::MIN as f64 {
192289
return Err(ftx.error("unsigned integer overflow"));
193290
}
194-
Value::UInt(v as u64)
291+
return Ok(Value::UInt(*v as u64));
195292
}
196-
Value::Int(v) => Value::UInt(
197-
v.try_into()
198-
.map_err(|_| ftx.error("unsigned integer overflow"))?,
199-
),
200-
Value::UInt(v) => Value::UInt(v),
201-
v => return Err(ftx.error(format!("cannot convert {:?} to uint", v))),
202-
})
293+
Value::Int(v) => {
294+
return (*v)
295+
.try_into()
296+
.map(Value::UInt)
297+
.map_err(|_| ftx.error("unsigned integer overflow"))
298+
}
299+
Value::External(v) if !v.is_opaque() => {
300+
return_external_conversion!(v as UInt);
301+
}
302+
_ => {}
303+
}
304+
305+
Err(ftx.error(format!("cannot convert {:?} to uint", this)))
203306
}
204307

205-
// Performs a type conversion on the target.
308+
/// Converts provided value into [`int`][Value::Int].
309+
///
310+
/// # Supported Conversions
311+
///
312+
/// - [`string`][Value::String] - will be parsed as `i64`.
313+
/// - [`float`][Value::Float] - will be cast if there's no overflow.
314+
/// - [`int`][Value::Int] - will be returned as is.
315+
/// - [`uint`][Value::UInt] - will be cast if there's no overflow.
316+
/// - [`external`][Value::External] - Will be converted to [`int`][Value::Int]
317+
/// if it's not opaque (i.e. implements
318+
/// [`AsValue`][crate::external::AsValue]), and value returned by
319+
/// [`to_value(ValueType::Int)`][crate::external::AsValue::to_value] is
320+
/// an [`int`][Value::Int].
206321
pub fn int(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
207-
Ok(match this {
208-
Value::String(v) => v
209-
.parse::<i64>()
210-
.map(Value::Int)
211-
.map_err(|e| ftx.error(format!("string parse error: {e}")))?,
322+
match &this {
323+
Value::Int(v) => return Ok(Value::Int(*v)),
324+
Value::String(v) => {
325+
return v
326+
.parse::<i64>()
327+
.map(Value::Int)
328+
.map_err(|e| ftx.error(format!("string parse error: {e}")))
329+
}
212330
Value::Float(v) => {
213-
if v > i64::MAX as f64 || v < i64::MIN as f64 {
331+
if *v > i64::MAX as f64 || *v < i64::MIN as f64 {
214332
return Err(ftx.error("integer overflow"));
215333
}
216-
Value::Int(v as i64)
334+
return Ok(Value::Int(*v as i64));
217335
}
218-
Value::Int(v) => Value::Int(v),
219-
Value::UInt(v) => Value::Int(v.try_into().map_err(|_| ftx.error("integer overflow"))?),
220-
v => return Err(ftx.error(format!("cannot convert {:?} to int", v))),
221-
})
336+
Value::UInt(v) => {
337+
return TryInto::<i64>::try_into(*v)
338+
.map(Value::Int)
339+
.map_err(|_| ftx.error("integer overflow"))
340+
}
341+
Value::External(v) if !v.is_opaque() => {
342+
return_external_conversion!(v as Int);
343+
}
344+
_ => {}
345+
}
346+
347+
Err(ftx.error(format!("cannot convert {:?} to int", this)))
222348
}
223349

224350
/// Returns true if a string starts with another string.
@@ -485,12 +611,21 @@ pub fn exists_one(
485611
}
486612
}
487613

488-
/// Duration parses the provided argument into a [`Value::Duration`] value.
489-
/// The argument must be string, and must be in the format of a duration. See
490-
/// the [`parse_duration`] documentation for more information on the supported
491-
/// formats.
614+
/// Duration converts the provided argument into a
615+
/// [`duration`][Value::Duration].
616+
///
617+
/// # Supported Conversions
618+
///
619+
/// - [`duration`][Value::Duration] - will be returned as is.
620+
/// - [`string`][Value::String] - will be parsed with [`parse_duration`].
621+
/// - [`external`][Value::External] - will be converted to bytes if it's not
622+
/// opaque (i.e. implements [`AsValue`][crate::external::AsValue]), and value
623+
/// returned by
624+
/// [`to_value(ValueType::Duration)`][crate::external::AsValue::to_value] is a
625+
/// [`duration`][Value::Duration].
626+
///
627+
/// # Parsing Result Examples
492628
///
493-
/// # Examples
494629
/// - `1h` parses as 1 hour
495630
/// - `1.5h` parses as 1 hour and 30 minutes
496631
/// - `1h30m` parses as 1 hour and 30 minutes
@@ -499,17 +634,74 @@ pub fn exists_one(
499634
/// - `1.5ms` parses as 1 millisecond and 500 microseconds
500635
/// - `1ns` parses as 1 nanosecond
501636
/// - `1.5ns` parses as 1 nanosecond (sub-nanosecond durations not supported)
502-
pub fn duration(value: Arc<String>) -> Result<Value> {
503-
Ok(Value::Duration(_duration(value.as_str())?))
637+
pub fn duration(Arguments(args): Arguments) -> Result<Value> {
638+
if args.len() > 1 {
639+
return Err(ExecutionError::InvalidArgumentCount {
640+
expected: 1,
641+
actual: args.len(),
642+
});
643+
}
644+
let value = match args.get(0) {
645+
Some(it) => it,
646+
None => return Err(ExecutionError::MissingArgumentOrTarget),
647+
};
648+
match value {
649+
Value::Duration(value) => return Ok(Value::Duration(*value)),
650+
Value::String(value) => return Ok(Value::Duration(_duration(value.as_str())?)),
651+
Value::External(external) if !external.is_opaque() => {
652+
return_external_conversion!(external as Duration);
653+
}
654+
_ => {}
655+
}
656+
657+
Err(ExecutionError::UnexpectedType {
658+
got: value.type_of().to_string(),
659+
want: "`string` or `external` convertible to `duration`".to_string(),
660+
})
504661
}
505662

506-
/// Timestamp parses the provided argument into a [`Value::Timestamp`] value.
507-
/// The
508-
pub fn timestamp(value: Arc<String>) -> Result<Value> {
509-
Ok(Value::Timestamp(
510-
DateTime::parse_from_rfc3339(value.as_str())
511-
.map_err(|e| ExecutionError::function_error("timestamp", e.to_string().as_str()))?,
512-
))
663+
/// Timestamp converts the provided argument into a
664+
/// [`timestamp`][Value::Timestamp].
665+
///
666+
/// # Supported Conversions
667+
///
668+
/// - [`timestamp`][Value::Timestamp] - will be returned as is.
669+
/// - [`string`][Value::String] - will be parsed as RFC3339 formatted timestamp.
670+
/// - [`external`][Value::External] - will be converted to
671+
/// [`timestamp`][Value::Timestamp] if it's not opaque (i.e. implements
672+
/// [`AsValue`][crate::external::AsValue]), and value returned by
673+
/// [`to_value(ValueType::Timestamp)`][crate::external::AsValue::to_value] is
674+
/// a [`timestamp`][Value::Timestamp].
675+
pub fn timestamp(Arguments(args): Arguments) -> Result<Value> {
676+
if args.len() > 1 {
677+
return Err(ExecutionError::InvalidArgumentCount {
678+
expected: 1,
679+
actual: args.len(),
680+
});
681+
}
682+
let value = match args.get(0) {
683+
Some(it) => it,
684+
None => return Err(ExecutionError::MissingArgumentOrTarget),
685+
};
686+
match value {
687+
Value::Timestamp(value) => return Ok(Value::Timestamp(*value)),
688+
Value::String(value) => {
689+
return Ok(Value::Timestamp(
690+
DateTime::parse_from_rfc3339(value.as_str()).map_err(|e| {
691+
ExecutionError::function_error("timestamp", e.to_string().as_str())
692+
})?,
693+
))
694+
}
695+
Value::External(external) if !external.is_opaque() => {
696+
return_external_conversion!(external as Timestamp);
697+
}
698+
_ => {}
699+
}
700+
701+
Err(ExecutionError::UnexpectedType {
702+
got: value.type_of().to_string(),
703+
want: "`string` or `external` convertible to `timestamp`".to_string(),
704+
})
513705
}
514706

515707
pub fn max(Arguments(args): Arguments) -> Result<Value> {

0 commit comments

Comments
 (0)