@@ -64,9 +64,9 @@ pub(crate) enum Kind {
6464 /// Derived from the source through the field type's own `EncryptFrom`.
6565 Derived {
6666 /// This field's own context, if it has one: a `#[stash(context =
67- /// "...")]` literal, or the `"< prefix>/< field>"` a `struct` derive
67+ /// "...")]` literal, or the `( prefix, field)` pair a `struct` derive
6868 /// infers. A context the caller passes extends it either way.
69- context : Option < LitStr > ,
69+ context : Option < OwnContext > ,
7070 /// With `struct = ..`: the plaintext field this one is derived
7171 /// from — its own name, or the `#[stash(from = field)]` override.
7272 /// `None` for a `plaintext` record, whose fields are all derived
@@ -96,7 +96,7 @@ impl Field {
9696 ///
9797 /// A field with a context of its own — a literal, or the one a `struct`
9898 /// derive infers — is derived under it as it is when the caller passes
99- /// `()`, and under it *extended* with the caller's (`("users/ age", id)`)
99+ /// `()`, and under it *extended* with the caller's (`(( "users", " age") , id)`)
100100 /// when the caller passes a `NonEmpty<_>`. A field with none is handed
101101 /// the caller's context as it is, and its type decides what that means:
102102 /// a nested `struct` derive composes it with its own contexts; a leaf
@@ -118,13 +118,35 @@ impl Field {
118118 }
119119}
120120
121+ /// A derived field's own context.
122+ #[ cfg_attr( test, derive( Debug ) ) ]
123+ pub ( crate ) enum OwnContext {
124+ /// `#[stash(context = "...")]`: one text part, exactly as written.
125+ Literal ( LitStr ) ,
126+ /// What a `struct` derive infers: the pair (container prefix, column),
127+ /// two parts, so it renders `prefix/column` without the column name
128+ /// having to be joined into, or kept out of, a string.
129+ Column { table : LitStr , column : LitStr } ,
130+ }
131+ impl OwnContext {
132+ /// The `NonEmpty` the derive hands `under` / `extend`.
133+ fn expr ( & self , krate : & Path ) -> TokenStream {
134+ match self {
135+ Self :: Literal ( lit) => quote ! ( #krate:: nonempty!( #lit) ) ,
136+ Self :: Column { table, column } => {
137+ quote ! ( #krate:: nonempty!( #table) . with( #column) )
138+ }
139+ }
140+ }
141+ }
142+
121143/// Where a derived field's context comes from. See [`Field::field_context`].
122144#[ cfg_attr( test, derive( Debug ) ) ]
123145pub ( crate ) enum FieldContext < ' a > {
124146 /// A context of the field's own — `#[stash(context = "...")]`, or the
125- /// `"< prefix>/< field>"` a `struct` derive infers: as it is under `()`,
147+ /// `( prefix, field)` pair a `struct` derive infers: as it is under `()`,
126148 /// extended with the caller's context under `NonEmpty<_>`.
127- Own ( & ' a LitStr ) ,
149+ Own ( & ' a OwnContext ) ,
128150 /// No context of its own: handed the caller's as it is — `()`, or the
129151 /// record's associated context.
130152 Caller ,
@@ -270,7 +292,7 @@ pub(crate) fn trait_impl(
270292
271293/// The fields, with what a `struct` derive (`prefix` is the container's
272294/// `context`) fills in: `from` is the field's own name and `context` is
273- /// ` "<prefix>/ <from>"`, each unless the field gives its own.
295+ /// the pair `( "<prefix>", " <from>") `, each unless the field gives its own.
274296/// `#[stash(nested)]` opts a field out of the inferred context — it is handed
275297/// the caller's as it is, which a nested `struct` derive (a type carrying its
276298/// own contexts) composes with them and a leaf accepts only as a
@@ -311,8 +333,8 @@ fn collect(fields: &Fields, prefix: Option<&LitStr>) -> Result<Vec<Field>> {
311333 if context. value ( ) . is_empty ( ) {
312334 let message = if prefix. is_some ( ) {
313335 "an empty `context` is rejected when a value is encrypted: name the \
314- field (e.g. \" users/ email\" ), or drop the attribute to use the inferred \
315- ` \" <context>/ <field>\" `"
336+ field (e.g. \" email\" ), or drop the attribute to use the inferred \
337+ pair `( \" <context>\" , \" <field>\" ) `"
316338 } else {
317339 "an empty `context` is rejected when a value is encrypted: name the \
318340 field (e.g. \" users/email\" ), or drop the attribute to hand the field \
@@ -359,22 +381,24 @@ fn collect(fields: &Fields, prefix: Option<&LitStr>) -> Result<Vec<Field>> {
359381 // is handed the caller's (`FieldContext::Caller`).
360382 None
361383 } else if let Some ( lit) = attrs. context {
362- Some ( lit)
384+ Some ( OwnContext :: Literal ( lit) )
363385 } else {
364386 let column = match & from {
365387 Member :: Named ( ident) => ident. to_string ( ) ,
366388 Member :: Unnamed ( index) => index. index . to_string ( ) ,
367389 } ;
368- let prefix = prefix. value ( ) ;
369- Some ( LitStr :: new ( & format ! ( "{prefix}/{column}" ) , member. span ( ) ) )
390+ Some ( OwnContext :: Column {
391+ table : prefix. clone ( ) ,
392+ column : LitStr :: new ( & column, member. span ( ) ) ,
393+ } )
370394 } ;
371395 Kind :: Derived {
372396 context,
373397 from : Some ( from) ,
374398 }
375399 }
376400 None => Kind :: Derived {
377- context : attrs. context ,
401+ context : attrs. context . map ( OwnContext :: Literal ) ,
378402 from : None ,
379403 } ,
380404 } ,
@@ -482,12 +506,13 @@ impl Record {
482506 let krate = & self . krate ;
483507 let span = field. ty . span ( ) ;
484508 match field. field_context ( ) {
485- FieldContext :: Own ( lit) => {
509+ FieldContext :: Own ( own) => {
510+ let own = respan ( own. expr ( krate) , span) ;
486511 if self . declared_contexts ( ) {
487- quote_spanned ! ( span=> . under( #krate :: nonempty! ( #lit ) ) )
512+ quote_spanned ! ( span=> . under( #own ) )
488513 } else {
489514 let threaded = respan ( self . threaded_context ( ) . into_token_stream ( ) , span) ;
490- quote_spanned ! ( span=> . extend:: <#threaded>( #krate :: nonempty! ( #lit ) ) )
515+ quote_spanned ! ( span=> . extend:: <#threaded>( #own ) )
491516 }
492517 }
493518 FieldContext :: Caller => {
@@ -530,13 +555,14 @@ impl Record {
530555 let krate = & self . krate ;
531556 match field. field_context ( ) {
532557 FieldContext :: Caller => quote ! ( :: core:: clone:: Clone :: clone( & __context) ) ,
533- FieldContext :: Own ( lit ) => {
558+ FieldContext :: Own ( own ) => {
534559 let method = if self . declared_contexts ( ) {
535560 quote ! ( under)
536561 } else {
537562 quote ! ( extend)
538563 } ;
539- quote ! ( :: core:: clone:: Clone :: clone( & __context) . #method( #krate:: nonempty!( #lit) ) )
564+ let own = own. expr ( krate) ;
565+ quote ! ( :: core:: clone:: Clone :: clone( & __context) . #method( #own) )
540566 }
541567 }
542568 }
@@ -584,7 +610,10 @@ mod tests {
584610 /// The field's own context, for assertions.
585611 fn own ( field : & Field ) -> String {
586612 match field. field_context ( ) {
587- FieldContext :: Own ( lit) => lit. value ( ) ,
613+ FieldContext :: Own ( OwnContext :: Literal ( lit) ) => lit. value ( ) ,
614+ FieldContext :: Own ( OwnContext :: Column { table, column } ) => {
615+ format ! ( "({}, {})" , table. value( ) , column. value( ) )
616+ }
588617 other => panic ! ( "expected a context of the field's own, got {other:?}" ) ,
589618 }
590619 }
@@ -824,10 +853,10 @@ mod tests {
824853 ) ;
825854 // Own name under the container's prefix.
826855 assert ! ( matches!( age. from( ) , Some ( Member :: Named ( m) ) if m == "age" ) ) ;
827- assert_eq ! ( own( age) , "user_profiles/ age" ) ;
856+ assert_eq ! ( own( age) , "( user_profiles, age) " ) ;
828857 // `from` overrides the field; the context follows the plaintext field.
829858 assert ! ( matches!( email. from( ) , Some ( Member :: Named ( m) ) if m == "email_address" ) ) ;
830- assert_eq ! ( own( email) , "user_profiles/ email_address" ) ;
859+ assert_eq ! ( own( email) , "( user_profiles, email_address) " ) ;
831860 // `context` is taken verbatim; like the inferred ones, the caller's
832861 // context extends it.
833862 assert ! ( matches!( name. from( ) , Some ( Member :: Named ( m) ) if m == "name" ) ) ;
@@ -846,8 +875,8 @@ mod tests {
846875 } )
847876 . unwrap ( ) ;
848877 assert ! ( matches!( record. fields[ 1 ] . from( ) , Some ( Member :: Unnamed ( i) ) if i. index == 1 ) ) ;
849- assert_eq ! ( own( & record. fields[ 0 ] ) , "readings/0 " ) ;
850- assert_eq ! ( own( & record. fields[ 1 ] ) , "readings/1 " ) ;
878+ assert_eq ! ( own( & record. fields[ 0 ] ) , "( readings, 0) " ) ;
879+ assert_eq ! ( own( & record. fields[ 1 ] ) , "( readings, 1) " ) ;
851880 }
852881
853882 #[ test]
0 commit comments