44//! are code spans, not links: a doc build of either feature set resolves.
55//!
66//! Two builds of this crate, one resolver each (ADR-0007, amended
7- //! 2026-10-06). With the `eql` feature, [`Resolver`] is `EqlTargets`,
8- //! which installs `eql-bindings`' by-name dispatch
9- //! (`eql_bindings::encryption::targets`): `se_targets` lists every EQL type
10- //! the catalog has, and a plan field naming a producible one (`TextEq`) is
11- //! run through that type's own `EncryptFrom` / `DecryptInto`, in the same
12- //! ZeroKMS request as the rest of the record. Without it, [`Resolver`] is
13- //! the engine's `NoTargets` (`stack_encrypt::dynamic::NoTargets`):
14- //! `se_targets` lists nothing, and a plan that
15- //! names a target is refused when it is parsed — at `se_plan_check`, before
16- //! any value crosses.
7+ //! 2026-10-06). With the `eql` feature, [`Resolver`] is `eql-bindings`'
8+ //! own resolver (`eql_bindings::encryption::EqlTargets`): `se_targets`
9+ //! lists every EQL type the catalog has, and a plan field naming a
10+ //! producible one (`TextEq`) is run through that type's own plan, in the
11+ //! same ZeroKMS request as the rest of the record. Without it,
12+ //! [`Resolver`] is the engine's `NoTargets`
13+ //! (`stack_encrypt::dynamic::NoTargets`): `se_targets` lists nothing, and
14+ //! a plan that names a target is refused when it is parsed — at
15+ //! `se_plan_check`, before any value crosses.
1716//!
1817//! The Go module embeds both: `encrypt` the build without EQL types,
1918//! `encrypt/eql` the build with them, registered on import, so a program
2019//! that names an EQL type links the build that has it.
2120
22- #[ cfg( not( feature = "eql" ) ) ]
23- use stack_encrypt:: dynamic:: NoTargets ;
24- #[ cfg( feature = "eql" ) ]
25- use stack_encrypt:: dynamic:: { TargetDescriptor , TargetError , TargetResolver } ;
26-
2721/// The resolver this build installs.
2822#[ cfg( feature = "eql" ) ]
29- pub type Resolver = EqlTargets ;
23+ pub type Resolver = eql_bindings :: encryption :: EqlTargets ;
3024/// The resolver this build installs.
3125#[ cfg( not( feature = "eql" ) ) ]
32- pub type Resolver = NoTargets ;
26+ pub type Resolver = stack_encrypt :: dynamic :: NoTargets ;
3327
3428/// This build's resolver, for the record operations.
3529pub fn resolver ( ) -> Resolver {
@@ -39,150 +33,11 @@ pub fn resolver() -> Resolver {
3933/// Whether this build holds the EQL types.
4034pub const HOLDS_EQL : bool = cfg ! ( feature = "eql" ) ;
4135
42- /// `eql-bindings`' by-name dispatch as the engine's resolver. The resolver
43- /// sees a type name and a field's label; the field's name is the
44- /// lowering's to add to a refusal.
45- #[ cfg( feature = "eql" ) ]
46- #[ derive( Clone , Copy , Debug , Default , PartialEq , Eq ) ]
47- pub struct EqlTargets ;
48-
49- #[ cfg( feature = "eql" ) ]
50- impl TargetResolver for EqlTargets {
51- fn targets ( & self ) -> Vec < TargetDescriptor > {
52- eql_bindings:: encryption:: targets:: targets ( )
53- . iter ( )
54- . map ( describe)
55- . collect ( )
56- }
57-
58- fn encrypt < ' a , K : ' static > (
59- & self ,
60- name : & str ,
61- options : Option < & stack_encrypt:: dynamic:: TargetOptions > ,
62- keyset : & ' a stack_encrypt:: KeysetCipher < ' _ , K > ,
63- label : & stack_encrypt:: Label ,
64- plaintext : stack_encrypt:: dynamic:: ValueFrame ,
65- ) -> Result < stack_encrypt:: Pending < ' a , Vec < u8 > , K > , TargetError > {
66- eql_bindings:: encryption:: targets:: encrypt_with ( name, options, keyset, label, plaintext)
67- . map_err ( convert)
68- }
69-
70- fn decrypt < ' a , K : ' static > (
71- & self ,
72- name : & str ,
73- cipher : & ' a stack_encrypt:: StackCipher < K > ,
74- label : & stack_encrypt:: Label ,
75- stored : & [ u8 ] ,
76- ) -> Result < stack_encrypt:: Pending < ' a , stack_encrypt:: dynamic:: ValueFrame , K > , TargetError >
77- {
78- eql_bindings:: encryption:: targets:: decrypt ( name, cipher, label, stored) . map_err ( convert)
79- }
80-
81- fn query < ' a , K : ' static > (
82- & self ,
83- name : & str ,
84- index : & stack_encrypt:: target:: IndexSpec ,
85- keyset : & ' a stack_encrypt:: KeysetCipher < ' _ , K > ,
86- label : & stack_encrypt:: Label ,
87- plaintext : stack_encrypt:: dynamic:: ValueFrame ,
88- ) -> Result < stack_encrypt:: Pending < ' a , Vec < u8 > , K > , TargetError > {
89- eql_bindings:: encryption:: targets:: query ( name, index, keyset, label, plaintext)
90- . map_err ( convert)
91- }
92- }
93-
94- /// An `eql-bindings` table row as the engine's descriptor. The two spell
95- /// one wire format; `eql-bindings` tests its serde form against this
96- /// crate's `to_value`.
97- #[ cfg( feature = "eql" ) ]
98- fn describe ( target : & eql_bindings:: encryption:: targets:: Target ) -> TargetDescriptor {
99- let descriptor = TargetDescriptor :: new (
100- target. name ,
101- target. family ,
102- target. suffix ,
103- target. plaintext_kind ( ) ,
104- target. sql_domain ,
105- target. indexes . iter ( ) . map ( |key| ( * key) . to_owned ( ) ) . collect ( ) ,
106- target. query . map ( str:: to_owned) ,
107- target. query_sql_domain . map ( str:: to_owned) ,
108- target. producible ,
109- target. reason . map ( str:: to_owned) ,
110- ) ;
111- // A type with a match index answers `matches` through its twin only
112- // when the engine knows the options its filter is built under.
113- let descriptor = match target. match_options ( ) {
114- Some ( options) => descriptor. with_match_options ( options) ,
115- None => descriptor,
116- } ;
117- // A document's orderings at a path bind as its leaf operand, its
118- // element predicate (`element_match`) as its elements operand, and its
119- // compiled predicate (`match`) as its predicate operand.
120- let descriptor = match target. leaf_query_sql_domain ( ) {
121- Some ( domain) => descriptor. with_leaf_query_sql_domain ( domain) ,
122- None => descriptor,
123- } ;
124- let descriptor = match target. elements_query_sql_domain ( ) {
125- Some ( domain) => descriptor. with_elements_query_sql_domain ( domain) ,
126- None => descriptor,
127- } ;
128- match target. predicate_query_sql_domain ( ) {
129- Some ( domain) => descriptor. with_predicate_query_sql_domain ( domain) ,
130- None => descriptor,
131- }
132- }
133-
134- /// An `eql-bindings` refusal as the engine's. The field name is left empty:
135- /// the lowering fills it in, since the resolver never sees it.
136- #[ cfg( feature = "eql" ) ]
137- fn convert ( error : eql_bindings:: encryption:: targets:: TargetError ) -> TargetError {
138- use eql_bindings:: encryption:: targets:: TargetError as Eql ;
139- match error {
140- Eql :: Unknown { name } => TargetError :: Unknown { name } ,
141- Eql :: Unproducible { name, reason } => TargetError :: Unproducible {
142- name : name. to_owned ( ) ,
143- reason : reason. to_owned ( ) ,
144- } ,
145- Eql :: NoQuery { name } => TargetError :: NoQuery {
146- name : name. to_owned ( ) ,
147- } ,
148- Eql :: Context { label } => TargetError :: Column {
149- name : String :: new ( ) ,
150- label,
151- reason : "an EQL column is a two-segment label, table and column" . to_owned ( ) ,
152- } ,
153- Eql :: Plaintext {
154- target,
155- expected,
156- found,
157- } => TargetError :: Plaintext {
158- name : String :: new ( ) ,
159- target : target. to_owned ( ) ,
160- expected : Some ( expected) ,
161- found,
162- } ,
163- // The engine's code for what was wrong with the document, never
164- // the document.
165- Eql :: Json { target, source } => TargetError :: Json {
166- name : String :: new ( ) ,
167- target : target. to_owned ( ) ,
168- reason : source. code ( ) ,
169- } ,
170- // The parser's kind and position, never its message: serde_json
171- // quotes the input it refused, and the input is stored ciphertext.
172- Eql :: Stored { target, source } => TargetError :: Stored {
173- name : String :: new ( ) ,
174- target : target. to_owned ( ) ,
175- reason : stack_encrypt:: diagnostic:: describe_json_error ( & source) ,
176- } ,
177- other => TargetError :: Other ( Box :: new ( other) ) ,
178- }
179- }
180-
18136#[ cfg( test) ]
18237mod tests {
18338 use super :: * ;
18439 #[ cfg( feature = "eql" ) ]
185- use stack_encrypt:: AsProtectedRef as _;
40+ use stack_encrypt:: dynamic :: { TargetError , TargetResolver as _} ;
18641
18742 #[ cfg( not( feature = "eql" ) ) ]
18843 #[ test]
@@ -279,112 +134,4 @@ mod tests {
279134 Err ( TargetError :: Unknown { .. } )
280135 ) ) ;
281136 }
282-
283- /// The JSON document's query domains this guest names, and so the JSON
284- /// operators the Go SDK is offered: the leaf operand, for the orderings
285- /// at a path; the element predicate, for `element_match`; and the
286- /// compiled predicate, for `match`. Every operator a field lists is one
287- /// the Go SDK's operator list (`parseOperatorName`) and stashgen read.
288- #[ cfg( feature = "eql" ) ]
289- #[ test]
290- fn a_document_names_the_leaf_element_and_predicate_domains ( ) {
291- use stack_encrypt:: dynamic:: { self , record, Op } ;
292- let targets = resolver ( ) . targets ( ) ;
293- let document = targets
294- . iter ( )
295- . find ( |t| t. name == "JsonSearch" )
296- . expect ( "JsonSearch" ) ;
297- assert_eq ! (
298- document. leaf_query_sql_domain. as_deref( ) ,
299- Some ( "eql_v4.query_json_leaf" )
300- ) ;
301- assert_eq ! (
302- document. elements_query_sql_domain. as_deref( ) ,
303- Some ( "eql_v4.query_json_elements" )
304- ) ;
305- assert_eq ! (
306- document. predicate_query_sql_domain. as_deref( ) ,
307- Some ( "eql_v4.query_json_predicate" )
308- ) ;
309- assert ! (
310- document. operators( ) . contains( & Op :: Match ) ,
311- "the type lists match: {:?}" ,
312- document. operators( )
313- ) ;
314- // The operators a field answers: the orderings and `element_match`
315- // only where it orders a path, and `match` on every field.
316- let field_operators = |ordered : & [ & str ] | {
317- let wire = serde_json:: json!( {
318- "profile" : {
319- "context" : [ "users" , "profile" ] ,
320- "type" : "json" ,
321- "null" : "sql" ,
322- "target" : { "name" : "JsonSearch" , "options" : { "ordered" : ordered} }
323- }
324- } ) ;
325- let frame = dynamic:: frame:: serde:: to_vec ( & wire) . expect ( "plan frame" ) ;
326- let plan = record:: plan_with ( frame. as_protected_ref ( ) , & resolver ( ) ) . expect ( "plan" ) ;
327- dynamic:: field_operators ( & plan, "profile" , & resolver ( ) )
328- . expect ( "operators" )
329- . into_iter ( )
330- . map ( |( op, _) | op)
331- . collect :: < Vec < _ > > ( )
332- } ;
333- let ordered = field_operators ( & [ "$.contacts[*].relationship" ] ) ;
334- for present in [ Op :: ElementMatch , Op :: Match ] {
335- assert ! ( ordered. contains( & present) , "{present:?}: {ordered:?}" ) ;
336- }
337- let unordered = field_operators ( & [ ] ) ;
338- assert ! ( unordered. contains( & Op :: Match ) , "{unordered:?}" ) ;
339- assert ! ( !unordered. contains( & Op :: ElementMatch ) , "{unordered:?}" ) ;
340- }
341-
342- #[ cfg( feature = "eql" ) ]
343- #[ test]
344- fn eql_refusals_convert_to_the_engines_with_the_field_left_to_the_lowering ( ) {
345- use eql_bindings:: encryption:: targets:: TargetError as Eql ;
346- assert ! ( matches!(
347- convert( Eql :: Context {
348- label: "tenant/users/email" . into( )
349- } ) ,
350- TargetError :: Column { name, label, .. } if name. is_empty( ) && label == "tenant/users/email"
351- ) ) ;
352- assert ! ( matches!(
353- convert( Eql :: Plaintext {
354- target: "TextEq" ,
355- expected: stack_encrypt:: dynamic:: ValueKind :: String ,
356- found: Some ( stack_encrypt:: dynamic:: ValueKind :: UInt64 ) ,
357- } ) ,
358- TargetError :: Plaintext { target, expected: Some ( stack_encrypt:: dynamic:: ValueKind :: String ) , .. } if target == "TextEq"
359- ) ) ;
360- // The engine asks a type's query only through an index the type
361- // carries, so one it does not is the engine's failure, not the
362- // caller's input.
363- assert ! ( matches!(
364- convert( Eql :: NoIndex {
365- name: "TextEq" ,
366- index: "ope" ,
367- } ) ,
368- TargetError :: Other ( _)
369- ) ) ;
370- }
371-
372- /// serde_json quotes the value it refused, and a stored EQL value holds
373- /// ciphertext and index terms: only its kind and position cross.
374- #[ cfg( feature = "eql" ) ]
375- #[ test]
376- fn a_stored_value_that_does_not_parse_quotes_none_of_it ( ) {
377- use eql_bindings:: encryption:: targets:: TargetError as Eql ;
378- let source = serde_json:: from_str :: < u8 > ( r#""marker-ciphertext""# ) . unwrap_err ( ) ;
379- let error = convert ( Eql :: Stored {
380- target : "TextEq" ,
381- source,
382- } ) ;
383- let TargetError :: Stored { reason, .. } = & error else {
384- panic ! ( "{error:?}" ) ;
385- } ;
386- assert_eq ! ( reason, "unexpected data at line 1 column 19" ) ;
387- let shown = format ! ( "{error} {:?}" , stack_encrypt:: ErrorPayload :: payload( & error) ) ;
388- assert ! ( !shown. contains( "marker" ) , "{shown}" ) ;
389- }
390137}
0 commit comments