grafbase_sdk/
jq_selection.rs1use std::iter::Empty;
22
23use core::hash::BuildHasher;
24use hashbrown::{DefaultHashBuilder, HashTable, hash_table::Entry};
25use jaq_core::{
26 Compiler, Ctx, Filter, Native, RcIter,
27 load::{Arena, File, Loader},
28};
29use jaq_json::Val;
30
31use crate::SdkError;
32
33pub struct JqSelection {
40 arena: Arena,
41 inputs: RcIter<Empty<Result<Val, String>>>,
43 selection_cache: HashTable<(String, usize)>,
53 filters: Vec<Filter<Native<Val>>>,
54}
55
56impl Default for JqSelection {
57 fn default() -> Self {
58 Self {
59 arena: Arena::default(),
60 inputs: RcIter::new(core::iter::empty()),
61 selection_cache: HashTable::new(),
62 filters: Vec::new(),
63 }
64 }
65}
66
67impl JqSelection {
68 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn select(
81 &mut self,
82 selection: &str,
83 data: serde_json::Value,
84 ) -> Result<impl Iterator<Item = Result<serde_json::Value, SdkError>> + '_, SdkError> {
85 let hasher = DefaultHashBuilder::default();
86 let hash = hasher.hash_one(selection);
87 let hasher = |val: &(String, usize)| hasher.hash_one(&val.0);
88
89 let idx = match self
90 .selection_cache
91 .entry(hash, |(key, _)| key.as_str() == selection, hasher)
92 {
93 Entry::Occupied(entry) => entry.get().1,
94 Entry::Vacant(vacant_entry) => {
95 let program = File {
96 code: selection,
97 path: (),
98 };
99
100 let loader = Loader::new(jaq_std::defs().chain(jaq_json::defs()));
101
102 let modules = loader.load(&self.arena, program).map_err(|e| {
103 let error = e.first().map(|e| e.0.code).unwrap_or_default();
104 format!("The selection is not valid jq syntax: `{error}`")
105 })?;
106
107 let filter = Compiler::default()
108 .with_funs(jaq_std::funs().chain(jaq_json::funs()))
109 .compile(modules)
110 .map_err(|e| {
111 let error = e.first().map(|e| e.0.code).unwrap_or_default();
112 format!("The selection is not valid jq syntax: `{error}`")
113 })?;
114
115 self.filters.push(filter);
116
117 let index = self.filters.len() - 1;
118 vacant_entry.insert((selection.to_string(), index));
119
120 index
121 }
122 };
123
124 let filter = &self.filters[idx];
125 let filtered = filter.run((Ctx::new([], &self.inputs), Val::from(data)));
126
127 Ok(filtered.map(|v| match v {
128 Ok(val) => Ok(serde_json::Value::from(val)),
129 Err(e) => Err(format!("{e}").into()),
130 }))
131 }
132}