pub struct StateSerialization { /* private fields */ }Expand description
Process-global field encoders, field decoders and tagged JSON decoders.
Registrations are exact names and replacements affect the next operation.
Each operation retains one registry snapshot and invokes callbacks without
holding its lock. The only construction entrypoint is Self::global.
use app_kit::workflow::StateSerialization;
StateSerialization::global().register_tag("text".into(), |value| {
value.get("value").cloned().ok_or("missing text value")
})?;Only field/tag JSON codecs enter this registry; actions, hooks, child executors and lower backends remain concrete configuration values. Registration does not persist callable identity; callers version their own portable codec payloads.
Implementations§
Source§impl StateSerialization
impl StateSerialization
Sourcepub fn global() -> &'static Self
pub fn global() -> &'static Self
Returns the one process-global registry, shared across all state owners.
use app_kit::workflow::{StateSerialization, WorkflowState};
assert!(std::ptr::eq(StateSerialization::global(), StateSerialization::global()));
StateSerialization::global().register_tag("global_counter".into(), |value| {
value.get("count").cloned().ok_or("missing count")
})?;
let encoded = serde_json::from_str(r#"{"count":{"__burr_serde__":"global_counter","count":3}}"#)?;
assert_eq!(WorkflowState::deserialize(encoded)?.values()["count"], 3);Examples found in repository?
55fn main() -> Result<(), AppError> {
56 let policy = CountPolicy { maximum: 100 };
57 let initial = WorkflowState::new(Map::from_iter([
58 ("count".into(), json!(1)),
59 ("labels".into(), json!([])),
60 ]))?;
61 let counted = initial.apply(
62 &StateChange::Increment {
63 values: vec![("count".into(), json!(3)), ("count".into(), json!(4))],
64 },
65 &policy,
66 )?;
67 let labeled = counted.apply(
68 &StateChange::Append {
69 values: vec![
70 ("labels".into(), json!("draft")),
71 ("labels".into(), json!("queued")),
72 ],
73 },
74 &policy,
75 )?;
76 assert_eq!(initial.values()["count"], 1);
77 assert_eq!(labeled.values()["count"], 8);
78 assert_eq!(labeled.values()["labels"], json!(["draft", "queued"]));
79
80 // A later invalid entry rejects the complete operation, including count + 1.
81 let rejected = labeled.apply(
82 &StateChange::Increment {
83 values: vec![("count".into(), json!(1)), ("labels".into(), json!(1))],
84 },
85 &policy,
86 );
87 assert!(matches!(rejected, Err(AppError::InvalidInput(_))));
88 assert_eq!(labeled.values()["count"], 8);
89
90 // This registration is process-global. This standalone example owns its
91 // process; an application should choose registrations during its setup.
92 StateSerialization::global().register_field(
93 "labels".into(),
94 |value| Ok::<_, &'static str>(json!({"version": 1, "items": value})),
95 |value| value.get("items").cloned().ok_or("missing labels.items"),
96 )?;
97 let encoded = labeled.serialize()?;
98 assert_eq!(
99 encoded["labels"],
100 json!({"version":1,"items":["draft","queued"]})
101 );
102 let decoded = WorkflowState::deserialize(encoded.clone())?;
103 assert_eq!(decoded, labeled);
104
105 StateSerialization::global().register_tag("example-money".into(), |value| {
106 value.get("minor").cloned().ok_or("missing money.minor")
107 })?;
108 let tagged = WorkflowState::deserialize(Map::from_iter([(
109 "amount".into(),
110 json!({"__burr_serde__":"example-money","minor":120}),
111 )]))?;
112 assert_eq!(tagged.values()["amount"], 120);
113 println!("{}", Value::Object(encoded));
114 Ok(())
115}Sourcepub fn register_field<S, D, SE, DE>(
&self,
field: String,
serialize: S,
deserialize: D,
) -> Result<(), AppError>
pub fn register_field<S, D, SE, DE>( &self, field: String, serialize: S, deserialize: D, ) -> Result<(), AppError>
Registers or replaces both transformations for one exact top-level field.
Encoders must return objects; decoders may return any valid JSON value. Callback errors retain their Display text plus field and direction context. Neither callback invocation nor destruction of replaced captures holds the registry lock. An in-flight operation keeps its original callbacks.
use app_kit::workflow::{StateSerialization, WorkflowState};
use serde_json::{json, Map, Value};
StateSerialization::global().register_field(
"distance_meters".into(),
|value| Ok::<_, String>(json!({"meters": value})),
|value| value.get("meters").cloned().ok_or("missing meters"),
)?;
let state = WorkflowState::new(Map::from_iter([
("distance_meters".into(), Value::from(12)),
]))?;
let encoded = state.serialize()?;
assert_eq!(encoded["distance_meters"], json!({"meters": 12}));
assert_eq!(WorkflowState::deserialize(encoded)?, state);§Errors
Returns Integration if the registry lock is poisoned. Callback results are
validated when encoding/decoding, not during registration; exact field names
replace prior registrations for future operations.
Examples found in repository?
55fn main() -> Result<(), AppError> {
56 let policy = CountPolicy { maximum: 100 };
57 let initial = WorkflowState::new(Map::from_iter([
58 ("count".into(), json!(1)),
59 ("labels".into(), json!([])),
60 ]))?;
61 let counted = initial.apply(
62 &StateChange::Increment {
63 values: vec![("count".into(), json!(3)), ("count".into(), json!(4))],
64 },
65 &policy,
66 )?;
67 let labeled = counted.apply(
68 &StateChange::Append {
69 values: vec![
70 ("labels".into(), json!("draft")),
71 ("labels".into(), json!("queued")),
72 ],
73 },
74 &policy,
75 )?;
76 assert_eq!(initial.values()["count"], 1);
77 assert_eq!(labeled.values()["count"], 8);
78 assert_eq!(labeled.values()["labels"], json!(["draft", "queued"]));
79
80 // A later invalid entry rejects the complete operation, including count + 1.
81 let rejected = labeled.apply(
82 &StateChange::Increment {
83 values: vec![("count".into(), json!(1)), ("labels".into(), json!(1))],
84 },
85 &policy,
86 );
87 assert!(matches!(rejected, Err(AppError::InvalidInput(_))));
88 assert_eq!(labeled.values()["count"], 8);
89
90 // This registration is process-global. This standalone example owns its
91 // process; an application should choose registrations during its setup.
92 StateSerialization::global().register_field(
93 "labels".into(),
94 |value| Ok::<_, &'static str>(json!({"version": 1, "items": value})),
95 |value| value.get("items").cloned().ok_or("missing labels.items"),
96 )?;
97 let encoded = labeled.serialize()?;
98 assert_eq!(
99 encoded["labels"],
100 json!({"version":1,"items":["draft","queued"]})
101 );
102 let decoded = WorkflowState::deserialize(encoded.clone())?;
103 assert_eq!(decoded, labeled);
104
105 StateSerialization::global().register_tag("example-money".into(), |value| {
106 value.get("minor").cloned().ok_or("missing money.minor")
107 })?;
108 let tagged = WorkflowState::deserialize(Map::from_iter([(
109 "amount".into(),
110 json!({"__burr_serde__":"example-money","minor":120}),
111 )]))?;
112 assert_eq!(tagged.values()["amount"], 120);
113 println!("{}", Value::Object(encoded));
114 Ok(())
115}Sourcepub fn register_tag<D, DE>(
&self,
tag: String,
deserialize: D,
) -> Result<(), AppError>
pub fn register_tag<D, DE>( &self, tag: String, deserialize: D, ) -> Result<(), AppError>
Registers or replaces a decoder for an exact __burr_serde__ tag string.
Empty strings are valid names. The decoder receives the complete original tagged object. Its output is terminal: it is not dispatched or recursively decoded again. Ordinary untagged containers are decoded recursively.
use app_kit::workflow::{StateSerialization, WorkflowState};
use serde_json::{json, Map};
StateSerialization::global().register_tag("distance".into(), |value| {
value.get("meters").cloned().ok_or("missing meters")
})?;
let state = WorkflowState::deserialize(Map::from_iter([(
"route".into(),
json!([{"__burr_serde__": "distance", "meters": 12}]),
)]))?;
assert_eq!(state.values()["route"], json!([12]));§Errors
Returns Integration if the registry lock is poisoned. Decoder failures occur
at decode time and preserve caller error text with tag context.
Examples found in repository?
55fn main() -> Result<(), AppError> {
56 let policy = CountPolicy { maximum: 100 };
57 let initial = WorkflowState::new(Map::from_iter([
58 ("count".into(), json!(1)),
59 ("labels".into(), json!([])),
60 ]))?;
61 let counted = initial.apply(
62 &StateChange::Increment {
63 values: vec![("count".into(), json!(3)), ("count".into(), json!(4))],
64 },
65 &policy,
66 )?;
67 let labeled = counted.apply(
68 &StateChange::Append {
69 values: vec![
70 ("labels".into(), json!("draft")),
71 ("labels".into(), json!("queued")),
72 ],
73 },
74 &policy,
75 )?;
76 assert_eq!(initial.values()["count"], 1);
77 assert_eq!(labeled.values()["count"], 8);
78 assert_eq!(labeled.values()["labels"], json!(["draft", "queued"]));
79
80 // A later invalid entry rejects the complete operation, including count + 1.
81 let rejected = labeled.apply(
82 &StateChange::Increment {
83 values: vec![("count".into(), json!(1)), ("labels".into(), json!(1))],
84 },
85 &policy,
86 );
87 assert!(matches!(rejected, Err(AppError::InvalidInput(_))));
88 assert_eq!(labeled.values()["count"], 8);
89
90 // This registration is process-global. This standalone example owns its
91 // process; an application should choose registrations during its setup.
92 StateSerialization::global().register_field(
93 "labels".into(),
94 |value| Ok::<_, &'static str>(json!({"version": 1, "items": value})),
95 |value| value.get("items").cloned().ok_or("missing labels.items"),
96 )?;
97 let encoded = labeled.serialize()?;
98 assert_eq!(
99 encoded["labels"],
100 json!({"version":1,"items":["draft","queued"]})
101 );
102 let decoded = WorkflowState::deserialize(encoded.clone())?;
103 assert_eq!(decoded, labeled);
104
105 StateSerialization::global().register_tag("example-money".into(), |value| {
106 value.get("minor").cloned().ok_or("missing money.minor")
107 })?;
108 let tagged = WorkflowState::deserialize(Map::from_iter([(
109 "amount".into(),
110 json!({"__burr_serde__":"example-money","minor":120}),
111 )]))?;
112 assert_eq!(tagged.values()["amount"], 120);
113 println!("{}", Value::Object(encoded));
114 Ok(())
115}Auto Trait Implementations§
impl !Freeze for StateSerialization
impl RefUnwindSafe for StateSerialization
impl Send for StateSerialization
impl Sync for StateSerialization
impl Unpin for StateSerialization
impl UnsafeUnpin for StateSerialization
impl UnwindSafe for StateSerialization
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
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
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.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> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§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::RequestSource§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.