Skip to content
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
57 changes: 57 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,60 @@ repos:
hooks:
- id: fmt
name: Fmt

- repo: local
hooks:
# Feature matrix testing to prevent breaking different configurations
- id: feature-matrix-check
name: Feature Matrix Check - Default
entry: cargo check --manifest-path=picojson/Cargo.toml
language: system
pass_filenames: false

- id: feature-matrix-no-defaults
name: Feature Matrix Check - No Defaults
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features
language: system
pass_filenames: false

- id: feature-matrix-int32-float
name: Feature Matrix Check - int32 + float
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int32,float"
language: system
pass_filenames: false

- id: feature-matrix-int32-float-skip
name: Feature Matrix Check - int32 + float-skip
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int32,float-skip"
language: system
pass_filenames: false

- id: feature-matrix-int32-float-error
name: Feature Matrix Check - int32 + float-error
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int32,float-error"
language: system
pass_filenames: false

- id: feature-matrix-int32-float-truncate
name: Feature Matrix Check - int32 + float-truncate
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int32,float-truncate"
language: system
pass_filenames: false

- id: feature-matrix-int64-float-skip
name: Feature Matrix Check - int64 + float-skip
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int64,float-skip"
language: system
pass_filenames: false

- id: feature-matrix-int64-float-error
name: Feature Matrix Check - int64 + float-error
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int64,float-error"
language: system
pass_filenames: false

- id: feature-matrix-int64-float-truncate
name: Feature Matrix Check - int64 + float-truncate
entry: cargo check --manifest-path=picojson/Cargo.toml --no-default-features --features "int64,float-truncate"
language: system
pass_filenames: false
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@ For production code please use [serde-json-core](https://crates.io/crates/serde-

## License

Apache 2.0; see [`LICENSE`](LICENSE) for details.
Apache 2.0; see [`LICENSE`](LICENSE) for details.
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
## TODO list
- API cleanup, rename things
- Constify what's possible
- Remove `.unwrap()` calls
- Remove unnecessary `'static` lifetimes
- Dependency cleanup
- Clippy cleanup
- Unify code in PullParser and StreamParser
- Address and remove `// TODO` comments
- Put all shippable features in one crate ( tokenizer, pull + push parsers )
- Clean up reference docs
- Provide user guide docs
- Direct defmt support
- Stack size benchmarks
- Code size benchmarks
- Sax-style push parser
- See if we can bring an Iterator impl back
7 changes: 7 additions & 0 deletions picojson/examples/no_float_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ fn parse_and_display(parser: &mut PullParser) {
println!(" → Manual parse as f64: {}", f);
}
}
// Handle variants that shouldn't be reachable in current configuration
_ => {
println!(
" → Unexpected variant for current configuration: {:?}",
num.parsed()
);
}
}
}
Ok(Event::Key(String::Borrowed(key))) => {
Expand Down
2 changes: 1 addition & 1 deletion picojson/src/direct_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ mod tests {
}
}

