1- //! The EQL types as Stack Encrypt's resolver.
2- //!
3- //! Stack Encrypt's dynamic interface runs a plan that arrives as data. A
4- //! field of that plan may name an EQL type, such as `"TextEq"`. Stack
5- //! Encrypt does not depend on this crate, so a program gives it the EQL
6- //! types through a [`TargetResolver`]. [`EqlTargets`] is that resolver. It
7- //! holds every type in [`targets`](super::targets::targets), and runs a
8- //! named type through [`encrypt_with`], [`decrypt`] and [`query`].
9- //!
10- //! ```
11- //! use eql_bindings::encryption::EqlTargets;
12- //! use stack_encrypt::dynamic::TargetResolver;
13- //!
14- //! let text_eq = EqlTargets.resolve("TextEq")?;
15- //! assert_eq!(text_eq.sql_domain, "public.eql_v4_text_eq");
16- //! assert_eq!(text_eq.indexes, ["eq"]);
17- //! # Ok::<(), stack_encrypt::dynamic::TargetError>(())
18- //! ```
19- //!
20- //! Pass `&EqlTargets` wherever the dynamic interface takes a resolver, such
21- //! as `stack_encrypt::dynamic::record::plan_with`.
1+ //! The EQL types as Stack Encrypt's resolver: [`EqlTargets`], and the two
2+ //! conversions it is built from. The module is private, so the type's own
3+ //! documentation is what a reader sees.
224
235use stack_encrypt:: dynamic:: { self , TargetDescriptor , TargetOptions , TargetResolver , ValueFrame } ;
246use stack_encrypt:: target:: IndexSpec ;
@@ -28,6 +10,26 @@ use super::targets::{self, decrypt, encrypt_with, query, Target, TargetError};
2810
2911/// Every EQL type this crate defines, as Stack Encrypt's resolver.
3012///
13+ /// Stack Encrypt's dynamic interface runs a plan that arrives as data. A
14+ /// field of that plan may name an EQL type, such as `"TextEq"`. Stack
15+ /// Encrypt does not depend on this crate, so a program gives it the EQL
16+ /// types through a [`TargetResolver`]. `EqlTargets` is that resolver. It
17+ /// holds every type in [`targets`](super::targets::targets), and runs a
18+ /// named type through [`encrypt_with`], [`decrypt`] and [`query`].
19+ ///
20+ /// Pass `&EqlTargets` wherever the dynamic interface takes a resolver, such
21+ /// as `stack_encrypt::dynamic::record::plan_with`.
22+ ///
23+ /// ```
24+ /// use eql_bindings::encryption::EqlTargets;
25+ /// use stack_encrypt::dynamic::TargetResolver;
26+ ///
27+ /// let text_eq = EqlTargets.resolve("TextEq")?;
28+ /// assert_eq!(text_eq.sql_domain, "public.eql_v4_text_eq");
29+ /// assert_eq!(text_eq.indexes, ["eq"]);
30+ /// # Ok::<(), stack_encrypt::dynamic::TargetError>(())
31+ /// ```
32+ ///
3133/// The resolver sees a type's name and a field's label. It never sees the
3234/// field's name, so a refusal leaves the field name empty and the engine
3335/// fills it in.
@@ -160,7 +162,15 @@ impl From<TargetError> for dynamic::TargetError {
160162 target : target. to_owned ( ) ,
161163 reason : stack_encrypt:: diagnostic:: describe_json_error ( & source) ,
162164 } ,
163- other => Self :: Other ( Box :: new ( other) ) ,
165+ // The engine refuses each of these before it calls the
166+ // resolver: it derives a document's queries itself, asks a query
167+ // only through an index the type carries, and refuses options on
168+ // a type with no JSON index when it builds the plan. Reaching
169+ // one is the engine's failure, not the caller's input. Each is
170+ // named, so a variant added later must be placed by hand.
171+ error @ ( TargetError :: DocumentQuery { .. }
172+ | TargetError :: NoIndex { .. }
173+ | TargetError :: Options { .. } ) => Self :: Other ( Box :: new ( error) ) ,
164174 }
165175 }
166176}
@@ -294,16 +304,66 @@ mod tests {
294304 dynamic:: TargetError :: from( TargetError :: NoQuery { name: "Text" } ) ,
295305 dynamic:: TargetError :: NoQuery { name } if name == "Text"
296306 ) ) ;
297- // The engine asks a type's query only through an index the type
298- // carries. One it does not carry is the engine's failure, not the
299- // caller's input.
300307 assert ! ( matches!(
301- dynamic:: TargetError :: from( TargetError :: NoIndex {
302- name: "TextEq " ,
303- index : "ope " ,
308+ dynamic:: TargetError :: from( TargetError :: Unproducible {
309+ name: "Json " ,
310+ reason : "not yet " ,
304311 } ) ,
305- dynamic:: TargetError :: Other ( _)
312+ dynamic:: TargetError :: Unproducible { name, reason }
313+ if name == "Json" && reason == "not yet"
314+ ) ) ;
315+ // A value that is not a document keeps the engine's code, and no
316+ // byte of the value.
317+ let Err ( source) = dynamic:: read_json ( & ValueFrame :: of ( r#"{"secret": "# ) . unwrap ( ) ) else {
318+ panic ! ( "the text is not a JSON document" ) ;
319+ } ;
320+ let code = source. code ( ) ;
321+ let error = dynamic:: TargetError :: from ( TargetError :: Json {
322+ target : "JsonSearch" ,
323+ source,
324+ } ) ;
325+ assert ! ( matches!(
326+ & error,
327+ dynamic:: TargetError :: Json { name, target, reason }
328+ if name. is_empty( ) && target == "JsonSearch" && * reason == code
306329 ) ) ;
330+ let shown = format ! ( "{error} {:?}" , stack_encrypt:: ErrorPayload :: payload( & error) ) ;
331+ assert ! ( !shown. contains( "secret" ) , "{shown}" ) ;
332+ // The engine refuses these three before it calls the resolver, so
333+ // reaching one is the engine's failure, not the caller's input.
334+ for refusal in [
335+ TargetError :: NoIndex {
336+ name : "TextEq" ,
337+ index : "ope" ,
338+ } ,
339+ TargetError :: DocumentQuery { name : "JsonSearch" } ,
340+ TargetError :: Options { name : "TextEq" } ,
341+ ] {
342+ assert ! ( matches!(
343+ dynamic:: TargetError :: from( refusal) ,
344+ dynamic:: TargetError :: Other ( _)
345+ ) ) ;
346+ }
347+ }
348+
349+ /// Why `Options` converts to `Other`: the engine refuses options on a
350+ /// type with no JSON index when it builds the plan, so the resolver is
351+ /// never asked to encrypt under them.
352+ #[ test]
353+ fn the_engine_refuses_options_on_a_type_without_a_json_index ( ) {
354+ let wire = serde_json:: json!( {
355+ "email" : {
356+ "context" : [ "users" , "email" ] ,
357+ "target" : { "name" : "TextEq" , "options" : { "ordered" : [ ] } }
358+ }
359+ } ) ;
360+ let frame = dynamic:: frame:: serde:: to_vec ( & wire) . expect ( "plan frame" ) ;
361+ let error = record:: plan_with ( frame. as_protected_ref ( ) , & EqlTargets ) . unwrap_err ( ) ;
362+ assert_eq ! (
363+ error. reason( ) ,
364+ Some ( dynamic:: Reason :: TargetOptions ) ,
365+ "{error:?}"
366+ ) ;
307367 }
308368
309369 /// `serde_json` quotes the value it refused, and a stored EQL value
0 commit comments