use std::iter::Empty;
use core::hash::BuildHasher;
use hashbrown::{DefaultHashBuilder, HashTable, hash_table::Entry};
use jaq_core::{
Compiler, Ctx, Filter, Native, RcIter,
load::{Arena, File, Loader},
};
use jaq_json::Val;
use crate::SdkError;
pub struct JqSelection {
arena: Arena,
inputs: RcIter<Empty<Result<Val, String>>>,
selection_cache: HashTable<(String, usize)>,
filters: Vec<Filter<Native<Val>>>,
}
impl Default for JqSelection {
fn default() -> Self {
Self {
arena: Arena::default(),
inputs: RcIter::new(core::iter::empty()),
selection_cache: HashTable::new(),
filters: Vec::new(),
}
}
}
impl JqSelection {
pub fn new() -> Self {
Self::default()
}
pub fn select(
&mut self,
selection: &str,
data: serde_json::Value,
) -> Result<impl Iterator<Item = Result<serde_json::Value, SdkError>> + '_, SdkError> {
let hasher = DefaultHashBuilder::default();
let hash = hasher.hash_one(selection);
let hasher = |val: &(String, usize)| hasher.hash_one(&val.0);
let idx = match self
.selection_cache
.entry(hash, |(key, _)| key.as_str() == selection, hasher)
{
Entry::Occupied(entry) => entry.get().1,
Entry::Vacant(vacant_entry) => {
let program = File {
code: selection,
path: (),
};
let loader = Loader::new(jaq_std::defs().chain(jaq_json::defs()));
let modules = loader.load(&self.arena, program).map_err(|e| {
let error = e.first().map(|e| e.0.code).unwrap_or_default();
format!("The selection is not valid jq syntax: `{error}`")
})?;
let filter = Compiler::default()
.with_funs(jaq_std::funs().chain(jaq_json::funs()))
.compile(modules)
.map_err(|e| {
let error = e.first().map(|e| e.0.code).unwrap_or_default();
format!("The selection is not valid jq syntax: `{error}`")
})?;
self.filters.push(filter);
let index = self.filters.len() - 1;
vacant_entry.insert((selection.to_string(), index));
index
}
};
let filter = &self.filters[idx];
let filtered = filter.run((Ctx::new([], &self.inputs), Val::from(data)));
Ok(filtered.map(|v| match v {
Ok(val) => Ok(serde_json::Value::from(val)),
Err(e) => Err(format!("{e}").into()),
}))
}
}