impl<'b> crate::number_parser::NumberExtractor for DirectBuffer<'b> {
impl crate::number_parser::NumberExtractor for DirectBuffer<'_> {
fn get_number_slice(
&self,
start: usize,
Expand Down
21 changes: 10 additions & 11 deletions picojson/src/json_number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,12 @@ pub enum NumberResult {
/// Integer too large for configured type (use raw string for exact representation)
IntegerOverflow,
/// Float value (only available with float feature)
#[cfg(feature = "float")]
Float(f64),
/// Float parsing disabled - behavior depends on configuration
#[cfg(not(feature = "float"))]
FloatDisabled,
/// Float encountered but skipped due to float-skip configuration
#[cfg(all(not(feature = "float"), feature = "float-skip"))]
FloatSkipped,
/// Float truncated to integer due to float-truncate configuration
#[cfg(all(not(feature = "float"), feature = "float-truncate"))]
FloatTruncated(ConfiguredInt),
}

Expand All @@ -50,7 +46,7 @@ pub enum JsonNumber<'a, 'b> {
Copied { raw: &'b str, parsed: NumberResult },
}

impl<'a, 'b> JsonNumber<'a, 'b> {
impl JsonNumber<'_, '_> {
/// Get the parsed NumberResult.
pub fn parsed(&self) -> &NumberResult {
match self {
Expand Down Expand Up @@ -115,7 +111,7 @@ impl<'a, 'b> JsonNumber<'a, 'b> {
}
}

impl<'a, 'b> AsRef<str> for JsonNumber<'a, 'b> {
impl AsRef<str> for JsonNumber<'_, '_> {
fn as_ref(&self) -> &str {
self.as_str()
}
Expand All @@ -129,7 +125,7 @@ impl Deref for JsonNumber<'_, '_> {
}
}

impl<'a, 'b> core::fmt::Display for JsonNumber<'a, 'b> {
impl core::fmt::Display for JsonNumber<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Display strategy: Show parsed value when available, fall back to raw string
// This provides the most meaningful representation across all configurations
Expand Down Expand Up @@ -177,10 +173,12 @@ pub(super) fn parse_float(s: &str) -> NumberResult {
pub(super) fn parse_float(s: &str) -> Result<NumberResult, ParseError> {
#[cfg(feature = "float-error")]
{
let _ = s; // Acknowledge parameter usage
Err(ParseError::FloatNotAllowed)
}
#[cfg(feature = "float-skip")]
{
let _ = s; // Acknowledge parameter usage
Ok(NumberResult::FloatSkipped)
}
#[cfg(feature = "float-truncate")]
Expand Down Expand Up @@ -209,6 +207,7 @@ pub(super) fn parse_float(s: &str) -> Result<NumberResult, ParseError> {
feature = "float-truncate"
)))]
{
let _ = s; // Acknowledge parameter usage
Ok(NumberResult::FloatDisabled)
}
}
Expand Down Expand Up @@ -282,12 +281,12 @@ mod tests {
#[cfg(feature = "float")]
fn test_json_number_float() {
let number = JsonNumber::Borrowed {
raw: "3.14159",
parsed: NumberResult::Float(3.14159),
raw: "3.25",
parsed: NumberResult::Float(3.25),
};
assert_eq!(number.as_str(), "3.14159");
assert_eq!(number.as_str(), "3.25");
assert_eq!(number.as_int(), None);
assert_eq!(number.as_f64(), Some(3.14159));
assert_eq!(number.as_f64(), Some(3.25));
assert!(!number.is_integer());
assert!(number.is_float());
}
Expand Down
6 changes: 3 additions & 3 deletions picojson/src/json_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub enum String<'a, 'b> {
Unescaped(&'b str),
}

impl<'a, 'b> String<'a, 'b> {
impl String<'_, '_> {
/// Returns the string as a `&str`, whether borrowed or unescaped.
pub fn as_str(&self) -> &str {
match self {
Expand All @@ -23,7 +23,7 @@ impl<'a, 'b> String<'a, 'b> {
}
}

impl<'a, 'b> AsRef<str> for String<'a, 'b> {
impl AsRef<str> for String<'_, '_> {
fn as_ref(&self) -> &str {
self.as_str()
}
Expand All @@ -40,7 +40,7 @@ impl Deref for String<'_, '_> {
}
}

impl<'a, 'b> core::fmt::Display for String<'a, 'b> {
impl core::fmt::Display for String<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
Expand Down
10 changes: 4 additions & 6 deletions picojson/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,11 @@
// Compile-time configuration validation
mod config_check;

mod tokenizer;
// Temporary internal alias, not exported
use tokenizer as ujson;
pub use tokenizer::ArrayBitStack;
mod ujson;
pub use ujson::ArrayBitStack;

pub use tokenizer::ArrayBitBucket;
pub use tokenizer::{BitBucket, BitStackConfig, BitStackStruct, DefaultConfig, DepthCounter};
pub use ujson::ArrayBitBucket;
pub use ujson::{BitBucket, BitStackConfig, BitStackStruct, DefaultConfig, DepthCounter};

mod copy_on_escape;

Expand Down
78 changes: 72 additions & 6 deletions picojson/src/pull_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ impl<'a, 'b> PullParser<'a, 'b, DefaultConfig> {
/// # Arguments
/// * `input` - A string slice containing the JSON data to be parsed.
/// * `scratch_buffer` - A mutable byte slice for temporary string unescaping operations.
/// This buffer needs to be at least as long as the longest
/// contiguous token (string, key, number) in the input.
/// This buffer needs to be at least as long as the longest
/// contiguous token (string, key, number) in the input.
///
/// # Example
/// ```
Expand Down Expand Up @@ -138,8 +138,8 @@ impl<'a, 'b, C: BitStackConfig> PullParser<'a, 'b, C> {
/// # Arguments
/// * `input` - A string slice containing the JSON data to be parsed.
/// * `scratch_buffer` - A mutable byte slice for temporary string unescaping operations.
/// This buffer needs to be at least as long as the longest
/// contiguous token (string, key, number) in the input.
/// This buffer needs to be at least as long as the longest
/// contiguous token (string, key, number) in the input.
pub fn with_config_and_buffer(input: &'a str, scratch_buffer: &'b mut [u8]) -> Self {
Self::with_config_and_buffer_from_slice(input.as_bytes(), scratch_buffer)
}
Expand Down Expand Up @@ -439,7 +439,7 @@ impl<'a, 'b, C: BitStackConfig> PullParser<'a, 'b, C> {
} else {
// No event available - this shouldn't happen since we ensured have_events() above
break Err(ParseError::UnexpectedState(
"No events available after ensuring events exist".into(),
"No events available after ensuring events exist",
));
}
}
Expand All @@ -449,7 +449,7 @@ impl<'a, 'b, C: BitStackConfig> PullParser<'a, 'b, C> {
#[cfg(test)]
mod tests {
use super::*;
use crate::String;
use crate::{ArrayBitStack, BitStackStruct, String};
use test_log::test;

#[test]
Expand Down Expand Up @@ -815,4 +815,70 @@ mod tests {
assert_eq!(parser.next_event(), Ok(Event::EndDocument));
assert_eq!(parser.next_event(), Ok(Event::EndDocument));
}

#[test]
fn test_with_config_constructors() {
// Test with_config constructor (no escapes)
let json = r#"{"simple": "no_escapes"}"#;
let mut parser = PullParser::<BitStackStruct<u64, u16>>::with_config(json);

assert_eq!(parser.next_event(), Ok(Event::StartObject));
assert_eq!(
parser.next_event(),
Ok(Event::Key(String::Borrowed("simple")))
);
assert_eq!(
parser.next_event(),
Ok(Event::String(String::Borrowed("no_escapes")))
);
assert_eq!(parser.next_event(), Ok(Event::EndObject));
assert_eq!(parser.next_event(), Ok(Event::EndDocument));
}

#[test]
fn test_with_config_and_buffer_constructors() {
// Test with_config_and_buffer constructor (with escapes)
let json = r#"{"escaped": "hello\nworld"}"#;
let mut scratch = [0u8; 64];
let mut parser =
PullParser::<BitStackStruct<u64, u16>>::with_config_and_buffer(json, &mut scratch);

assert_eq!(parser.next_event(), Ok(Event::StartObject));
assert_eq!(
parser.next_event(),
Ok(Event::Key(String::Borrowed("escaped")))
);

if let Ok(Event::String(s)) = parser.next_event() {
assert_eq!(s.as_ref(), "hello\nworld"); // Escape should be processed
} else {
panic!("Expected String event");
}

assert_eq!(parser.next_event(), Ok(Event::EndObject));
assert_eq!(parser.next_event(), Ok(Event::EndDocument));
}

#[test]
fn test_alternative_config_deep_nesting() {
// Test that custom BitStack configs can handle deeper nesting
let json = r#"{"a":{"b":{"c":{"d":{"e":"deep"}}}}}"#;
let mut scratch = [0u8; 64];
let mut parser =
PullParser::<ArrayBitStack<8, u32, u16>>::with_config_and_buffer(json, &mut scratch);

// Parse the deep structure
let mut depth = 0;
while let Ok(event) = parser.next_event() {
match event {
Event::StartObject => depth += 1,
Event::EndObject => depth -= 1,
Event::EndDocument => break,
_ => {}
}
}

// Should have successfully parsed a 5-level deep structure
assert_eq!(depth, 0); // All objects should be closed
}
}
Loading
Loading