use crate::{
NodeId, PagingMode, ParamError, ParamNames, ParamParser, PredicateArena, PredicateNode,
Projection, QueryBuilder, QueryInput, QueryPolicy, RawParams, SearchEntity, SortOrder,
UserQuery,
};
impl<E: SearchEntity> UserQuery<E> {
pub fn to_rpc(&self, policy: &QueryPolicy) -> Result<RawParams, ParamError> {
self.to_rpc_with_names(policy, policy.names())
}
pub fn to_rpc_with_names(
&self,
policy: &QueryPolicy,
names: &ParamNames,
) -> Result<RawParams, ParamError> {
self.into_user_query(policy)?;
let target = policy
.clone()
.with_param_names(names.clone())
.map_err(|error| ParamError::ConflictingParameters(error.to_string()))?;
let mut entries = Vec::new();
if let Some(root) = self.predicate().root() {
let expression = encode_node::<E>(self.predicate(), root, policy, &mut entries, None)?;
entries.push((names.group_expr.clone(), expression));
}
if let Projection::Fields(fields) = self.projection() {
entries.push((
names.only_select.clone(),
fields
.iter()
.map(|id| E::META.field(*id).expect("validated field").public_name)
.collect::<Vec<_>>()
.join(","),
));
}
for length in 0..=self.order().len() {
let mut order = if length == 0 {
E::META
.default_order
.iter()
.copied()
.map(Into::into)
.collect::<Vec<_>>()
} else {
self.order()[..length].to_vec()
};
for stable in E::META.stable_order {
if !order.iter().any(|item| item.field == stable.field) {
order.push((*stable).into());
}
}
if order == self.order() {
if length > 0 {
entries.push((
names.order_by.clone(),
self.order()[..length]
.iter()
.map(|order| {
format!(
"{}:{}",
E::META
.field(order.field)
.expect("validated order")
.public_name,
if order.direction == SortOrder::Asc {
"asc"
} else {
"desc"
}
)
})
.collect::<Vec<_>>()
.join(","),
));
}
break;
}
}
let paging = self.paging();
if self.is_forced_empty() {
entries.push((
names.size.clone(),
(u64::from(
policy
.limits()
.max_page_size
.min(E::META.limits.max_page_size),
) + 1)
.to_string(),
));
} else {
if paging.explicit_size || paging.size != 15 {
entries.push((names.size.clone(), paging.size.to_string()));
}
match &paging.mode {
PagingMode::Page { page } if paging.explicit_page => {
entries.push((names.page.clone(), page.to_string()));
}
PagingMode::Offset => {
entries.push((names.offset.clone(), paging.offset.to_string()));
}
PagingMode::Cursor(cursor) => {
let token = match cursor {
None => String::new(),
Some(cursor) => policy
.cursor_codec()
.ok_or_else(|| invalid("cursor codec missing"))?
.encode::<E>(self.order(), cursor.values())?,
};
entries.push((names.cursor.clone(), token));
}
PagingMode::Page { .. } => {}
}
}
let raw = RawParams::from_entries(entries, target.limits())?;
let decoded = ParamParser::new(&target)?.parse::<E>(&raw)?;
if signature(self.predicate()) != signature(decoded.predicate())
|| self.order() != decoded.order()
|| self.projection() != decoded.projection()
|| self.paging() != decoded.paging()
|| self.is_forced_empty() != decoded.is_forced_empty()
{
return Err(invalid("RPC normalization changes query semantics"));
}
Ok(raw)
}
}
impl<E: SearchEntity> QueryBuilder<'_, E> {
pub fn build_for_rpc(self) -> Result<RawParams, ParamError> {
let policy = self.policy;
self.build()?.to_rpc(policy)
}
pub fn build_for_rpc_with_names(self, names: &ParamNames) -> Result<RawParams, ParamError> {
let policy = self.policy;
self.build()?.to_rpc_with_names(policy, names)
}
}
fn encode_node<E: SearchEntity>(
arena: &PredicateArena,
id: NodeId,
policy: &QueryPolicy,
entries: &mut Vec<(String, String)>,
parent_is_and: Option<bool>,
) -> Result<String, ParamError> {
match arena.node(id).expect("validated predicate") {
PredicateNode::Condition(condition) => {
let group = format!("q{}", id.0);
let field = E::META.field(condition.field).expect("validated field");
let key = format!("{group}.{}", field.public_name);
entries.push((
format!("{key}-op"),
condition.operator.short_name().to_owned(),
));
if condition.ignore_case {
entries.push((format!("{key}-ic"), "true".to_owned()));
}
for value in &condition.values {
let input_field = crate::FieldMeta {
value_type: condition.operator.input_type(field.value_type),
..*field
};
let text = if let Some(codec) = policy.codec_for::<E>(field) {
let text = codec.format_input(&input_field, value).map_err(|reason| {
ParamError::InvalidValue {
field: field.public_name.to_owned(),
reason,
}
})?;
if codec.parse(&input_field, &text).ok().as_ref() != Some(value) {
return Err(invalid("codec has no lossless RPC representation"));
}
text
} else {
value.to_external_text()
};
entries.push((key.clone(), text));
}
Ok(group)
}
PredicateNode::And(children) | PredicateNode::Or(children) => {
let and = matches!(arena.node(id), Some(PredicateNode::And(_)));
let separator = if and { "&" } else { "|" };
let expressions = children
.iter()
.map(|child| encode_node::<E>(arena, *child, policy, entries, Some(and)))
.collect::<Result<Vec<_>, _>>()?;
let expression = expressions.join(separator);
Ok(if !and && parent_is_and == Some(true) {
format!("({expression})")
} else {
expression
})
}
}
}
#[derive(PartialEq)]
enum Expression {
Condition(crate::Condition),
Junction(bool, Vec<Self>),
}
fn signature(arena: &PredicateArena) -> Option<Expression> {
fn node(arena: &PredicateArena, id: NodeId) -> Expression {
match arena.node(id).expect("validated predicate") {
PredicateNode::Condition(condition) => Expression::Condition(condition.clone()),
PredicateNode::And(children) | PredicateNode::Or(children) => {
let and = matches!(arena.node(id), Some(PredicateNode::And(_)));
let mut output = Vec::new();
for child in children {
match node(arena, *child) {
Expression::Junction(kind, nested) if kind == and => output.extend(nested),
child => output.push(child),
}
}
if output.len() == 1 {
output.remove(0)
} else {
Expression::Junction(and, output)
}
}
}
}
arena.root().map(|root| node(arena, root))
}
fn invalid(reason: &str) -> ParamError {
ParamError::InvalidValue {
field: "rpc".to_owned(),
reason: reason.to_owned(),
}
}