Skip to main content

EvaluationSession

Struct EvaluationSession 

Source
pub struct EvaluationSession { /* private fields */ }
Expand description

Request-scoped fact loading and caching state.

A session is intended to live for one request or one authorization pass. It owns registered fact sources and caches loaded facts by key type. The cache is deliberately not process-global. Cached facts and cached errors are dropped with the session, so permission revocations or backend changes are observed by the next request’s session rather than being held process-wide.

There is intentionally no time-based (TTL) cache: freshness is governed by session lifetime — drop the session to drop its cache. If you need caching that outlives a single session (a process-wide cache with a TTL, say), layer it inside a FactSource implementation. A source can hold its own expiring cache and be shared across sessions through FactRegistry, which keeps the session a simple request-scoped layer on top.

Implementations§

Source§

impl EvaluationSession

Source

pub fn new() -> Self

Creates an empty request-scoped session.

Source

pub fn empty() -> Self

Creates an explicitly empty request-scoped session.

This is equivalent to Self::new. It can make call sites clearer when only fact-free policies are expected and no fact sources are registered. For very hot fact-free loops, use Self::shared_empty to avoid allocating a new empty session per call.

Examples found in repository?
examples/delegating_policy.rs (line 170)
143async fn main() {
144    let owner_id = Uuid::new_v4();
145    let author_id = Uuid::new_v4();
146
147    let comment = Comment {
148        author_id,
149        document: Document { owner_id },
150    };
151
152    let author = Principal {
153        user_id: author_id,
154        is_admin: false,
155    };
156    let document_owner = Principal {
157        user_id: owner_id,
158        is_admin: false,
159    };
160    let admin = Principal {
161        user_id: Uuid::new_v4(),
162        is_admin: true,
163    };
164    let stranger = Principal {
165        user_id: Uuid::new_v4(),
166        is_admin: false,
167    };
168
169    let checker = comment_checker();
170    let session = EvaluationSession::empty();
171    let action = EditComment;
172    let context = ();
173
174    let cases = [
175        ("author", &author),
176        ("document owner (not author)", &document_owner),
177        ("admin (not author/owner)", &admin),
178        ("unrelated user", &stranger),
179    ];
180    for (who, principal) in cases {
181        let granted = checker
182            .bind(&session, principal, &action, &context)
183            .check(&comment)
184            .await
185            .is_granted();
186        println!(
187            "{who:<28} can edit the comment? {}",
188            if granted { "yes" } else { "no" }
189        );
190    }
191
192    // The document owner is not the comment author, so the direct rule denies;
193    // the delegating policy then asks the document checker, which grants. The
194    // trace shows the decision crossing the domain boundary.
195    println!("\nTrace — document owner (not the author) editing the comment:");
196    let decision = checker
197        .bind(&session, &document_owner, &action, &context)
198        .check(&comment)
199        .await;
200    println!("{}", decision.display_trace());
201
202    assert!(checker
203        .bind(&session, &author, &action, &context)
204        .check(&comment)
205        .await
206        .is_granted());
207    assert!(checker
208        .bind(&session, &document_owner, &action, &context)
209        .check(&comment)
210        .await
211        .is_granted());
212    assert!(checker
213        .bind(&session, &admin, &action, &context)
214        .check(&comment)
215        .await
216        .is_granted());
217    assert!(!checker
218        .bind(&session, &stranger, &action, &context)
219        .check(&comment)
220        .await
221        .is_granted());
222}
More examples
Hide additional examples
examples/mfa_freshness_context.rs (line 186)
170async fn main() {
171    let alice = User {
172        id: Uuid::new_v4(),
173        roles: vec!["finance".into()],
174    };
175    let small_refund = RefundRequest {
176        id: Uuid::new_v4(),
177        amount_cents: 5_000, // $50
178    };
179    let large_refund = RefundRequest {
180        id: Uuid::new_v4(),
181        amount_cents: 5_000_000, // $50,000
182    };
183
184    let now = SystemTime::now();
185    let checker = build_checker();
186    let session = EvaluationSession::empty();
187
188    // Case 1: small refund, no MFA at all. Granted — the high-value
189    // rule doesn't apply below the threshold, so the role grant decides.
190    let small_no_mfa = ApprovalContext {
191        current_time: now,
192        mfa_verified_at: None,
193    };
194    let r = checker
195        .bind(&session, &alice, &Approve, &small_no_mfa)
196        .check(&small_refund)
197        .await;
198    report("small refund, no MFA", &r);
199    r.assert_granted_by("FinanceCanApproveRefunds");
200
201    // Case 2: large refund, no MFA. Forbidden by the freshness rule —
202    // the veto overrides Alice's role grant.
203    let r = checker
204        .bind(&session, &alice, &Approve, &small_no_mfa)
205        .check(&large_refund)
206        .await;
207    report("large refund, no MFA", &r);
208    r.assert_forbidden_by("HighValueRequiresFreshMfa");
209
210    // Case 3: large refund, MFA reasserted 8 minutes ago. Stale → forbidden.
211    let stale = ApprovalContext {
212        current_time: now,
213        mfa_verified_at: Some(now - Duration::from_secs(8 * 60)),
214    };
215    let r = checker
216        .bind(&session, &alice, &Approve, &stale)
217        .check(&large_refund)
218        .await;
219    report("large refund, MFA 8m old", &r);
220    r.assert_forbidden_by("HighValueRequiresFreshMfa");
221
222    // Case 4: large refund, MFA reasserted 30 seconds ago. The deny rule
223    // is not applicable, so the role grant decides.
224    let fresh = ApprovalContext {
225        current_time: now,
226        mfa_verified_at: Some(now - Duration::from_secs(30)),
227    };
228    let r = checker
229        .bind(&session, &alice, &Approve, &fresh)
230        .check(&large_refund)
231        .await;
232    report("large refund, MFA 30s old", &r);
233    r.assert_granted_by("FinanceCanApproveRefunds");
234
235    // The point: cases 2-4 all use the same subject and resource. The
236    // only thing that varies is `ApprovalContext`. That's exactly the
237    // signal that the rule belongs in `Context`, not on User or
238    // RefundRequest.
239}
examples/rbac_policy.rs (line 100)
67async fn main() {
68    // The first resolver reads the requirement off the resource/action; the
69    // second extracts the subject's roles. The role type (`Role`) is inferred
70    // from the closures' return types.
71    let rbac_policy = RbacPolicy::<DocumentDomain, _, _>::new(
72        |_action: &ReadAction, doc: &Document| doc.required_roles.iter().copied().collect(),
73        |user: &User| user.roles.iter().copied().collect(),
74    );
75
76    let mut checker = PermissionChecker::<DocumentDomain>::new();
77    checker.add_policy(rbac_policy);
78
79    let admin = user("admin", [Role::Admin]);
80    let editor = user("editor", [Role::Editor]);
81    let multi_role = user("editor+viewer", [Role::Editor, Role::Viewer]);
82    let no_roles = user("no-roles", []);
83
84    let admin_doc = document("admin handbook", [Role::Admin]);
85    let editor_doc = document("style guide", [Role::Editor]);
86    let shared_doc = document("team wiki", [Role::Editor, Role::Viewer]);
87
88    // (user, document, expected outcome)
89    let cases = [
90        (&admin, &admin_doc, true),
91        (&admin, &editor_doc, false), // admin role is not editor — no hierarchy here
92        (&editor, &admin_doc, false),
93        (&editor, &editor_doc, true),
94        (&multi_role, &editor_doc, true),
95        (&multi_role, &shared_doc, true),
96        (&no_roles, &admin_doc, false),
97        (&no_roles, &shared_doc, false),
98    ];
99
100    let session = EvaluationSession::empty();
101    let action = ReadAction;
102    let context = ();
103
104    println!("{:<16} {:<16} verdict", "user", "document");
105    println!("{}", "-".repeat(42));
106    for (user, document, expected_granted) in cases {
107        let decision = checker
108            .bind(&session, user, &action, &context)
109            .check(document)
110            .await;
111        println!(
112            "{:<16} {:<16} {}",
113            user.name,
114            document.name,
115            if decision.is_granted() {
116                "GRANTED"
117            } else {
118                "DENIED"
119            }
120        );
121        assert_eq!(decision.is_granted(), expected_granted);
122    }
123
124    // Note the admin/style-guide denial above: `RbacPolicy` is a flat
125    // role-match with no built-in hierarchy. If admins should read
126    // everything, either include `Role::Admin` in each document's required
127    // set or add a separate admin-override policy to the checker.
128
129    println!("\nWhy the editor is denied the admin handbook:");
130    let decision = checker
131        .bind(&session, &editor, &action, &context)
132        .check(&admin_doc)
133        .await;
134    println!("{}", decision.display_trace());
135}
examples/policy_builder.rs (line 133)
85async fn main() {
86    let mut checker = PermissionChecker::<AdminDomain>::new();
87    checker.add_policy(scoped_permission_policy());
88    checker.add_policy(global_admin_policy());
89
90    let org1 = Organization { id: "org-1".into() };
91    let org2 = Organization { id: "org-2".into() };
92
93    let org1_admin = StaffUser {
94        name: "org1-admin",
95        permissions: vec![GroupPermission {
96            scope: "edit_user_settings",
97            entity: "org-1".into(),
98        }],
99    };
100    let org2_admin = StaffUser {
101        name: "org2-admin",
102        permissions: vec![GroupPermission {
103            scope: "edit_user_settings",
104            entity: "org-2".into(),
105        }],
106    };
107    let no_grants = StaffUser {
108        name: "no-grants",
109        permissions: vec![],
110    };
111    let global_admin = StaffUser {
112        name: "global-admin",
113        permissions: vec![GroupPermission {
114            scope: "global_admin",
115            entity: String::new(),
116        }],
117    };
118
119    // (user, action, organization, expected outcome)
120    let cases = [
121        // Scoped grant matches its own org…
122        (&org1_admin, AdminAction::EditUserSettings, &org1, true),
123        // …but not another org, and not another scope on the same org.
124        (&org2_admin, AdminAction::EditUserSettings, &org1, false),
125        (&org1_admin, AdminAction::EditOrgSettings, &org1, false),
126        (&org2_admin, AdminAction::EditUserSettings, &org2, true),
127        (&no_grants, AdminAction::EditUserSettings, &org1, false),
128        // The global admin passes via the subject-axis policy on any org.
129        (&global_admin, AdminAction::EditOrgSettings, &org1, true),
130    ];
131
132    for (user, action, org, expected_granted) in cases {
133        let session = EvaluationSession::empty();
134        let decision = checker.bind(&session, user, &action, &()).check(org).await;
135        println!(
136            "{:<12} {:?} on {}: {}",
137            user.name,
138            action,
139            org.id,
140            if decision.is_granted() {
141                "GRANTED"
142            } else {
143                "DENIED"
144            }
145        );
146        assert_eq!(decision.is_granted(), expected_granted);
147    }
148
149    // The trace names the policy that decided; for a denial it shows every
150    // policy that was consulted and why each said no.
151    println!("\nWhy org2-admin cannot edit user settings on org-1:");
152    let session = EvaluationSession::empty();
153    let decision = checker
154        .bind(&session, &org2_admin, &AdminAction::EditUserSettings, &())
155        .check(&org1)
156        .await;
157    println!("{}", decision.display_trace());
158}
examples/factsource_n_plus_one.rs (line 244)
219async fn main() {
220    // Same supplier, same hierarchy, same invoices for both shapes.
221    let supplier_org = Uuid::new_v4();
222    let customer = Uuid::new_v4();
223    let supplier = Supplier {
224        user_id: Uuid::new_v4(),
225        org_id: supplier_org,
226    };
227    let routes = std::collections::HashMap::from([(supplier_org, customer)]);
228    let hierarchy = Arc::new(HierarchyService::new(routes));
229
230    let invoices: Vec<Invoice> = (0..25)
231        .map(|_| Invoice {
232            id: Uuid::new_v4(),
233            customer_id: customer,
234        })
235        .collect();
236
237    // ---- WRONG ----
238    let mut wrong_checker = PermissionChecker::<SupplierInvoiceDomain>::new();
239    wrong_checker.add_policy(WrongSupplierPolicy {
240        hierarchy: Arc::clone(&hierarchy),
241    });
242
243    hierarchy.reset();
244    let session = EvaluationSession::empty();
245    let visible = wrong_checker
246        .bind(&session, &supplier, &ViewAction, &())
247        .filter(invoices.clone())
248        .await;
249    let wrong_calls = hierarchy.calls();
250    println!(
251        "[wrong] {} invoices -> {} hierarchy lookups (N+1, redundant)",
252        visible.len(),
253        wrong_calls,
254    );
255    // Check the lesson (call count) before the bookkeeping (item count)
256    // so a regression in the dedup logic surfaces here, not in a
257    // confusing length mismatch.
258    assert_eq!(
259        wrong_calls, 25,
260        "the wrong shape pays one hierarchy call per item",
261    );
262    assert_eq!(visible.len(), 25);
263
264    // ---- RIGHT ----
265    let mut right_checker = PermissionChecker::<SupplierInvoiceDomain>::new();
266    right_checker.add_policy(RightSupplierPolicy);
267
268    hierarchy.reset();
269    let load_many_calls = Arc::new(AtomicUsize::new(0));
270    let session = FactRegistry::builder()
271        .with_arc::<CustomerForOrg>(Arc::new(CustomerForOrgSource {
272            hierarchy: Arc::clone(&hierarchy),
273            load_many_calls: Arc::clone(&load_many_calls),
274        }))
275        .build()
276        .session();
277    let visible = right_checker
278        .bind(&session, &supplier, &ViewAction, &())
279        .filter(invoices)
280        .await;
281    let right_calls = hierarchy.calls();
282    let batch_calls = load_many_calls.load(Ordering::SeqCst);
283    println!(
284        "[right] {} invoices ->  {} hierarchy lookup  ({} batched load_many call, deduped through the session)",
285        visible.len(),
286        right_calls,
287        batch_calls,
288    );
289    assert_eq!(
290        right_calls, 1,
291        "the session deduplicates: one supplier_org, one backend call",
292    );
293    assert_eq!(
294        batch_calls, 1,
295        "the session batches: one load_many call covering the unique key set",
296    );
297    assert_eq!(visible.len(), 25);
298}
examples/deny_override.rs (line 155)
122async fn main() {
123    let owner_id = Uuid::new_v4();
124    let admin = User {
125        id: Uuid::new_v4(),
126        is_admin: true,
127        suspended: false,
128    };
129    let suspended_owner = User {
130        id: owner_id,
131        is_admin: false,
132        suspended: true,
133    };
134    let owner = User {
135        id: owner_id,
136        is_admin: false,
137        suspended: false,
138    };
139    let stranger = User {
140        id: Uuid::new_v4(),
141        is_admin: false,
142        suspended: false,
143    };
144
145    let normal_doc = Document {
146        owner_id,
147        legal_hold: false,
148    };
149    let held_doc = Document {
150        owner_id,
151        legal_hold: true,
152    };
153
154    let checker = document_checker();
155    let session = EvaluationSession::empty();
156    let action = Access;
157    let context = ();
158
159    // (subject, resource, label)
160    let cases = [
161        (&admin, &normal_doc, "admin, normal doc"),
162        (&owner, &normal_doc, "owner, own normal doc"),
163        (&admin, &held_doc, "admin, LEGAL-HOLD doc"),
164        (&suspended_owner, &normal_doc, "SUSPENDED owner, own doc"),
165        (&stranger, &normal_doc, "stranger, someone else's doc"),
166    ];
167
168    println!("{:<32} {:>10} forbidden by", "case", "decision");
169    println!("{}", "-".repeat(60));
170    for (subject, document, label) in cases {
171        let decision = checker
172            .bind(&session, subject, &action, &context)
173            .check(document)
174            .await;
175        println!(
176            "{label:<32} {:>10} {}",
177            verdict(decision.is_granted()),
178            decision.forbidden_by().unwrap_or("-"),
179        );
180    }
181
182    // The grants still work where nothing blocks them.
183    checker
184        .bind(&session, &admin, &action, &context)
185        .check(&normal_doc)
186        .await
187        .assert_granted_by("AdminOverride");
188    checker
189        .bind(&session, &owner, &action, &context)
190        .check(&normal_doc)
191        .await
192        .assert_granted_by("DocumentOwner");
193
194    // The block rules override every grant — even the admin override.
195    checker
196        .bind(&session, &admin, &action, &context)
197        .check(&held_doc)
198        .await
199        .assert_forbidden_by("LegalHold");
200    checker
201        .bind(&session, &suspended_owner, &action, &context)
202        .check(&normal_doc)
203        .await
204        .assert_forbidden_by("AccountSuspended");
205
206    // Default deny is untouched: no grant, no access — and no forbid
207    // either, which `forbidden_by()` distinguishes for the caller.
208    let stranger_decision = checker
209        .bind(&session, &stranger, &action, &context)
210        .check(&normal_doc)
211        .await;
212    stranger_decision.assert_denied();
213    assert_eq!(stranger_decision.forbidden_by(), None);
214
215    // Show the mechanism on the headline case: the forbid-effect policy is
216    // evaluated first and ends the evaluation; the allow set is never
217    // consulted.
218    println!("\nWhy 'admin, LEGAL-HOLD doc' is blocked:");
219    let decision = checker
220        .bind(&session, &admin, &action, &context)
221        .check(&held_doc)
222        .await;
223    println!("{}", decision.display_trace());
224
225    scoped_exclusion_demo().await;
226}
227
228// ---- scoped exclusion: a deny that gates only one grant path --------
229
230/// `Effect::Forbid` is a *global* veto: it blocks every grant path in the
231/// checker. When a block rule should only gate one grant path — here,
232/// muted users lose collaborator access but owners and admins keep
233/// theirs — scope it with combinators instead:
234/// `AndPolicy[ grant_arm, NotPolicy(block) ]`. The block policy in this local
235/// shape should be an ordinary grant-style predicate, not `.forbid()`;
236/// `Forbidden` is active and would still veto globally.
237async fn scoped_exclusion_demo() {
238    #[derive(Debug, Clone)]
239    struct Member {
240        is_owner: bool,
241        is_collaborator: bool,
242        muted: bool,
243    }
244    #[derive(Debug, Clone)]
245    struct Thread;
246
247    struct ThreadDomain;
248
249    impl PolicyDomain for ThreadDomain {
250        type Subject = Member;
251        type Action = Access;
252        type Resource = Thread;
253        type Context = ();
254    }
255
256    let owner_policy = PolicyBuilder::<ThreadDomain>::new("ThreadOwner")
257        .subjects(|member| member.is_owner)
258        .build();
259    let collaborator_policy: Arc<dyn Policy<ThreadDomain>> = Arc::from(
260        PolicyBuilder::<ThreadDomain>::new("Collaborator")
261            .subjects(|member| member.is_collaborator)
262            .build(),
263    );
264    // The block rule for the scoped case *grants when it matches* so that
265    // `NotPolicy` can invert it into a local gate. Compare with the
266    // checker-level rules above, where `Effect::Forbid` keeps natural
267    // polarity — this inversion is the price of scoping, which is why a
268    // global block should prefer `Effect::Forbid`.
269    let muted = PolicyBuilder::<ThreadDomain>::new("Muted")
270        .subjects(|member| member.muted)
271        .build();
272
273    let collaborator_unless_muted = AndPolicy::try_new(vec![
274        collaborator_policy,
275        Arc::new(NotPolicy::new(muted)) as Arc<dyn Policy<ThreadDomain>>,
276    ])
277    .expect("gate has the grant arm and the guard");
278
279    let mut checker = PermissionChecker::<ThreadDomain>::named("ThreadChecker");
280    checker.add_policy(owner_policy);
281    checker.add_policy(collaborator_unless_muted);
282
283    let muted_collaborator = Member {
284        is_owner: false,
285        is_collaborator: true,
286        muted: true,
287    };
288    let muted_owner = Member {
289        is_owner: true,
290        is_collaborator: false,
291        muted: true,
292    };
293
294    let session = EvaluationSession::empty();
295    let action = Access;
296    let context = ();
297
298    // The mute only gates the collaborator path...
299    checker
300        .bind(&session, &muted_collaborator, &action, &context)
301        .check(&Thread)
302        .await
303        .assert_denied();
304    // ...the owner path is untouched, which a global Effect::Forbid mute
305    // could not express.
306    checker
307        .bind(&session, &muted_owner, &action, &context)
308        .check(&Thread)
309        .await
310        .assert_granted_by("ThreadOwner");
311
312    println!("\nScoped exclusion: muted collaborator blocked, muted owner unaffected.");
313}
Source

