Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for tag hints #205

Merged
merged 2 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion buffer/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,17 @@ impl<'sval> sval::Stream<'sval> for ValueBuf<'sval> {
})
}

fn tag_hint(
&mut self,
tag: &sval::Tag,
) -> sval::Result {
self.try_catch(|buf| {
buf.push_kind(ValueKind::TagHint {
tag: tag.clone(),
})
})
}

fn record_begin(
&mut self,
tag: Option<&sval::Tag>,
Expand Down Expand Up @@ -856,6 +867,9 @@ enum ValueKind<'sval> {
label: Option<sval::Label<'static>>,
index: Option<sval::Index>,
},
TagHint {
tag: sval::Tag,
},
Enum {
len: usize,
tag: Option<sval::Tag>,
Expand Down Expand Up @@ -955,7 +969,8 @@ impl<'sval> ValueBuf<'sval> {
| ValueKind::F64(_)
| ValueKind::Text(_)
| ValueKind::Binary(_)
| ValueKind::Tag { .. } => return Err(Error::invalid_value("can't end at this index")),
| ValueKind::Tag { .. }
| ValueKind::TagHint { .. } => return Err(Error::invalid_value("can't end at this index")),
} = len;

Ok(())
Expand Down Expand Up @@ -1032,6 +1047,9 @@ impl<'sval> ValuePart<'sval> {
crate::assert_static(label);
crate::assert_static(index)
}
ValueKind::TagHint { tag } => {
crate::assert_static(tag);
}
ValueKind::Enum {
len,
tag,
Expand Down Expand Up @@ -1215,6 +1233,9 @@ impl<'sval> sval_ref::ValueRef<'sval> for ValueSlice<'sval> {
ValueKind::Tag { tag, label, index } => {
stream.tag(tag.as_ref(), label.as_ref(), index.as_ref())?;
}
ValueKind::TagHint { tag } => {
stream.tag_hint(tag)?;
}
ValueKind::Enum {
len,
tag,
Expand Down Expand Up @@ -2221,6 +2242,66 @@ mod alloc_tests {
assert_eq!(expected, &*value.parts);
}

#[test]
fn buffer_tag_hints() {
let mut value = ValueBuf::new();

value.tag_hint(&sval::Tag::new("test")).unwrap();

value.seq_begin(Some(2)).unwrap();

value.seq_value_begin().unwrap();
value.tag_hint(&sval::Tag::new("test")).unwrap();
value.bool(false).unwrap();
value.seq_value_end().unwrap();

value.seq_value_begin().unwrap();
value.bool(true).unwrap();
value.seq_value_end().unwrap();

value.seq_end().unwrap();

value.tag_hint(&sval::Tag::new("test")).unwrap();

let expected = vec![
ValuePart {
kind: ValueKind::TagHint {
tag: sval::Tag::new("test"),
},
},
ValuePart {
kind: ValueKind::Seq {
len: 5,
num_entries_hint: Some(2),
},
},
ValuePart {
kind: ValueKind::SeqValue { len: 2 },
},
ValuePart {
kind: ValueKind::TagHint {
tag: sval::Tag::new("test"),
},
},
ValuePart {
kind: ValueKind::Bool(false),
},
ValuePart {
kind: ValueKind::SeqValue { len: 1 },
},
ValuePart {
kind: ValueKind::Bool(true),
},
ValuePart {
kind: ValueKind::TagHint {
tag: sval::Tag::new("test"),
},
},
];

assert_eq!(expected, &*value.parts);
}

#[test]
fn buffer_roundtrip() {
for value_1 in [
Expand Down
10 changes: 10 additions & 0 deletions dynamic/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ mod private {
index: Option<&sval::Index>,
) -> sval::Result;

fn dispatch_tag_hint(&mut self, tag: &sval::Tag) -> sval::Result;

fn dispatch_record_begin(
&mut self,
tag: Option<&sval::Tag>,
Expand Down Expand Up @@ -368,6 +370,10 @@ impl<'sval, R: sval::Stream<'sval>> private::DispatchStream<'sval> for R {
self.tag(tag, label, index)
}

fn dispatch_tag_hint(&mut self, tag: &sval::Tag) -> sval::Result {
self.tag_hint(tag)
}

fn dispatch_record_begin(
&mut self,
tag: Option<&sval::Tag>,
Expand Down Expand Up @@ -646,6 +652,10 @@ macro_rules! impl_stream {
self.erase_stream().0.dispatch_tag(tag, label, index)
}

fn tag_hint(&mut self, tag: &sval::Tag) -> sval::Result {
self.erase_stream().0.dispatch_tag_hint(tag)
}

fn record_begin(&mut self, tag: Option<&sval::Tag>, label: Option<&sval::Label>, index: Option<&sval::Index>, num_entries_hint: Option<usize>) -> sval::Result {
self.erase_stream().0.dispatch_record_begin(tag, label, index, num_entries_hint)
}
Expand Down
11 changes: 11 additions & 0 deletions flatten/src/flattener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,17 @@ impl<'sval, S: Flatten<'sval>> Stream<'sval> for Flattener<'sval, S> {
)
}

#[inline]
fn tag_hint(
&mut self,
tag: &Tag,
) -> sval::Result {
self.value(
|buf| buf.tag_hint(tag),
|stream| stream.tag_hint(tag),
)
}

#[inline]
fn record_begin(
&mut self,
Expand Down
4 changes: 4 additions & 0 deletions flatten/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ impl<'sval, S: Stream<'sval>> Stream<'sval> for PassThru<S> {
self.stream.tag(tag, label, index)
}

fn tag_hint(&mut self, tag: &Tag) -> sval::Result {
self.stream.tag_hint(tag)
}

fn record_begin(
&mut self,
tag: Option<&Tag>,
Expand Down
5 changes: 5 additions & 0 deletions nested/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/*!
A variant of [`sval::Stream`] for cases where a recursive API is needed.

# Limitations

Streaming via `sval_nested` will discard any [`sval::Stream::tag_hint`]s. `sval` allows tag hints to
appear anywhere in a stream, but this library enforces a stricter lifecycle, making those hints unreliable.
*/

#![cfg_attr(not(test), no_std)]
Expand Down
32 changes: 32 additions & 0 deletions src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@ pub trait Stream<'sval> {
default_stream::tag(self, tag, label, index)
}

/**
Use a tag as a hint without streaming it as a value.

Hints may be given at any point in a stream and may be interpreted by a stream in any way, but can't be required for a correct result.
*/
fn tag_hint(&mut self, tag: &Tag) -> Result {
default_stream::tag_hint(self, tag)
}

/**
Start a record type.

Expand Down Expand Up @@ -667,6 +676,12 @@ macro_rules! impl_stream_forward {
($($forward)*).tag(tag, label, index)
}

#[inline]
fn tag_hint(&mut self, tag: &Tag) -> Result {
let $bind = self;
($($forward)*).tag_hint(tag)
}

#[inline]
fn record_begin(&mut self, tag: Option<&Tag>, label: Option<&Label>, index: Option<&Index>, num_entries: Option<usize>) -> Result {
let $bind = self;
Expand Down Expand Up @@ -975,6 +990,11 @@ impl<'a, 'b, S: Stream<'a> + ?Sized> Stream<'b> for Computed<S> {
self.0.tag(tag, label, index)
}

#[inline]
fn tag_hint(&mut self, tag: &Tag) -> Result {
self.0.tag_hint(tag)
}

#[inline]
fn record_begin(
&mut self,
Expand Down Expand Up @@ -1399,6 +1419,18 @@ pub mod default_stream {
stream.tagged_end(tag, label, index)
}

/**
Use a tag as a hint without streaming it as a value.

Hints may be given at any point in a stream and may be interpreted by a stream in any way, but can't be required for a correct result.
*/
pub fn tag_hint<'sval>(stream: &mut (impl Stream<'sval> + ?Sized), tag: &Tag) -> Result {
let _ = stream;
let _ = tag;

Ok(())
}

/**
Start a record type.

Expand Down
71 changes: 64 additions & 7 deletions test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,30 @@ pub fn assert_tokens<'sval, V: sval::Value + ?Sized>(value: &'sval V, tokens: &[
let mut stream = TokenBuf::new();

match value.stream(&mut stream) {
Ok(()) => assert_eq!(
tokens,
stream.as_tokens(),
"{} != {}",
sval_fmt::stream_to_string(AsValue(tokens)),
sval_fmt::stream_to_string(AsValue(stream.as_tokens()))
),
Ok(()) => {
assert_eq!(
tokens,
stream.as_tokens(),
"{} != {}",
sval_fmt::stream_to_string(AsValue(tokens)),
sval_fmt::stream_to_string(AsValue(stream.as_tokens()))
);

#[cfg(test)]
{
let mut dyn_stream = &mut TokenBuf::new();

value.stream(&mut dyn_stream as &mut dyn sval_dynamic::Stream<'sval>).unwrap();

assert_eq!(
tokens,
dyn_stream.as_tokens(),
"(dyn) {} != {}",
sval_fmt::stream_to_string(AsValue(tokens)),
sval_fmt::stream_to_string(AsValue(dyn_stream.as_tokens()))
);
}
},
Err(_) => stream.fail::<V>(),
}
}
Expand Down Expand Up @@ -122,6 +139,10 @@ pub enum Token<'a> {
Option<sval::Index>,
),
/**
[`sval::Stream::tag_hint`]
*/
TagHint(sval::Tag),
/**
[`sval::Stream::text_begin`].
*/
TextBegin(Option<usize>),
Expand Down Expand Up @@ -327,6 +348,9 @@ impl<'a, 'b> sval::Value for AsValue<'a, 'b> {
Token::Tag(tag, label, index) => {
stream.tag(tag.as_ref(), label.as_ref(), index.as_ref())?
}
Token::TagHint(tag) => {
stream.tag_hint(tag)?;
}
Token::TextBegin(num_bytes) => stream.text_begin(*num_bytes)?,
Token::TextFragment(v) => stream.text_fragment(*v)?,
Token::TextFragmentComputed(v) => stream.text_fragment_computed(&**v)?,
Expand Down Expand Up @@ -680,6 +704,12 @@ impl<'sval> sval::Stream<'sval> for TokenBuf<'sval> {
Ok(())
}

fn tag_hint(&mut self, tag: &sval::Tag) -> sval::Result {
self.push(Token::TagHint(tag.clone()));

Ok(())
}

fn record_begin(
&mut self,
tag: Option<&sval::Tag>,
Expand Down Expand Up @@ -1086,6 +1116,33 @@ mod tests {
);
}

#[test]
fn stream_tag_hints() {
struct WithHints;

impl sval::Value for WithHints {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(
&'sval self,
stream: &mut S,
) -> sval::Result {
stream.tag_hint(&sval::Tag::new("test"))?;

stream.value(&42)?;

stream.tag_hint(&sval::Tag::new("test"))
}
}

assert_tokens(
&WithHints,
&[
Token::TagHint(sval::Tag::new("test")),
Token::I32(42),
Token::TagHint(sval::Tag::new("test")),
],
);
}

#[test]
fn stream_invalid() {
#[derive(Debug)]
Expand Down
Loading