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>
impl<T> Attribute<T>
Sourcepub fn new(name: impl Into<String>) -> Self
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.
Sourcepub fn indexed(self, index: AttributeIndex) -> Self
pub fn indexed(self, index: AttributeIndex) -> Self
Enables Flow-search indexing with the selected index type.
Sourcepub fn sync_to_attribute_store(self) -> Self
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.
Sourcepub fn hard_delete_on_null(self) -> Selfwhere
T: 'static,
pub fn hard_delete_on_null(self) -> Selfwhere
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::indexedorSelf::sync_to_attribute_store. Registration returnsSdkError::FlowDefinitionfor either combination. Client::wait_for_attribute_matchrejects a hard-delete Attribute withSdkError::InvalidArgumentbefore 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_optionsand 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_nullfrom 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)
}Sourcepub fn get(&self, context: &Context) -> HandlerResult<Option<T>>where
T: Value,
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.
Sourcepub fn get_required(&self, context: &Context) -> HandlerResult<T>where
T: Value,
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.
Sourcepub fn set(&self, context: &mut Context, value: T) -> HandlerResult<()>where
T: Value,
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.
Sourcepub fn delete(&self, context: &mut Context) -> HandlerResult<()>
pub fn delete(&self, context: &mut Context) -> HandlerResult<()>
Stages deletion of the current value.
Deleting a missing Attribute is valid.
Sourcepub fn lock(&self) -> AttributeLock
pub fn lock(&self) -> AttributeLock
Creates a lock request for Step options or an RPC definition.
Trait Implementations§
Auto Trait Implementations§
impl<T> Freeze for Attribute<T>
impl<T> RefUnwindSafe for Attribute<T>
impl<T> Send for Attribute<T>
impl<T> Sync for Attribute<T>
impl<T> Unpin for Attribute<T>
impl<T> UnsafeUnpin for Attribute<T>
impl<T> UnwindSafe for Attribute<T>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request