pub fn shared_empty() -> &'static Self

Returns a process-wide empty session for hot paths that never use fact sources.

This avoids allocating a new empty session for fact-free checks in tight loops. Only use it when no fact-backed policies are expected. Fact-backed paths should build a FactRegistry during application setup and call FactRegistry::session per request.

Source

pub async fn get<K>(&self, key: K) -> FactLoadResult<K::Value>
where K: FactKey,

Loads one fact through the session cache.

Examples found in repository?
examples/factsource_n_plus_one.rs (line 202)
197    async fn evaluate(&self, ctx: &EvalCtx<'_, SupplierInvoiceDomain>) -> PolicyEvalResult {
198        // Ask the session, not the backend service directly. The
199        // first call inside this request triggers `load_many`; every
200        // subsequent call with the same key (e.g. another invoice in
201        // the same batch) hits the request-scoped cache.
202        match ctx.session.get(CustomerForOrg(ctx.subject.org_id)).await {
203            FactLoadResult::Found(Some(customer_id)) if customer_id == ctx.resource.customer_id => {
204                ctx.grant("subject's supplier org bills under the invoice's customer")
205            }
206            _ => ctx.not_applicable(
207                "subject's supplier org does not bill under the invoice's customer",
208            ),
209        }
210    }
Source

pub async fn get_many<K>(&self, keys: &[K]) -> Vec<FactLoadResult<K::Value>>
where K: FactKey,

Loads facts through the session cache.

Results preserve input order and duplicate keys. Missing cache entries are deduplicated before they are loaded, then chunked according to the source’s FactSource::max_batch_size hint.

Trait Implementations§

Source§

impl Clone for EvaluationSession

Source§

fn clone(&self) -> EvaluationSession

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Default for EvaluationSession

Source§

fn default() -> EvaluationSession

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more