use ciborium::Value as CborValue;
use vantage_core::error;
use vantage_dataset::traits::Result;
use vantage_expressions::Expression;
use super::{FilterStrategy, Order, PaginationMode, RestApi};
impl RestApi {
pub(super) fn endpoint_url(
&self,
table_name: &str,
conditions: &[&Expression<CborValue>],
) -> Result<(String, Vec<usize>)> {
if table_name.matches('{').count() != crate::rest::placeholders(table_name).count() {
return Err(error!(
"Unclosed `{` in table name URI template",
table_name = table_name
));
}
let mut consumed = Vec::new();
let mut path = table_name.to_string();
for placeholder in crate::rest::placeholders(table_name) {
let (idx, value) = conditions
.iter()
.enumerate()
.find_map(|(i, cond)| {
if consumed.contains(&i) {
return None;
}
let (field, value) = crate::condition_to_query_param(cond)?;
(field == placeholder).then_some((i, value))
})
.ok_or_else(|| {
error!(
"No eq-condition provided for URI placeholder",
placeholder = placeholder,
table_name = table_name
)
})?;
consumed.push(idx);
path = path.replacen(&format!("{{{placeholder}}}"), &urlencode(&value), 1);
}
Ok((format!("{}/{}", self.base_url, path), consumed))
}
pub(super) fn split_filters(
&self,
conds: &[&Expression<CborValue>],
consumed: Vec<usize>,
) -> (Vec<usize>, Vec<(String, String)>) {
if self.filter_strategy == FilterStrategy::Client {
let filters = conds
.iter()
.enumerate()
.filter(|(i, _)| !consumed.contains(i))
.filter_map(|(_, c)| crate::condition_to_query_param(c))
.collect();
((0..conds.len()).collect(), filters)
} else {
(consumed, Vec::new())
}
}
pub(super) fn build_query_string(
&self,
window: Option<(i64, i64)>,
conditions: &[&Expression<CborValue>],
consumed: &[usize],
order: Order<'_>,
) -> String {
let mut params: Vec<(String, String)> = Vec::new();
if !self.no_pagination
&& let Some((offset, limit)) = window
{
let offset = offset.max(0);
let limit = limit.max(1);
let page_value = match self.pagination.mode {
PaginationMode::Skip => offset.to_string(),
PaginationMode::Page => (offset / limit + 1).to_string(),
};
params.push((self.pagination.page.clone(), page_value));
params.push((self.pagination.limit.clone(), limit.to_string()));
}
if let (Some(spec), Some((field, dir))) = (&self.ordering, order) {
params.push((spec.param.clone(), spec.value(field, dir)));
}
for (i, cond) in conditions.iter().enumerate() {
if consumed.contains(&i) {
continue;
}
if let Some((field, value)) = crate::condition_to_query_param(cond) {
params.push((field, value));
}
}
if params.is_empty() {
return String::new();
}
let mut s = String::from("?");
for (i, (k, v)) in params.iter().enumerate() {
if i > 0 {
s.push('&');
}
s.push_str(&urlencode(k));
s.push('=');
s.push_str(&urlencode(v));
}
s
}
}
fn urlencode(s: &str) -> String {
urlencoding::encode(s).into_owned()
}
pub(super) fn join_query(endpoint: &str, query: &str) -> String {
match query.strip_prefix('?') {
Some(rest) if endpoint.contains('?') => format!("{endpoint}&{rest}"),
_ => format!("{endpoint}{query}"),
}
}