fission_core/context.rs
1//! Reducer context and effect builder.
2//!
3//! When a reducer needs to emit side-effects or inspect the [`ActionInput`]
4//! that triggered it, it receives a [`ReducerContext`]. The context provides
5//! an [`Effects`] builder for issuing capabilities, jobs, services, and
6//! runtime-control effects plus binding callback actions.
7
8use crate::action::{Action, ActionEnvelope, AppState};
9use crate::async_runtime::{
10 JobRef, JobRequestPayload, JobSpec, ServiceBindings, ServiceCommandPayload, ServiceSlot,
11 ServiceSpec, ServiceStartPayload, ServiceStopPayload,
12};
13use crate::capability::{
14 CapabilityInvocationPayload, CapabilityType, OperationCapability, OperationCapabilityInvocation,
15};
16use crate::effect::{ActionInput, Effect, EffectEnvelope, RuntimeEffect};
17use crate::platform::{
18 CancelNotificationRequest, NotificationPermissionRequest, NotificationRequest,
19 PushRegistrationRequest, SetBadgeCountRequest, CANCEL_ALL_NOTIFICATIONS, CANCEL_NOTIFICATION,
20 GET_NOTIFICATION_SETTINGS, REGISTER_PUSH_NOTIFICATIONS, REQUEST_NOTIFICATION_PERMISSION,
21 SCHEDULE_NOTIFICATION, SET_BADGE_COUNT, SHOW_NOTIFICATION, UNREGISTER_PUSH_NOTIFICATIONS,
22};
23use crate::platform_barcode::{
24 BarcodeImageDecodeRequest, BarcodeScanRequest, CANCEL_BARCODE_SCAN, DECODE_BARCODE_IMAGE,
25 SCAN_BARCODE,
26};
27use crate::platform_biometric::{
28 BiometricAuthenticateRequest, AUTHENTICATE_BIOMETRIC, CANCEL_BIOMETRIC_AUTHENTICATION,
29 GET_BIOMETRIC_AVAILABILITY,
30};
31use crate::platform_bluetooth::{
32 BluetoothAdvertiseRequest, BluetoothConnectRequest, BluetoothDisconnectRequest,
33 BluetoothPermissionRequest, BluetoothReadRequest, BluetoothScanRequest,
34 BluetoothStopAdvertiseRequest, BluetoothWriteRequest, CONNECT_BLUETOOTH_DEVICE,
35 DISCONNECT_BLUETOOTH_DEVICE, GET_BLUETOOTH_AVAILABILITY, READ_BLUETOOTH_CHARACTERISTIC,
36 REQUEST_BLUETOOTH_PERMISSION, SCAN_BLUETOOTH_DEVICES, START_BLUETOOTH_ADVERTISING,
37 STOP_BLUETOOTH_ADVERTISING, WRITE_BLUETOOTH_CHARACTERISTIC,
38};
39use crate::platform_camera::{
40 CameraCaptureRequest, CameraFlashlightRequest, CameraPermissionRequest, CANCEL_CAMERA_CAPTURE,
41 CAPTURE_PHOTO, GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION, SET_CAMERA_FLASHLIGHT,
42};
43use crate::platform_clipboard::{
44 ClipboardContent, ClipboardWriteTextRequest, CLEAR_CLIPBOARD, READ_CLIPBOARD_CONTENT,
45 READ_CLIPBOARD_TEXT, WRITE_CLIPBOARD_CONTENT, WRITE_CLIPBOARD_TEXT,
46};
47use crate::platform_geolocation::{
48 GeolocationPermissionRequest, GeolocationPositionRequest, GET_CURRENT_POSITION,
49 GET_GEOLOCATION_PERMISSION, REQUEST_GEOLOCATION_PERMISSION,
50};
51use crate::platform_haptics::{
52 HapticImpactRequest, HapticNotificationRequest, HapticPatternRequest, HAPTIC_IMPACT,
53 HAPTIC_NOTIFICATION, HAPTIC_PATTERN, HAPTIC_SELECTION,
54};
55use crate::platform_microphone::{
56 MicrophoneCaptureRequest, MicrophonePermissionRequest, CANCEL_MICROPHONE_CAPTURE,
57 CAPTURE_MICROPHONE_AUDIO, GET_MICROPHONE_AVAILABILITY, REQUEST_MICROPHONE_PERMISSION,
58};
59use crate::platform_nfc::{
60 NfcEmulationRequest, NfcScanRequest, NfcWriteRequest, CANCEL_NFC_SESSION, EMULATE_NFC_TAG,
61 GET_NFC_AVAILABILITY, SCAN_NFC_TAG, WRITE_NFC_TAG,
62};
63use crate::platform_passkey::{
64 PasskeyAuthenticationRequest, PasskeyRegistrationRequest, AUTHENTICATE_PASSKEY,
65 CANCEL_PASSKEY_OPERATION, GET_PASSKEY_AVAILABILITY, REGISTER_PASSKEY,
66};
67use crate::platform_volume::{
68 VolumeAdjustRequest, VolumeSetRequest, VolumeStream, ADJUST_VOLUME_LEVEL, GET_VOLUME_LEVEL,
69 SET_VOLUME_LEVEL,
70};
71use crate::platform_wifi::{
72 WifiConnectRequest, WifiDisconnectRequest, WifiPermissionRequest, WifiScanRequest,
73 CONNECT_WIFI_NETWORK, DISCONNECT_WIFI_NETWORK, GET_WIFI_AVAILABILITY, REQUEST_WIFI_PERMISSION,
74 SCAN_WIFI_NETWORKS,
75};
76use crate::registry::{ActionRegistry, IntoHandler};
77use std::marker::PhantomData;
78
79/// The context passed to modern 3-argument reducer handlers.
80///
81/// Provides access to the [`Effects`] builder (for emitting side-effects) and
82/// the [`ActionInput`] that accompanied the dispatch (e.g. effect results,
83/// pointer coordinates, drop payloads).
84///
85/// # Example
86///
87/// ```rust,ignore
88/// fn handle_click(
89/// state: &mut AppState,
90/// action: ClickAction,
91/// ctx: &mut ReducerContext<AppState>,
92/// ) {
93/// // Read pointer position from the input
94/// if let Some((x, y, _, _)) = ctx.input.as_pointer() {
95/// state.last_click = (x, y);
96/// }
97/// // Issue a capability effect
98/// ctx.effects.capability(MY_CAPABILITY, request);
99/// }
100/// ```
101pub struct ReducerContext<'a, 'b, 'c, S: AppState> {
102 /// Mutable reference to the effects builder.
103 pub effects: &'a mut Effects<'b, S>,
104 /// The input data that accompanied this action dispatch.
105 pub input: &'c ActionInput,
106}
107
108/// Builder for emitting side-effects from within a reducer.
109///
110/// `Effects` accumulates [`EffectEnvelope`] values that the runtime collects
111/// after the reducer returns. Each effect can carry optional `on_ok` and
112/// `on_err` callbacks.
113///
114/// # Example
115///
116/// ```rust,ignore
117/// fn handle_save(
118/// state: &mut MyState,
119/// _action: Save,
120/// ctx: &mut ReducerContext<MyState>,
121/// ) {
122/// ctx.effects.capability(MY_CAPABILITY, request)
123/// .on_ok(ctx.effects.bind(SaveOk, handle_save_ok as fn(&mut MyState, SaveOk)))
124/// .on_err(ctx.effects.bind(SaveErr, handle_save_err as fn(&mut MyState, SaveErr)));
125/// }
126/// ```
127pub struct Effects<'a, S: AppState> {
128 /// Accumulated effect envelopes, drained by the runtime after the reducer.
129 pub out: Vec<EffectEnvelope>,
130 next_req_id: u64,
131 pub(crate) registry: Option<&'a mut ActionRegistry<S>>,
132 _phantom: PhantomData<S>,
133}
134
135impl<'a, S: AppState> Effects<'a, S> {
136 pub fn new(next_req_id: u64, registry: &'a mut ActionRegistry<S>) -> Self {
137 Self {
138 out: Vec::new(),
139 next_req_id,
140 registry: Some(registry),
141 _phantom: PhantomData,
142 }
143 }
144
145 pub fn new_headless(next_req_id: u64) -> Self {
146 Self {
147 out: Vec::new(),
148 next_req_id,
149 registry: None,
150 _phantom: PhantomData,
151 }
152 }
153
154 pub fn bind<A: Action, H>(&mut self, action: A, handler: H) -> ActionEnvelope
155 where
156 H: IntoHandler<S, A> + Send + Sync + 'static,
157 {
158 if let Some(registry) = &mut self.registry {
159 registry.register(handler);
160 }
161 ActionEnvelope {
162 id: A::static_id(),
163 payload: action.encode(),
164 }
165 }
166
167 pub fn add(&mut self, effect: Effect) -> u64 {
168 let req_id = self.next_req_id;
169 self.next_req_id += 1;
170
171 self.out.push(EffectEnvelope {
172 req_id,
173 effect,
174 on_ok: None,
175 on_err: None,
176 service_bindings: None,
177 resource: None,
178 });
179 req_id
180 }
181
182 pub fn capability<C: OperationCapability>(
183 &mut self,
184 capability: CapabilityType<C>,
185 request: C::Request,
186 ) -> EffectBuilder<'_, 'a, S> {
187 let req_id = self.next_req_id;
188 self.next_req_id += 1;
189 let request =
190 serde_json::to_vec(&request).expect("capability request serialization must succeed");
191
192 let index = self.out.len();
193 self.out.push(EffectEnvelope {
194 req_id,
195 effect: Effect::Capability(CapabilityInvocationPayload::Operation(
196 OperationCapabilityInvocation {
197 capability_name: capability.name.to_string(),
198 request,
199 },
200 )),
201 on_ok: None,
202 on_err: None,
203 service_bindings: None,
204 resource: None,
205 });
206
207 EffectBuilder {
208 effects: self,
209 index,
210 }
211 }
212
213 /// Starts a typed notification capability request.
214 ///
215 /// Use this from reducers when the app needs the host to request
216 /// notification permission, show or schedule a notification, update a badge,
217 /// or register for push delivery. The returned builder records a capability
218 /// effect; it does not display anything until the reducer has returned and
219 /// the active shell processes queued effects.
220 pub fn notifications(&mut self) -> NotificationEffects<'_, 'a, S> {
221 NotificationEffects { effects: self }
222 }
223
224 /// Starts a typed NFC capability request.
225 ///
226 /// Use this when the app needs the host to read, write, emulate, or cancel
227 /// an NFC session. The helper keeps NFC prompts, tag records, and timeout
228 /// choices in typed request values so reducers do not call platform NFC APIs
229 /// directly.
230 pub fn nfc(&mut self) -> NfcEffects<'_, 'a, S> {
231 NfcEffects { effects: self }
232 }
233
234 /// Starts a typed biometric authentication capability request.
235 ///
236 /// Use this for host-owned local user verification such as fingerprint,
237 /// face, or device credential fallback. The result reports whether the host
238 /// verified the local user; it is not a network identity assertion.
239 pub fn biometrics(&mut self) -> BiometricEffects<'_, 'a, S> {
240 BiometricEffects { effects: self }
241 }
242
243 /// Starts a typed passkey/WebAuthn credential capability request.
244 ///
245 /// Use this for account sign-in, re-authentication, or credential
246 /// registration flows where the server verifies WebAuthn data. This is
247 /// intentionally separate from `biometrics()`: the host may use biometrics
248 /// to unlock a passkey, but the app receives credential data, not raw face
249 /// or fingerprint state.
250 pub fn passkeys(&mut self) -> PasskeyEffects<'_, 'a, S> {
251 PasskeyEffects { effects: self }
252 }
253
254 /// Starts a typed Bluetooth capability request.
255 ///
256 /// Use this for nearby-device workflows such as adapter availability,
257 /// permission requests, scanning, connecting, characteristic reads and
258 /// writes, and advertising. Scans and connections are host-owned operations
259 /// because permission and hardware behavior differ sharply by platform.
260 pub fn bluetooth(&mut self) -> BluetoothEffects<'_, 'a, S> {
261 BluetoothEffects { effects: self }
262 }
263
264 /// Starts a typed barcode scanner capability request.
265 ///
266 /// Use this when the host should run a live scanner session or decode image
267 /// bytes into barcode results. Live scanning normally depends on camera
268 /// permission; image decoding can be tested without camera hardware.
269 pub fn barcode_scanner(&mut self) -> BarcodeScannerEffects<'_, 'a, S> {
270 BarcodeScannerEffects { effects: self }
271 }
272
273 /// Starts a typed camera and flashlight capability request.
274 ///
275 /// Use this for camera availability, permission, still photo capture, and
276 /// torch control. The returned helper emits requests to the shell host so
277 /// the app state does not depend on a particular camera API.
278 pub fn camera(&mut self) -> CameraEffects<'_, 'a, S> {
279 CameraEffects { effects: self }
280 }
281
282 /// Starts a typed clipboard capability request.
283 ///
284 /// Use this for user-visible copy and paste flows. Platforms may restrict
285 /// clipboard access to focused windows, secure browser contexts, or direct
286 /// user gestures, so reducers should handle errors as normal outcomes.
287 pub fn clipboard(&mut self) -> ClipboardEffects<'_, 'a, S> {
288 ClipboardEffects { effects: self }
289 }
290
291 /// Starts a typed geolocation capability request.
292 ///
293 /// Use this when the app needs permission state or a current location. The
294 /// request controls accuracy, timeout, and cache age so the host can choose
295 /// an appropriate platform location call.
296 pub fn geolocation(&mut self) -> GeolocationEffects<'_, 'a, S> {
297 GeolocationEffects { effects: self }
298 }
299
300 /// Starts a typed haptic feedback capability request.
301 ///
302 /// Use this for tactile feedback tied to meaningful interactions such as
303 /// impact, notification, selection, or a bounded pattern. Unsupported hosts
304 /// should return a typed error rather than pretending vibration occurred.
305 pub fn haptics(&mut self) -> HapticEffects<'_, 'a, S> {
306 HapticEffects { effects: self }
307 }
308
309 /// Starts a typed microphone capability request.
310 ///
311 /// Use this for microphone availability, permission, bounded audio capture,
312 /// and cancellation. Captures should be explicit and time-bounded because
313 /// recording is a sensitive host-owned operation.
314 pub fn microphone(&mut self) -> MicrophoneEffects<'_, 'a, S> {
315 MicrophoneEffects { effects: self }
316 }
317
318 /// Starts a typed Wi-Fi capability request.
319 ///
320 /// Use this for adapter availability, permission, scanning, connecting, and
321 /// disconnecting where the platform allows app-level Wi-Fi management.
322 /// Many platforms treat Wi-Fi information as location-sensitive, so reducers
323 /// should handle permission and unsupported errors explicitly.
324 pub fn wifi(&mut self) -> WifiEffects<'_, 'a, S> {
325 WifiEffects { effects: self }
326 }
327
328 /// Starts a typed host volume-control capability request.
329 ///
330 /// Use this for app-approved media, notification, alarm, call, or system
331 /// stream adjustments. Some platforms expose only media-element volume or no
332 /// system-volume control, so callers should treat unsupported errors as
333 /// normal platform outcomes.
334 pub fn volume(&mut self) -> VolumeEffects<'_, 'a, S> {
335 VolumeEffects { effects: self }
336 }
337
338 pub fn app<J: JobSpec>(
339 &mut self,
340 job: JobRef<J>,
341 request: J::Request,
342 ) -> EffectBuilder<'_, 'a, S> {
343 let req_id = self.next_req_id;
344 self.next_req_id += 1;
345 let payload = serde_json::to_vec(&request).expect("job request serialization must succeed");
346 let index = self.out.len();
347 self.out.push(EffectEnvelope {
348 req_id,
349 effect: Effect::Job(JobRequestPayload {
350 job_name: job.name.to_string(),
351 payload,
352 }),
353 on_ok: None,
354 on_err: None,
355 service_bindings: None,
356 resource: None,
357 });
358 EffectBuilder {
359 effects: self,
360 index,
361 }
362 }
363
364 pub fn start_service<Svc: ServiceSpec>(
365 &mut self,
366 slot: ServiceSlot<Svc>,
367 config: Svc::Config,
368 ) -> ServiceStartBuilder<'_, 'a, S> {
369 let req_id = self.next_req_id;
370 self.next_req_id += 1;
371 let index = self.out.len();
372 let config =
373 serde_json::to_vec(&config).expect("service config serialization must succeed");
374 self.out.push(EffectEnvelope {
375 req_id,
376 effect: Effect::StartService(ServiceStartPayload {
377 service_name: slot.ty.name.to_string(),
378 slot_key: slot.slot_key().to_string(),
379 config,
380 }),
381 on_ok: None,
382 on_err: None,
383 service_bindings: Some(ServiceBindings::default()),
384 resource: None,
385 });
386 ServiceStartBuilder {
387 effects: self,
388 index,
389 }
390 }
391
392 pub fn command<Svc: ServiceSpec>(
393 &mut self,
394 slot: ServiceSlot<Svc>,
395 command: Svc::Command,
396 ) -> EffectBuilder<'_, 'a, S> {
397 let req_id = self.next_req_id;
398 self.next_req_id += 1;
399 let index = self.out.len();
400 let payload =
401 serde_json::to_vec(&command).expect("service command serialization must succeed");
402 self.out.push(EffectEnvelope {
403 req_id,
404 effect: Effect::ServiceCommand(ServiceCommandPayload {
405 service_name: slot.ty.name.to_string(),
406 slot_key: slot.slot_key().to_string(),
407 payload,
408 }),
409 on_ok: None,
410 on_err: None,
411 service_bindings: None,
412 resource: None,
413 });
414 EffectBuilder {
415 effects: self,
416 index,
417 }
418 }
419
420 pub fn stop_service<Svc: ServiceSpec>(
421 &mut self,
422 slot: ServiceSlot<Svc>,
423 ) -> EffectBuilder<'_, 'a, S> {
424 let req_id = self.next_req_id;
425 self.next_req_id += 1;
426 let index = self.out.len();
427 self.out.push(EffectEnvelope {
428 req_id,
429 effect: Effect::StopService(ServiceStopPayload {
430 service_name: slot.ty.name.to_string(),
431 slot_key: slot.slot_key().to_string(),
432 }),
433 on_ok: None,
434 on_err: None,
435 service_bindings: None,
436 resource: None,
437 });
438 EffectBuilder {
439 effects: self,
440 index,
441 }
442 }
443
444 pub fn cancel(&mut self, req_id: u64) {
445 self.add(Effect::Runtime(RuntimeEffect::Cancel { req_id }));
446 }
447
448 pub fn release_resource(&mut self, resource_id: u64) {
449 self.add(Effect::Runtime(RuntimeEffect::ReleaseResource {
450 resource_id,
451 }));
452 }
453}
454
455/// Convenience builder for the standard notification host capabilities.
456pub struct NotificationEffects<'a, 'b, S: AppState> {
457 effects: &'a mut Effects<'b, S>,
458}
459
460impl<'a, 'b, S: AppState> NotificationEffects<'a, 'b, S> {
461 /// Requests notification permission from the active host.
462 ///
463 /// `request` declares which notification features the app wants, such as
464 /// alerts, badges, sounds, or provisional delivery. The returned
465 /// `EffectBuilder` should normally bind success and error actions so the
466 /// reducer can update state after the user responds to the platform prompt.
467 pub fn request_permission(
468 self,
469 request: NotificationPermissionRequest,
470 ) -> EffectBuilder<'a, 'b, S> {
471 self.effects
472 .capability(REQUEST_NOTIFICATION_PERMISSION, request)
473 }
474
475 /// Queries the host's current notification settings without showing a prompt.
476 ///
477 /// Use this before rendering notification-dependent controls or when a
478 /// settings screen needs to explain why delivery is unavailable. The success
479 /// action receives `NotificationSettings`.
480 pub fn settings(self) -> EffectBuilder<'a, 'b, S> {
481 self.effects.capability(GET_NOTIFICATION_SETTINGS, ())
482 }
483
484 /// Shows an immediate local notification through the host.
485 ///
486 /// `request` supplies the stable notification id, visible title/body, badge,
487 /// sound policy, optional deep link, and action buttons. Use `schedule`
488 /// instead when delivery should happen at a future time.
489 pub fn show(self, request: NotificationRequest) -> EffectBuilder<'a, 'b, S> {
490 self.effects.capability(SHOW_NOTIFICATION, request)
491 }
492
493 /// Schedules a local notification for future delivery.
494 ///
495 /// The `schedule` field on `request` controls the delivery time. Hosts may
496 /// reject schedules they cannot persist, cannot deliver in the background, or
497 /// cannot map to the platform notification model.
498 pub fn schedule(self, request: NotificationRequest) -> EffectBuilder<'a, 'b, S> {
499 self.effects.capability(SCHEDULE_NOTIFICATION, request)
500 }
501
502 /// Cancels one pending or displayed notification by id.
503 ///
504 /// Use the same `NotificationId` that was used for `show` or `schedule`. A
505 /// host may treat cancelling an unknown id as success if the desired final
506 /// state is already true.
507 pub fn cancel(self, request: CancelNotificationRequest) -> EffectBuilder<'a, 'b, S> {
508 self.effects.capability(CANCEL_NOTIFICATION, request)
509 }
510
511 /// Cancels all notifications owned by this app where the host supports it.
512 ///
513 /// Use this for sign-out, workspace switching, or clearing a notification
514 /// center state that no longer matches app state. Hosts that cannot bulk
515 /// cancel should return `NotificationError`.
516 pub fn cancel_all(self) -> EffectBuilder<'a, 'b, S> {
517 self.effects.capability(CANCEL_ALL_NOTIFICATIONS, ())
518 }
519
520 /// Sets or clears the app badge count.
521 ///
522 /// `request.count = Some(n)` asks the host to show a badge count.
523 /// `request.count = None` clears the badge. Badge support varies by desktop
524 /// shell, launcher, browser, and mobile platform.
525 pub fn set_badge_count(self, request: SetBadgeCountRequest) -> EffectBuilder<'a, 'b, S> {
526 self.effects.capability(SET_BADGE_COUNT, request)
527 }
528
529 /// Registers the app for remote or push notifications.
530 ///
531 /// `request` carries provider-specific public registration inputs such as a
532 /// web push application-server key, Android sender id, or requested topics.
533 /// Secrets and store credentials belong in host configuration, not in app
534 /// state.
535 pub fn register_push(self, request: PushRegistrationRequest) -> EffectBuilder<'a, 'b, S> {
536 self.effects
537 .capability(REGISTER_PUSH_NOTIFICATIONS, request)
538 }
539
540 /// Unregisters the app from remote or push notification delivery.
541 ///
542 /// Use this during sign-out, account removal, or when a user disables remote
543 /// notifications. The host should invalidate or delete its platform token
544 /// where the provider supports that operation.
545 pub fn unregister_push(self) -> EffectBuilder<'a, 'b, S> {
546 self.effects.capability(UNREGISTER_PUSH_NOTIFICATIONS, ())
547 }
548}
549
550/// Convenience builder for standard NFC host capabilities.
551pub struct NfcEffects<'a, 'b, S: AppState> {
552 effects: &'a mut Effects<'b, S>,
553}
554
555impl<'a, 'b, S: AppState> NfcEffects<'a, 'b, S> {
556 /// Queries whether NFC is supported, enabled, and which NFC modes are available.
557 ///
558 /// Use this before showing scan/write controls so the UI can distinguish a
559 /// missing NFC chip from a disabled adapter or unsupported operation.
560 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
561 self.effects.capability(GET_NFC_AVAILABILITY, ())
562 }
563
564 /// Starts a one-shot NFC scan session.
565 ///
566 /// `request` declares allowed technologies, optional user-facing prompt text,
567 /// timeout, and whether multiple records should be collected. The success
568 /// action receives an `NfcTag` when the host reads a compatible tag.
569 pub fn scan_tag(self, request: NfcScanRequest) -> EffectBuilder<'a, 'b, S> {
570 self.effects.capability(SCAN_NFC_TAG, request)
571 }
572
573 /// Starts an NFC tag write session.
574 ///
575 /// `request.records` contains the portable NDEF-like records to write. Hosts
576 /// may require the user to tap a writable tag after the operation starts and
577 /// may reject read-only or incompatible tags.
578 pub fn write_tag(self, request: NfcWriteRequest) -> EffectBuilder<'a, 'b, S> {
579 self.effects.capability(WRITE_NFC_TAG, request)
580 }
581
582 /// Requests host NFC card emulation for the supplied records.
583 ///
584 /// Use this only on platforms and devices that support card emulation for
585 /// the product scenario. Many hosts support scanning but not emulation.
586 pub fn emulate_tag(self, request: NfcEmulationRequest) -> EffectBuilder<'a, 'b, S> {
587 self.effects.capability(EMULATE_NFC_TAG, request)
588 }
589
590 /// Cancels the active NFC session, if one is running.
591 ///
592 /// Use this when the user dismisses the screen that started scanning, writing,
593 /// or emulation. Hosts may return success when no session is active.
594 pub fn cancel_session(self) -> EffectBuilder<'a, 'b, S> {
595 self.effects.capability(CANCEL_NFC_SESSION, ())
596 }
597}
598
599/// Convenience builder for standard biometric host capabilities.
600pub struct BiometricEffects<'a, 'b, S: AppState> {
601 effects: &'a mut Effects<'b, S>,
602}
603
604impl<'a, 'b, S: AppState> BiometricEffects<'a, 'b, S> {
605 /// Queries local biometric support and enrollment state.
606 ///
607 /// Use this before presenting a biometric-only path. The result tells the app
608 /// whether the host supports biometric verification, whether credentials are
609 /// enrolled, which modalities may be available, and whether device credential
610 /// fallback is possible.
611 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
612 self.effects.capability(GET_BIOMETRIC_AVAILABILITY, ())
613 }
614
615 /// Asks the host to authenticate the current local user.
616 ///
617 /// `request.reason` should explain why verification is needed before the
618 /// platform prompt appears. The success action receives
619 /// `BiometricAuthenticateResult`, which reports the modality and whether a
620 /// device credential fallback was used.
621 pub fn authenticate(self, request: BiometricAuthenticateRequest) -> EffectBuilder<'a, 'b, S> {
622 self.effects.capability(AUTHENTICATE_BIOMETRIC, request)
623 }
624
625 /// Cancels an active biometric authentication prompt where the host permits it.
626 ///
627 /// Use this when the screen that requested verification is closed or the app
628 /// changes state before the user responds. Some platform prompts cannot be
629 /// cancelled programmatically after display.
630 pub fn cancel_authentication(self) -> EffectBuilder<'a, 'b, S> {
631 self.effects.capability(CANCEL_BIOMETRIC_AUTHENTICATION, ())
632 }
633}
634
635/// Convenience builder for standard passkey/WebAuthn host capabilities.
636pub struct PasskeyEffects<'a, 'b, S: AppState> {
637 effects: &'a mut Effects<'b, S>,
638}
639
640impl<'a, 'b, S: AppState> PasskeyEffects<'a, 'b, S> {
641 /// Queries passkey support for the active host and origin.
642 ///
643 /// Use this before showing passkey-specific registration or sign-in controls.
644 /// The result tells the app whether the host supports passkeys, whether the
645 /// current context is secure enough for credential APIs, and whether platform
646 /// or conditional UI authenticators may be available.
647 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
648 self.effects.capability(GET_PASSKEY_AVAILABILITY, ())
649 }
650
651 /// Requests creation of a new passkey credential.
652 ///
653 /// `request.challenge` must come from the relying-party server and must be
654 /// verified by that server when the success action receives
655 /// `PasskeyRegistrationResult`. Do not generate production challenges in the
656 /// UI reducer or trust registration data until the backend verifies it.
657 pub fn register(self, request: PasskeyRegistrationRequest) -> EffectBuilder<'a, 'b, S> {
658 self.effects.capability(REGISTER_PASSKEY, request)
659 }
660
661 /// Requests authentication with an existing passkey credential.
662 ///
663 /// `request.challenge` must come from the server, and the returned
664 /// `PasskeyAuthenticationResult` must be verified by the server before the
665 /// app treats the user as signed in. The host only gathers credential data.
666 pub fn authenticate(self, request: PasskeyAuthenticationRequest) -> EffectBuilder<'a, 'b, S> {
667 self.effects.capability(AUTHENTICATE_PASSKEY, request)
668 }
669
670 /// Cancels an active passkey prompt where the host permits cancellation.
671 ///
672 /// Use this when the sign-in or registration screen disappears before the
673 /// host credential picker completes. Some browser or operating-system
674 /// prompts cannot be cancelled once shown.
675 pub fn cancel(self) -> EffectBuilder<'a, 'b, S> {
676 self.effects.capability(CANCEL_PASSKEY_OPERATION, ())
677 }
678}
679
680/// Convenience builder for standard Bluetooth host capabilities.
681pub struct BluetoothEffects<'a, 'b, S: AppState> {
682 effects: &'a mut Effects<'b, S>,
683}
684
685impl<'a, 'b, S: AppState> BluetoothEffects<'a, 'b, S> {
686 /// Queries Bluetooth adapter, permission, and mode availability.
687 ///
688 /// Use this before showing scan, connect, or advertise controls. The result
689 /// lets the UI distinguish missing hardware, disabled Bluetooth, permission
690 /// denial, and hosts that support only classic or Low Energy Bluetooth.
691 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
692 self.effects.capability(GET_BLUETOOTH_AVAILABILITY, ())
693 }
694
695 /// Requests Bluetooth or nearby-device permission from the host.
696 ///
697 /// `request.reason` should explain the product feature that needs nearby
698 /// devices. Hosts map the request to the platform permission model, which may
699 /// include Bluetooth, location, or nearby-device prompts depending on target.
700 pub fn request_permission(
701 self,
702 request: BluetoothPermissionRequest,
703 ) -> EffectBuilder<'a, 'b, S> {
704 self.effects
705 .capability(REQUEST_BLUETOOTH_PERMISSION, request)
706 }
707
708 /// Scans for Bluetooth devices matching the request filters.
709 ///
710 /// `request.service_uuids` narrows discovery to product-relevant services.
711 /// `timeout_ms` should be set for user-driven scans so the host does not keep
712 /// nearby-device discovery running indefinitely.
713 pub fn scan_devices(self, request: BluetoothScanRequest) -> EffectBuilder<'a, 'b, S> {
714 self.effects.capability(SCAN_BLUETOOTH_DEVICES, request)
715 }
716
717 /// Connects to a discovered or previously known Bluetooth device.
718 ///
719 /// `request.device_id` must come from a trusted host result or stored pairing
720 /// flow. The success action receives a `BluetoothConnection` whose
721 /// `connection_id` is used for later read, write, and disconnect requests.
722 pub fn connect_device(self, request: BluetoothConnectRequest) -> EffectBuilder<'a, 'b, S> {
723 self.effects.capability(CONNECT_BLUETOOTH_DEVICE, request)
724 }
725
726 /// Disconnects a previously opened Bluetooth connection.
727 ///
728 /// `request.connection_id` should be the id returned by `connect_device`.
729 /// Use this when the user leaves the device workflow or when the app no
730 /// longer needs the peripheral.
731 pub fn disconnect_device(
732 self,
733 request: BluetoothDisconnectRequest,
734 ) -> EffectBuilder<'a, 'b, S> {
735 self.effects
736 .capability(DISCONNECT_BLUETOOTH_DEVICE, request)
737 }
738
739 /// Reads one Bluetooth characteristic from an active connection.
740 ///
741 /// `request` names the connection, service UUID, and characteristic UUID.
742 /// Hosts should return `BluetoothError` when the connection is gone or the
743 /// characteristic is unavailable.
744 pub fn read_characteristic(self, request: BluetoothReadRequest) -> EffectBuilder<'a, 'b, S> {
745 self.effects
746 .capability(READ_BLUETOOTH_CHARACTERISTIC, request)
747 }
748
749 /// Writes bytes to one Bluetooth characteristic.
750 ///
751 /// `request.with_response` lets the app choose between acknowledged and
752 /// unacknowledged writes where the platform supports both. Reducers should
753 /// still handle connection loss and permission errors as normal outcomes.
754 pub fn write_characteristic(self, request: BluetoothWriteRequest) -> EffectBuilder<'a, 'b, S> {
755 self.effects
756 .capability(WRITE_BLUETOOTH_CHARACTERISTIC, request)
757 }
758
759 /// Starts Bluetooth advertising for hosts that allow apps to advertise.
760 ///
761 /// `request` supplies the service UUID, optional service data, display name,
762 /// and timeout. Mobile and browser platforms often restrict advertising more
763 /// heavily than scanning or connecting.
764 pub fn start_advertising(self, request: BluetoothAdvertiseRequest) -> EffectBuilder<'a, 'b, S> {
765 self.effects
766 .capability(START_BLUETOOTH_ADVERTISING, request)
767 }
768
769 /// Stops a Bluetooth advertising session.
770 ///
771 /// `request.advertisement_id` should be the id returned by
772 /// `start_advertising`. Hosts may also stop advertisements automatically when
773 /// their timeout expires or the app moves to a background state.
774 pub fn stop_advertising(
775 self,
776 request: BluetoothStopAdvertiseRequest,
777 ) -> EffectBuilder<'a, 'b, S> {
778 self.effects.capability(STOP_BLUETOOTH_ADVERTISING, request)
779 }
780}
781
782/// Convenience builder for standard barcode scanner host capabilities.
783pub struct BarcodeScannerEffects<'a, 'b, S: AppState> {
784 effects: &'a mut Effects<'b, S>,
785}
786
787impl<'a, 'b, S: AppState> BarcodeScannerEffects<'a, 'b, S> {
788 /// Starts a live barcode scanning session.
789 ///
790 /// `request.formats` should list only formats the product accepts. The host
791 /// may open a camera UI, display `prompt`, and return one or more decoded
792 /// barcode values depending on `allow_multiple`.
793 pub fn scan(self, request: BarcodeScanRequest) -> EffectBuilder<'a, 'b, S> {
794 self.effects.capability(SCAN_BARCODE, request)
795 }
796
797 /// Decodes barcode data from image bytes supplied by the app.
798 ///
799 /// Use this when the image already exists, such as a file import or camera
800 /// frame captured elsewhere. The host should not request camera permission
801 /// for this operation unless its decoder specifically requires it.
802 pub fn decode_image(self, request: BarcodeImageDecodeRequest) -> EffectBuilder<'a, 'b, S> {
803 self.effects.capability(DECODE_BARCODE_IMAGE, request)
804 }
805
806 /// Cancels the active live barcode scanning session.
807 ///
808 /// Use this when the user leaves the scanning screen or chooses another input
809 /// path. Hosts may treat cancellation of a non-running session as success.
810 pub fn cancel_scan(self) -> EffectBuilder<'a, 'b, S> {
811 self.effects.capability(CANCEL_BARCODE_SCAN, ())
812 }
813}
814
815/// Convenience builder for standard camera host capabilities.
816pub struct CameraEffects<'a, 'b, S: AppState> {
817 effects: &'a mut Effects<'b, S>,
818}
819
820impl<'a, 'b, S: AppState> CameraEffects<'a, 'b, S> {
821 /// Queries camera permission and available camera devices.
822 ///
823 /// Use this before showing camera-specific controls. The result contains the
824 /// current permission state and host-visible devices, including facing
825 /// direction and flashlight availability where known.
826 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
827 self.effects.capability(GET_CAMERA_AVAILABILITY, ())
828 }
829
830 /// Requests camera permission from the host.
831 ///
832 /// `request.reason` can carry product-facing context for hosts that support a
833 /// pre-prompt or custom rationale. The success action receives the resulting
834 /// `CameraPermission` state.
835 pub fn request_permission(self, request: CameraPermissionRequest) -> EffectBuilder<'a, 'b, S> {
836 self.effects.capability(REQUEST_CAMERA_PERMISSION, request)
837 }
838
839 /// Captures a still photo through the selected host camera.
840 ///
841 /// `request` chooses camera id or facing direction, optional resolution, image
842 /// format, flash behavior, and quality. The success action receives image
843 /// bytes plus dimensions and content type.
844 pub fn capture_photo(self, request: CameraCaptureRequest) -> EffectBuilder<'a, 'b, S> {
845 self.effects.capability(CAPTURE_PHOTO, request)
846 }
847
848 /// Enables, disables, or adjusts the camera flashlight where supported.
849 ///
850 /// `request.camera_id` selects the device, `enabled` chooses the desired
851 /// state, and `intensity` optionally requests a platform-specific brightness
852 /// level from 0 to 100. Many desktop cameras have no torch.
853 pub fn set_flashlight(self, request: CameraFlashlightRequest) -> EffectBuilder<'a, 'b, S> {
854 self.effects.capability(SET_CAMERA_FLASHLIGHT, request)
855 }
856
857 /// Cancels an active camera capture session.
858 ///
859 /// Use this when the user dismisses the camera flow before a photo is
860 /// returned. Hosts may return success when there is no active capture.
861 pub fn cancel_capture(self) -> EffectBuilder<'a, 'b, S> {
862 self.effects.capability(CANCEL_CAMERA_CAPTURE, ())
863 }
864}
865
866/// Convenience builder for standard clipboard host capabilities.
867pub struct ClipboardEffects<'a, 'b, S: AppState> {
868 effects: &'a mut Effects<'b, S>,
869}
870
871impl<'a, 'b, S: AppState> ClipboardEffects<'a, 'b, S> {
872 /// Reads text from the host clipboard.
873 ///
874 /// Use this in response to an explicit paste action. The success action
875 /// receives `ClipboardText` with `None` when there is no readable text.
876 pub fn read_text(self) -> EffectBuilder<'a, 'b, S> {
877 self.effects.capability(READ_CLIPBOARD_TEXT, ())
878 }
879
880 /// Writes plain text to the host clipboard.
881 ///
882 /// `request.text` should be the exact text the user asked to copy. Some hosts
883 /// may require focus or a user gesture before accepting the write.
884 pub fn write_text(self, request: ClipboardWriteTextRequest) -> EffectBuilder<'a, 'b, S> {
885 self.effects.capability(WRITE_CLIPBOARD_TEXT, request)
886 }
887
888 /// Reads typed clipboard content from the host.
889 ///
890 /// Use this when the product can accept richer content than plain text. The
891 /// success action receives zero or more `ClipboardItem` values with content
892 /// types and bytes.
893 pub fn read_content(self) -> EffectBuilder<'a, 'b, S> {
894 self.effects.capability(READ_CLIPBOARD_CONTENT, ())
895 }
896
897 /// Writes typed content items to the host clipboard.
898 ///
899 /// `request.items` should list content types the target host can expose.
900 /// Include a `text/plain` item when possible so paste targets have a portable
901 /// fallback.
902 pub fn write_content(self, request: ClipboardContent) -> EffectBuilder<'a, 'b, S> {
903 self.effects.capability(WRITE_CLIPBOARD_CONTENT, request)
904 }
905
906 /// Clears app-visible clipboard content where the host supports it.
907 ///
908 /// Use this for explicit privacy actions such as Clear copied password. Some
909 /// platforms may not allow apps to clear global clipboard state.
910 pub fn clear(self) -> EffectBuilder<'a, 'b, S> {
911 self.effects.capability(CLEAR_CLIPBOARD, ())
912 }
913}
914
915/// Convenience builder for standard geolocation host capabilities.
916pub struct GeolocationEffects<'a, 'b, S: AppState> {
917 effects: &'a mut Effects<'b, S>,
918}
919
920impl<'a, 'b, S: AppState> GeolocationEffects<'a, 'b, S> {
921 /// Reads the current geolocation permission state without showing a prompt.
922 ///
923 /// Use this to decide whether a screen should show a request button, an
924 /// explanation, or a platform-settings hint. The result is host-reported and
925 /// may change outside the app.
926 pub fn permission(self) -> EffectBuilder<'a, 'b, S> {
927 self.effects.capability(GET_GEOLOCATION_PERMISSION, ())
928 }
929
930 /// Requests geolocation permission from the host.
931 ///
932 /// `request.precise` asks for precise coordinates when the platform exposes a
933 /// precise/approximate distinction. `request.background` should only be set
934 /// for product flows that genuinely need background location and have matching
935 /// platform configuration.
936 pub fn request_permission(
937 self,
938 request: GeolocationPermissionRequest,
939 ) -> EffectBuilder<'a, 'b, S> {
940 self.effects
941 .capability(REQUEST_GEOLOCATION_PERMISSION, request)
942 }
943
944 /// Requests the current location from the host.
945 ///
946 /// `request.high_accuracy`, `timeout_ms`, and `maximum_age_ms` let the app
947 /// trade precision, speed, power use, and cached values. The success action
948 /// receives latitude, longitude, accuracy, and optional motion metadata.
949 pub fn current_position(self, request: GeolocationPositionRequest) -> EffectBuilder<'a, 'b, S> {
950 self.effects.capability(GET_CURRENT_POSITION, request)
951 }
952}
953
954/// Convenience builder for standard haptic host capabilities.
955pub struct HapticEffects<'a, 'b, S: AppState> {
956 effects: &'a mut Effects<'b, S>,
957}
958
959impl<'a, 'b, S: AppState> HapticEffects<'a, 'b, S> {
960 /// Plays impact-style haptic feedback.
961 ///
962 /// Use this for physical-feeling interactions such as completing a drag,
963 /// snapping to a position, or confirming a strong action. The `style` field
964 /// tells the host how heavy the feedback should feel.
965 pub fn impact(self, request: HapticImpactRequest) -> EffectBuilder<'a, 'b, S> {
966 self.effects.capability(HAPTIC_IMPACT, request)
967 }
968
969 /// Plays notification-style haptic feedback.
970 ///
971 /// Use this to reinforce success, warning, or error states when tactile
972 /// feedback improves understanding. It should not replace visible or spoken
973 /// feedback for accessibility.
974 pub fn notification(self, request: HapticNotificationRequest) -> EffectBuilder<'a, 'b, S> {
975 self.effects.capability(HAPTIC_NOTIFICATION, request)
976 }
977
978 /// Plays selection-change haptic feedback.
979 ///
980 /// Use this for picker movement, segmented-control changes, or other repeated
981 /// selection adjustments where a light tick helps the user track movement.
982 pub fn selection(self) -> EffectBuilder<'a, 'b, S> {
983 self.effects.capability(HAPTIC_SELECTION, ())
984 }
985
986 /// Plays a bounded custom haptic pattern.
987 ///
988 /// `request.steps` contains duration and intensity values. Keep patterns
989 /// short and meaningful; hosts may reject long, empty, or unsupported
990 /// patterns.
991 pub fn pattern(self, request: HapticPatternRequest) -> EffectBuilder<'a, 'b, S> {
992 self.effects.capability(HAPTIC_PATTERN, request)
993 }
994}
995
996/// Convenience builder for standard microphone host capabilities.
997pub struct MicrophoneEffects<'a, 'b, S: AppState> {
998 effects: &'a mut Effects<'b, S>,
999}
1000
1001impl<'a, 'b, S: AppState> MicrophoneEffects<'a, 'b, S> {
1002 /// Queries microphone permission and available input devices.
1003 ///
1004 /// Use this before showing recording controls. The result tells the app
1005 /// whether microphone permission is granted and which host input devices are
1006 /// visible.
1007 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
1008 self.effects.capability(GET_MICROPHONE_AVAILABILITY, ())
1009 }
1010
1011 /// Requests microphone permission from the host.
1012 ///
1013 /// `request.reason` can be used by hosts that support a product-specific
1014 /// rationale before the platform prompt. The success action receives the
1015 /// resulting `MicrophonePermission` state.
1016 pub fn request_permission(
1017 self,
1018 request: MicrophonePermissionRequest,
1019 ) -> EffectBuilder<'a, 'b, S> {
1020 self.effects
1021 .capability(REQUEST_MICROPHONE_PERMISSION, request)
1022 }
1023
1024 /// Captures bounded audio from the selected microphone.
1025 ///
1026 /// `request.duration_ms` must define the intended capture length. Optional
1027 /// sample rate, channel count, and sample format let the host choose the
1028 /// closest supported recording configuration.
1029 pub fn capture_audio(self, request: MicrophoneCaptureRequest) -> EffectBuilder<'a, 'b, S> {
1030 self.effects.capability(CAPTURE_MICROPHONE_AUDIO, request)
1031 }
1032
1033 /// Cancels an active microphone capture session.
1034 ///
1035 /// Use this when the user stops recording, closes the screen, or chooses a
1036 /// different input path before the bounded capture completes.
1037 pub fn cancel_capture(self) -> EffectBuilder<'a, 'b, S> {
1038 self.effects.capability(CANCEL_MICROPHONE_CAPTURE, ())
1039 }
1040}
1041
1042/// Convenience builder for standard Wi-Fi host capabilities.
1043pub struct WifiEffects<'a, 'b, S: AppState> {
1044 effects: &'a mut Effects<'b, S>,
1045}
1046
1047impl<'a, 'b, S: AppState> WifiEffects<'a, 'b, S> {
1048 /// Queries current Wi-Fi adapter and connection availability.
1049 ///
1050 /// Use this before showing scan or connect controls. The result can include
1051 /// whether the adapter is enabled and which network, if any, is connected.
1052 pub fn availability(self) -> EffectBuilder<'a, 'b, S> {
1053 self.effects.capability(GET_WIFI_AVAILABILITY, ())
1054 }
1055
1056 /// Requests Wi-Fi or nearby-network permission from the host.
1057 ///
1058 /// `request.reason` should describe the feature that needs network discovery
1059 /// or management. Hosts may map this to Wi-Fi, nearby-device, or location
1060 /// permission prompts depending on platform policy.
1061 pub fn request_permission(self, request: WifiPermissionRequest) -> EffectBuilder<'a, 'b, S> {
1062 self.effects.capability(REQUEST_WIFI_PERMISSION, request)
1063 }
1064
1065 /// Scans for nearby Wi-Fi networks where the host permits scanning.
1066 ///
1067 /// `request.ssid_prefix` narrows results for device-setup flows,
1068 /// `include_hidden` asks the host to include hidden networks when possible,
1069 /// and `timeout_ms` bounds the scan.
1070 pub fn scan_networks(self, request: WifiScanRequest) -> EffectBuilder<'a, 'b, S> {
1071 self.effects.capability(SCAN_WIFI_NETWORKS, request)
1072 }
1073
1074 /// Requests connection to one Wi-Fi network.
1075 ///
1076 /// `request` carries SSID, optional passphrase, security type, and hidden
1077 /// network flag. Hosts may reject connections that require user confirmation,
1078 /// saved network profiles, entitlements, or administrator privileges.
1079 pub fn connect_network(self, request: WifiConnectRequest) -> EffectBuilder<'a, 'b, S> {
1080 self.effects.capability(CONNECT_WIFI_NETWORK, request)
1081 }
1082
1083 /// Requests disconnection from a Wi-Fi network.
1084 ///
1085 /// `request.ssid` can limit the operation to a specific network when the host
1086 /// supports that distinction. Some platforms do not allow apps to disconnect
1087 /// global network state.
1088 pub fn disconnect_network(self, request: WifiDisconnectRequest) -> EffectBuilder<'a, 'b, S> {
1089 self.effects.capability(DISCONNECT_WIFI_NETWORK, request)
1090 }
1091}
1092
1093/// Convenience builder for standard volume-control host capabilities.
1094pub struct VolumeEffects<'a, 'b, S: AppState> {
1095 effects: &'a mut Effects<'b, S>,
1096}
1097
1098impl<'a, 'b, S: AppState> VolumeEffects<'a, 'b, S> {
1099 /// Reads the current level for one host volume stream.
1100 ///
1101 /// `stream` identifies the logical audio stream the app cares about. Hosts
1102 /// map that stream to the closest platform mixer or media channel and return
1103 /// a `VolumeLevel` with level and mute state.
1104 pub fn get_level(self, stream: VolumeStream) -> EffectBuilder<'a, 'b, S> {
1105 self.effects.capability(GET_VOLUME_LEVEL, stream)
1106 }
1107
1108 /// Sets the level and optional mute state for one host volume stream.
1109 ///
1110 /// `request.level` is a percentage-like value from 0 to 100. Hosts should
1111 /// clamp or reject values they cannot represent and return a typed error when
1112 /// the platform does not expose system volume control.
1113 pub fn set_level(self, request: VolumeSetRequest) -> EffectBuilder<'a, 'b, S> {
1114 self.effects.capability(SET_VOLUME_LEVEL, request)
1115 }
1116
1117 /// Adjusts a host volume stream relative to its current level.
1118 ///
1119 /// `request.direction` chooses increase, decrease, or toggle mute, and
1120 /// `request.step` controls the requested amount. Use this for keyboard-like
1121 /// or remote-control volume actions.
1122 pub fn adjust_level(self, request: VolumeAdjustRequest) -> EffectBuilder<'a, 'b, S> {
1123 self.effects.capability(ADJUST_VOLUME_LEVEL, request)
1124 }
1125}
1126
1127/// Fluent builder returned by [`Effects::capability`], [`Effects::app`], and
1128/// related effect constructors.
1129///
1130/// Attach `on_ok` and `on_err` callback envelopes before the builder is dropped.
1131///
1132/// # Example
1133///
1134/// ```rust,ignore
1135/// ctx.effects.capability(MY_CAPABILITY, request)
1136/// .on_ok(ok_envelope)
1137/// .on_err(err_envelope)
1138/// .dispatch(); // optional -- dropping also finalises
1139/// ```
1140pub struct EffectBuilder<'a, 'b, S: AppState> {
1141 effects: &'a mut Effects<'b, S>,
1142 index: usize,
1143}
1144
1145impl<'a, 'b, S: AppState> EffectBuilder<'a, 'b, S> {
1146 pub fn on_ok(self, action: ActionEnvelope) -> Self {
1147 self.effects.out[self.index].on_ok = Some(action);
1148 self
1149 }
1150
1151 pub fn on_err(self, action: ActionEnvelope) -> Self {
1152 self.effects.out[self.index].on_err = Some(action);
1153 self
1154 }
1155
1156 pub fn dispatch(self) {
1157 // Drop
1158 }
1159}
1160
1161pub struct ServiceStartBuilder<'a, 'b, S: AppState> {
1162 effects: &'a mut Effects<'b, S>,
1163 index: usize,
1164}
1165
1166impl<'a, 'b, S: AppState> ServiceStartBuilder<'a, 'b, S> {
1167 pub fn on_started(self, action: ActionEnvelope) -> Self {
1168 if let Some(bindings) = self.effects.out[self.index].service_bindings.as_mut() {
1169 bindings.on_started = Some(action);
1170 }
1171 self
1172 }
1173
1174 pub fn on_start_failed(self, action: ActionEnvelope) -> Self {
1175 if let Some(bindings) = self.effects.out[self.index].service_bindings.as_mut() {
1176 bindings.on_start_failed = Some(action);
1177 }
1178 self
1179 }
1180
1181 pub fn on_event(self, action: ActionEnvelope) -> Self {
1182 if let Some(bindings) = self.effects.out[self.index].service_bindings.as_mut() {
1183 bindings.on_event = Some(action);
1184 }
1185 self
1186 }
1187
1188 pub fn on_stopped(self, action: ActionEnvelope) -> Self {
1189 if let Some(bindings) = self.effects.out[self.index].service_bindings.as_mut() {
1190 bindings.on_stopped = Some(action);
1191 }
1192 self
1193 }
1194
1195 pub fn on_command_ok(self, action: ActionEnvelope) -> Self {
1196 if let Some(bindings) = self.effects.out[self.index].service_bindings.as_mut() {
1197 bindings.on_command_ok = Some(action);
1198 }
1199 self
1200 }
1201
1202 pub fn on_command_err(self, action: ActionEnvelope) -> Self {
1203 if let Some(bindings) = self.effects.out[self.index].service_bindings.as_mut() {
1204 bindings.on_command_err = Some(action);
1205 }
1206 self
1207 }
1208
1209 pub fn dispatch(self) {}
1210}