use serde_json::Value;
use std::cmp;
use std::convert::TryInto;
use crate::error::Error;
use crate::js_op;
use crate::NULL;
pub fn cat(items: &Vec<&Value>) -> Result<Value, Error> {
let mut rv = String::from("");
items
.into_iter()
.map(|i| match i {
Value::String(i_string) => Ok(i_string.clone()),
_ => Ok(js_op::to_string(i)),
})
.fold(Ok(&mut rv), |acc: Result<&mut String, Error>, i| {
let rv = acc?;
rv.push_str(&i?);
Ok(rv)
})?;
Ok(Value::String(rv))
}
pub fn substr(items: &Vec<&Value>) -> Result<Value, Error> {
let (string_arg, idx_arg) = (items[0], items[1]);
let limit_opt: Option<&Value>;
if items.len() > 2 {
limit_opt = Some(items[2]);
} else {
limit_opt = None;
}
let string = match string_arg {
Value::String(s) => s,
_ => {
return Err(Error::InvalidArgument {
value: string_arg.clone(),
operation: "substr".into(),
reason: "First argument to substr must be a string".into(),
})
}
};
let idx = match idx_arg {
Value::Number(n) => {
if let Some(int) = n.as_i64() {
int
} else {
return Err(Error::InvalidArgument {
value: idx_arg.clone(),
operation: "substr".into(),
reason: "Second argument to substr must be an integer".into(),
});
}
}
_ => {
return Err(Error::InvalidArgument {
value: idx_arg.clone(),
operation: "substr".into(),
reason: "Second argument to substr must be a number".into(),
})
}
};
let limit = limit_opt
.map(|limit_arg| match limit_arg {
Value::Number(n) => {
if let Some(int) = n.as_i64() {
Ok(int)
} else {
Err(Error::InvalidArgument {
value: limit_arg.clone(),
operation: "substr".into(),
reason: "Optional third argument to substr must be an integer".into(),
})
}
}
_ => Err(Error::InvalidArgument {
value: limit_arg.clone(),
operation: "substr".into(),
reason: "Optional third argument to substr must be a number".into(),
}),
})
.transpose()?;
let string_len = string.len();
let idx_abs: usize = idx.abs().try_into().map_err(|e| Error::InvalidArgument {
value: idx_arg.clone(),
operation: "substr".into(),
reason: format!(
"The number {} is too large to index strings on this system",
e
),
})?;
let start_idx = match idx {
idx if idx < 0 => string_len.checked_sub(idx_abs).unwrap_or(0),
_ => cmp::min(string_len, idx_abs),
};
let end_idx = match limit {
None => string_len,
Some(l) => {
let limit_abs: usize = l.abs().try_into().map_err(|e| Error::InvalidArgument {
value: limit_opt.or(Some(&NULL)).map(|v| v.clone()).unwrap(),
operation: "substr".into(),
reason: format!(
"The number {} is too large to index strings on this system",
e
),
})?;
match l {
l if l < 0 => string_len.checked_sub(limit_abs).unwrap_or(0),
_ => cmp::min(
string_len,
start_idx.checked_add(limit_abs).unwrap_or(string_len),
),
}
}
};
let count_in_substr = end_idx.checked_sub(start_idx).unwrap_or(0);
Ok(Value::String(
string
.chars()
.skip(start_idx)
.take(count_in_substr)
.collect(),
))
}