From dadf9d4f50a6664cd5345187eab18829d9738462 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 13:05:04 +1000 Subject: [PATCH 1/2] add support for tag hints --- buffer/src/value.rs | 28 +++++++++++++++++++++++++++- dynamic/src/stream.rs | 10 ++++++++++ flatten/src/flattener.rs | 11 +++++++++++ flatten/src/map.rs | 4 ++++ src/stream.rs | 32 ++++++++++++++++++++++++++++++++ test/src/lib.rs | 18 ++++++++++++++++++ 6 files changed, 102 insertions(+), 1 deletion(-) diff --git a/buffer/src/value.rs b/buffer/src/value.rs index dbf78cbf..884e41d7 100644 --- a/buffer/src/value.rs +++ b/buffer/src/value.rs @@ -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>, @@ -856,6 +867,9 @@ enum ValueKind<'sval> { label: Option>, index: Option, }, + TagHint { + tag: sval::Tag, + }, Enum { len: usize, tag: Option, @@ -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(()) @@ -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, @@ -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, @@ -2221,6 +2242,11 @@ mod alloc_tests { assert_eq!(expected, &*value.parts); } + #[test] + fn buffer_tag_hints() { + todo!() + } + #[test] fn buffer_roundtrip() { for value_1 in [ diff --git a/dynamic/src/stream.rs b/dynamic/src/stream.rs index 374bb2a7..50d1df23 100644 --- a/dynamic/src/stream.rs +++ b/dynamic/src/stream.rs @@ -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>, @@ -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>, @@ -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) -> sval::Result { self.erase_stream().0.dispatch_record_begin(tag, label, index, num_entries_hint) } diff --git a/flatten/src/flattener.rs b/flatten/src/flattener.rs index 3f52811b..a3c412f1 100644 --- a/flatten/src/flattener.rs +++ b/flatten/src/flattener.rs @@ -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, diff --git a/flatten/src/map.rs b/flatten/src/map.rs index a372b603..ba300f01 100644 --- a/flatten/src/map.rs +++ b/flatten/src/map.rs @@ -262,6 +262,10 @@ impl<'sval, S: Stream<'sval>> Stream<'sval> for PassThru { 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>, diff --git a/src/stream.rs b/src/stream.rs index 8e8efe6b..a5c4154a 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -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. @@ -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) -> Result { let $bind = self; @@ -975,6 +990,11 @@ impl<'a, 'b, S: Stream<'a> + ?Sized> Stream<'b> for Computed { 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, @@ -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. diff --git a/test/src/lib.rs b/test/src/lib.rs index e343e533..6a3186eb 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -122,6 +122,10 @@ pub enum Token<'a> { Option, ), /** + [`sval::Stream::tag_hint`] + */ + TagHint(sval::Tag), + /** [`sval::Stream::text_begin`]. */ TextBegin(Option), @@ -327,6 +331,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)?, @@ -680,6 +687,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>, @@ -1086,6 +1099,11 @@ mod tests { ); } + #[test] + fn stream_tag_hints() { + todo!() + } + #[test] fn stream_invalid() { #[derive(Debug)] From f8c65666bbab45a7867a797823f8fa36fe81e00c Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 20:52:29 +1000 Subject: [PATCH 2/2] fill in tests for tag_hints --- buffer/src/value.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++- nested/src/lib.rs | 5 ++++ test/src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/buffer/src/value.rs b/buffer/src/value.rs index 884e41d7..8534aa04 100644 --- a/buffer/src/value.rs +++ b/buffer/src/value.rs @@ -2244,7 +2244,62 @@ mod alloc_tests { #[test] fn buffer_tag_hints() { - todo!() + 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] diff --git a/nested/src/lib.rs b/nested/src/lib.rs index e430180d..fecf9008 100644 --- a/nested/src/lib.rs +++ b/nested/src/lib.rs @@ -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)] diff --git a/test/src/lib.rs b/test/src/lib.rs index 6a3186eb..1f70d27f 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -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::(), } } @@ -1101,7 +1118,29 @@ mod tests { #[test] fn stream_tag_hints() { - todo!() + 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]