Skip to main content

Attribute

Struct Attribute 

Source
pub struct Attribute<T> { /* private fields */ }
Expand description

Defines one durable, typed value stored with a Flow.

Declare Attributes on the Flow, add them to crate::PersistenceSchema, and reuse the same definition from Step and RPC code. Values use Serde serialization. get distinguishes a missing value with None; get_required converts absence into HandlerError.

§Examples

use dex_sdk::{Attribute, AttributeIndex};

let status = Attribute::<String>::new("status")
    .indexed(AttributeIndex::keyword());

Implementations§

Source§

impl<T> Attribute<T>

Source

pub fn new(name: impl Into<String>) -> Self

Defines an unindexed Attribute with a stable name.

Slash is prohibited because it is a reserved character.

Source

pub fn indexed(self, index: AttributeIndex) -> Self

Enables Flow-search indexing with the selected index type.

Source

pub fn sync_to_attribute_store(self) -> Self

Enables asynchronous latest-state projection to the Flow’s selected Attribute Store.

The Flow must select a server-configured store with FlowConfig::attribute_store_names. Deletions project SQL NULL, and projection failures never roll back the Flow Attribute write.

Source

pub fn hard_delete_on_null(self) -> Self
where T: 'static,

Stores this Attribute’s values only in the Dex blob store and permanently deletes the stored value when the Attribute is deleted or set to null.

Use this option for short-lived secrets, such as API keys or access tokens. Dex never writes the value into Flow history, regardless of its size. Flow history keeps only a reference to the stored value. Self::delete, Context::delete_attribute, and a write whose value encodes to null permanently delete the stored value before the write commits. A deleted value reads as an absent Attribute, so Self::get returns Ok(None).

Step handlers, RPC handlers, and Attribute reads receive the value inline. The SDK never stores the value in the local BlobCache.

§Supported values

Only values that encode to a string or an object are supported. Strings, Serde structs, maps, sequences, and bytes are supported. Numeric and Boolean values are rejected. Registry::register returns SdkError::FlowDefinition when T is a Rust integer, floating-point, or bool type, or an Option of one. Every write also checks its encoded value. A value of another type that encodes to a number or a Boolean, such as a newtype around u64, fails when it is written. Deletion and null values are always allowed.

To store a numeric or Boolean secret, wrap it in a struct so that it encodes as an object:

use dex_sdk::Attribute;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct OneTimeCode {
    code: u32,
}

let one_time_code = Attribute::<OneTimeCode>::new("one-time-code").hard_delete_on_null();
§Restrictions
  • This option cannot be combined with Self::indexed or Self::sync_to_attribute_store. Registration returns SdkError::FlowDefinition for either combination.
  • Client::wait_for_attribute_match rejects a hard-delete Attribute with SdkError::InvalidArgument before it contacts Dex.
  • Action and Action permission conditions cannot read a hard-delete Attribute. Dex matches those conditions against stored Attribute values, which hard-delete Attributes never keep in Flow history. Use an ordinary Attribute as the condition source instead.
  • Every write must use a definition whose hard-delete setting matches the definition that the Flow registered. A mismatched write is rejected.
  • The Dex Server must have an enabled blob store. The Server rejects writes when its blob store is disabled.
  • The Dex Server must support protocol version 3 or later. A Worker learns the protocol version when it starts. A Client requests it once, before its first hard-delete write, and reuses the result. Writes fail with an error when the Server is older.
§Limitations
  • Each Flow keeps one stored copy per Attribute key. Every write overwrites it in place.
  • A Step or RPC attempt writes or deletes the stored value before Dex commits the attempt’s result. A retried attempt, other Steps, and Attribute reads can therefore observe a write or deletion from an attempt that later failed.
  • When concurrent Steps or RPCs write the same key, the final stored value may not be the value of the last committed write.
  • A Flow start can fail because a Flow with the same Flow ID is already running. That failed start still overwrites the running Flow’s stored value. A null initial value deletes it. This applies to Client::start_flow_with_options and to SubFlow starts.
  • Executions that reuse a Flow ID share the stored value.
  • Resetting a Flow does not restore a deleted value. A deleted value reads as absent.
  • A Flow retry or a restarted SubFlow starts from the original initial value. If an earlier run deleted that value, the new run reads it as absent.
  • Removing hard_delete_on_null from a definition leaves the stored value in place until Dex removes the Flow. Delete the value with the hard-delete definition before removing it.
  • Stored values remain until they are deleted, or until Dex removes the Flow through retention and blob cleanup.
  • Blob store backups and bucket versioning can retain copies of stored values.
§Examples
use dex_sdk::{Attribute, Context, HandlerResult, PersistenceSchema, StartFlowOptions};
use std::sync::LazyLock;

static API_KEY: LazyLock<Attribute<String>> =
    LazyLock::new(|| Attribute::new("api-key").hard_delete_on_null());

// Register the definition in the Flow's persistence schema.
let schema = PersistenceSchema::new().attribute(&API_KEY);

// Supply the first secret when the Flow starts.
let options = StartFlowOptions::new().initial_attribute(&API_KEY, "key-1".to_owned());

// A Step reads the secret inline and rotates it.
fn rotate_api_key(context: &mut Context) -> HandlerResult<()> {
    let current = API_KEY.get_required(context)?;
    API_KEY.set(context, format!("{current}-rotated"))
}

// A later Step permanently deletes the stored secret.
fn revoke_api_key(context: &mut Context) -> HandlerResult<()> {
    API_KEY.delete(context)
}
Source

pub fn get(&self, context: &Context) -> HandlerResult<Option<T>>
where T: Value,

Reads the current value from a Step or RPC invocation.

Returns Ok(None) when no value is stored.

§Errors

Returns HandlerError when the payload cannot be decoded.

Source

pub fn get_required(&self, context: &Context) -> HandlerResult<T>
where T: Value,

Reads the current value and treats absence as an error.

§Errors

Returns HandlerError when the Attribute is missing or cannot be decoded.

Source

pub fn set(&self, context: &mut Context, value: T) -> HandlerResult<()>
where T: Value,

Stages value for durable persistence in the current invocation.

§Errors

Returns HandlerError when the value cannot be encoded or indexed.

Source

pub fn delete(&self, context: &mut Context) -> HandlerResult<()>

Stages deletion of the current value.

Deleting a missing Attribute is valid.

Source

pub fn lock(&self) -> AttributeLock

Creates a lock request for Step options or an RPC definition.

Trait Implementations§

Source§

impl<T> Clone for Attribute<T>

Source§

fn clone(&self) -> Self

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

Auto Trait Implementations§

§

impl<T> Freeze for Attribute<T>
where PhantomData<fn() -> T>: Freeze,

§

impl<T> RefUnwindSafe for Attribute<T>
where PhantomData<fn() -> T>: RefUnwindSafe,

§

impl<T> Send for Attribute<T>
where PhantomData<fn() -> T>: Send,

§

impl<T> Sync for Attribute<T>
where PhantomData<fn() -> T>: Sync,

§

impl<T> Unpin for Attribute<T>
where PhantomData<fn() -> T>: Unpin,

§

impl<T> UnsafeUnpin for Attribute<T>
where PhantomData<fn() -> T>: UnsafeUnpin,

§

impl<T> UnwindSafe for Attribute<T>
where PhantomData<fn() -> T>: UnwindSafe,

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> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
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> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
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 = !

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

fn try_from(value: U) -> Result<T, !>

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