use serde::Serialize;
use crate::domain_event::{DomainEventBodyContract, DomainEventContract};
use crate::projection::lower::{ProjectionBodyMetadata, ProjectionPortableType};
use crate::{
DomainEvent, DomainEventBodyKind, DomainEventDescriptor, DomainState, ProjectionEnvelopeField,
ProjectionEventSelector, ProjectionValue,
};
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum CommandProjectionPreviewSource {
InputPath { path: Vec<String> },
GeneratedDefaultPath { path: Vec<String> },
TrustedPreset { name: String, codec: String },
Constant { value: ProjectionValue },
Null,
Absent,
Unknown,
ServerOnly,
}
impl CommandProjectionPreviewSource {
pub fn input(path: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self::InputPath {
path: path.into_iter().map(Into::into).collect(),
}
}
pub fn generated_default(path: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self::GeneratedDefaultPath {
path: path.into_iter().map(Into::into).collect(),
}
}
pub fn trusted(name: impl Into<String>, codec: impl Into<String>) -> Self {
Self::TrustedPreset {
name: name.into(),
codec: codec.into(),
}
}
pub fn constant(value: ProjectionValue) -> Self {
Self::Constant { value }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct CommandProjectionPreviewField {
pub(crate) body_path: Vec<String>,
pub(crate) envelope: Option<ProjectionEnvelopeField>,
#[serde(skip)]
pub(crate) body_type: Option<ProjectionPortableType>,
#[serde(skip)]
pub(crate) body_rust_type: Option<&'static str>,
#[serde(skip)]
pub(crate) body_nullable: Option<bool>,
#[serde(skip)]
pub(crate) body_always_present: Option<bool>,
pub(crate) source: CommandProjectionPreviewSource,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct CommandProjectionPreview {
pub(crate) selectors: Vec<ProjectionEventSelector>,
pub(crate) declaration_errors: Vec<String>,
pub(crate) fields: Vec<CommandProjectionPreviewField>,
}
impl CommandProjectionPreview {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn events(mut self, events: CommandProjectionEventSet) -> Self {
self.selectors = events.selectors;
self.declaration_errors = events.declaration_errors;
self
}
#[must_use]
pub fn field(
mut self,
body_path: impl IntoIterator<Item = impl Into<String>>,
source: CommandProjectionPreviewSource,
) -> Self {
self.fields.push(CommandProjectionPreviewField {
body_path: body_path.into_iter().map(Into::into).collect(),
envelope: None,
body_type: None,
body_rust_type: None,
body_nullable: None,
body_always_present: None,
source,
});
self
}
#[must_use]
pub fn envelope(
mut self,
field: ProjectionEnvelopeField,
source: CommandProjectionPreviewSource,
) -> Self {
self.fields.push(CommandProjectionPreviewField {
body_path: Vec::new(),
envelope: Some(field),
body_type: None,
body_rust_type: None,
body_nullable: None,
body_always_present: None,
source,
});
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub(crate) struct CommandProjectionEventPreview {
pub selector: ProjectionEventSelector,
pub preview: CommandProjectionPreview,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct CommandProjectionPureReduce {
pub fn_name: String,
pub client_module: String,
pub client_export: String,
pub wasm_package: String,
pub wasm_export: String,
pub model: String,
pub key: Vec<CommandProjectionPureArg>,
pub args: Vec<CommandProjectionPureArg>,
pub assign: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct CommandProjectionPureArg {
pub name: String,
pub source: CommandProjectionPreviewSource,
}
impl CommandProjectionPureReduce {
pub fn client_module(
fn_name: impl Into<String>,
client_module: impl Into<String>,
client_export: impl Into<String>,
model: impl Into<String>,
) -> Self {
Self {
fn_name: fn_name.into(),
client_module: client_module.into(),
client_export: client_export.into(),
wasm_package: String::new(),
wasm_export: String::new(),
model: model.into(),
key: Vec::new(),
args: Vec::new(),
assign: Vec::new(),
}
}
pub fn wasm(
fn_name: impl Into<String>,
wasm_package: impl Into<String>,
wasm_export: impl Into<String>,
model: impl Into<String>,
) -> Self {
Self {
fn_name: fn_name.into(),
client_module: String::new(),
client_export: String::new(),
wasm_package: wasm_package.into(),
wasm_export: wasm_export.into(),
model: model.into(),
key: Vec::new(),
args: Vec::new(),
assign: Vec::new(),
}
}
#[deprecated(note = "use client_module() or wasm()")]
pub fn new(
fn_name: impl Into<String>,
client_module: impl Into<String>,
client_export: impl Into<String>,
model: impl Into<String>,
) -> Self {
Self::client_module(fn_name, client_module, client_export, model)
}
#[must_use]
pub fn key_input(
mut self,
field: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.key.push(CommandProjectionPureArg {
name: field.into(),
source: CommandProjectionPreviewSource::input(path),
});
self
}
#[must_use]
pub fn arg_input(
mut self,
name: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.args.push(CommandProjectionPureArg {
name: name.into(),
source: CommandProjectionPreviewSource::input(path),
});
self
}
#[must_use]
pub fn assign(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.assign.extend(fields.into_iter().map(Into::into));
self.assign.sort();
self.assign.dedup();
self
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub(crate) struct CommandProjectionEvents {
pub selectors: Vec<ProjectionEventSelector>,
pub previews: Vec<CommandProjectionEventPreview>,
#[serde(skip)]
pub inferred_values: Vec<CommandProjectionEventPreview>,
pub pure_reduces: Vec<CommandProjectionPureReduce>,
pub declaration_errors: Vec<String>,
}
impl CommandProjectionEvents {
pub(crate) fn add_event_set(&mut self, events: CommandProjectionEventSet) {
self.selectors.extend(events.selectors);
self.declaration_errors.extend(events.declaration_errors);
}
pub(crate) fn add_preview(&mut self, preview: CommandProjectionPreview) {
self.declaration_errors
.extend(preview.declaration_errors.clone());
if preview.selectors.is_empty() {
self.declaration_errors
.push("projection preview must bind exactly one emitted event variant".to_owned());
return;
}
if preview.selectors.len() != 1 {
self.declaration_errors.push(
"projection preview must bind one exact event variant, not a multi-event set"
.to_owned(),
);
return;
}
self.previews
.extend(preview.selectors.iter().cloned().map(|selector| {
CommandProjectionEventPreview {
selector,
preview: preview.clone(),
}
}));
}
pub(crate) fn add_inferred_values(&mut self, values: CommandProjectionPreview) {
self.declaration_errors
.extend(values.declaration_errors.clone());
if values.selectors.len() != 1 {
self.declaration_errors.push(
"inferred transition values must bind exactly one emitted event variant".to_owned(),
);
return;
}
let selector = values.selectors[0].clone();
if let Some(existing) = self
.inferred_values
.iter_mut()
.find(|candidate| candidate.selector == selector)
{
existing.preview.fields.extend(values.fields);
existing
.preview
.declaration_errors
.extend(values.declaration_errors);
} else {
self.inferred_values.push(CommandProjectionEventPreview {
selector,
preview: values,
});
}
}
pub(crate) fn add_authenticated_user_field(
&mut self,
rust_field: &str,
values: CommandProjectionPreview,
) {
if values.fields.len() != 1 {
self.declaration_errors.push(
format!(
"authenticated-user inference field `{rust_field}` is not one exact emitted-event body field"
),
);
return;
}
self.add_inferred_values(values);
}
pub(crate) fn add_pure_reduce(&mut self, reduce: CommandProjectionPureReduce) {
self.pure_reduces.push(reduce);
}
pub(crate) fn canonicalize_and_validate(&mut self, command: &str) -> Result<(), String> {
if let Some(error) = self.declaration_errors.first() {
return Err(format!(
"typed command `{command}` has an invalid domain-event declaration: {error}"
));
}
self.selectors
.sort_by(ProjectionEventSelector::canonical_cmp);
if self.selectors.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(format!(
"typed command `{command}` repeats an exact emitted domain event selector"
));
}
for pair in self.selectors.windows(2) {
if pair[0].event_name() == pair[1].event_name()
&& pair[0].event_version() == pair[1].event_version()
&& pair[0] != pair[1]
{
return Err(format!(
"typed command `{command}` declares conflicting schemas for domain event `{}` v{}",
pair[0].event_name(),
pair[0].event_version()
));
}
}
canonicalize_preview_declarations(command, &self.selectors, &mut self.previews, false)?;
canonicalize_preview_declarations(
command,
&self.selectors,
&mut self.inferred_values,
true,
)?;
for reduce in &mut self.pure_reduces {
if reduce.fn_name.trim().is_empty() || reduce.model.trim().is_empty() {
return Err(format!(
"typed command `{command}` pure reduce requires non-empty fn and model"
));
}
let hand =
!reduce.client_module.trim().is_empty() || !reduce.client_export.trim().is_empty();
let wasm =
!reduce.wasm_package.trim().is_empty() || !reduce.wasm_export.trim().is_empty();
if hand == wasm {
return Err(format!(
"typed command `{command}` pure reduce `{}` must declare either client_module+client_export or wasm_package+wasm_export (not both, not neither)",
reduce.fn_name
));
}
if hand
&& (reduce.client_module.trim().is_empty()
|| reduce.client_export.trim().is_empty())
{
return Err(format!(
"typed command `{command}` pure reduce `{}` client module requires non-empty client_module and client_export",
reduce.fn_name
));
}
if wasm
&& (reduce.wasm_package.trim().is_empty() || reduce.wasm_export.trim().is_empty())
{
return Err(format!(
"typed command `{command}` pure reduce `{}` wasm package requires non-empty wasm_package and wasm_export",
reduce.fn_name
));
}
if reduce.key.is_empty() {
return Err(format!(
"typed command `{command}` pure reduce `{}` requires at least one key field",
reduce.fn_name
));
}
if reduce.assign.is_empty() {
return Err(format!(
"typed command `{command}` pure reduce `{}` requires at least one assign field",
reduce.fn_name
));
}
reduce.key.sort_by(|a, b| a.name.cmp(&b.name));
reduce.args.sort_by(|a, b| a.name.cmp(&b.name));
reduce.assign.sort();
reduce.assign.dedup();
for arg in reduce.key.iter().chain(reduce.args.iter()) {
match &arg.source {
CommandProjectionPreviewSource::InputPath { path }
| CommandProjectionPreviewSource::GeneratedDefaultPath { path } => {
validate_path(command, "pure reduce", path)?;
}
CommandProjectionPreviewSource::TrustedPreset { name, codec } => {
if name.trim().is_empty() || codec.trim().is_empty() {
return Err(format!(
"typed command `{command}` pure reduce trusted preset name and codec must not be empty"
));
}
}
other => {
return Err(format!(
"typed command `{command}` pure reduce `{}` arg `{}` uses unsupported source {other:?}",
reduce.fn_name, arg.name
));
}
}
}
}
self.pure_reduces
.sort_by(|left, right| left.fn_name.cmp(&right.fn_name));
if self
.pure_reduces
.windows(2)
.any(|pair| pair[0].fn_name == pair[1].fn_name)
{
return Err(format!(
"typed command `{command}` repeats pure reduce fn name"
));
}
Ok(())
}
}
fn canonicalize_preview_declarations(
command: &str,
selectors: &[ProjectionEventSelector],
previews: &mut [CommandProjectionEventPreview],
inferred: bool,
) -> Result<(), String> {
for preview in previews {
if selectors
.binary_search_by(|selector| selector.canonical_cmp(&preview.selector))
.is_err()
{
let source = if inferred {
"infers transition values"
} else {
"declares preview provenance"
};
return Err(format!(
"typed command `{command}` {source} outside its exact emitted event set"
));
}
preview.preview.fields.sort_by_key(preview_field_key);
for pair in preview.preview.fields.windows(2) {
if preview_field_key(&pair[0]) == preview_field_key(&pair[1]) {
let source = if inferred {
"inferred transition value"
} else {
"preview provenance"
};
return Err(format!(
"typed command `{command}` repeats {source} for one event value"
));
}
}
for field in &preview.preview.fields {
if field.envelope.is_none() {
validate_path(command, "emitted body", &field.body_path)?;
}
match &field.source {
CommandProjectionPreviewSource::InputPath { path }
| CommandProjectionPreviewSource::GeneratedDefaultPath { path } => {
validate_path(command, "preview input", path)?;
}
CommandProjectionPreviewSource::TrustedPreset { name, codec } => {
if name.trim().is_empty() || codec.trim().is_empty() {
return Err(format!(
"typed command `{command}` preview trusted preset name and codec must not be empty"
));
}
}
CommandProjectionPreviewSource::ServerOnly => {
return Err(format!(
"typed command `{command}` cannot expose server-only preview provenance"
));
}
CommandProjectionPreviewSource::Constant { .. }
| CommandProjectionPreviewSource::Null
| CommandProjectionPreviewSource::Absent
| CommandProjectionPreviewSource::Unknown => {}
}
}
}
Ok(())
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CommandProjectionEventSet {
selectors: Vec<ProjectionEventSelector>,
declaration_errors: Vec<String>,
}
#[doc(hidden)]
pub fn __command_projection_events(
descriptors: impl IntoIterator<Item = Result<DomainEventDescriptor, String>>,
) -> CommandProjectionEventSet {
let mut events = CommandProjectionEventSet::default();
for descriptor in descriptors {
let descriptor = match descriptor {
Ok(descriptor) => descriptor,
Err(error) => {
events.declaration_errors.push(error);
continue;
}
};
match ProjectionEventSelector::try_from_descriptor(&descriptor) {
Ok(selector) => events.selectors.push(selector),
Err(error) => events.declaration_errors.push(error.to_string()),
}
}
events
}
#[doc(hidden)]
pub fn __command_projection_event_descriptor<E: DomainEventContract>(
) -> Result<DomainEventDescriptor, String> {
let descriptor = E::descriptor();
if descriptor.name != E::EVENT_NAME {
return Err(format!(
"event contract name `{}` differs from descriptor name `{}`",
E::EVENT_NAME,
descriptor.name
));
}
if descriptor.version != E::EVENT_VERSION {
return Err(format!(
"event contract `{}` version {} differs from descriptor version {}",
E::EVENT_NAME,
E::EVENT_VERSION,
descriptor.version
));
}
Ok(descriptor)
}
#[doc(hidden)]
pub fn __command_projection_state_preview<E, S>(
fields: Vec<(&'static str, CommandProjectionPreviewSource)>,
) -> CommandProjectionPreview
where
E: DomainEventBodyContract<S>,
S: DomainState + ProjectionBodyMetadata,
{
let descriptor = __command_projection_event_descriptor::<E>().and_then(|descriptor| {
let expected = DomainEventDescriptor::state::<S>(E::EVENT_NAME, E::EVENT_VERSION);
if descriptor != expected || descriptor.body.kind != DomainEventBodyKind::State {
return Err(format!(
"state preview event contract `{}` does not exactly describe `{}` state",
E::EVENT_NAME,
std::any::type_name::<S>()
));
}
Ok(descriptor)
});
structured_preview::<S>(__command_projection_events([descriptor]), fields)
}
#[doc(hidden)]
pub fn __command_projection_state_known_values<E, S>(
fields: Vec<(&'static str, CommandProjectionPreviewSource)>,
) -> CommandProjectionPreview
where
E: DomainEventBodyContract<S>,
S: DomainState + ProjectionBodyMetadata,
{
let descriptor = __command_projection_event_descriptor::<E>().and_then(|descriptor| {
let expected = DomainEventDescriptor::state::<S>(E::EVENT_NAME, E::EVENT_VERSION);
if descriptor != expected || descriptor.body.kind != DomainEventBodyKind::State {
return Err(format!(
"inferred transition event contract `{}` does not exactly describe `{}` state",
E::EVENT_NAME,
std::any::type_name::<S>()
));
}
Ok(descriptor)
});
let mut values =
CommandProjectionPreview::new().events(__command_projection_events([descriptor]));
for (rust_name, source) in fields {
let Some(field) = S::PROJECTION_FIELDS
.iter()
.find(|field| field.rust_name == rust_name && field.present)
else {
continue;
};
values.fields.push(CommandProjectionPreviewField {
body_path: vec![field.wire_name.to_owned()],
envelope: None,
body_type: Some(field.portable_type),
body_rust_type: Some(field.rust_type),
body_nullable: Some(field.nullable),
body_always_present: Some(field.always_present),
source,
});
}
values
}
#[doc(hidden)]
pub fn __command_projection_event_preview<E, B>(
fields: Vec<(&'static str, CommandProjectionPreviewSource)>,
) -> CommandProjectionPreview
where
E: DomainEventBodyContract<B>,
B: DomainEvent + ProjectionBodyMetadata,
{
let descriptor = __command_projection_event_descriptor::<E>().and_then(|descriptor| {
if descriptor != B::DESCRIPTOR || descriptor.body.kind != DomainEventBodyKind::Event {
return Err(format!(
"event preview contract `{}` differs from its exact typed body descriptor",
E::EVENT_NAME
));
}
Ok(descriptor)
});
structured_preview::<B>(__command_projection_events([descriptor]), fields)
}
fn structured_preview<B: ProjectionBodyMetadata>(
events: CommandProjectionEventSet,
fields: Vec<(&'static str, CommandProjectionPreviewSource)>,
) -> CommandProjectionPreview {
let mut preview = CommandProjectionPreview::new().events(events);
for (rust_name, source) in fields {
match B::PROJECTION_FIELDS
.iter()
.find(|field| field.rust_name == rust_name && field.present)
{
Some(field) => preview.fields.push(CommandProjectionPreviewField {
body_path: vec![field.wire_name.to_owned()],
envelope: None,
body_type: Some(field.portable_type),
body_rust_type: Some(field.rust_type),
body_nullable: Some(field.nullable),
body_always_present: Some(field.always_present),
source,
}),
None => preview.declaration_errors.push(format!(
"state preview references unknown body field `{rust_name}`"
)),
}
}
preview
}
#[doc(hidden)]
pub fn __command_projection_preview_constant(
value: impl Serialize,
) -> CommandProjectionPreviewSource {
match serde_json::to_value(value)
.map_err(|error| error.to_string())
.and_then(|value| ProjectionValue::try_from_json(value).map_err(|error| error.to_string()))
{
Ok(value) => CommandProjectionPreviewSource::Constant { value },
Err(_) => CommandProjectionPreviewSource::Unknown,
}
}
pub trait CommandEventSet {
fn command_event_set() -> CommandProjectionEventSet;
fn command_event_known_values() -> Vec<CommandProjectionPreview> {
Vec::new()
}
}
impl<E: DomainEventContract> CommandEventSet for E {
fn command_event_set() -> CommandProjectionEventSet {
__command_projection_events([__command_projection_event_descriptor::<E>()])
}
}
macro_rules! impl_command_event_set_tuple {
($($E:ident),+) => {
impl<$($E: DomainEventContract),+> CommandEventSet for ($($E,)+) {
fn command_event_set() -> CommandProjectionEventSet {
__command_projection_events([
$(__command_projection_event_descriptor::<$E>()),+
])
}
}
};
}
impl_command_event_set_tuple!(E1, E2);
impl_command_event_set_tuple!(E1, E2, E3);
impl_command_event_set_tuple!(E1, E2, E3, E4);
impl_command_event_set_tuple!(E1, E2, E3, E4, E5);
impl_command_event_set_tuple!(E1, E2, E3, E4, E5, E6);
impl_command_event_set_tuple!(E1, E2, E3, E4, E5, E6, E7);
impl_command_event_set_tuple!(E1, E2, E3, E4, E5, E6, E7, E8);
#[macro_export]
macro_rules! events {
($($event:ty),+ $(,)?) => {
$crate::graphql::__command_projection_events([
$($crate::graphql::__command_projection_event_descriptor::<$event>()),+
])
};
}
#[macro_export]
macro_rules! state_preview {
(
$event:ty => $state:ty { $($fields:tt)* }
) => {{
$crate::graphql::__command_projection_state_preview::<$event, $state>(
$crate::__distributed_state_preview_fields!(@collect [] ; $($fields)*)
)
}};
}
#[macro_export]
macro_rules! event_preview {
(
$event:ty => $body:ty { $($fields:tt)* }
) => {{
$crate::graphql::__command_projection_event_preview::<$event, $body>(
$crate::__distributed_state_preview_fields!(@collect [] ; $($fields)*)
)
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __distributed_state_preview_fields {
(@collect [$($out:expr,)*] ; ..unknown $(,)?) => {
vec![$($out,)*]
};
(@collect [$($out:expr,)*] ; ) => {
vec![$($out,)*]
};
(@collect [$($out:expr,)*] ;
$field:ident : input.$first:ident $(.$rest:ident)*,
$($tail:tt)*
) => {
$crate::__distributed_state_preview_fields!(
@collect [
$($out,)*
(
stringify!($field),
$crate::graphql::CommandProjectionPreviewSource::input([
stringify!($first) $(, stringify!($rest))*
])
),
];
$($tail)*
)
};
(@collect [$($out:expr,)*] ;
$field:ident : generated.$first:ident $(.$rest:ident)*,
$($tail:tt)*
) => {
$crate::__distributed_state_preview_fields!(
@collect [
$($out,)*
(
stringify!($field),
$crate::graphql::CommandProjectionPreviewSource::generated_default([
stringify!($first) $(, stringify!($rest))*
])
),
];
$($tail)*
)
};
(@collect [$($out:expr,)*] ;
$field:ident : trusted($name:expr, $codec:expr),
$($tail:tt)*
) => {
$crate::__distributed_state_preview_fields!(
@collect [
$($out,)*
(
stringify!($field),
$crate::graphql::CommandProjectionPreviewSource::trusted($name, $codec)
),
];
$($tail)*
)
};
(@collect [$($out:expr,)*] ; $field:ident : unknown, $($tail:tt)*) => {
$crate::__distributed_state_preview_fields!(
@collect [$($out,)* (stringify!($field), $crate::graphql::CommandProjectionPreviewSource::Unknown),];
$($tail)*
)
};
(@collect [$($out:expr,)*] ; $field:ident : absent, $($tail:tt)*) => {
$crate::__distributed_state_preview_fields!(
@collect [$($out,)* (stringify!($field), $crate::graphql::CommandProjectionPreviewSource::Absent),];
$($tail)*
)
};
(@collect [$($out:expr,)*] ; $field:ident : null, $($tail:tt)*) => {
$crate::__distributed_state_preview_fields!(
@collect [$($out,)* (stringify!($field), $crate::graphql::CommandProjectionPreviewSource::Null),];
$($tail)*
)
};
(@collect [$($out:expr,)*] ; $field:ident : $constant:path, $($tail:tt)*) => {
$crate::__distributed_state_preview_fields!(
@collect [
$($out,)*
(
stringify!($field),
$crate::graphql::__command_projection_preview_constant($constant)
),
];
$($tail)*
)
};
(@collect [$($out:expr,)*] ; $field:ident : $constant:literal, $($tail:tt)*) => {
$crate::__distributed_state_preview_fields!(
@collect [
$($out,)*
(
stringify!($field),
$crate::graphql::__command_projection_preview_constant($constant)
),
];
$($tail)*
)
};
}
fn validate_path(command: &str, label: &str, path: &[String]) -> Result<(), String> {
if path.is_empty() || path.iter().any(|segment| segment.trim().is_empty()) {
return Err(format!(
"typed command `{command}` {label} path must contain only non-empty segments"
));
}
Ok(())
}
fn preview_field_key(field: &CommandProjectionPreviewField) -> (u8, Vec<String>) {
match field.envelope {
Some(envelope) => (
1,
vec![serde_json::to_string(&envelope)
.expect("projection envelope field serialization cannot fail")],
),
None => (0, field.body_path.clone()),
}
}