├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── bench ├── .gitignore ├── Cargo.toml └── benches │ ├── context.rs │ └── reader.rs ├── codecov.yml ├── examples ├── anyhow.rs ├── debug.rs ├── external.rs ├── ini.rs ├── json.rs ├── nom.rs ├── streaming.rs └── zerocopy.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── display.rs │ ├── general.rs │ └── string.rs ├── src ├── display │ ├── error.rs │ ├── input.rs │ ├── mod.rs │ ├── section.rs │ └── unit.rs ├── error │ ├── backtrace.rs │ ├── context.rs │ ├── expected │ │ ├── length.rs │ │ ├── mod.rs │ │ ├── valid.rs │ │ └── value.rs │ ├── fatal.rs │ ├── invalid.rs │ ├── length.rs │ ├── mod.rs │ ├── retry.rs │ ├── traits.rs │ └── value.rs ├── input │ ├── bound.rs │ ├── byte_len.rs │ ├── bytes │ │ ├── array.rs │ │ ├── mod.rs │ │ ├── pattern.rs │ │ └── prefix.rs │ ├── entry.rs │ ├── mod.rs │ ├── pattern.rs │ ├── prefix.rs │ ├── span.rs │ ├── string │ │ ├── maybe.rs │ │ ├── mod.rs │ │ ├── pattern.rs │ │ └── prefix.rs │ ├── token.rs │ └── traits.rs ├── lib.rs ├── reader │ ├── bytes.rs │ ├── input.rs │ ├── mod.rs │ └── peek.rs ├── support │ ├── core.rs │ ├── mod.rs │ ├── nom.rs │ ├── std.rs │ └── zc.rs └── util │ ├── fast.rs │ ├── mod.rs │ ├── slice.rs │ └── utf8.rs └── tests ├── common.rs ├── test_display.rs ├── test_errors.rs ├── test_input.rs ├── test_nom.rs ├── test_pattern.rs ├── test_reader.rs ├── test_reader_bytes.rs ├── test_reader_string.rs ├── test_span.rs └── test_usage.rs /.github/ FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [avitex] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | os: [ubuntu-latest] 8 | rust-toolchain: [stable, nightly] 9 | fail-fast: false 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Install Rust toolchain 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: ${{ matrix.rust-toolchain }} 18 | components: clippy, rustfmt 19 | override: true 20 | - name: Verify versions 21 | run: rustc --version && rustup --version && cargo --version 22 | - name: Cache build artifacts 23 | id: cache-cargo 24 | uses: actions/cache@v2 25 | with: 26 | path: | 27 | ~/.cargo/registry 28 | ~/.cargo/git 29 | target 30 | key: ${{ runner.os }}-cargo-${{ matrix.rust-toolchain }} 31 | - name: Test code with default features 32 | run: cargo test 33 | - name: Test code with all features 34 | run: cargo test --all-features 35 | - name: Test code with no default features 36 | run: cargo test --no-default-features 37 | - name: Lint code 38 | if: ${{ matrix.rust-toolchain == 'stable' }} 39 | run: cargo fmt -- --check && cargo clippy --all-features 40 | code_coverage: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@v2 45 | - name: Install Rust nightly components 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | profile: minimal 49 | toolchain: nightly 50 | components: llvm-tools-preview 51 | - name: Install cargo-llvm-cov 52 | run: curl -LsSf https://github.com/taiki-e/cargo-llvm-cov/releases/latest/download/cargo-llvm-cov-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin 53 | - name: Generate code coverage 54 | run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info 55 | - name: Upload to codecov.io 56 | uses: codecov/codecov-action@v1 57 | with: 58 | token: ${{secrets.CODECOV_TOKEN}} 59 | files: lcov.info 60 | fail_ci_if_error: true 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.wip 2 | /target 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dangerous" 3 | version = "0.10.0" 4 | authors = ["avitex "] 5 | edition = "2021" 6 | rust-version = "1.57" 7 | description = "Safely and explicitly parse untrusted / dangerous data" 8 | categories = ["no-std", "parsing"] 9 | documentation = "https://docs.rs/dangerous" 10 | homepage = "https://github.com/avitex/rust-dangerous" 11 | repository = "https://github.com/avitex/rust-dangerous" 12 | license = "MIT" 13 | readme = "README.md" 14 | include = ["src/**/*", "tests/**/*", "examples/**/*", "README.md", "LICENSE", "Cargo.toml"] 15 | keywords = ["parsing", "simd", "untrusted"] 16 | 17 | [features] 18 | default = ["std", "full-backtrace", "simd", "unicode"] 19 | # Enables `std::error::Error` support. 20 | std = ["alloc"] 21 | # Enables allocations. 22 | alloc = [] 23 | # Enables all supported SIMD optimisations. 24 | simd = ["std", "memchr/std", "bytecount/runtime-dispatch-simd"] 25 | # Enables improved unicode printing support. 26 | unicode = ["unicode-width"] 27 | # Enables full context backtraces. 28 | full-backtrace = ["alloc"] 29 | 30 | [dependencies] 31 | zc = { version = "0.4", optional = true, default-features = false } 32 | nom = { version = "7", features = ["alloc"], optional = true, default-features = false } 33 | regex = { version = "1.4", optional = true } 34 | memchr = { version = "2.4", optional = true, default-features = false } 35 | bytecount = { version = "0.6", optional = true } 36 | unicode-width = { version = "0.1", optional = true } 37 | 38 | [dev-dependencies] 39 | zc = "0.4" 40 | paste = "1.0" 41 | indoc = "1.0" 42 | anyhow = "1.0" 43 | imap-proto = "0.15" 44 | colored-diff = "0.2.2" 45 | 46 | [[example]] 47 | name = "json" 48 | required-features = ["std"] 49 | 50 | [[example]] 51 | name = "streaming" 52 | required-features = ["std"] 53 | 54 | [[example]] 55 | name = "zerocopy" 56 | required-features = ["zc"] 57 | 58 | [[example]] 59 | name = "nom" 60 | required-features = ["nom"] 61 | 62 | [[test]] 63 | name = "test_nom" 64 | required-features = ["nom", "full-backtrace"] 65 | 66 | [package.metadata.docs.rs] 67 | all-features = true 68 | rustdoc-args = ["--cfg", "docsrs"] 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2021 James Dyson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/avitex/rust-dangerous/workflows/build/badge.svg)](https://github.com/avitex/rust-dangerous/actions?query=workflow:build) 2 | [![Coverage Status](https://codecov.io/gh/avitex/rust-dangerous/branch/master/graph/badge.svg?token=X2LXHI8VYL)](https://codecov.io/gh/avitex/rust-dangerous) 3 | [![Crate](https://img.shields.io/crates/v/dangerous.svg)](https://crates.io/crates/dangerous) 4 | [![Docs](https://docs.rs/dangerous/badge.svg)](https://docs.rs/dangerous) 5 | 6 | # rust-dangerous 7 | 8 | **Rust library for safely and explicitly handling untrusted aka `dangerous` data** 9 | Documentation hosted on [docs.rs](https://docs.rs/dangerous). 10 | 11 | ```toml 12 | dangerous = "0.10" 13 | ``` 14 | 15 | ## Goals 16 | 17 | - Fast parsing. 18 | - Fast to compile. 19 | - Zero panics \[1]. 20 | - Zero-cost abstractions. 21 | - Minimal dependencies \[2]. 22 | - Retry/stream protocol support. 23 | - `no-std` / suitable for embedded. 24 | - Zero heap-allocations on success paths \[3]. 25 | - Primitive type support. 26 | - Optional verbose errors. 27 | - Optional SIMD optimisations where possible. 28 | 29 | **\[1]** Panics due to OOM are out-of-scope. Disable heap-allocations if this is 30 | a concern. 31 | **\[2]** Zero dependencies when both `unicode` and `simd` features are disabled. 32 | **\[3]** Zero heap-allocations when the `full-backtrace` feature is disabled. 33 | 34 | This library's intentions are to provide a simple interface for explicitly 35 | parsing untrusted data safely. `dangerous` really shines with parsing binary or 36 | simple text data formats and protocols. It is not a deserialisation library like 37 | what `serde` provides, but you could write a parser with `dangerous` that could 38 | be used within a deserialiser. 39 | 40 | Panics and unhandled/unacknowledged data are two footguns this library seeks to 41 | prevent. An optional, but solid, debugging interface with sane input formatting 42 | and helpful errors is included to weed out problems before, or after they arise 43 | in production. 44 | 45 | ## Usage 46 | 47 | ```rust 48 | fn decode_message<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 49 | where 50 | E: Error<'i>, 51 | { 52 | r.context("message", |r| { 53 | // Expect version 1 54 | r.context("version", |r| r.consume(0x01))?; 55 | // Read the body length 56 | let body_len = r.context("body len", |r| r.read())?; 57 | // Take the body input 58 | let body = r.context("body", |r| { 59 | let body_input = r.take(body_len as usize)?; 60 | // Decode the body input as a UTF-8 str 61 | body_input.to_dangerous_str() 62 | })?; 63 | // We did it! 64 | Ok(Message { body }) 65 | }) 66 | } 67 | 68 | let input = dangerous::input(/* data */); 69 | let result: Result<_, Invalid> = input.read_all(decode_message); 70 | ``` 71 | 72 | ## Errors 73 | 74 | Custom errors for protocols often do not provide much context around why and 75 | where a specific problem occurs within input. Passing down errors as simple as 76 | `core::str::Utf8Error` may be useful enough to debug while in development, 77 | however when just written into logs without the input/context, often amount to 78 | noise. At this stage you are almost better off with a simple input error. 79 | 80 | This problem is amplified with any trivial recursive-descent parser as the 81 | context around a sub-slice is lost, rendering any error offsets useless when 82 | passed back up to the root. `dangerous` fixes this by capturing the context 83 | around and above the error. 84 | 85 | Ever tried working backwards from something like this? 86 | 87 | ``` 88 | [ERRO]: ahhh!: Utf8Error { valid_up_to: 2, error_len: Some(1) } 89 | ``` 90 | 91 | Wouldn't it be better if this was the alternative? 92 | 93 | ``` 94 | [ERRO]: ahhh!: error reading message: failed to convert input into string: expected utf-8 code point 95 | > [01 05 68 65 ff 6c 6f] 96 | ^^ 97 | additional: 98 | error offset: 4, input length: 7 99 | backtrace: 100 | 1. `read all input` 101 | 2. `` (expected message) 102 | 3. `` (expected body) 103 | 4. `convert input into string` (expected utf-8 code point) 104 | ``` 105 | 106 | ## Inspiration 107 | 108 | This project was originally inspired by [untrusted](https://github.com/briansmith/untrusted). 109 | -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dangerous-bench" 3 | version = "0.0.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [[bench]] 8 | name = "reader" 9 | harness = false 10 | 11 | [[bench]] 12 | name = "context" 13 | harness = false 14 | 15 | [dependencies] 16 | criterion = { version = "0.3", features = ["real_blackbox"] } 17 | 18 | [dependencies.dangerous] 19 | path = ".." 20 | 21 | # Prevent this from interfering with workspaces 22 | [workspace] 23 | members = ["."] 24 | -------------------------------------------------------------------------------- /bench/benches/context.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use dangerous::{Error, Expected, Fatal, Input, Invalid}; 4 | 5 | fn expected<'i, E>(bytes: &'i [u8]) -> Result<(), E> 6 | where 7 | E: Error<'i>, 8 | { 9 | dangerous::input(bytes).read_all(|r| { 10 | r.context("foo", |r| { 11 | r.context("bar", |r| { 12 | r.context("hello", |r| r.context("world", |r| r.consume(b"o"))) 13 | }) 14 | }) 15 | }) 16 | } 17 | 18 | fn bench_fatal(c: &mut Criterion) { 19 | c.bench_function("fatal_ok", |b| { 20 | b.iter(|| expected::(black_box(b"o"))) 21 | }); 22 | c.bench_function("fatal_err", |b| { 23 | b.iter(|| expected::(black_box(b"e"))) 24 | }); 25 | } 26 | 27 | fn bench_invalid(c: &mut Criterion) { 28 | c.bench_function("invalid_ok", |b| { 29 | b.iter(|| expected::(black_box(b"o"))) 30 | }); 31 | c.bench_function("invalid_err", |b| { 32 | b.iter(|| expected::(black_box(b"e"))) 33 | }); 34 | } 35 | 36 | fn bench_expected(c: &mut Criterion) { 37 | c.bench_function("expected_ok", |b| { 38 | b.iter(|| expected::(black_box(b"o"))) 39 | }); 40 | c.bench_function("expected_err", |b| { 41 | b.iter(|| expected::(black_box(b"e"))) 42 | }); 43 | c.bench_function("expected_ok_boxed", |b| { 44 | b.iter(|| expected::>(black_box(b"o"))) 45 | }); 46 | c.bench_function("expected_err_boxed", |b| { 47 | b.iter(|| expected::>(black_box(b"e"))) 48 | }); 49 | } 50 | 51 | criterion_group!(benches, bench_fatal, bench_invalid, bench_expected); 52 | criterion_main!(benches); 53 | -------------------------------------------------------------------------------- /bench/benches/reader.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use dangerous::{input, ByteArray, BytesReader, Input, Invalid}; 4 | 5 | fn bench_consume(c: &mut Criterion) { 6 | c.bench_function("consume_u8_ok", |b| { 7 | b.iter(|| { 8 | input(black_box(&[1u8; 1])) 9 | .read_all(|r: &mut BytesReader<'_, Invalid>| r.consume(1)) 10 | .unwrap(); 11 | }) 12 | }); 13 | c.bench_function("consume_u8_err", |b| { 14 | b.iter(|| { 15 | let _ = input(black_box(&[1u8; 1])) 16 | .read_all(|r: &mut BytesReader<'_, Invalid>| r.consume(2)) 17 | .unwrap_err(); 18 | }) 19 | }); 20 | c.bench_function("consume_bytes_ok", |b| { 21 | b.iter(|| { 22 | input(black_box(&[1u8; 2])) 23 | .read_all(|r: &mut BytesReader<'_, Invalid>| r.consume(&[1, 1])) 24 | .unwrap(); 25 | }) 26 | }); 27 | c.bench_function("consume_bytes_err", |b| { 28 | b.iter(|| { 29 | let _ = input(black_box(&[1u8; 2])) 30 | .read_all(|r: &mut BytesReader<'_, Invalid>| r.consume(&[2, 2])) 31 | .unwrap_err(); 32 | }) 33 | }); 34 | } 35 | 36 | fn bench_read_num(c: &mut Criterion) { 37 | c.bench_function("read_u16_le", |b| { 38 | b.iter(|| { 39 | input(black_box(&[1u8; 2])) 40 | .read_all(|r: &mut BytesReader<'_, Invalid>| { 41 | r.take_array() 42 | .map(ByteArray::into_dangerous) 43 | .map(u16::from_le_bytes) 44 | }) 45 | .unwrap(); 46 | }) 47 | }); 48 | 49 | c.bench_function("read_u32_le", |b| { 50 | b.iter(|| { 51 | input(black_box(&[1u8; 4])) 52 | .read_all(|r: &mut BytesReader<'_, Invalid>| { 53 | r.take_array() 54 | .map(ByteArray::into_dangerous) 55 | .map(u32::from_le_bytes) 56 | }) 57 | .unwrap(); 58 | }) 59 | }); 60 | 61 | c.bench_function("read_u64_le", |b| { 62 | b.iter(|| { 63 | input(black_box(&[1u8; 8])) 64 | .read_all(|r: &mut BytesReader<'_, Invalid>| { 65 | r.take_array() 66 | .map(ByteArray::into_dangerous) 67 | .map(u64::from_le_bytes) 68 | }) 69 | .unwrap(); 70 | }) 71 | }); 72 | } 73 | 74 | fn bench_peek_eq(c: &mut Criterion) { 75 | c.bench_function("peek_eq", |b| { 76 | b.iter(|| { 77 | input(black_box(&[1u8; 2])) 78 | .read_all(|r: &mut BytesReader<'_, Invalid>| { 79 | if r.peek_eq(&[1]) { 80 | r.skip(2) 81 | } else { 82 | r.skip(0) 83 | } 84 | }) 85 | .unwrap(); 86 | }) 87 | }); 88 | 89 | c.bench_function("peek_eq", |b| { 90 | b.iter(|| { 91 | input(black_box(&[1u8; 2])) 92 | .read_all( 93 | |r: &mut BytesReader<'_, Invalid>| { 94 | if r.peek_eq(1) { 95 | r.skip(2) 96 | } else { 97 | r.skip(0) 98 | } 99 | }, 100 | ) 101 | .unwrap(); 102 | }) 103 | }); 104 | } 105 | 106 | criterion_group!(benches, bench_peek_eq, bench_consume, bench_read_num); 107 | criterion_main!(benches); 108 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | coverage: 3 | status: 4 | patch: off 5 | github_checks: 6 | annotations: false 7 | -------------------------------------------------------------------------------- /examples/anyhow.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use dangerous::{BytesReader, Error, Expected, Input}; 3 | 4 | fn main() { 5 | dangerous::input(b"hello") 6 | .read_all(parse::>) 7 | .map_err(|err| anyhow::Error::msg(err.to_string())) 8 | .context("my anyhow context 1") 9 | .context("my anyhow context 2") 10 | .unwrap(); 11 | } 12 | 13 | fn parse<'i, E>(r: &mut BytesReader<'i, E>) -> Result<(), E> 14 | where 15 | E: Error<'i>, 16 | { 17 | r.context("my value", |r| r.consume(b"world")) 18 | } 19 | -------------------------------------------------------------------------------- /examples/debug.rs: -------------------------------------------------------------------------------- 1 | use dangerous::{Expected, Input}; 2 | 3 | fn main() { 4 | // Expect length 5 | dbg!(&dangerous::input(b"A\xC3\xA9 \xC2") 6 | .to_dangerous_str::>() 7 | .unwrap_err()); 8 | // Expect valid 9 | dbg!(&dangerous::input(b"A\xC3\xA9 \xFF") 10 | .to_dangerous_str::>() 11 | .unwrap_err()); 12 | // Expect value 13 | dbg!(&dangerous::input(b"hello") 14 | .read_all::<_, _, Expected<'_>>(|r| r.consume(b"world")) 15 | .unwrap_err()); 16 | // Expecte value: fatal 17 | dbg!(&dangerous::input(b"hello") 18 | .into_bound() 19 | .read_all::<_, _, Expected<'_>>(|r| r.consume(b"world")) 20 | .unwrap_err()); 21 | } 22 | -------------------------------------------------------------------------------- /examples/external.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use dangerous::{error, BytesReader, Error, Expected, Input}; 4 | 5 | fn main() { 6 | println!("=== VALID PARSE ==="); 7 | let input = dangerous::input(b"foo"); 8 | let result: Result<_, Expected<'_>> = input.read_all(read_custom); 9 | println!("{:?}", result.unwrap()); 10 | 11 | println!("\n=== INVALID PARSE ==="); 12 | let input = dangerous::input(b"bar"); 13 | let error: Expected<'_> = input.read_all(read_custom).unwrap_err(); 14 | println!("{:#}", error); 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct Custom<'i>(&'i str); 19 | 20 | impl<'i> TryFrom> for Custom<'i> { 21 | type Error = ParseCustomError; 22 | 23 | fn try_from(s: dangerous::String<'i>) -> Result { 24 | if s.as_dangerous() == "foo" { 25 | Ok(Self(s.as_dangerous())) 26 | } else { 27 | Err(ParseCustomError) 28 | } 29 | } 30 | } 31 | 32 | pub struct ParseCustomError; 33 | 34 | impl<'i> error::External<'i> for ParseCustomError { 35 | fn push_backtrace(self, error: E) -> E 36 | where 37 | E: error::WithContext<'i>, 38 | { 39 | error.with_context(error::ExternalContext { 40 | operation: Some("read my custom type"), 41 | expected: Some("my custom type"), 42 | }) 43 | } 44 | } 45 | 46 | fn read_custom<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 47 | where 48 | E: Error<'i>, 49 | { 50 | r.take_remaining_str()? 51 | .into_external("my custom type", Custom::try_from) 52 | } 53 | -------------------------------------------------------------------------------- /examples/ini.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates a basic but rather tolerant ini parser, which most 2 | //! certainly does not obey to the standard. 3 | //! 4 | //! ``` 5 | //! echo 'hello = world' | cargo run --example ini 6 | //! ``` 7 | use std::io::{self, Read}; 8 | 9 | use dangerous::{BytesReader, Error, Expected, Input}; 10 | 11 | fn main() { 12 | let mut input_data = Vec::new(); 13 | io::stdin() 14 | .read_to_end(&mut input_data) 15 | .expect("read input"); 16 | let input = dangerous::input(input_data.as_slice()); 17 | match input.read_all(read_ini::>) { 18 | Ok(ini) => println!("{:#?}", ini), 19 | Err(e) => eprintln!("{:#}", e), 20 | }; 21 | } 22 | 23 | #[derive(Debug, PartialEq, Eq)] 24 | struct Pair<'a> { 25 | name: &'a str, 26 | value: &'a str, 27 | } 28 | 29 | #[derive(Default, Debug, PartialEq, Eq)] 30 | struct Document<'a> { 31 | globals: Vec>, 32 | sections: Vec>, 33 | } 34 | 35 | #[derive(Debug, PartialEq, Eq)] 36 | struct Section<'a> { 37 | name: &'a str, 38 | properties: Vec>, 39 | } 40 | 41 | fn read_ini<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 42 | where 43 | E: Error<'i>, 44 | { 45 | skip_whitespace_or_comment(r, ConsumeTo::NextToken); 46 | Ok(match r.peek_read_opt() { 47 | None => Document::default(), 48 | Some(b'[') => Document { 49 | globals: vec![], 50 | sections: read_sections(r)?, 51 | }, 52 | Some(_) => Document { 53 | globals: read_zero_or_more_properties_until_section(r)?, 54 | sections: read_sections(r)?, 55 | }, 56 | }) 57 | } 58 | 59 | fn read_sections<'i, E>(r: &mut BytesReader<'i, E>) -> Result>, E> 60 | where 61 | E: Error<'i>, 62 | { 63 | let mut sections = Vec::new(); 64 | while !r.at_end() { 65 | sections.push(read_section(r)?); 66 | } 67 | Ok(sections) 68 | } 69 | 70 | fn read_zero_or_more_properties_until_section<'i, E>( 71 | r: &mut BytesReader<'i, E>, 72 | ) -> Result>, E> 73 | where 74 | E: Error<'i>, 75 | { 76 | let mut out = Vec::new(); 77 | fn is_bare_text(c: u8) -> bool { 78 | !(c.is_ascii_whitespace() || c == b'=' || c == b'[') 79 | } 80 | 81 | skip_whitespace_or_comment(r, ConsumeTo::NextToken); 82 | while !(r.at_end() || r.peek_eq(b'[')) { 83 | r.context("property", |r| { 84 | skip_whitespace_or_comment(r, ConsumeTo::NextToken); 85 | let name = r.context("name", |r| { 86 | r.take_while(is_bare_text) 87 | .into_non_empty::()? 88 | .to_dangerous_str() 89 | })?; 90 | skip_whitespace_or_comment(r, ConsumeTo::EndOfLine); 91 | 92 | r.consume(b'=')?; 93 | 94 | skip_whitespace_or_comment(r, ConsumeTo::EndOfLine); 95 | let value = r.context("value", |r| { 96 | r.take_while(|c| c != b';' && c != b'\n' && c != b'=' && c != b'[') 97 | .into_non_empty::()? 98 | .to_dangerous_str() 99 | .map(str::trim) 100 | })?; 101 | skip_whitespace_or_comment(r, ConsumeTo::NextToken); 102 | out.push(Pair { name, value }); 103 | Ok(()) 104 | })?; 105 | } 106 | Ok(out) 107 | } 108 | 109 | fn read_section<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 110 | where 111 | E: Error<'i>, 112 | { 113 | skip_whitespace_or_comment(r, ConsumeTo::NextToken); 114 | r.consume(b'[')?; 115 | let name = r.context("section name", |r| { 116 | r.take_while(|c| c != b']' && c != b'\n') 117 | .into_non_empty::()? 118 | .to_dangerous_str() 119 | .map(str::trim) 120 | })?; 121 | r.consume(b']')?; 122 | 123 | r.verify("newline after section", |r| { 124 | r.take_while(|c: u8| c.is_ascii_whitespace()) 125 | .as_dangerous() 126 | .contains(&b'\n') 127 | })?; 128 | 129 | let properties = read_zero_or_more_properties_until_section(r)?; 130 | Ok(Section { name, properties }) 131 | } 132 | 133 | enum ConsumeTo { 134 | NextToken, 135 | EndOfLine, 136 | } 137 | 138 | fn skip_whitespace_or_comment(r: &mut BytesReader<'_, E>, to_where: ConsumeTo) { 139 | fn skip_comment(r: &mut BytesReader<'_, E>) -> usize { 140 | if r.peek_eq(b';') { 141 | r.take_until_opt(b'\n').len() 142 | } else { 143 | 0 144 | } 145 | } 146 | 147 | let (mut last, mut current) = (0, 0); 148 | loop { 149 | current += skip_comment(r); 150 | current += r 151 | .take_while(|c: u8| { 152 | let iwb = c.is_ascii_whitespace(); 153 | iwb && match to_where { 154 | ConsumeTo::NextToken => true, 155 | ConsumeTo::EndOfLine => c != b'\n', 156 | } 157 | }) 158 | .len(); 159 | if last == current { 160 | break; 161 | } 162 | last = current; 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | mod tests { 168 | use super::*; 169 | 170 | const GLOBALS_WITHOUT_SECTIONS: &[u8] = b" 171 | ; comment before 172 | hello = value 173 | a = b ; comment 174 | ; comment after 175 | "; 176 | 177 | const SECTION_WITHOUT_VALUES: &[u8] = b" 178 | ; comment before 179 | [ section name ] 180 | ; comment after 181 | "; 182 | 183 | const INI: &[u8] = b"language=rust ; awesome 184 | 185 | [ section ] 186 | name = dangerous ; 187 | type = manual 188 | 189 | [empty section] 190 | "; 191 | 192 | #[test] 193 | fn test_section_without_values() { 194 | let section = dangerous::input(SECTION_WITHOUT_VALUES) 195 | .read_all(read_section::>) 196 | .unwrap(); 197 | assert_eq!( 198 | section, 199 | Section { 200 | name: "section name", 201 | properties: vec![] 202 | }, 203 | ) 204 | } 205 | 206 | #[test] 207 | fn test_complete_ini() { 208 | let document = dangerous::input(INI) 209 | .read_all(read_ini::>) 210 | .unwrap(); 211 | assert_eq!( 212 | document, 213 | Document { 214 | globals: vec![Pair { 215 | name: "language", 216 | value: "rust" 217 | }], 218 | sections: vec![ 219 | Section { 220 | name: "section", 221 | properties: vec![ 222 | Pair { 223 | name: "name", 224 | value: "dangerous" 225 | }, 226 | Pair { 227 | name: "type", 228 | value: "manual" 229 | } 230 | ] 231 | }, 232 | Section { 233 | name: "empty section", 234 | properties: vec![] 235 | } 236 | ] 237 | }, 238 | ) 239 | } 240 | 241 | #[test] 242 | fn test_global_values_with_comments() { 243 | let values = dangerous::input(GLOBALS_WITHOUT_SECTIONS) 244 | .read_all(read_zero_or_more_properties_until_section::>) 245 | .unwrap(); 246 | assert_eq!( 247 | values, 248 | vec![ 249 | Pair { 250 | name: "hello", 251 | value: "value" 252 | }, 253 | Pair { 254 | name: "a", 255 | value: "b" 256 | } 257 | ] 258 | ) 259 | } 260 | 261 | #[test] 262 | fn test_document_without_sections() { 263 | let document = dangerous::input(GLOBALS_WITHOUT_SECTIONS) 264 | .read_all(read_ini::>) 265 | .unwrap(); 266 | assert_eq!( 267 | document, 268 | Document { 269 | globals: vec![ 270 | Pair { 271 | name: "hello", 272 | value: "value" 273 | }, 274 | Pair { 275 | name: "a", 276 | value: "b" 277 | } 278 | ], 279 | sections: vec![] 280 | } 281 | ) 282 | } 283 | 284 | #[test] 285 | fn empty_input() { 286 | let ini = dangerous::input(b"") 287 | .read_all(read_ini::>) 288 | .unwrap(); 289 | assert_eq!(ini, Document::default()); 290 | 291 | let ini = dangerous::input(b" \n ; empty ") 292 | .read_all(read_ini::>) 293 | .unwrap(); 294 | assert_eq!(ini, Document::default()) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /examples/json.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates a simple JSON parser (doesn't support escaping 2 | //! fully). 3 | //! 4 | //! ``` 5 | //! echo '{ "hello": "bob" }' | cargo run --example json 6 | //! ``` 7 | use std::io::{self, Read}; 8 | 9 | use dangerous::{BytesReader, Error, Expected, Input}; 10 | 11 | fn main() { 12 | let mut input_data = Vec::new(); 13 | io::stdin() 14 | .read_to_end(&mut input_data) 15 | .expect("read input"); 16 | let input = dangerous::input(input_data.as_slice()); 17 | match input.read_all(read_value::>>) { 18 | Ok(json) => println!("{:#?}", json), 19 | Err(e) => eprintln!("{:#}", e), 20 | } 21 | } 22 | 23 | #[derive(Debug)] 24 | enum Value<'a> { 25 | Null, 26 | Bool(bool), 27 | Str(&'a str), 28 | Number(f64), 29 | Array(Vec>), 30 | Object(Vec<(&'a str, Value<'a>)>), 31 | } 32 | 33 | fn read_value<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 34 | where 35 | E: Error<'i>, 36 | { 37 | skip_whitespace(r); 38 | let value = r.try_expect("json value", |r| { 39 | let value = match r.peek_read()? { 40 | b'"' => Value::Str(read_str(r)?), 41 | b'{' => Value::Object(read_map(r)?), 42 | b'[' => Value::Array(read_arr(r)?), 43 | b'n' => { 44 | read_null(r)?; 45 | Value::Null 46 | } 47 | b't' | b'f' => Value::Bool(read_bool(r)?), 48 | c if c.is_ascii_digit() => Value::Number(read_num(r)?), 49 | _ => return Ok(None), 50 | }; 51 | Ok(Some(value)) 52 | })?; 53 | skip_whitespace(r); 54 | Ok(value) 55 | } 56 | 57 | fn read_arr<'i, E>(r: &mut BytesReader<'i, E>) -> Result>, E> 58 | where 59 | E: Error<'i>, 60 | { 61 | skip_whitespace(r); 62 | r.context("json array", |r| { 63 | let mut items = Vec::new(); 64 | r.consume(b'[')?; 65 | skip_whitespace(r); 66 | if !r.peek_eq(b']') { 67 | loop { 68 | let val = read_value(r)?; 69 | skip_whitespace(r); 70 | items.push(val); 71 | if !r.at_end() && r.peek_eq(b',') { 72 | r.skip(1)?; 73 | continue; 74 | } else { 75 | break; 76 | } 77 | } 78 | } 79 | skip_whitespace(r); 80 | r.consume(b']')?; 81 | Ok(items) 82 | }) 83 | } 84 | 85 | fn read_map<'i, E>(r: &mut BytesReader<'i, E>) -> Result)>, E> 86 | where 87 | E: Error<'i>, 88 | { 89 | skip_whitespace(r); 90 | r.context("json object", |r| { 91 | let mut items = Vec::new(); 92 | r.consume(b'{')?; 93 | skip_whitespace(r); 94 | if !r.peek_eq(b'}') { 95 | loop { 96 | let key = r.context("json object key", read_str)?; 97 | skip_whitespace(r); 98 | r.consume(b':')?; 99 | skip_whitespace(r); 100 | let val = read_value(r)?; 101 | skip_whitespace(r); 102 | items.push((key, val)); 103 | if !r.at_end() && r.peek_eq(b',') { 104 | r.skip(1)?; 105 | continue; 106 | } else { 107 | break; 108 | } 109 | } 110 | } 111 | r.consume(b'}')?; 112 | Ok(items) 113 | }) 114 | } 115 | 116 | fn read_str<'i, E>(r: &mut BytesReader<'i, E>) -> Result<&'i str, E> 117 | where 118 | E: Error<'i>, 119 | { 120 | skip_whitespace(r); 121 | r.context("json string", |r| { 122 | r.consume(b'"')?; 123 | let mut last_was_escape = false; 124 | let s = r.take_while(|c| match c { 125 | b'\\' => { 126 | last_was_escape = true; 127 | true 128 | } 129 | b'"' => { 130 | let should_continue = last_was_escape; 131 | last_was_escape = false; 132 | should_continue 133 | } 134 | _ => { 135 | last_was_escape = false; 136 | true 137 | } 138 | }); 139 | r.consume(b'"')?; 140 | s.to_dangerous_str() 141 | }) 142 | } 143 | 144 | fn read_null<'i, E>(r: &mut BytesReader<'i, E>) -> Result<(), E> 145 | where 146 | E: Error<'i>, 147 | { 148 | skip_whitespace(r); 149 | r.context("json null", |r| r.consume(b"null")) 150 | } 151 | 152 | fn read_bool<'i, E>(r: &mut BytesReader<'i, E>) -> Result 153 | where 154 | E: Error<'i>, 155 | { 156 | skip_whitespace(r); 157 | r.try_expect("json boolean", |r| match r.peek_read()? { 158 | b't' => r.consume(b"true").map(|()| Some(true)), 159 | b'f' => r.consume(b"false").map(|()| Some(false)), 160 | _ => Ok(None), 161 | }) 162 | } 163 | 164 | fn read_num<'i, E>(r: &mut BytesReader<'i, E>) -> Result 165 | where 166 | E: Error<'i>, 167 | { 168 | skip_whitespace(r); 169 | r.context("json number", |r| { 170 | r.try_take_consumed(|r| { 171 | r.try_verify("first byte is digit", |r| { 172 | r.read().map(|c| c.is_ascii_digit()) 173 | })?; 174 | r.skip_while(|c: u8| c.is_ascii_digit() || c == b'.'); 175 | Ok(()) 176 | })? 177 | .1 178 | .into_string::()? 179 | .into_external("f64", |i| i.as_dangerous().parse()) 180 | }) 181 | } 182 | 183 | fn skip_whitespace(r: &mut BytesReader<'_, E>) { 184 | r.skip_while(|c: u8| c.is_ascii_whitespace()); 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use super::*; 190 | 191 | #[test] 192 | fn test_value_size() { 193 | // If true, we box Expected! 194 | assert!(core::mem::size_of::>() < 128); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /examples/nom.rs: -------------------------------------------------------------------------------- 1 | use imap_proto::{parser, types}; 2 | 3 | use dangerous::{BytesReader, Error, Expected, Input}; 4 | 5 | fn main() { 6 | println!("=== VALID PARSE ==="); 7 | let input = dangerous::input(&b"* LIST (\\HasNoChildren) \".\" INBOX.Tests\r\n"[..]); 8 | let result: Result<_, Expected<'_>> = input.read_all(read_imap_response); 9 | println!("{:?}", result.unwrap()); 10 | 11 | println!("\n=== INVALID PARSE ==="); 12 | let input = dangerous::input(&b"* LIST (\\HasNoChildren) \".\" IN\"BOX.Tests\r\n"[..]); 13 | let error: Expected<'_> = input.read_all(read_imap_response).unwrap_err(); 14 | println!("{:#}", error); 15 | 16 | println!("\n=== INVALID PARSE: TRAILING INPUT ==="); 17 | let input = 18 | dangerous::input(&b"* LIST (\\HasNoChildren) \".\" INBOX.Tests\r\ni am trailing"[..]); 19 | let error: Expected<'_> = input.read_all(read_imap_response).unwrap_err(); 20 | println!("{:#}", error); 21 | } 22 | 23 | fn read_imap_response<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 24 | where 25 | E: Error<'i>, 26 | { 27 | r.try_external("IMAP response", |i| { 28 | parser::parse_response(i.as_dangerous()) 29 | .map(|(remaining, response)| (i.len() - remaining.len(), response)) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /examples/streaming.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates a protocol being read from a stream. 2 | //! 3 | //! The simple protocol encodes messages with a single byte for versioning 4 | //! followed by a single byte that denotes the the UTF-8 body length we need to 5 | //! read. Our protocol expects a version of `1`. 6 | 7 | use std::error::Error as StdError; 8 | use std::io; 9 | 10 | use dangerous::{BytesReader, Error, Expected, Input, Invalid, ToRetryRequirement}; 11 | 12 | const VALID_MESSAGE: &[u8] = &[ 13 | 0x01, // version: 1 14 | 0x05, // body length: 5 15 | b'h', b'e', b'l', b'l', b'o', // the body value 16 | ]; 17 | 18 | const INVALID_MESSAGE: &[u8] = &[ 19 | 0x01, // version: 1 20 | 0x05, // body length: 5 21 | b'h', b'e', 0xff, b'l', b'o', // the body value with invalid UTF-8 22 | ]; 23 | 24 | #[derive(Debug)] 25 | struct Message<'a> { 26 | body: &'a str, 27 | } 28 | 29 | fn main() { 30 | let mut decoder = Decoder::new(); 31 | 32 | // Read a valid message 33 | let message = decoder 34 | .read_and_decode_message(&mut Stream::new(VALID_MESSAGE)) 35 | .unwrap(); 36 | 37 | println!("{}", message.body); 38 | 39 | // Read a invalid message 40 | let err = decoder 41 | .read_and_decode_message(&mut Stream::new(INVALID_MESSAGE)) 42 | .unwrap_err(); 43 | 44 | eprintln!("error reading message: {}", err); 45 | } 46 | 47 | pub struct Decoder { 48 | buf: [u8; 256], 49 | } 50 | 51 | impl Decoder { 52 | fn new() -> Self { 53 | Self { buf: [0u8; 256] } 54 | } 55 | 56 | fn read_and_decode_message<'i, R>( 57 | &'i mut self, 58 | mut read: R, 59 | ) -> Result, Box> 60 | where 61 | R: io::Read, 62 | { 63 | let mut written_cur = 0; 64 | let mut expects_cur = 0; 65 | loop { 66 | // Read bytes into buffer 67 | written_cur += read.read(&mut self.buf[written_cur..])?; 68 | // Only decode the buffer if we have enough bytes to try again 69 | if expects_cur > written_cur { 70 | println!( 71 | "not enough to decode, waiting for {} bytes", 72 | expects_cur - written_cur 73 | ); 74 | continue; 75 | } 76 | // Try and decode the input, working out if we need more, or the 77 | // input is invalid. 78 | // 79 | // FIXME: This would realistically return the decoded message or any 80 | // error, but we can't mut borrow and return immutable within a loop 81 | // yet. See: https://github.com/rust-lang/rust/issues/51132 82 | let input = dangerous::input(&self.buf[..written_cur]); 83 | match input.read_all(decode_message) { 84 | Err(err) => match Invalid::to_retry_requirement(&err) { 85 | Some(req) => { 86 | expects_cur += req.continue_after(); 87 | continue; 88 | } 89 | None => break, 90 | }, 91 | Ok(_) => break, 92 | } 93 | } 94 | // Decode the input returning the message or any error, see above why 95 | // this is required. 96 | dangerous::input(&self.buf[..written_cur]) 97 | .read_all(decode_message) 98 | .map_err(Box::::into) 99 | } 100 | } 101 | 102 | fn decode_message<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 103 | where 104 | E: Error<'i>, 105 | { 106 | r.context("message", |r| { 107 | // Expect version 1 108 | r.context("version", |r| r.consume(0x01))?; 109 | // Read the body length 110 | let body_len = r.context("body len", BytesReader::read)?; 111 | // Take the body input 112 | let body = r.context("body", |r| { 113 | let body_input = r.take(body_len as usize)?; 114 | // Decode the body input as a UTF-8 str 115 | body_input.to_dangerous_str() 116 | })?; 117 | // We did it! 118 | Ok(Message { body }) 119 | }) 120 | } 121 | 122 | // Dummy reader that reads one byte at a time 123 | struct Stream { 124 | cur: usize, 125 | src: &'static [u8], 126 | } 127 | 128 | impl Stream { 129 | fn new(src: &'static [u8]) -> Self { 130 | Self { src, cur: 0 } 131 | } 132 | } 133 | 134 | impl io::Read for Stream { 135 | fn read(&mut self, buf: &mut [u8]) -> Result { 136 | // If we have read all of the message, the connection is dead 137 | if self.cur == self.src.len() { 138 | return Err(io::Error::from(io::ErrorKind::NotConnected)); 139 | } 140 | // Copy the byte across to the buffer at the cursor 141 | buf[0] = self.src[self.cur]; 142 | // Increase the cursor for next invoke 143 | self.cur += 1; 144 | // Return the number of bytes we read 145 | Ok(1) 146 | } 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use super::*; 152 | 153 | #[test] 154 | fn test_message_size() { 155 | // If true, we box Expected! 156 | assert!(core::mem::size_of::>() < 128); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /examples/zerocopy.rs: -------------------------------------------------------------------------------- 1 | use dangerous::{BytesReader, Error, Expected, Input}; 2 | use zc::Zc; 3 | 4 | fn main() { 5 | let buf = Vec::from(&b"thisisatag,thisisanothertag"[..]); 6 | let result = Zc::new(buf, parse_bytes); 7 | dbg!(&result); 8 | } 9 | 10 | fn parse_bytes<'i>(bytes: &'i [u8]) -> Result, Expected<'i>> { 11 | dangerous::input(bytes).read_all(parse) 12 | } 13 | 14 | fn parse<'i, E>(r: &mut BytesReader<'i, E>) -> Result, E> 15 | where 16 | E: Error<'i>, 17 | { 18 | let mut parts = Vec::new(); 19 | loop { 20 | let s = r.take_until_opt(b',').to_dangerous_str::()?; 21 | parts.push(s); 22 | if !r.consume_opt(b',') { 23 | return Ok(parts); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dangerous-fuzz" 3 | version = "0.0.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | 13 | [dependencies.dangerous] 14 | path = ".." 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [[bin]] 21 | name = "general" 22 | path = "fuzz_targets/general.rs" 23 | test = false 24 | doc = false 25 | 26 | [[bin]] 27 | name = "string" 28 | path = "fuzz_targets/string.rs" 29 | test = false 30 | doc = false 31 | 32 | [[bin]] 33 | name = "display" 34 | path = "fuzz_targets/display.rs" 35 | test = false 36 | doc = false 37 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/display.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use dangerous::{Expected, Input}; 4 | use libfuzzer_sys::fuzz_target; 5 | use std::fmt::{self, Write}; 6 | 7 | fuzz_target!(|data_len_and_width: (&[u8], usize, usize)| { 8 | let (data, len, width) = data_len_and_width; 9 | let input = dangerous::input(data); 10 | 11 | if let Err(err) = input.to_dangerous_str::() { 12 | write!(DummyWrite, "{}", err).unwrap(); 13 | } 14 | 15 | write!(DummyWrite, "{}", input.display().full()).unwrap(); 16 | write!(DummyWrite, "{}", input.display().head(width)).unwrap(); 17 | write!(DummyWrite, "{}", input.display().head(width)).unwrap(); 18 | write!(DummyWrite, "{}", input.display().tail(width)).unwrap(); 19 | write!(DummyWrite, "{}", input.display().head_tail(width)).unwrap(); 20 | 21 | if let (Some(input_span), _) = input.clone().read_infallible(|r| r.take_opt(len)) { 22 | write!( 23 | DummyWrite, 24 | "{}", 25 | input.display().span(input_span.span(), width) 26 | ) 27 | .unwrap(); 28 | } 29 | }); 30 | 31 | struct DummyWrite; 32 | 33 | impl fmt::Write for DummyWrite { 34 | fn write_str(&mut self, _: &str) -> fmt::Result { 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/general.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use dangerous::{Expected, Input}; 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | macro_rules! read_partial { 7 | ($input:expr, $read_fn:expr) => { 8 | let _ = $input.read_partial::<_, _, Expected>($read_fn); 9 | }; 10 | } 11 | 12 | fuzz_target!(|data_length_and_patterns: (&[u8], usize, u8, &[u8])| { 13 | let (data, len, byte_pattern, slice_pattern) = data_length_and_patterns; 14 | let input = dangerous::input(data); 15 | 16 | // read 17 | read_partial!(input.clone(), |r| r.read()); 18 | // peek 19 | read_partial!(input.clone(), |r| r.peek_read()); 20 | read_partial!(input.clone(), |r| r.peek(len).map(drop)); 21 | read_partial!(input.clone(), |r| Ok(r.peek_eq(slice_pattern))); 22 | // consume 23 | read_partial!(input.clone(), |r| r.consume(byte_pattern)); 24 | read_partial!(input.clone(), |r| r.consume(slice_pattern)); 25 | // take/skip 26 | read_partial!(input.clone(), |r| r.take(len)); 27 | read_partial!(input.clone(), |r| r.skip(len)); 28 | // (take/skip/try_take/try_skip)_while 29 | read_partial!(input.clone(), |r| Ok(r.skip_while(byte_pattern))); 30 | read_partial!(input.clone(), |r| Ok(r.skip_while(slice_pattern))); 31 | read_partial!(input.clone(), |r| Ok(r.take_while(|c| c == byte_pattern))); 32 | read_partial!(input.clone(), |r| Ok(r.skip_while(|c| c == byte_pattern))); 33 | read_partial!(input.clone(), |r| r 34 | .try_take_while(|c| Ok(c == byte_pattern))); 35 | read_partial!(input.clone(), |r| r 36 | .try_skip_while(|c| Ok(c == byte_pattern))); 37 | // (take/skip)_until 38 | read_partial!(input.clone(), |r| Ok(r.take_until(byte_pattern))); 39 | read_partial!(input.clone(), |r| Ok(r.take_until(slice_pattern))); 40 | read_partial!(input.clone(), |r| Ok( 41 | r.take_until_opt(|c| c == byte_pattern) 42 | )); 43 | read_partial!(input.clone(), |r| Ok( 44 | r.take_until_opt(|c| c == byte_pattern) 45 | )); 46 | }); 47 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/string.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use dangerous::{Expected, Input}; 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | macro_rules! read_partial { 7 | ($input:expr, $read_fn:expr) => { 8 | let _ = $input.read_partial::<_, _, Expected>($read_fn); 9 | }; 10 | } 11 | 12 | fuzz_target!(|data_and_delim: (&[u8], char)| { 13 | let (data, delim) = data_and_delim; 14 | let input = dangerous::input(data); 15 | 16 | read_partial!(input.clone(), |r| r.take_str_while(|c| c == delim)); 17 | read_partial!(input.clone(), |r| r.skip_str_while(|c| c == delim)); 18 | read_partial!(input.clone(), |r| r.try_take_str_while(|c| Ok(c == delim))); 19 | read_partial!(input.clone(), |r| r.try_skip_str_while(|c| Ok(c == delim))); 20 | }); 21 | -------------------------------------------------------------------------------- /src/display/error.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{self, Context}; 2 | use crate::fmt::{self, Write}; 3 | use crate::input::{Bytes, Input}; 4 | 5 | use super::{DisplayBase, InputDisplay, PreferredFormat}; 6 | 7 | const DEFAULT_MAX_WIDTH: usize = 80; 8 | const INVALID_SPAN_ERROR: &str = "\ 9 | note: error span is not within the error input indicating the 10 | concrete error being used has a bug. Consider raising an 11 | issue with the maintainer! 12 | "; 13 | 14 | /// Provides configurable [`error::Details`] formatting. 15 | #[derive(Clone)] 16 | #[must_use = "error displays must be written"] 17 | pub struct ErrorDisplay<'a, T> { 18 | error: &'a T, 19 | banner: bool, 20 | format: PreferredFormat, 21 | input_max_width: usize, 22 | } 23 | 24 | impl<'a, 'i, T> ErrorDisplay<'a, T> 25 | where 26 | T: error::Details<'i>, 27 | { 28 | /// Create a new `ErrorDisplay` given [`error::Details`]. 29 | pub fn new(error: &'a T) -> Self { 30 | let format = if error.input().is_string() { 31 | PreferredFormat::Str 32 | } else { 33 | PreferredFormat::Bytes 34 | }; 35 | Self { 36 | error, 37 | format, 38 | banner: false, 39 | input_max_width: DEFAULT_MAX_WIDTH, 40 | } 41 | } 42 | 43 | /// Derive an `ErrorDisplay` from a [`fmt::Formatter`] with defaults. 44 | pub fn from_formatter(error: &'a T, f: &fmt::Formatter<'_>) -> Self { 45 | if f.alternate() { 46 | Self::new(error).str_hint() 47 | } else { 48 | Self::new(error) 49 | } 50 | } 51 | 52 | /// Set whether or not a banner should printed around the error. 53 | pub fn banner(mut self, value: bool) -> Self { 54 | self.banner = value; 55 | self 56 | } 57 | 58 | /// Set the `max-width` for wrapping error output. 59 | pub fn input_max_width(mut self, value: usize) -> Self { 60 | self.input_max_width = value; 61 | self 62 | } 63 | 64 | /// Hint to the formatter that the [`crate::Input`] is a UTF-8 `str`. 65 | pub fn str_hint(self) -> Self { 66 | match self.format { 67 | PreferredFormat::Bytes | PreferredFormat::BytesAscii => { 68 | self.format(PreferredFormat::Str) 69 | } 70 | _ => self, 71 | } 72 | } 73 | 74 | /// Set the preferred way to format the [`Input`]. 75 | pub fn format(mut self, format: PreferredFormat) -> Self { 76 | self.format = format; 77 | self 78 | } 79 | 80 | fn write_sections(&self, w: &mut dyn Write) -> fmt::Result { 81 | let input = self.error.input(); 82 | let root = self.error.backtrace().root(); 83 | // Write description 84 | w.write_str("failed to ")?; 85 | root.operation().description(w)?; 86 | w.write_str(": ")?; 87 | self.error.description(w)?; 88 | w.write_char('\n')?; 89 | // Write inputs 90 | let input_display = self.configure_input_display(input.display()); 91 | let input = input.into_bytes(); 92 | if let Some(expected_value) = self.error.expected() { 93 | let expected_display = self.configure_input_display(expected_value.display()); 94 | w.write_str("expected:\n")?; 95 | write_input(w, expected_display, false)?; 96 | w.write_str("in:\n")?; 97 | } 98 | if root.span.is_within(input.span()) { 99 | write_input(w, input_display.span(root.span, self.input_max_width), true)?; 100 | } else { 101 | w.write_str(INVALID_SPAN_ERROR)?; 102 | w.write_str("input:\n")?; 103 | write_input(w, input_display, false)?; 104 | } 105 | // Write additional 106 | w.write_str("additional:\n ")?; 107 | if let Some(span_range) = root.span.range_of(input.span()) { 108 | if matches!( 109 | self.format, 110 | PreferredFormat::Str | PreferredFormat::StrCjk | PreferredFormat::BytesAscii 111 | ) { 112 | w.write_str("error line: ")?; 113 | w.write_usize(line_offset(&input, span_range.start))?; 114 | w.write_str(", ")?; 115 | } 116 | w.write_str("error offset: ")?; 117 | w.write_usize(span_range.start)?; 118 | w.write_str(", input length: ")?; 119 | w.write_usize(input.len())?; 120 | } else { 121 | w.write_str("error: ")?; 122 | DisplayBase::fmt(&root.span, w)?; 123 | w.write_str("input: ")?; 124 | DisplayBase::fmt(&input.span(), w)?; 125 | } 126 | w.write_char('\n')?; 127 | // Write context backtrace 128 | w.write_str("backtrace:")?; 129 | let mut child_index = 1; 130 | let mut last_parent_depth = 0; 131 | let write_success = self.error.backtrace().walk(&mut |parent_depth, context| { 132 | let mut write = || { 133 | w.write_str("\n ")?; 134 | if parent_depth == last_parent_depth { 135 | w.write_str(" ")?; 136 | w.write_usize(child_index)?; 137 | child_index += 1; 138 | } else { 139 | child_index = 1; 140 | last_parent_depth = parent_depth; 141 | w.write_usize(parent_depth)?; 142 | } 143 | w.write_str(". `")?; 144 | context.operation().description(w)?; 145 | w.write_char('`')?; 146 | if context.has_expected() { 147 | w.write_str(" (expected ")?; 148 | context.expected(w)?; 149 | w.write_char(')')?; 150 | } 151 | fmt::Result::Ok(()) 152 | }; 153 | write().is_ok() 154 | }); 155 | if write_success { 156 | Ok(()) 157 | } else { 158 | Err(fmt::Error) 159 | } 160 | } 161 | 162 | fn configure_input_display<'b>(&self, display: InputDisplay<'b>) -> InputDisplay<'b> { 163 | display.format(self.format) 164 | } 165 | } 166 | 167 | impl<'a, 'i, T> fmt::DisplayBase for ErrorDisplay<'a, T> 168 | where 169 | T: error::Details<'i>, 170 | { 171 | fn fmt(&self, w: &mut dyn Write) -> fmt::Result { 172 | if self.banner { 173 | w.write_str("\n-- INPUT ERROR ---------------------------------------------\n")?; 174 | self.write_sections(w)?; 175 | w.write_str("\n------------------------------------------------------------\n") 176 | } else { 177 | self.write_sections(w) 178 | } 179 | } 180 | } 181 | 182 | impl<'a, 'i, T> fmt::Debug for ErrorDisplay<'a, T> 183 | where 184 | T: error::Details<'i>, 185 | { 186 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 187 | fmt::DisplayBase::fmt(self, f) 188 | } 189 | } 190 | 191 | impl<'a, 'i, T> fmt::Display for ErrorDisplay<'a, T> 192 | where 193 | T: error::Details<'i>, 194 | { 195 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 196 | fmt::DisplayBase::fmt(self, f) 197 | } 198 | } 199 | 200 | fn line_offset(input: &Bytes<'_>, span_offset: usize) -> usize { 201 | match input.clone().split_at_opt(span_offset) { 202 | Some((before_span, _)) => before_span.count(b'\n') + 1, 203 | // Will never be reached in practical usage but we handle to avoid 204 | // unwrapping. 205 | None => 0, 206 | } 207 | } 208 | 209 | fn write_input(w: &mut dyn Write, input: InputDisplay<'_>, underline: bool) -> fmt::Result { 210 | let input = input.prepare(); 211 | w.write_str("> ")?; 212 | fmt::DisplayBase::fmt(&input, w)?; 213 | w.write_char('\n')?; 214 | if underline { 215 | w.write_str(" ")?; 216 | fmt::DisplayBase::fmt(&input.underline(), w)?; 217 | w.write_char('\n')?; 218 | } 219 | Ok(()) 220 | } 221 | -------------------------------------------------------------------------------- /src/display/mod.rs: -------------------------------------------------------------------------------- 1 | //! Display support. 2 | 3 | mod error; 4 | mod input; 5 | mod section; 6 | mod unit; 7 | 8 | use core::fmt::{Formatter, Result}; 9 | 10 | pub use self::error::ErrorDisplay; 11 | pub use self::input::{InputDisplay, PreferredFormat}; 12 | 13 | /// Library specific display trait that accepts a [`Write`] without requiring a 14 | /// formatter. 15 | pub trait DisplayBase { 16 | /// Formats `self` given the provided [`Write`]. 17 | /// 18 | /// # Errors 19 | /// 20 | /// Returns a [`core::fmt::Error`] if failed to write. 21 | fn fmt(&self, w: &mut dyn Write) -> Result; 22 | } 23 | 24 | impl DisplayBase for &T 25 | where 26 | T: DisplayBase, 27 | { 28 | fn fmt(&self, w: &mut dyn Write) -> Result { 29 | (**self).fmt(w) 30 | } 31 | } 32 | 33 | impl DisplayBase for &'static str { 34 | fn fmt(&self, w: &mut dyn Write) -> Result { 35 | w.write_str(self) 36 | } 37 | } 38 | 39 | /// Library specific [`Write`] trait for formatting. 40 | pub trait Write { 41 | /// Writes a string slice into this writer, returning whether the write 42 | /// succeeded. 43 | /// 44 | /// # Errors 45 | /// 46 | /// Returns a [`core::fmt::Error`] if failed to write. 47 | fn write_str(&mut self, s: &str) -> Result; 48 | 49 | /// Writes a char into this writer, returning whether the write succeeded. 50 | /// 51 | /// # Errors 52 | /// 53 | /// Returns a [`core::fmt::Error`] if failed to write. 54 | fn write_char(&mut self, c: char) -> Result; 55 | 56 | /// Writes a usize into this writer, returning whether the write succeeded. 57 | /// 58 | /// # Errors 59 | /// 60 | /// Returns a [`core::fmt::Error`] if failed to write. 61 | fn write_usize(&mut self, v: usize) -> Result; 62 | 63 | /// Writes a byte as hex into this writer, returning whether the write 64 | /// succeeded. 65 | /// 66 | /// The byte as hex must be always two characters long (zero-padded). 67 | /// 68 | /// # Errors 69 | /// 70 | /// Returns a [`core::fmt::Error`] if failed to write. 71 | fn write_hex(&mut self, b: u8) -> Result { 72 | fn digit(b: u8) -> char { 73 | if b > 9 { 74 | (b'a' + (b - 10)) as char 75 | } else { 76 | (b'0' + b) as char 77 | } 78 | } 79 | self.write_char(digit(b >> 4))?; 80 | self.write_char(digit(b & 0x0F)) 81 | } 82 | } 83 | 84 | impl Write for &mut T 85 | where 86 | T: Write, 87 | { 88 | fn write_str(&mut self, s: &str) -> Result { 89 | (**self).write_str(s) 90 | } 91 | 92 | fn write_char(&mut self, c: char) -> Result { 93 | (**self).write_char(c) 94 | } 95 | 96 | fn write_usize(&mut self, v: usize) -> Result { 97 | (**self).write_usize(v) 98 | } 99 | 100 | fn write_hex(&mut self, b: u8) -> Result { 101 | (**self).write_hex(b) 102 | } 103 | } 104 | 105 | impl<'a> Write for Formatter<'a> { 106 | fn write_str(&mut self, s: &str) -> Result { 107 | core::fmt::Write::write_str(self, s) 108 | } 109 | 110 | fn write_char(&mut self, c: char) -> Result { 111 | core::fmt::Write::write_char(self, c) 112 | } 113 | 114 | fn write_usize(&mut self, v: usize) -> Result { 115 | core::fmt::Display::fmt(&v, self) 116 | } 117 | } 118 | 119 | /////////////////////////////////////////////////////////////////////////////// 120 | 121 | pub(crate) fn byte_count(w: &mut dyn Write, count: usize) -> Result { 122 | match count { 123 | 0 => w.write_str("no bytes"), 124 | 1 => w.write_str("1 byte"), 125 | n => { 126 | w.write_usize(n)?; 127 | w.write_str(" bytes") 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/display/unit.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "unicode")] 2 | use unicode_width::UnicodeWidthChar; 3 | 4 | use crate::fmt::{self, Write}; 5 | use crate::util::utf8::CharIter; 6 | 7 | /////////////////////////////////////////////////////////////////////////////// 8 | // Byte display 9 | 10 | pub(super) fn byte_display_width(b: u8, show_ascii: bool) -> usize { 11 | if show_ascii { 12 | match b { 13 | b'\"' | b'\'' | b'\n' | b'\r' | b'\t' => "'\\x'".len(), 14 | c if c.is_ascii_graphic() => "'x'".len(), 15 | _ => "xx".len(), 16 | } 17 | } else { 18 | "xx".len() 19 | } 20 | } 21 | 22 | pub(super) fn byte_display_write(b: u8, show_ascii: bool, w: &mut dyn Write) -> fmt::Result { 23 | if show_ascii { 24 | match b { 25 | b'\"' => w.write_str("'\\\"'"), 26 | b'\'' => w.write_str("'\\''"), 27 | b'\n' => w.write_str("'\\n'"), 28 | b'\r' => w.write_str("'\\r'"), 29 | b'\t' => w.write_str("'\\t'"), 30 | b if b.is_ascii_graphic() => { 31 | w.write_char('\'')?; 32 | w.write_char(b as char)?; 33 | w.write_char('\'') 34 | } 35 | b => w.write_hex(b), 36 | } 37 | } else { 38 | w.write_hex(b) 39 | } 40 | } 41 | 42 | fn byte_next_front(bytes: &mut &[u8], show_ascii: bool) -> Option> { 43 | if bytes.is_empty() { 44 | None 45 | } else { 46 | let unit = Unit::byte(bytes[0], show_ascii); 47 | *bytes = &bytes[1..]; 48 | Some(Ok(unit)) 49 | } 50 | } 51 | 52 | fn byte_next_back(bytes: &mut &[u8], show_ascii: bool) -> Option> { 53 | if bytes.is_empty() { 54 | None 55 | } else { 56 | let end = bytes.len() - 1; 57 | let unit = Unit::byte(bytes[end], show_ascii); 58 | *bytes = &bytes[..end]; 59 | Some(Ok(unit)) 60 | } 61 | } 62 | 63 | /////////////////////////////////////////////////////////////////////////////// 64 | // Char display 65 | 66 | pub(super) fn char_display_width(c: char, cjk: bool) -> usize { 67 | c.escape_debug() 68 | .fold(0, |acc, c| acc + unicode_width(c, cjk)) 69 | } 70 | 71 | pub(super) fn char_display_write(c: char, w: &mut dyn Write) -> fmt::Result { 72 | for c in c.escape_debug() { 73 | w.write_char(c)?; 74 | } 75 | Ok(()) 76 | } 77 | 78 | #[cfg(feature = "unicode")] 79 | #[inline] 80 | fn unicode_width(c: char, cjk: bool) -> usize { 81 | if cjk { c.width_cjk() } else { c.width() }.unwrap_or(1) 82 | } 83 | 84 | #[cfg(not(feature = "unicode"))] 85 | #[inline] 86 | fn unicode_width(_c: char, _cjk: bool) -> usize { 87 | 1 88 | } 89 | 90 | fn char_next_front(bytes: &mut &[u8], cjk: bool) -> Option> { 91 | let mut iter = CharIter::new(bytes); 92 | let result = iter 93 | .next() 94 | .map(|result| result.map(|c| Unit::unicode(c, cjk)).map_err(drop)); 95 | *bytes = iter.as_slice(); 96 | result 97 | } 98 | 99 | fn char_next_back(bytes: &mut &[u8], cjk: bool) -> Option> { 100 | let mut iter = CharIter::new(bytes); 101 | let result = iter 102 | .next_back() 103 | .map(|result| result.map(|c| Unit::unicode(c, cjk)).map_err(drop)); 104 | *bytes = iter.as_slice(); 105 | result 106 | } 107 | 108 | /////////////////////////////////////////////////////////////////////////////// 109 | // Unit iterator 110 | 111 | #[derive(Copy, Clone)] 112 | pub(super) struct Unit { 113 | pub(super) len_utf8: usize, 114 | pub(super) display_cost: usize, 115 | } 116 | 117 | impl Unit { 118 | pub(super) fn byte(b: u8, show_ascii: bool) -> Self { 119 | Self { 120 | display_cost: byte_display_width(b, show_ascii), 121 | len_utf8: 1, 122 | } 123 | } 124 | 125 | pub(super) fn unicode(c: char, cjk: bool) -> Self { 126 | Self { 127 | display_cost: char_display_width(c, cjk), 128 | len_utf8: c.len_utf8(), 129 | } 130 | } 131 | } 132 | 133 | type UnitIterFn = fn(&mut &[u8], bool) -> Option>; 134 | 135 | #[derive(Clone)] 136 | pub(super) struct UnitIter<'a> { 137 | bytes: &'a [u8], 138 | modifier: bool, 139 | next_front: UnitIterFn, 140 | next_back: UnitIterFn, 141 | } 142 | 143 | impl<'a> UnitIter<'a> { 144 | pub(super) fn new_byte(bytes: &'a [u8], show_ascii: bool) -> Self { 145 | Self { 146 | bytes, 147 | modifier: show_ascii, 148 | next_front: byte_next_front, 149 | next_back: byte_next_back, 150 | } 151 | } 152 | 153 | pub(super) fn new_char(bytes: &'a [u8], cjk: bool) -> Self { 154 | Self { 155 | bytes, 156 | modifier: cjk, 157 | next_front: char_next_front, 158 | next_back: char_next_back, 159 | } 160 | } 161 | 162 | pub(super) fn has_next(&self) -> bool { 163 | !self.bytes.is_empty() 164 | } 165 | 166 | pub(super) fn as_slice(&self) -> &[u8] { 167 | self.bytes 168 | } 169 | 170 | pub(super) fn next_front(&mut self) -> Option> { 171 | (self.next_front)(&mut self.bytes, self.modifier) 172 | } 173 | 174 | pub(super) fn next_back(&mut self) -> Option> { 175 | (self.next_back)(&mut self.bytes, self.modifier) 176 | } 177 | 178 | pub(super) fn rev(self) -> Self { 179 | Self { 180 | bytes: self.bytes, 181 | modifier: self.modifier, 182 | next_front: self.next_back, 183 | next_back: self.next_front, 184 | } 185 | } 186 | 187 | pub(super) fn skip_head_bytes(mut self, len: usize) -> Self { 188 | self.bytes = if self.bytes.len() > len { 189 | &self.bytes[len..] 190 | } else { 191 | &[] 192 | }; 193 | self 194 | } 195 | 196 | pub(super) fn skip_tail_bytes(mut self, len: usize) -> Self { 197 | self.bytes = if self.bytes.len() > len { 198 | &self.bytes[..self.bytes.len() - len] 199 | } else { 200 | &[] 201 | }; 202 | self 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/error/backtrace.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "alloc")] 2 | use alloc::{boxed::Box, vec::Vec}; 3 | #[cfg(feature = "alloc")] 4 | use core::iter; 5 | 6 | use super::{Context, CoreContext}; 7 | 8 | /// Implemented for walkable stacks of [`Context`]s collected from an error. 9 | pub trait Backtrace: 'static { 10 | /// The root context. 11 | fn root(&self) -> CoreContext; 12 | 13 | /// Return the total number of contexts. 14 | fn count(&self) -> usize; 15 | 16 | /// Walk the context backtrace, starting with the highest context to the root. 17 | /// 18 | /// Returns `true` if all of the stack available was walked, `false` if not. 19 | fn walk<'a>(&'a self, f: &mut BacktraceWalker<'a>) -> bool; 20 | } 21 | 22 | /// Implemented for [`Backtrace`] builders. 23 | pub trait BacktraceBuilder { 24 | /// See [`WithContext::PASSTHROUGH`]. 25 | /// 26 | /// [`WithContext::PASSTHROUGH`]: crate::error::WithContext::PASSTHROUGH 27 | const PASSTHROUGH: bool = false; 28 | 29 | /// Create the builder from a root expected context. 30 | fn from_root(context: CoreContext) -> Self; 31 | 32 | /// Push a context onto the stack. 33 | fn push(&mut self, context: impl Context); 34 | } 35 | 36 | /// A dynamic function for walking a context backtrace. 37 | /// 38 | /// Returns `true` if the walk should continue, `false` if not. 39 | /// 40 | /// # Parameters 41 | /// 42 | /// - `parent depth` (the parent depth of the context starting from `1`). 43 | /// - `` (the context at the provided depth). 44 | /// 45 | /// # Parent depth 46 | /// 47 | /// Contexts are returned from the top of the stack to the bottom. Child 48 | /// contexts will follow after a parent context and will share the same `parent 49 | /// depth` value. 50 | pub type BacktraceWalker<'a> = dyn FnMut(usize, &dyn Context) -> bool + 'a; 51 | 52 | /////////////////////////////////////////////////////////////////////////////// 53 | // Root context backtrace 54 | 55 | /// A [`Backtrace`] that only contains the root [`CoreContext`]. 56 | pub struct RootBacktrace { 57 | context: CoreContext, 58 | } 59 | 60 | impl BacktraceBuilder for RootBacktrace { 61 | const PASSTHROUGH: bool = true; 62 | 63 | fn from_root(context: CoreContext) -> Self { 64 | Self { context } 65 | } 66 | 67 | fn push(&mut self, _context: impl Context) {} 68 | } 69 | 70 | impl Backtrace for RootBacktrace { 71 | fn root(&self) -> CoreContext { 72 | self.context 73 | } 74 | 75 | fn count(&self) -> usize { 76 | 1 77 | } 78 | 79 | fn walk<'a>(&'a self, f: &mut BacktraceWalker<'a>) -> bool { 80 | f(1, &self.context) 81 | } 82 | } 83 | 84 | /////////////////////////////////////////////////////////////////////////////// 85 | // Full backtrace 86 | 87 | /// A [`Backtrace`] that contains all [`Context`]s collected. 88 | #[cfg(feature = "alloc")] 89 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 90 | pub struct FullBacktrace { 91 | root: CoreContext, 92 | stack: Vec>, 93 | } 94 | 95 | #[cfg(feature = "alloc")] 96 | impl BacktraceBuilder for FullBacktrace { 97 | fn from_root(context: CoreContext) -> Self { 98 | Self { 99 | root: context, 100 | stack: Vec::with_capacity(32), 101 | } 102 | } 103 | 104 | fn push(&mut self, context: impl Context) { 105 | self.stack.push(Box::new(context)); 106 | } 107 | } 108 | 109 | #[cfg(feature = "alloc")] 110 | impl Backtrace for FullBacktrace { 111 | fn root(&self) -> CoreContext { 112 | self.root 113 | } 114 | 115 | fn count(&self) -> usize { 116 | self.stack.len() + 1 117 | } 118 | 119 | fn walk<'a>(&'a self, f: &mut BacktraceWalker<'a>) -> bool { 120 | let root_as_dyn: &dyn Context = &self.root; 121 | let stack_iter = self.stack.iter().map(|context| { 122 | let context: &dyn Context = context.as_ref(); 123 | context 124 | }); 125 | let items_iter = iter::once(root_as_dyn).chain(stack_iter).rev(); 126 | let child_iter = &mut items_iter.clone().filter(|context| context.is_child()); 127 | let mut depth = 0; 128 | let mut children_skipped = 0; 129 | // Starts from the top context, with children before their parent. 130 | for context in items_iter { 131 | if context.is_child() { 132 | children_skipped += 1; 133 | } else { 134 | depth += 1; 135 | if !f(depth, context) { 136 | return false; 137 | } 138 | for child in child_iter.take(children_skipped) { 139 | if !f(depth, child) { 140 | return false; 141 | } 142 | } 143 | children_skipped = 0; 144 | } 145 | } 146 | true 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/error/expected/length.rs: -------------------------------------------------------------------------------- 1 | use crate::display::byte_count; 2 | use crate::error::{CoreContext, Length, RetryRequirement, ToRetryRequirement}; 3 | use crate::fmt; 4 | use crate::input::MaybeString; 5 | 6 | /// An error representing a failed requirement for a length of 7 | /// [`Input`](crate::Input). 8 | #[must_use = "error must be handled"] 9 | pub struct ExpectedLength<'i> { 10 | pub(crate) len: Length, 11 | pub(crate) context: CoreContext, 12 | pub(crate) input: MaybeString<'i>, 13 | } 14 | 15 | #[allow(clippy::len_without_is_empty)] 16 | impl<'i> ExpectedLength<'i> { 17 | /// The length that was expected in a context. 18 | /// 19 | /// This doesn't not take into account the section of input being processed 20 | /// when this error occurred. If you wish to work out the requirement to 21 | /// continue processing input use [`to_retry_requirement()`]. 22 | /// 23 | /// [`to_retry_requirement()`]: Self::to_retry_requirement() 24 | #[inline(always)] 25 | pub fn len(&self) -> Length { 26 | self.len 27 | } 28 | 29 | /// The [`CoreContext`] around the error. 30 | #[must_use] 31 | #[inline(always)] 32 | pub fn context(&self) -> CoreContext { 33 | self.context 34 | } 35 | 36 | /// The [`Input`](crate::Input) provided in the context when the error occurred. 37 | #[inline(always)] 38 | pub fn input(&self) -> MaybeString<'i> { 39 | self.input.clone() 40 | } 41 | } 42 | 43 | impl<'i> fmt::Debug for ExpectedLength<'i> { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | f.debug_struct("ExpectedLength") 46 | .field("len", &self.len()) 47 | .field("context", &self.context().debug_for(self.input())) 48 | .field("input", &self.input()) 49 | .finish() 50 | } 51 | } 52 | 53 | impl<'i> fmt::DisplayBase for ExpectedLength<'i> { 54 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 55 | w.write_str("found ")?; 56 | byte_count(w, self.context.span.len())?; 57 | w.write_str(" when ")?; 58 | self.len.fmt(w)?; 59 | w.write_str(" was expected") 60 | } 61 | } 62 | 63 | impl<'i> fmt::Display for ExpectedLength<'i> { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | fmt::DisplayBase::fmt(self, f) 66 | } 67 | } 68 | 69 | impl<'i> ToRetryRequirement for ExpectedLength<'i> { 70 | #[inline] 71 | fn to_retry_requirement(&self) -> Option { 72 | if self.is_fatal() { 73 | None 74 | } else { 75 | let had = self.context.span.len(); 76 | let needed = self.len().min(); 77 | RetryRequirement::from_had_and_needed(had, needed) 78 | } 79 | } 80 | 81 | /// Returns `true` if `max()` has a value. 82 | #[inline] 83 | fn is_fatal(&self) -> bool { 84 | self.input.is_bound() || self.len().max().is_some() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/error/expected/mod.rs: -------------------------------------------------------------------------------- 1 | mod length; 2 | mod valid; 3 | mod value; 4 | 5 | pub use self::length::ExpectedLength; 6 | pub use self::valid::ExpectedValid; 7 | pub use self::value::ExpectedValue; 8 | 9 | #[cfg(feature = "alloc")] 10 | use alloc::boxed::Box; 11 | 12 | use crate::display::ErrorDisplay; 13 | use crate::error::{ 14 | Backtrace, BacktraceBuilder, Context, Details, RetryRequirement, ToRetryRequirement, Value, 15 | WithContext, 16 | }; 17 | use crate::fmt; 18 | use crate::input::{Input, MaybeString}; 19 | 20 | #[cfg(feature = "full-backtrace")] 21 | type ExpectedBacktrace = crate::error::FullBacktrace; 22 | #[cfg(not(feature = "full-backtrace"))] 23 | type ExpectedBacktrace = crate::error::RootBacktrace; 24 | 25 | /// An error that [`Details`] what went wrong while reading and may be retried. 26 | /// 27 | /// - Enable the `full-backtrace` feature (enabled by default), to collect of 28 | /// all contexts with [`Expected`]. 29 | /// - It is generally recommended for better performance to box `Expected` if 30 | /// the structures being returned from parsing are smaller than or equal to 31 | /// `~128 bytes`. This is because the `Expected` structure is `192 - 216 32 | /// bytes` large on 64 bit systems and successful parses may be hindered by 33 | /// the time to move the `Result` value. By boxing `Expected` the 34 | /// size becomes only `8 bytes`. When in doubt, write a benchmark. 35 | /// 36 | /// See [`crate::error`] for additional documentation around the error system. 37 | #[must_use = "error must be handled"] 38 | pub struct Expected<'i, S = ExpectedBacktrace> { 39 | input: MaybeString<'i>, 40 | trace: S, 41 | kind: ExpectedKind<'i>, 42 | } 43 | 44 | enum ExpectedKind<'i> { 45 | /// An exact value was expected in a context. 46 | Value(ExpectedValue<'i>), 47 | /// A valid value was expected in a context. 48 | Valid(ExpectedValid<'i>), 49 | /// A length was expected in a context. 50 | Length(ExpectedLength<'i>), 51 | } 52 | 53 | impl<'i, S> Expected<'i, S> 54 | where 55 | S: Backtrace, 56 | { 57 | /// Returns an `ErrorDisplay` for formatting. 58 | pub fn display(&self) -> ErrorDisplay<'_, Self> { 59 | ErrorDisplay::new(self) 60 | } 61 | } 62 | 63 | impl<'i, S> Expected<'i, S> 64 | where 65 | S: BacktraceBuilder, 66 | { 67 | #[inline(always)] 68 | fn add_input(&mut self, input: impl Input<'i>) { 69 | if self.input.span().is_within(input.span()) { 70 | self.input = input.into_maybe_string(); 71 | } 72 | } 73 | 74 | #[inline(always)] 75 | fn add_context(&mut self, context: impl Context) { 76 | self.trace.push(context); 77 | } 78 | 79 | fn from_kind(kind: ExpectedKind<'i>) -> Self { 80 | let (input, context) = match &kind { 81 | ExpectedKind::Valid(err) => (err.input(), err.context()), 82 | ExpectedKind::Value(err) => (err.input(), err.context()), 83 | ExpectedKind::Length(err) => (err.input(), err.context()), 84 | }; 85 | Self { 86 | kind, 87 | input, 88 | trace: S::from_root(context), 89 | } 90 | } 91 | } 92 | 93 | impl<'i, S> Details<'i> for Expected<'i, S> 94 | where 95 | S: Backtrace, 96 | { 97 | fn input(&self) -> MaybeString<'i> { 98 | self.input.clone() 99 | } 100 | 101 | fn expected(&self) -> Option> { 102 | match &self.kind { 103 | ExpectedKind::Value(err) => Some(err.expected()), 104 | ExpectedKind::Valid(_) | ExpectedKind::Length(_) => None, 105 | } 106 | } 107 | 108 | fn description(&self, f: &mut dyn fmt::Write) -> fmt::Result { 109 | match &self.kind { 110 | ExpectedKind::Value(err) => fmt::DisplayBase::fmt(err, f), 111 | ExpectedKind::Valid(err) => fmt::DisplayBase::fmt(err, f), 112 | ExpectedKind::Length(err) => fmt::DisplayBase::fmt(err, f), 113 | } 114 | } 115 | 116 | fn backtrace(&self) -> &dyn Backtrace { 117 | &self.trace 118 | } 119 | } 120 | 121 | impl<'i, S> ToRetryRequirement for Expected<'i, S> { 122 | fn to_retry_requirement(&self) -> Option { 123 | match &self.kind { 124 | ExpectedKind::Value(err) => err.to_retry_requirement(), 125 | ExpectedKind::Valid(err) => err.to_retry_requirement(), 126 | ExpectedKind::Length(err) => err.to_retry_requirement(), 127 | } 128 | } 129 | 130 | fn is_fatal(&self) -> bool { 131 | match &self.kind { 132 | ExpectedKind::Value(err) => err.is_fatal(), 133 | ExpectedKind::Valid(err) => err.is_fatal(), 134 | ExpectedKind::Length(err) => err.is_fatal(), 135 | } 136 | } 137 | } 138 | 139 | #[cfg(feature = "alloc")] 140 | impl<'i, S> ToRetryRequirement for Box> { 141 | fn to_retry_requirement(&self) -> Option { 142 | (**self).to_retry_requirement() 143 | } 144 | 145 | fn is_fatal(&self) -> bool { 146 | (**self).is_fatal() 147 | } 148 | } 149 | 150 | impl<'i, S> WithContext<'i> for Expected<'i, S> 151 | where 152 | S: BacktraceBuilder, 153 | { 154 | const PASSTHROUGH: bool = S::PASSTHROUGH; 155 | 156 | fn with_input(mut self, input: impl Input<'i>) -> Self { 157 | self.add_input(input); 158 | self 159 | } 160 | 161 | fn with_context(mut self, context: impl Context) -> Self { 162 | self.add_context(context); 163 | self 164 | } 165 | } 166 | 167 | #[cfg(feature = "alloc")] 168 | impl<'i, S> WithContext<'i> for Box> 169 | where 170 | S: BacktraceBuilder, 171 | { 172 | const PASSTHROUGH: bool = S::PASSTHROUGH; 173 | 174 | fn with_input(mut self, input: impl Input<'i>) -> Self { 175 | self.add_input(input); 176 | self 177 | } 178 | 179 | fn with_context(mut self, context: impl Context) -> Self { 180 | self.add_context(context); 181 | self 182 | } 183 | } 184 | 185 | impl<'i, S> fmt::Debug for Expected<'i, S> 186 | where 187 | S: Backtrace, 188 | { 189 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 190 | ErrorDisplay::from_formatter(self, f).banner(true).fmt(f) 191 | } 192 | } 193 | 194 | impl<'i, S> fmt::Display for Expected<'i, S> 195 | where 196 | S: Backtrace, 197 | { 198 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 199 | ErrorDisplay::from_formatter(self, f).fmt(f) 200 | } 201 | } 202 | 203 | impl<'i, S> From> for Expected<'i, S> 204 | where 205 | S: BacktraceBuilder, 206 | { 207 | fn from(err: ExpectedLength<'i>) -> Self { 208 | Self::from_kind(ExpectedKind::Length(err)) 209 | } 210 | } 211 | 212 | #[cfg(feature = "alloc")] 213 | impl<'i, S> From> for Box> 214 | where 215 | S: BacktraceBuilder, 216 | { 217 | fn from(expected: ExpectedLength<'i>) -> Box> { 218 | Box::new(expected.into()) 219 | } 220 | } 221 | 222 | impl<'i, S> From> for Expected<'i, S> 223 | where 224 | S: BacktraceBuilder, 225 | { 226 | fn from(err: ExpectedValid<'i>) -> Self { 227 | Self::from_kind(ExpectedKind::Valid(err)) 228 | } 229 | } 230 | 231 | #[cfg(feature = "alloc")] 232 | impl<'i, S> From> for Box> 233 | where 234 | S: BacktraceBuilder, 235 | { 236 | fn from(expected: ExpectedValid<'i>) -> Box> { 237 | Box::new(expected.into()) 238 | } 239 | } 240 | 241 | impl<'i, S> From> for Expected<'i, S> 242 | where 243 | S: BacktraceBuilder, 244 | { 245 | fn from(err: ExpectedValue<'i>) -> Self { 246 | Self::from_kind(ExpectedKind::Value(err)) 247 | } 248 | } 249 | 250 | #[cfg(feature = "alloc")] 251 | impl<'i, S> From> for Box> 252 | where 253 | S: BacktraceBuilder, 254 | { 255 | fn from(expected: ExpectedValue<'i>) -> Box> { 256 | Box::new(expected.into()) 257 | } 258 | } 259 | 260 | #[cfg(test)] 261 | mod tests { 262 | use super::*; 263 | 264 | #[test] 265 | #[cfg(all(target_pointer_width = "64", not(feature = "full-backtrace")))] 266 | fn test_expected_size() { 267 | // Update the docs if this value changes. 268 | assert_eq!(core::mem::size_of::>(), 192); 269 | } 270 | 271 | #[test] 272 | #[cfg(all(target_pointer_width = "64", feature = "full-backtrace"))] 273 | fn test_expected_size() { 274 | // Update the docs if this value changes. 275 | assert_eq!(core::mem::size_of::>(), 216); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/error/expected/valid.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{CoreContext, RetryRequirement, ToRetryRequirement}; 2 | use crate::fmt; 3 | use crate::input::MaybeString; 4 | 5 | /// An error representing a failed requirement for a valid 6 | /// [`Input`](crate::Input). 7 | #[must_use = "error must be handled"] 8 | pub struct ExpectedValid<'i> { 9 | pub(crate) retry_requirement: Option, 10 | pub(crate) context: CoreContext, 11 | pub(crate) input: MaybeString<'i>, 12 | } 13 | 14 | impl<'i> ExpectedValid<'i> { 15 | /// The [`CoreContext`] around the error. 16 | #[inline(always)] 17 | #[must_use] 18 | pub fn context(&self) -> CoreContext { 19 | self.context 20 | } 21 | 22 | /// The [`Input`](crate::Input) provided in the context when the error 23 | /// occurred. 24 | #[inline(always)] 25 | pub fn input(&self) -> MaybeString<'i> { 26 | self.input.clone() 27 | } 28 | } 29 | 30 | impl<'i> fmt::Debug for ExpectedValid<'i> { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | let mut debug = f.debug_struct("ExpectedValid"); 33 | 34 | debug.field("retry_requirement", &self.retry_requirement); 35 | debug.field("context", &self.context().debug_for(self.input())); 36 | debug.field("input", &self.input()); 37 | 38 | debug.finish() 39 | } 40 | } 41 | 42 | impl<'i> fmt::DisplayBase for ExpectedValid<'i> { 43 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 44 | w.write_str("expected ")?; 45 | self.context.expected.fmt(w) 46 | } 47 | } 48 | 49 | impl<'i> fmt::Display for ExpectedValid<'i> { 50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 | fmt::DisplayBase::fmt(self, f) 52 | } 53 | } 54 | 55 | impl<'i> ToRetryRequirement for ExpectedValid<'i> { 56 | #[inline] 57 | fn to_retry_requirement(&self) -> Option { 58 | if self.is_fatal() { 59 | None 60 | } else { 61 | self.retry_requirement 62 | } 63 | } 64 | 65 | #[inline] 66 | fn is_fatal(&self) -> bool { 67 | self.input.is_bound() || self.retry_requirement.is_none() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/error/expected/value.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Value; 2 | use crate::error::{CoreContext, RetryRequirement, ToRetryRequirement}; 3 | use crate::fmt; 4 | use crate::input::MaybeString; 5 | 6 | /// An error representing a failed exact value requirement of 7 | /// [`Input`](crate::Input). 8 | #[must_use = "error must be handled"] 9 | pub struct ExpectedValue<'i> { 10 | pub(crate) expected: Value<'i>, 11 | pub(crate) context: CoreContext, 12 | pub(crate) input: MaybeString<'i>, 13 | } 14 | 15 | impl<'i> ExpectedValue<'i> { 16 | /// The [`Input`](crate::Input) value that was expected. 17 | #[inline(always)] 18 | pub fn expected(&self) -> Value<'i> { 19 | self.expected 20 | } 21 | 22 | /// The [`CoreContext`] around the error. 23 | #[must_use] 24 | #[inline(always)] 25 | pub fn context(&self) -> CoreContext { 26 | self.context 27 | } 28 | 29 | /// The [`Input`](crate::Input) provided in the context when the error occurred. 30 | #[inline(always)] 31 | pub fn input(&self) -> MaybeString<'i> { 32 | self.input.clone() 33 | } 34 | } 35 | 36 | impl<'i> fmt::Debug for ExpectedValue<'i> { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | f.debug_struct("ExpectedValue") 39 | .field("expected", &self.expected()) 40 | .field("context", &self.context().debug_for(self.input())) 41 | .field("input", &self.input()) 42 | .finish() 43 | } 44 | } 45 | 46 | impl<'i> fmt::DisplayBase for ExpectedValue<'i> { 47 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 48 | if self.is_fatal() { 49 | w.write_str("found a different value to the exact expected") 50 | } else { 51 | w.write_str("not enough input to match expected value") 52 | } 53 | } 54 | } 55 | 56 | impl<'i> fmt::Display for ExpectedValue<'i> { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | fmt::DisplayBase::fmt(self, f) 59 | } 60 | } 61 | 62 | impl<'i> ToRetryRequirement for ExpectedValue<'i> { 63 | #[inline] 64 | fn to_retry_requirement(&self) -> Option { 65 | if self.is_fatal() { 66 | None 67 | } else { 68 | let needed = self.expected().as_bytes().len(); 69 | let had = self.context.span.len(); 70 | RetryRequirement::from_had_and_needed(had, needed) 71 | } 72 | } 73 | 74 | /// Returns `true` if the value could never match and `false` if the matching 75 | /// was incomplete. 76 | #[inline] 77 | fn is_fatal(&self) -> bool { 78 | if self.input.is_bound() { 79 | return true; 80 | } 81 | match self.context.span.of(self.input.as_dangerous_bytes()) { 82 | Some(found) => !self.expected().as_bytes().starts_with(found), 83 | None => true, 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/error/fatal.rs: -------------------------------------------------------------------------------- 1 | use crate::fmt; 2 | use crate::input::Input; 3 | 4 | use super::{ 5 | Context, ExpectedLength, ExpectedValid, ExpectedValue, RetryRequirement, ToRetryRequirement, 6 | WithContext, 7 | }; 8 | 9 | /// An error that has no details around what went wrong and cannot be retried. 10 | /// 11 | /// This is the most performant and simplistic catch-all error, but it doesn't 12 | /// provide any context to debug problems well and cannot be used in streaming 13 | /// contexts. 14 | /// 15 | /// See [`crate::error`] for additional documentation around the error system. 16 | /// 17 | /// # Example 18 | /// 19 | /// ``` 20 | /// use dangerous::{Input, Fatal}; 21 | /// 22 | /// let error: Fatal = dangerous::input(b"").read_all(|r| { 23 | /// r.read() 24 | /// }).unwrap_err(); 25 | /// 26 | /// assert_eq!( 27 | /// error.to_string(), 28 | /// "invalid input", 29 | /// ); 30 | /// ``` 31 | #[derive(Debug, PartialEq)] 32 | #[must_use = "error must be handled"] 33 | pub struct Fatal; 34 | 35 | impl fmt::DisplayBase for Fatal { 36 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 37 | w.write_str("invalid input") 38 | } 39 | } 40 | 41 | impl fmt::Display for Fatal { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | fmt::DisplayBase::fmt(self, f) 44 | } 45 | } 46 | 47 | impl<'i> WithContext<'i> for Fatal { 48 | const PASSTHROUGH: bool = true; 49 | 50 | #[inline(always)] 51 | fn with_input(self, _input: impl Input<'i>) -> Self { 52 | self 53 | } 54 | 55 | #[inline(always)] 56 | fn with_context(self, _context: impl Context) -> Self { 57 | self 58 | } 59 | } 60 | 61 | impl ToRetryRequirement for Fatal { 62 | fn to_retry_requirement(&self) -> Option { 63 | None 64 | } 65 | 66 | fn is_fatal(&self) -> bool { 67 | true 68 | } 69 | } 70 | 71 | impl<'i> From> for Fatal { 72 | fn from(_: ExpectedValue<'i>) -> Self { 73 | Self 74 | } 75 | } 76 | 77 | impl<'i> From> for Fatal { 78 | fn from(_: ExpectedLength<'i>) -> Self { 79 | Self 80 | } 81 | } 82 | 83 | impl<'i> From> for Fatal { 84 | fn from(_: ExpectedValid<'i>) -> Self { 85 | Self 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/error/invalid.rs: -------------------------------------------------------------------------------- 1 | use crate::fmt; 2 | use crate::input::Input; 3 | 4 | use super::{ 5 | Backtrace, Context, Expected, ExpectedLength, ExpectedValid, ExpectedValue, RetryRequirement, 6 | ToRetryRequirement, WithContext, 7 | }; 8 | 9 | /// An error that has no details around what went wrong other than a 10 | /// [`RetryRequirement`] if the error is not fatal. 11 | /// 12 | /// This is the most performant and simplistic catch-all **retryable** error, 13 | /// but it doesn't provide any context to debug problems well. 14 | /// 15 | /// See [`crate::error`] for additional documentation around the error system. 16 | /// 17 | /// # Example 18 | /// 19 | /// ``` 20 | /// use dangerous::{Input, Invalid}; 21 | /// 22 | /// let error: Invalid = dangerous::input(b"").read_all(|r| { 23 | /// r.read() 24 | /// }).unwrap_err(); 25 | /// 26 | /// assert_eq!( 27 | /// error.to_string(), 28 | /// "invalid input: needs 1 byte more to continue processing", 29 | /// ); 30 | /// ``` 31 | #[derive(Debug, Copy, Clone, PartialEq)] 32 | #[must_use = "error must be handled"] 33 | pub struct Invalid { 34 | retry_requirement: Option, 35 | } 36 | 37 | impl Invalid { 38 | /// Create a fatal `Invalid` error. 39 | #[inline(always)] 40 | pub fn fatal() -> Self { 41 | Self { 42 | retry_requirement: None, 43 | } 44 | } 45 | 46 | /// Create a retryable `Invalid` error. 47 | #[inline(always)] 48 | pub fn retry(requirement: RetryRequirement) -> Self { 49 | Self { 50 | retry_requirement: Some(requirement), 51 | } 52 | } 53 | } 54 | 55 | impl fmt::DisplayBase for Invalid { 56 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 57 | w.write_str("invalid input")?; 58 | if let Some(retry_requirement) = self.retry_requirement { 59 | w.write_str(": needs ")?; 60 | fmt::DisplayBase::fmt(&retry_requirement, w)?; 61 | w.write_str(" to continue processing")?; 62 | } 63 | Ok(()) 64 | } 65 | } 66 | 67 | impl fmt::Display for Invalid { 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 69 | fmt::DisplayBase::fmt(self, f) 70 | } 71 | } 72 | 73 | impl ToRetryRequirement for Invalid { 74 | #[inline(always)] 75 | fn to_retry_requirement(&self) -> Option { 76 | self.retry_requirement 77 | } 78 | } 79 | 80 | impl<'i> WithContext<'i> for Invalid { 81 | const PASSTHROUGH: bool = true; 82 | 83 | #[inline(always)] 84 | fn with_input(self, _input: impl Input<'i>) -> Self { 85 | self 86 | } 87 | 88 | #[inline(always)] 89 | fn with_context(self, _context: impl Context) -> Self { 90 | self 91 | } 92 | } 93 | 94 | impl<'i, S> From> for Invalid 95 | where 96 | S: Backtrace, 97 | { 98 | #[inline(always)] 99 | fn from(err: Expected<'i, S>) -> Self { 100 | err.to_retry_requirement().into() 101 | } 102 | } 103 | 104 | impl<'i> From> for Invalid { 105 | #[inline(always)] 106 | fn from(err: ExpectedValue<'i>) -> Self { 107 | err.to_retry_requirement().into() 108 | } 109 | } 110 | 111 | impl<'i> From> for Invalid { 112 | #[inline(always)] 113 | fn from(err: ExpectedLength<'i>) -> Self { 114 | err.to_retry_requirement().into() 115 | } 116 | } 117 | 118 | impl<'i> From> for Invalid { 119 | #[inline(always)] 120 | fn from(err: ExpectedValid<'i>) -> Self { 121 | err.to_retry_requirement().into() 122 | } 123 | } 124 | 125 | impl From> for Invalid { 126 | #[inline(always)] 127 | fn from(retry_requirement: Option) -> Self { 128 | Self { retry_requirement } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/error/length.rs: -------------------------------------------------------------------------------- 1 | use crate::display::byte_count; 2 | use crate::fmt; 3 | 4 | /// Length that was expected in an operation. 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 6 | #[must_use] 7 | pub enum Length { 8 | /// A minimum length was expected. 9 | AtLeast(usize), 10 | /// An exact length was expected. 11 | Exactly(usize), 12 | } 13 | 14 | impl Length { 15 | /// The minimum length that was expected. 16 | #[must_use] 17 | #[inline(always)] 18 | pub fn min(self) -> usize { 19 | match self { 20 | Length::AtLeast(min) | Length::Exactly(min) => min, 21 | } 22 | } 23 | 24 | /// The maximum length that was expected, if applicable. 25 | #[must_use] 26 | #[inline(always)] 27 | pub fn max(self) -> Option { 28 | match self { 29 | Length::AtLeast(_) => None, 30 | Length::Exactly(max) => Some(max), 31 | } 32 | } 33 | } 34 | 35 | impl fmt::DisplayBase for Length { 36 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 37 | match *self { 38 | Self::AtLeast(min) => { 39 | w.write_str("at least ")?; 40 | byte_count(w, min) 41 | } 42 | Self::Exactly(exact) => { 43 | w.write_str("exactly ")?; 44 | byte_count(w, exact) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/error/mod.rs: -------------------------------------------------------------------------------- 1 | //! Error support. 2 | //! 3 | //! - If you want the fastest error which has no debugging information, 4 | //! [`Fatal`] or [`Invalid`] (retryable) has you covered. 5 | //! - If you want an error that is still designed to be fast, but also includes 6 | //! debugging information, [`Expected`] will meet your uh, expectations... If 7 | //! the feature `full-backtrace` is enabled, [`Expected`] uses 8 | //! [`FullBacktrace`], [`RootBacktrace`] if not. 9 | //! - If you require more verbosity, consider creating custom [`Context`]s 10 | //! before jumping to custom errors. If you do require a custom error, 11 | //! implementing it is easy enough. Just implement [`WithContext`] and 12 | //! [`From`] for [`ExpectedValue`], [`ExpectedLength`] and [`ExpectedValid`] 13 | //! and you'll be on your merry way. Additionally implement [`Details`] to 14 | //! support lovely error printing and [`ToRetryRequirement`] for streaming 15 | //! protocols. 16 | //! 17 | //! Most of what `dangerous` supports out of the box is good to go. If you need 18 | //! to stretch out performance more, or provide additional functionality on what 19 | //! is provided, the error system should be flexible for those requirements. If 20 | //! it's not, consider opening an issue. 21 | 22 | mod backtrace; 23 | mod context; 24 | mod expected; 25 | mod fatal; 26 | mod invalid; 27 | mod length; 28 | mod retry; 29 | mod traits; 30 | mod value; 31 | 32 | #[cfg(feature = "alloc")] 33 | pub use self::backtrace::FullBacktrace; 34 | pub use self::backtrace::{Backtrace, BacktraceBuilder, BacktraceWalker, RootBacktrace}; 35 | pub use self::context::{ 36 | Context, CoreContext, CoreExpected, CoreOperation, ExternalContext, Operation, WithChildContext, 37 | }; 38 | pub use self::expected::{Expected, ExpectedLength, ExpectedValid, ExpectedValue}; 39 | pub use self::fatal::Fatal; 40 | pub use self::invalid::Invalid; 41 | pub use self::length::Length; 42 | pub use self::retry::{RetryRequirement, ToRetryRequirement}; 43 | pub use self::traits::{Details, Error, External, WithContext}; 44 | pub use self::value::Value; 45 | 46 | pub(crate) use self::context::with_context; 47 | -------------------------------------------------------------------------------- /src/error/retry.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZeroUsize; 2 | 3 | use crate::display::byte_count; 4 | use crate::fmt; 5 | 6 | /// An indicator of how many bytes are required to continue processing input. 7 | /// 8 | /// Although the value allows you to estimate how much more input you need till 9 | /// you can continue processing the input, it is a very granular value and may 10 | /// result in a lot of wasted reprocessing of input if not handled correctly. 11 | #[must_use] 12 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 13 | pub struct RetryRequirement(NonZeroUsize); 14 | 15 | impl RetryRequirement { 16 | /// Create a new `RetryRequirement`. 17 | /// 18 | /// If the provided value is `0`, this signifies processing can't be 19 | /// retried. If the provided value is greater than `0`, this signifies the 20 | /// amount of additional input bytes required to continue processing. 21 | #[must_use] 22 | pub fn new(value: usize) -> Option { 23 | NonZeroUsize::new(value).map(Self::from_continue_after) 24 | } 25 | 26 | /// Create a retry requirement from a count of how many bytes we had and 27 | /// how many we needed. 28 | #[must_use] 29 | pub fn from_had_and_needed(had: usize, needed: usize) -> Option { 30 | Self::new(needed.saturating_sub(had)) 31 | } 32 | 33 | /// Create a retry requirement from a count of how many bytes are required 34 | /// to continue processing input. 35 | pub fn from_continue_after(continue_after: NonZeroUsize) -> Self { 36 | Self(continue_after) 37 | } 38 | 39 | /// An indicator of how many bytes are required to continue processing input, if 40 | /// applicable. 41 | /// 42 | /// Although the value allows you to estimate how much more input you need till 43 | /// you can continue processing the input, it is a very granular value and may 44 | /// result in a lot of wasted reprocessing of input if not handled correctly. 45 | #[must_use] 46 | pub fn continue_after(self) -> usize { 47 | self.0.get() 48 | } 49 | 50 | /// Returns a `NonZeroUsize` wrapped variant of `continue_after`. 51 | #[must_use] 52 | pub fn continue_after_non_zero(self) -> NonZeroUsize { 53 | self.0 54 | } 55 | } 56 | 57 | impl fmt::DisplayBase for RetryRequirement { 58 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 59 | byte_count(w, self.continue_after())?; 60 | w.write_str(" more") 61 | } 62 | } 63 | 64 | impl fmt::Display for RetryRequirement { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | fmt::DisplayBase::fmt(self, f) 67 | } 68 | } 69 | 70 | /// Implemented for errors that return a [`RetryRequirement`]. 71 | /// 72 | 73 | /// 74 | /// [`External`]: crate::error::External 75 | pub trait ToRetryRequirement { 76 | /// Returns the requirement, if applicable, to retry processing the `Input`. 77 | fn to_retry_requirement(&self) -> Option; 78 | 79 | /// Returns `true` if [`to_retry_requirement()`] will return `None`, or 80 | /// `false` if `Some(RetryRequirement)`. 81 | /// 82 | /// [`to_retry_requirement()`]: Self::to_retry_requirement() 83 | fn is_fatal(&self) -> bool { 84 | self.to_retry_requirement().is_none() 85 | } 86 | } 87 | 88 | impl ToRetryRequirement for RetryRequirement { 89 | fn to_retry_requirement(&self) -> Option { 90 | Some(*self) 91 | } 92 | } 93 | 94 | impl ToRetryRequirement for Option 95 | where 96 | T: ToRetryRequirement, 97 | { 98 | fn to_retry_requirement(&self) -> Option { 99 | self.as_ref() 100 | .and_then(ToRetryRequirement::to_retry_requirement) 101 | } 102 | 103 | fn is_fatal(&self) -> bool { 104 | self.is_none() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/error/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::fmt; 2 | use crate::input::{Input, MaybeString, Span}; 3 | 4 | use super::{ 5 | Backtrace, Context, ExpectedLength, ExpectedValid, ExpectedValue, RetryRequirement, 6 | ToRetryRequirement, Value, 7 | }; 8 | 9 | /// Auto-trait for [`WithContext`], [`ToRetryRequirement`] and 10 | /// `From`. 11 | pub trait Error<'i>: 12 | WithContext<'i> 13 | + ToRetryRequirement 14 | + From> 15 | + From> 16 | + From> 17 | { 18 | } 19 | 20 | impl<'i, T> Error<'i> for T where 21 | T: WithContext<'i> 22 | + ToRetryRequirement 23 | + From> 24 | + From> 25 | + From> 26 | { 27 | } 28 | 29 | /// Implemented for errors that collect [`Context`]s. 30 | pub trait WithContext<'i>: Sized { 31 | /// If `true` indicates the error does not care about any provided contexts. 32 | /// 33 | /// Defaults to `false`. 34 | /// 35 | /// This can be used for selecting a verbose error at compile time on an 36 | /// external parser if it is known this error will actually use the 37 | /// collected backtrace. 38 | const PASSTHROUGH: bool = false; 39 | 40 | /// Returns `Self` with a parent [`Input`]. 41 | #[must_use] 42 | fn with_input(self, input: impl Input<'i>) -> Self; 43 | 44 | /// Return `Self` with a context. 45 | /// 46 | /// This method is used for adding contexts to errors bubbling up. 47 | #[must_use] 48 | fn with_context(self, context: impl Context) -> Self; 49 | } 50 | 51 | /// Required details around an error to produce a verbose report on what went 52 | /// wrong when processing input. 53 | /// 54 | /// If you're not interested in errors of this nature and only wish to know 55 | /// whether or not the input was correctly processed, you'll wish to use the 56 | /// concrete type [`Invalid`] and all of the computations around verbose 57 | /// erroring will be removed in compilation. 58 | /// 59 | /// [`Invalid`]: crate::Invalid 60 | pub trait Details<'i> { 61 | /// The input in its entirety that was being processed when an error 62 | /// occurred. 63 | /// 64 | /// The error itself will have the details and the specific section of input 65 | /// that caused the error. This value simply allows us to see the bigger 66 | /// picture given granular errors in a large amount of input. 67 | fn input(&self) -> MaybeString<'i>; 68 | 69 | /// The expected value, if applicable. 70 | fn expected(&self) -> Option>; 71 | 72 | /// The description of what went wrong while processing the input. 73 | /// 74 | /// Descriptions should be simple and written in lowercase. 75 | /// 76 | /// # Errors 77 | /// 78 | /// Returns a [`fmt::Error`] if failed to write to the formatter. 79 | fn description(&self, w: &mut dyn fmt::Write) -> fmt::Result; 80 | 81 | /// The walkable [`Backtrace`] to the original context around the error 82 | /// that occurred. 83 | fn backtrace(&self) -> &dyn Backtrace; 84 | } 85 | 86 | /// Implemented for errors that aren't a first-class citizen to `dangerous` but 87 | /// wish to add additional information. 88 | /// 89 | /// External errors are consumed with [`Input::into_external()`] or 90 | /// [`Reader::try_external()`]. 91 | /// 92 | /// [`Input::into_external()`]: crate::Input::into_external() 93 | /// [`Reader::try_external()`]: crate::Reader::try_external() 94 | pub trait External<'i>: Sized { 95 | /// The specific section of input that caused an error. 96 | fn span(&self) -> Option { 97 | None 98 | } 99 | 100 | /// Returns the requirement, if applicable, to retry processing the `Input`. 101 | /// 102 | /// [`External`] errors are designed for producers of errors and won't 103 | /// expose themselves directly to consumers so they do not require 104 | /// `ToRetryRequirement` implementations but can return a 105 | /// [`RetryRequirement`]. 106 | /// 107 | /// [`ToRetryRequirement`]: crate::ToRetryRequirement 108 | fn retry_requirement(&self) -> Option { 109 | None 110 | } 111 | 112 | /// Pushes a child backtrace to the base error generated. 113 | /// 114 | /// Push from the bottom of the trace (from the source of the error) up. 115 | fn push_backtrace(self, error: E) -> E 116 | where 117 | E: WithContext<'i>, 118 | { 119 | error 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/error/value.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use crate::display::InputDisplay; 4 | use crate::fmt; 5 | use crate::input::{Bound, Bytes, Input}; 6 | use crate::util::utf8::CharBytes; 7 | 8 | /// Value that was expected in an operation. 9 | #[derive(Copy, Clone)] 10 | #[must_use] 11 | pub struct Value<'i>(ValueInner<'i>); 12 | 13 | #[derive(Copy, Clone)] 14 | enum ValueInner<'i> { 15 | Byte(u8), 16 | Char(CharBytes), 17 | Bytes(&'i [u8]), 18 | String(&'i str), 19 | } 20 | 21 | impl<'i> Value<'i> { 22 | /// Returns the value as bytes. 23 | #[must_use] 24 | pub fn as_bytes(&self) -> &[u8] { 25 | match &self.0 { 26 | ValueInner::Byte(v) => slice::from_ref(v), 27 | ValueInner::Char(v) => v.as_bytes(), 28 | ValueInner::Bytes(v) => v, 29 | ValueInner::String(v) => v.as_bytes(), 30 | } 31 | } 32 | 33 | /// Returns an [`InputDisplay`] for formatting. 34 | pub fn display(&self) -> InputDisplay<'_> { 35 | let display = Bytes::new(self.as_bytes(), Bound::StartEnd).display(); 36 | match self.0 { 37 | ValueInner::Byte(_) | ValueInner::Bytes(_) => display, 38 | ValueInner::Char(_) | ValueInner::String(_) => display.str_hint(), 39 | } 40 | } 41 | } 42 | 43 | impl<'i> fmt::Debug for Value<'i> { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | let name = match self.0 { 46 | ValueInner::Byte(_) => "Byte", 47 | ValueInner::Char(_) => "Char", 48 | ValueInner::Bytes(_) => "Bytes", 49 | ValueInner::String(_) => "String", 50 | }; 51 | let display = self.display().with_formatter(f); 52 | f.debug_tuple(name).field(&display).finish() 53 | } 54 | } 55 | 56 | impl<'i> From for Value<'i> { 57 | fn from(v: u8) -> Self { 58 | Self(ValueInner::Byte(v)) 59 | } 60 | } 61 | 62 | impl<'i> From for Value<'i> { 63 | fn from(v: char) -> Self { 64 | Self(ValueInner::Char(v.into())) 65 | } 66 | } 67 | 68 | impl<'i> From<&'i [u8]> for Value<'i> { 69 | #[inline(always)] 70 | fn from(v: &'i [u8]) -> Self { 71 | Self(ValueInner::Bytes(v)) 72 | } 73 | } 74 | 75 | impl<'i> From<&'i str> for Value<'i> { 76 | #[inline(always)] 77 | fn from(v: &'i str) -> Self { 78 | Self(ValueInner::String(v)) 79 | } 80 | } 81 | 82 | impl<'i, const N: usize> From<&'i [u8; N]> for Value<'i> { 83 | #[inline(always)] 84 | fn from(v: &'i [u8; N]) -> Self { 85 | Self(ValueInner::Bytes(v)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/input/bound.rs: -------------------------------------------------------------------------------- 1 | /// Indication of whether [`Input`](crate::Input) will change in futher passes. 2 | /// 3 | /// Used for retry functionality if enabled. 4 | #[must_use] 5 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 6 | pub enum Bound { 7 | /// Both sides of the [`Input`](crate::Input) may change in further passes. 8 | None, 9 | /// The start of the [`Input`](crate::Input) in further passes will not change. 10 | /// 11 | /// The end of the [`Input`](crate::Input) may however change in further passes. 12 | Start, 13 | /// Both sides of the [`Input`](crate::Input) in further passes will not change. 14 | StartEnd, 15 | } 16 | 17 | impl Bound { 18 | #[inline(always)] 19 | pub(crate) fn force_close() -> Self { 20 | Bound::StartEnd 21 | } 22 | 23 | /// An end is opened when it is detected a `take_consumed` reader could have 24 | /// continued. 25 | #[inline(always)] 26 | pub(crate) fn open_end(self) -> Self { 27 | match self { 28 | // If at least the start is bound make sure the end is unbound. 29 | Bound::StartEnd | Bound::Start => Bound::Start, 30 | // If the start is unbound both sides of the input are unbound. 31 | Bound::None => Bound::None, 32 | } 33 | } 34 | 35 | /// An end is closed when a known length of input is sucessfully taken. 36 | #[inline(always)] 37 | pub(crate) fn close_end(self) -> Self { 38 | // We don't care if the input has no bounds. The only place input with 39 | // no bounds can originate is when a reader has reached the end of input 40 | // and could have consumed more. In other words - input with no bounds 41 | // is always empty. A length of zero taken from input with no bounds 42 | // will always succeed but the first half will have both sides bound to 43 | // prevent deadlocks. 44 | let _ = self; 45 | Bound::force_close() 46 | } 47 | 48 | #[inline(always)] 49 | pub(crate) fn for_end(self) -> Self { 50 | match self { 51 | // If both sides are bounded nothing will change. 52 | Bound::StartEnd => Bound::StartEnd, 53 | // As we have skipped to the end without checking, we don't know 54 | // where the start is, perhaps the true end is not known yet! 55 | Bound::Start | Bound::None => Bound::None, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/input/byte_len.rs: -------------------------------------------------------------------------------- 1 | /// Implemented for types that return a guaranteed byte length. 2 | /// 3 | /// # Safety 4 | /// 5 | /// The implementation **must** guarantee that the value returned is correct as 6 | /// it is used for unchecked memory operations and an incorrect implementation 7 | /// would introduce invalid memory access. 8 | pub unsafe trait ByteLength { 9 | /// Returns the byte length of the value. 10 | fn byte_len(&self) -> usize; 11 | } 12 | 13 | unsafe impl ByteLength for &T 14 | where 15 | T: ByteLength, 16 | { 17 | #[inline(always)] 18 | fn byte_len(&self) -> usize { 19 | (*self).byte_len() 20 | } 21 | } 22 | 23 | unsafe impl ByteLength for u8 { 24 | /// Always returns `1`. 25 | #[inline(always)] 26 | fn byte_len(&self) -> usize { 27 | 1 28 | } 29 | } 30 | 31 | unsafe impl ByteLength for char { 32 | /// Returns the length of bytes for the UTF-8 character. 33 | #[inline(always)] 34 | fn byte_len(&self) -> usize { 35 | self.len_utf8() 36 | } 37 | } 38 | 39 | unsafe impl ByteLength for [u8] { 40 | /// Returns the length of the byte slice. 41 | #[inline(always)] 42 | fn byte_len(&self) -> usize { 43 | self.len() 44 | } 45 | } 46 | 47 | unsafe impl ByteLength for str { 48 | /// Returns the length of the underlying byte slice for the UTF-8 string. 49 | #[inline(always)] 50 | fn byte_len(&self) -> usize { 51 | self.len() 52 | } 53 | } 54 | 55 | unsafe impl ByteLength for [u8; N] { 56 | /// Returns the length (`N`) of the byte array. 57 | #[inline(always)] 58 | fn byte_len(&self) -> usize { 59 | N 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/input/bytes/array.rs: -------------------------------------------------------------------------------- 1 | use crate::display::InputDisplay; 2 | use crate::{fmt, Bound, Bytes, Input, Span}; 3 | 4 | /// Fixed size byte array taken from [`Input`]. 5 | #[derive(Clone)] 6 | pub struct ByteArray<'i, const N: usize>(&'i [u8; N]); 7 | 8 | impl<'i, const N: usize> ByteArray<'i, N> { 9 | pub(crate) const fn new(bytes: &'i [u8; N]) -> Self { 10 | Self(bytes) 11 | } 12 | 13 | /// Returns the underlying byte array reference. 14 | /// 15 | /// See [`Bytes::as_dangerous`] for naming. 16 | #[must_use] 17 | #[inline(always)] 18 | pub fn as_dangerous(&self) -> &'i [u8; N] { 19 | self.0 20 | } 21 | 22 | /// Returns the underlying byte array. 23 | /// 24 | /// This will copy the bytes from the reference. 25 | /// 26 | /// See [`Bytes::as_dangerous`] for naming. 27 | #[must_use] 28 | #[inline(always)] 29 | pub fn into_dangerous(self) -> [u8; N] { 30 | *self.as_dangerous() 31 | } 32 | 33 | /// Consumes `self` into [`Bytes`]. 34 | #[inline(always)] 35 | pub fn into_bytes(self) -> Bytes<'i> { 36 | Bytes::new(self.as_dangerous(), Bound::StartEnd) 37 | } 38 | 39 | /// Returns a [`Span`] from the start of `self` to the end. 40 | pub fn span(&self) -> Span { 41 | Span::from(self.as_dangerous().as_ref()) 42 | } 43 | 44 | /// Returns an [`InputDisplay`] for formatting. 45 | pub fn display(&self) -> InputDisplay<'i> { 46 | self.clone().into_bytes().display() 47 | } 48 | } 49 | 50 | /////////////////////////////////////////////////////////////////////////////// 51 | // Equality 52 | 53 | impl<'i, const N: usize> PartialEq for ByteArray<'i, N> { 54 | #[inline(always)] 55 | fn eq(&self, other: &Self) -> bool { 56 | self.as_dangerous() == other.as_dangerous() 57 | } 58 | } 59 | 60 | impl<'i, const N: usize> PartialEq<[u8]> for ByteArray<'i, N> { 61 | #[inline(always)] 62 | fn eq(&self, other: &[u8]) -> bool { 63 | self.as_dangerous() == other 64 | } 65 | } 66 | 67 | impl<'i, const N: usize> PartialEq<[u8]> for &ByteArray<'i, N> { 68 | #[inline(always)] 69 | fn eq(&self, other: &[u8]) -> bool { 70 | self.as_dangerous() == other 71 | } 72 | } 73 | 74 | impl<'i, const N: usize> PartialEq<&[u8]> for ByteArray<'i, N> { 75 | #[inline(always)] 76 | fn eq(&self, other: &&[u8]) -> bool { 77 | self.as_dangerous() == *other 78 | } 79 | } 80 | 81 | impl<'i, const N: usize> PartialEq> for [u8] { 82 | #[inline(always)] 83 | fn eq(&self, other: &ByteArray<'i, N>) -> bool { 84 | self == other.as_dangerous() 85 | } 86 | } 87 | 88 | impl<'i, const N: usize> PartialEq> for ByteArray<'i, N> { 89 | #[inline(always)] 90 | fn eq(&self, other: &Bytes<'i>) -> bool { 91 | self == other.as_dangerous() 92 | } 93 | } 94 | 95 | /////////////////////////////////////////////////////////////////////////////// 96 | // Formatting 97 | 98 | impl<'i, const N: usize> fmt::Debug for ByteArray<'i, N> { 99 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 100 | let display = self.display().with_formatter(f); 101 | f.debug_tuple("ByteArray").field(&display).finish() 102 | } 103 | } 104 | 105 | impl<'i, const N: usize> fmt::DisplayBase for ByteArray<'i, N> { 106 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 107 | self.display().fmt(w) 108 | } 109 | } 110 | 111 | impl<'i, const N: usize> fmt::Display for ByteArray<'i, N> { 112 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 113 | self.display().with_formatter(f).fmt(f) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/input/bytes/pattern.rs: -------------------------------------------------------------------------------- 1 | use crate::input::{Bytes, Pattern}; 2 | use crate::util::fast; 3 | 4 | /////////////////////////////////////////////////////////////////////////////// 5 | // Fn pattern 6 | 7 | unsafe impl<'i, F> Pattern> for F 8 | where 9 | F: FnMut(u8) -> bool, 10 | { 11 | fn find_match(self, input: &Bytes<'i>) -> Option<(usize, usize)> { 12 | input 13 | .as_dangerous() 14 | .iter() 15 | .copied() 16 | .position(self) 17 | .map(|i| (i, 1)) 18 | } 19 | 20 | fn find_reject(mut self, input: &Bytes<'i>) -> Option { 21 | input 22 | .as_dangerous() 23 | .iter() 24 | .copied() 25 | .enumerate() 26 | .find_map(|(i, b)| if (self)(b) { None } else { Some(i) }) 27 | } 28 | } 29 | 30 | /////////////////////////////////////////////////////////////////////////////// 31 | // Token pattern 32 | 33 | unsafe impl<'i> Pattern> for u8 { 34 | fn find_match(self, input: &Bytes<'i>) -> Option<(usize, usize)> { 35 | fast::find_u8_match(self, input.as_dangerous()).map(|index| (index, 1)) 36 | } 37 | 38 | fn find_reject(self, input: &Bytes<'i>) -> Option { 39 | fast::find_u8_reject(self, input.as_dangerous()) 40 | } 41 | } 42 | 43 | /////////////////////////////////////////////////////////////////////////////// 44 | // Sub-slice pattern 45 | 46 | unsafe impl<'i> Pattern> for &[u8] { 47 | fn find_match(self, input: &Bytes<'i>) -> Option<(usize, usize)> { 48 | fast::find_slice_match(self, input.as_dangerous()).map(|index| (index, self.len())) 49 | } 50 | 51 | fn find_reject(self, input: &Bytes<'i>) -> Option { 52 | fast::find_slice_reject(self, input.as_dangerous()) 53 | } 54 | } 55 | 56 | unsafe impl<'i, const N: usize> Pattern> for &[u8; N] { 57 | fn find_match(self, input: &Bytes<'i>) -> Option<(usize, usize)> { 58 | fast::find_slice_match(self, input.as_dangerous()).map(|index| (index, self.len())) 59 | } 60 | 61 | fn find_reject(self, input: &Bytes<'i>) -> Option { 62 | fast::find_slice_reject(self, input.as_dangerous()) 63 | } 64 | } 65 | 66 | unsafe impl<'i> Pattern> for &str { 67 | fn find_match(self, input: &Bytes<'i>) -> Option<(usize, usize)> { 68 | fast::find_slice_match(self.as_bytes(), input.as_dangerous()) 69 | .map(|index| (index, self.len())) 70 | } 71 | 72 | fn find_reject(self, input: &Bytes<'i>) -> Option { 73 | fast::find_slice_reject(self.as_bytes(), input.as_dangerous()) 74 | } 75 | } 76 | 77 | /////////////////////////////////////////////////////////////////////////////// 78 | // Regex pattern 79 | 80 | #[cfg(feature = "regex")] 81 | unsafe impl<'i> Pattern> for ®ex::bytes::Regex { 82 | fn find_match(self, input: &Bytes<'i>) -> Option<(usize, usize)> { 83 | regex::bytes::Regex::find(self, input.as_dangerous()) 84 | .map(|m| (m.start(), m.end() - m.start())) 85 | } 86 | 87 | fn find_reject(self, input: &Bytes<'i>) -> Option { 88 | let mut maybe_reject = 0; 89 | loop { 90 | match regex::bytes::Regex::find_at(self, input.as_dangerous(), maybe_reject) { 91 | Some(m) if input.len() == m.end() => return None, 92 | Some(m) => { 93 | maybe_reject = m.end(); 94 | } 95 | None => return Some(maybe_reject), 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/input/bytes/prefix.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use crate::input::{Bytes, Prefix}; 4 | use crate::util::utf8::CharBytes; 5 | 6 | unsafe impl<'i> Prefix> for u8 { 7 | #[inline(always)] 8 | fn is_prefix_of(&self, input: &Bytes<'i>) -> bool { 9 | input.as_dangerous().starts_with(slice::from_ref(self)) 10 | } 11 | } 12 | 13 | unsafe impl<'i> Prefix> for char { 14 | #[inline(always)] 15 | fn is_prefix_of(&self, input: &Bytes<'i>) -> bool { 16 | let bytes = CharBytes::from(*self); 17 | input.as_dangerous().starts_with(bytes.as_bytes()) 18 | } 19 | } 20 | 21 | unsafe impl<'i> Prefix> for [u8] { 22 | #[inline(always)] 23 | fn is_prefix_of(&self, input: &Bytes<'i>) -> bool { 24 | input.as_dangerous().starts_with(self) 25 | } 26 | } 27 | 28 | unsafe impl<'i> Prefix> for str { 29 | #[inline(always)] 30 | fn is_prefix_of(&self, input: &Bytes<'i>) -> bool { 31 | input.as_dangerous().starts_with(self.as_bytes()) 32 | } 33 | } 34 | 35 | unsafe impl<'i, const N: usize> Prefix> for [u8; N] { 36 | #[inline(always)] 37 | fn is_prefix_of(&self, input: &Bytes<'i>) -> bool { 38 | input.as_dangerous().starts_with(&self[..]) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/input/entry.rs: -------------------------------------------------------------------------------- 1 | use super::{Bound, Bytes, Input, String}; 2 | 3 | pub trait IntoInput<'i>: Copy { 4 | type Input: Input<'i>; 5 | 6 | fn into_input(self) -> Self::Input; 7 | } 8 | 9 | impl<'i, T> IntoInput<'i> for &T 10 | where 11 | T: IntoInput<'i>, 12 | { 13 | type Input = T::Input; 14 | 15 | #[inline(always)] 16 | fn into_input(self) -> Self::Input { 17 | (*self).into_input() 18 | } 19 | } 20 | 21 | impl<'i> IntoInput<'i> for &'i [u8] { 22 | type Input = Bytes<'i>; 23 | 24 | #[inline(always)] 25 | fn into_input(self) -> Self::Input { 26 | Bytes::new(self, Bound::Start) 27 | } 28 | } 29 | 30 | impl<'i> IntoInput<'i> for &'i str { 31 | type Input = String<'i>; 32 | 33 | #[inline(always)] 34 | fn into_input(self) -> Self::Input { 35 | String::new(self, Bound::Start) 36 | } 37 | } 38 | 39 | impl<'i, const N: usize> IntoInput<'i> for &'i [u8; N] { 40 | type Input = Bytes<'i>; 41 | 42 | #[inline(always)] 43 | fn into_input(self) -> Self::Input { 44 | Bytes::new(self, Bound::Start) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/input/mod.rs: -------------------------------------------------------------------------------- 1 | //! Input support. 2 | 3 | mod bound; 4 | mod byte_len; 5 | mod bytes; 6 | mod entry; 7 | mod pattern; 8 | mod prefix; 9 | mod span; 10 | mod string; 11 | mod token; 12 | mod traits; 13 | 14 | pub use self::bound::Bound; 15 | pub use self::byte_len::ByteLength; 16 | pub use self::bytes::{ByteArray, Bytes}; 17 | pub use self::pattern::Pattern; 18 | pub use self::prefix::Prefix; 19 | pub use self::span::Span; 20 | pub use self::string::{MaybeString, String}; 21 | pub use self::token::{Token, TokenType}; 22 | pub use self::traits::Input; 23 | 24 | pub(crate) use self::entry::IntoInput; 25 | pub(crate) use self::traits::{Private, PrivateExt}; 26 | -------------------------------------------------------------------------------- /src/input/pattern.rs: -------------------------------------------------------------------------------- 1 | /// Implemented for structures that can be found within an 2 | /// [`Input`](crate::Input). 3 | /// 4 | /// You can search for a `char` or `&str` within either `Bytes` or `String`, but 5 | /// only a `u8` and `&[u8]` within `Bytes`. 6 | /// 7 | /// Empty slices are invalid patterns and have the following behaviour: 8 | /// 9 | /// - Finding a match of a empty slice pattern will return `None`. 10 | /// - Finding a reject of a empty slice pattern will return `Some(0)`. 11 | /// 12 | /// With the `simd` feature enabled pattern searches are SIMD optimised where 13 | /// possible. 14 | /// 15 | /// With the `regex` feature enabled, you can search for regex patterns. 16 | /// 17 | /// # Safety 18 | /// 19 | /// The implementation must return valid indexes and lengths for splitting input 20 | /// as these are not checked. 21 | pub unsafe trait Pattern { 22 | /// Returns the byte index and byte length of the first match and `None` if 23 | /// there was no match. 24 | fn find_match(self, input: &I) -> Option<(usize, usize)>; 25 | 26 | /// Returns the byte index of the first reject and `None` if there was no 27 | /// reject. 28 | fn find_reject(self, input: &I) -> Option; 29 | } 30 | -------------------------------------------------------------------------------- /src/input/prefix.rs: -------------------------------------------------------------------------------- 1 | use super::ByteLength; 2 | 3 | /// Implemented for types that can be a prefix for a given input. 4 | /// 5 | /// # Safety 6 | /// 7 | /// The implementation **must** guarantee that the value returned is correct as 8 | /// it is used for unchecked memory operations and an incorrect implementation 9 | /// would introduce invalid memory access. 10 | pub unsafe trait Prefix: ByteLength { 11 | /// Returns `true` if `self` is a prefix of the given input. 12 | fn is_prefix_of(&self, input: &I) -> bool; 13 | } 14 | 15 | unsafe impl<'i, T: ?Sized, I> Prefix for &T 16 | where 17 | T: Prefix, 18 | { 19 | #[inline(always)] 20 | fn is_prefix_of(&self, input: &I) -> bool { 21 | T::is_prefix_of(self, input) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/input/span.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Range; 2 | use core::ptr::NonNull; 3 | use core::slice; 4 | 5 | use crate::display::InputDisplay; 6 | use crate::fmt; 7 | use crate::input::{Input, MaybeString}; 8 | 9 | /// Range of [`Input`]. 10 | /// 11 | /// Spans are specific to the input chain they were created in as the range is 12 | /// stored as raw start and end pointers. 13 | /// 14 | /// You can create a span from either [`Input::span()`] or from a raw slice via 15 | /// [`Span::from()`]. 16 | /// 17 | /// [`Input`]: crate::Input 18 | /// [`Input::span()`]: crate::Input::span() 19 | #[must_use] 20 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] 21 | pub struct Span { 22 | start: NonNull, 23 | end: NonNull, 24 | } 25 | 26 | impl Span { 27 | /// Returns the number of bytes spanned. 28 | #[must_use] 29 | #[inline(always)] 30 | pub fn len(self) -> usize { 31 | self.end.as_ptr() as usize - self.start.as_ptr() as usize 32 | } 33 | 34 | /// Returns `true` if no bytes are spanned. 35 | #[must_use] 36 | #[inline(always)] 37 | pub fn is_empty(self) -> bool { 38 | self.start == self.end 39 | } 40 | 41 | /// Returns `true` if the span is completely within the bounds of the 42 | /// specified parent. 43 | /// 44 | /// # Examples 45 | /// 46 | /// ``` 47 | /// use dangerous::Span; 48 | /// 49 | /// let bytes = [0_u8; 64]; 50 | /// 51 | /// // Within 52 | /// let parent = Span::from(&bytes[16..32]); 53 | /// let child = Span::from(&bytes[20..24]); 54 | /// assert!(child.is_within(parent)); 55 | /// assert!(parent.is_within(parent)); 56 | /// 57 | /// // Left out of bound 58 | /// let parent = Span::from(&bytes[16..32]); 59 | /// let child = Span::from(&bytes[15..24]); 60 | /// assert!(!child.is_within(parent)); 61 | /// 62 | /// // Right out of bound 63 | /// let parent = Span::from(&bytes[16..32]); 64 | /// let child = Span::from(&bytes[20..33]); 65 | /// assert!(!child.is_within(parent)); 66 | /// 67 | /// // Both out of bound 68 | /// let parent = Span::from(&bytes[16..32]); 69 | /// let child = Span::from(&bytes[15..33]); 70 | /// assert!(!child.is_within(parent)); 71 | /// ``` 72 | #[must_use] 73 | #[inline(always)] 74 | pub fn is_within(self, other: Span) -> bool { 75 | other.start <= self.start && other.end >= self.end 76 | } 77 | 78 | /// Returns `true` if `self` points to the start of `other`, spanning no bytes. 79 | /// 80 | /// # Example 81 | /// 82 | /// ``` 83 | /// use dangerous::Span; 84 | /// 85 | /// let bytes = &[1, 2, 3, 4][..]; 86 | /// let span = Span::from(bytes); 87 | /// 88 | /// assert!(span.start().is_start_of(span)); 89 | /// assert!(!span.is_start_of(span)); 90 | /// ``` 91 | #[must_use] 92 | #[inline(always)] 93 | pub fn is_start_of(self, other: Span) -> bool { 94 | self.is_empty() && other.start == self.start 95 | } 96 | 97 | /// Returns `true` if `self` points to the end of `other`, spanning no bytes. 98 | /// 99 | /// # Example 100 | /// 101 | /// ``` 102 | /// use dangerous::Span; 103 | /// 104 | /// let bytes = &[1, 2, 3, 4][..]; 105 | /// let span = Span::from(bytes); 106 | /// 107 | /// assert!(span.end().is_end_of(span)); 108 | /// assert!(!span.is_end_of(span)); 109 | /// ``` 110 | #[must_use] 111 | #[inline(always)] 112 | pub fn is_end_of(self, other: Span) -> bool { 113 | self.is_empty() && other.end == self.end 114 | } 115 | 116 | /// Returns `true` if `self` overlaps the start of `other`. 117 | /// 118 | /// # Example 119 | /// 120 | /// ``` 121 | /// use dangerous::Span; 122 | /// 123 | /// let all = b"0123456789"; 124 | /// let a = Span::from(&all[0..9]); 125 | /// let b = Span::from(&all[6..9]); 126 | /// 127 | /// assert!(a.is_overlapping_start_of(b)); 128 | /// 129 | /// ``` 130 | #[must_use] 131 | #[inline(always)] 132 | pub fn is_overlapping_start_of(self, other: Span) -> bool { 133 | other.start > self.start 134 | } 135 | 136 | /// Returns `true` if `self` overlaps the end of `other`. 137 | #[must_use] 138 | #[inline(always)] 139 | pub fn is_overlapping_end_of(self, other: Span) -> bool { 140 | other.end < self.end 141 | } 142 | 143 | /// Returns `true` if `self`'s start is within `other`. 144 | #[must_use] 145 | #[inline(always)] 146 | #[allow(clippy::suspicious_operation_groupings)] 147 | pub fn is_start_within(self, other: Span) -> bool { 148 | self.start >= other.start && self.start < other.end 149 | } 150 | 151 | /// Returns `true` if `self`'s end is within `other`. 152 | #[must_use] 153 | #[inline(always)] 154 | #[allow(clippy::suspicious_operation_groupings)] 155 | pub fn is_end_within(self, other: Span) -> bool { 156 | self.end >= other.start && self.end < other.end 157 | } 158 | 159 | /// Returns a span pointing to the start of self, spanning no bytes. 160 | pub fn start(self) -> Self { 161 | Self { 162 | start: self.start, 163 | end: self.start, 164 | } 165 | } 166 | 167 | /// Returns a span pointing to the end of self, spanning no bytes. 168 | pub fn end(self) -> Self { 169 | Self { 170 | start: self.end, 171 | end: self.end, 172 | } 173 | } 174 | 175 | /// Returns the sub slice of the provided parent `self` refers to or `None` 176 | /// if `self` is not within the parent or does not align with start and end 177 | /// token boundaries. 178 | /// 179 | /// You can get a span of [`Input`], `&str` or `&[u8]`. 180 | /// 181 | /// # Example 182 | /// 183 | /// ``` 184 | /// use dangerous::Span; 185 | /// 186 | /// let parent = &[1, 2, 3, 4][..]; 187 | /// let sub = &parent[1..2]; 188 | /// assert_eq!(Span::from(sub).of(parent), Some(sub)); 189 | /// 190 | /// let non_span = Span::from(&[1, 2, 2, 4][..]); 191 | /// assert_eq!(non_span.of(parent), None); 192 | /// ``` 193 | #[must_use] 194 | pub fn of

(self, parent: P) -> Option

195 | where 196 | P: Parent, 197 | { 198 | parent.extract(self) 199 | } 200 | 201 | /// Returns `Some(Range)` with the `start` and `end` offsets of `self` 202 | /// within the `parent`. `None` is returned if `self` is not within in the 203 | /// `parent`. 204 | /// 205 | /// # Example 206 | /// 207 | /// ``` 208 | /// use dangerous::Span; 209 | /// 210 | /// let parent = &[1, 2, 3, 4][..]; 211 | /// let sub_range = 1..2; 212 | /// let sub = &parent[sub_range.clone()]; 213 | /// 214 | /// assert_eq!(Span::from(sub).range_of(parent.into()), Some(sub_range)) 215 | /// ``` 216 | #[must_use] 217 | #[inline(always)] 218 | pub fn range_of(self, parent: Span) -> Option> { 219 | if self.is_within(parent) { 220 | let start_offset = self.start.as_ptr() as usize - parent.start.as_ptr() as usize; 221 | let end_offset = self.end.as_ptr() as usize - parent.start.as_ptr() as usize; 222 | Some(start_offset..end_offset) 223 | } else { 224 | None 225 | } 226 | } 227 | 228 | /// Returns `None` if the span is empty, `Some(Self)` if not. 229 | /// 230 | /// # Example 231 | /// 232 | /// ``` 233 | /// use dangerous::Span; 234 | /// 235 | /// let bytes = &[0][..]; 236 | /// assert!(Span::from(bytes).non_empty().is_some()); 237 | /// 238 | /// let bytes = &[][..]; 239 | /// assert!(Span::from(bytes).non_empty().is_none()); 240 | /// ``` 241 | #[must_use] 242 | #[inline(always)] 243 | pub fn non_empty(self) -> Option { 244 | if self.is_empty() { 245 | None 246 | } else { 247 | Some(self) 248 | } 249 | } 250 | 251 | /// Wraps the span with improved debugging support given the containing 252 | /// input. 253 | #[inline(always)] 254 | #[allow(clippy::needless_pass_by_value)] 255 | pub fn debug_for(self, input: MaybeString<'_>) -> DebugFor<'_> { 256 | DebugFor { 257 | bytes: input.as_dangerous_bytes(), 258 | str_hint: input.is_string(), 259 | span: self, 260 | } 261 | } 262 | } 263 | 264 | impl fmt::DisplayBase for Span { 265 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 266 | w.write_str("(ptr: ")?; 267 | w.write_usize(self.start.as_ptr() as usize)?; 268 | w.write_str(", len: ")?; 269 | w.write_usize(self.len())?; 270 | w.write_char(')') 271 | } 272 | } 273 | 274 | impl From<&[u8]> for Span { 275 | #[inline(always)] 276 | fn from(value: &[u8]) -> Self { 277 | let range = value.as_ptr_range(); 278 | // SAFETY: it is invalid for a slice ptr to be null. 279 | // See: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html 280 | unsafe { 281 | Self { 282 | start: NonNull::new_unchecked(range.start as _), 283 | end: NonNull::new_unchecked(range.end as _), 284 | } 285 | } 286 | } 287 | } 288 | 289 | impl From<&str> for Span { 290 | #[inline(always)] 291 | fn from(value: &str) -> Self { 292 | Self::from(value.as_bytes()) 293 | } 294 | } 295 | 296 | impl fmt::Debug for Span { 297 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 298 | f.debug_struct("Span") 299 | .field("ptr", &self.start) 300 | .field("len", &self.len()) 301 | .finish() 302 | } 303 | } 304 | 305 | // SAFETY: Span can only dereference the data pointed to if the data is present 306 | // and passed as a parent. This makes the internal pointers safe to alias across 307 | // threads as the parent data enforces the aliasing rules. 308 | unsafe impl Send for Span {} 309 | 310 | // SAFETY: Span can only dereference the data pointed to if the data is present 311 | // and passed as a parent. This makes the internal pointers safe to alias across 312 | // threads as the parent data enforces the aliasing rules. 313 | unsafe impl Sync for Span {} 314 | 315 | #[must_use] 316 | pub struct DebugFor<'a> { 317 | span: Span, 318 | str_hint: bool, 319 | bytes: &'a [u8], 320 | } 321 | 322 | impl<'a> fmt::Debug for DebugFor<'a> { 323 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 324 | match self.span.of(self.bytes) { 325 | Some(valid) => { 326 | let display = InputDisplay::from_bytes(valid).with_formatter(f); 327 | let display = if self.str_hint { 328 | display.str_hint() 329 | } else { 330 | display 331 | }; 332 | f.debug_tuple("Span").field(&display).finish() 333 | } 334 | None => fmt::Debug::fmt(&self.span, f), 335 | } 336 | } 337 | } 338 | 339 | pub trait Parent: Sized { 340 | fn extract(self, span: Span) -> Option; 341 | } 342 | 343 | impl Parent for &[u8] { 344 | #[inline(always)] 345 | fn extract(self, span: Span) -> Option { 346 | if span.is_within(self.into()) { 347 | // SAFETY: we have checked that the slice is valid within the 348 | // parent, so we can create a slice from our bounds. 349 | unsafe { Some(slice::from_raw_parts(span.start.as_ptr(), span.len())) } 350 | } else { 351 | None 352 | } 353 | } 354 | } 355 | 356 | impl Parent for &str { 357 | #[inline] 358 | fn extract(self, span: Span) -> Option { 359 | span.range_of(self.into()).and_then(|range| { 360 | if self.is_char_boundary(range.start) && self.is_char_boundary(range.end) { 361 | Some(&self[range]) 362 | } else { 363 | None 364 | } 365 | }) 366 | } 367 | } 368 | 369 | impl<'i, T> Parent for T 370 | where 371 | T: Input<'i>, 372 | { 373 | #[inline] 374 | fn extract(self, span: Span) -> Option { 375 | span.range_of(self.span()).and_then(|range| { 376 | if self.verify_token_boundary(range.start).is_ok() 377 | && self.verify_token_boundary(range.end).is_ok() 378 | { 379 | // SAFETY: we have checked that the range start and end are 380 | // valid boundary indexes within the parent, so we can split 381 | // with them. 382 | let sub = unsafe { 383 | let (_, tail) = self.split_at_byte_unchecked(range.start); 384 | let (sub, _) = tail.split_at_byte_unchecked(range.end - range.start); 385 | sub 386 | }; 387 | Some(sub) 388 | } else { 389 | None 390 | } 391 | }) 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/input/string/maybe.rs: -------------------------------------------------------------------------------- 1 | use crate::display::InputDisplay; 2 | use crate::fmt; 3 | use crate::input::{Bound, Bytes, Input, Span, String}; 4 | 5 | /// [`String`] if known UTF-8, [`Bytes`] if not. 6 | #[derive(Clone)] 7 | #[must_use = "input must be consumed"] 8 | pub enum MaybeString<'i> { 9 | /// The [`Input`] is not known to be UTF-8. 10 | Bytes(Bytes<'i>), 11 | /// The [`Input`] is known to be UTF-8. 12 | String(String<'i>), 13 | } 14 | 15 | impl<'i> MaybeString<'i> { 16 | /// Returns `true` if he [`Input`] is known to be UTF-8. 17 | #[must_use] 18 | pub fn is_string(&self) -> bool { 19 | match self { 20 | Self::Bytes(_) => false, 21 | Self::String(_) => true, 22 | } 23 | } 24 | 25 | /// Returns a [`Span`] from the start of `self` to the end. 26 | pub fn span(&self) -> Span { 27 | match self { 28 | Self::Bytes(v) => v.span(), 29 | Self::String(v) => v.span(), 30 | } 31 | } 32 | 33 | /// Consumes `self` into [`Bytes`]. 34 | pub fn into_bytes(self) -> Bytes<'i> { 35 | match self { 36 | Self::Bytes(v) => v.into_bytes(), 37 | Self::String(v) => v.into_bytes(), 38 | } 39 | } 40 | 41 | /// Returns an [`InputDisplay`] for formatting. 42 | pub fn display(&self) -> InputDisplay<'i> { 43 | match self { 44 | Self::Bytes(v) => v.display(), 45 | Self::String(v) => v.display(), 46 | } 47 | } 48 | 49 | /// Returns `true` if [`Self::bound()`] is [`Bound::StartEnd`]. 50 | #[must_use] 51 | pub fn is_bound(&self) -> bool { 52 | self.bound() == Bound::StartEnd 53 | } 54 | 55 | /// Returns the inner [`Input`]s [`Bound`]. 56 | pub fn bound(&self) -> Bound { 57 | match self { 58 | Self::Bytes(v) => v.bound(), 59 | Self::String(v) => v.bound(), 60 | } 61 | } 62 | 63 | pub(crate) fn as_dangerous_bytes(&self) -> &'i [u8] { 64 | match self { 65 | Self::Bytes(v) => v.as_dangerous(), 66 | Self::String(v) => v.as_dangerous().as_bytes(), 67 | } 68 | } 69 | } 70 | 71 | /////////////////////////////////////////////////////////////////////////////// 72 | // Formatting 73 | 74 | impl<'i> fmt::Debug for MaybeString<'i> { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 76 | match self { 77 | Self::Bytes(v) => v.fmt(f), 78 | Self::String(v) => v.fmt(f), 79 | } 80 | } 81 | } 82 | 83 | impl<'i> fmt::DisplayBase for MaybeString<'i> { 84 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 85 | self.display().fmt(w) 86 | } 87 | } 88 | 89 | impl<'i> fmt::Display for MaybeString<'i> { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | self.display().fmt(f) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/input/string/mod.rs: -------------------------------------------------------------------------------- 1 | mod maybe; 2 | mod pattern; 3 | mod prefix; 4 | 5 | use core::str; 6 | 7 | use crate::display::InputDisplay; 8 | use crate::error::{ 9 | CoreContext, CoreExpected, CoreOperation, ExpectedLength, ExpectedValid, Length, 10 | }; 11 | use crate::fmt; 12 | use crate::util::{fast, slice, utf8}; 13 | 14 | pub use self::maybe::MaybeString; 15 | 16 | use super::{Bound, Bytes, Input, Private}; 17 | 18 | /// UTF-8 [`Input`]. 19 | #[derive(Clone)] 20 | #[must_use = "input must be consumed"] 21 | pub struct String<'i> { 22 | utf8: Bytes<'i>, 23 | } 24 | 25 | impl<'i> String<'i> { 26 | pub(crate) const fn new(s: &'i str, bound: Bound) -> Self { 27 | Self { 28 | utf8: Bytes::new(s.as_bytes(), bound), 29 | } 30 | } 31 | 32 | /// Returns the number of UTF-8 characters in the string. 33 | /// 34 | /// It is recommended to enable the `bytecount` dependency when using this 35 | /// function for better performance. 36 | #[must_use] 37 | pub fn num_chars(&self) -> usize { 38 | fast::num_chars(self.as_dangerous()) 39 | } 40 | 41 | /// Returns `true` if the underlying byte slice length is zero. 42 | #[must_use] 43 | #[inline(always)] 44 | pub fn is_empty(&self) -> bool { 45 | self.as_dangerous().is_empty() 46 | } 47 | 48 | /// Returns the underlying string slice. 49 | /// 50 | /// See [`Bytes::as_dangerous`] for naming. 51 | #[must_use] 52 | #[inline(always)] 53 | pub fn as_dangerous(&self) -> &'i str { 54 | // SAFETY: string container guarantees valid UTF-8 bytes. 55 | unsafe { utf8::from_unchecked(self.utf8.as_dangerous()) } 56 | } 57 | 58 | /// Returns the underlying string slice if it is not empty. 59 | /// 60 | /// See [`Bytes::as_dangerous`] for naming. 61 | /// 62 | /// # Errors 63 | /// 64 | /// Returns [`ExpectedLength`] if the input is empty. 65 | pub fn to_dangerous_non_empty(&self) -> Result<&'i str, E> 66 | where 67 | E: From>, 68 | { 69 | if self.is_empty() { 70 | Err(E::from(ExpectedLength { 71 | len: Length::AtLeast(1), 72 | context: CoreContext { 73 | span: self.span(), 74 | operation: CoreOperation::IntoString, 75 | expected: CoreExpected::NonEmpty, 76 | }, 77 | input: self.clone().into_maybe_string(), 78 | })) 79 | } else { 80 | Ok(self.as_dangerous()) 81 | } 82 | } 83 | 84 | /// Decodes [`Bytes`] into a UTF-8 [`String`]. 85 | /// 86 | /// # Errors 87 | /// 88 | /// Returns [`ExpectedValid`] if the input could never be valid UTF-8 and 89 | /// [`ExpectedLength`] if a UTF-8 code point was cut short. 90 | #[inline(always)] 91 | #[allow(clippy::needless_pass_by_value)] 92 | pub fn from_utf8(utf8: Bytes<'i>) -> Result, E> 93 | where 94 | E: From>, 95 | E: From>, 96 | { 97 | utf8.to_dangerous_str().map(|s| Self::new(s, utf8.bound())) 98 | } 99 | 100 | /// Construct a `String` from unchecked [`Bytes`]. 101 | /// 102 | /// # Safety 103 | /// 104 | /// Caller must ensure that the provides [`Bytes`] are valid UTF-8. 105 | #[inline(always)] 106 | pub unsafe fn from_utf8_unchecked(utf8: Bytes<'i>) -> Self { 107 | Self { utf8 } 108 | } 109 | } 110 | 111 | impl<'i> Input<'i> for String<'i> { 112 | type Token = char; 113 | 114 | #[inline(always)] 115 | fn bound(&self) -> Bound { 116 | self.utf8.bound() 117 | } 118 | 119 | #[inline(always)] 120 | fn into_bytes(self) -> Bytes<'i> { 121 | self.utf8 122 | } 123 | 124 | #[inline(always)] 125 | fn into_string(self) -> Result, E> 126 | where 127 | E: From>, 128 | E: From>, 129 | { 130 | Ok(self) 131 | } 132 | 133 | #[inline(always)] 134 | fn into_bound(mut self) -> Self { 135 | self.utf8 = self.utf8.into_bound(); 136 | self 137 | } 138 | 139 | #[inline(always)] 140 | fn into_maybe_string(self) -> MaybeString<'i> { 141 | MaybeString::String(self) 142 | } 143 | 144 | fn display(&self) -> InputDisplay<'i> { 145 | InputDisplay::new(self).str_hint() 146 | } 147 | 148 | #[inline(always)] 149 | fn split_at_opt(self, mid: usize) -> Option<(Self, Self)> { 150 | let string = self.as_dangerous(); 151 | let iter = &mut string.chars(); 152 | if iter.nth(mid.saturating_sub(1)).is_some() { 153 | let byte_mid = string.as_bytes().len() - iter.as_str().as_bytes().len(); 154 | // SAFETY: we take byte_mid as the difference between the parent 155 | // string and the remaining string left over from the char iterator. 156 | // This means both the index can only ever be valid and the bytes in 157 | // between are valid UTF-8. 158 | Some(unsafe { self.split_at_byte_unchecked(byte_mid) }) 159 | } else { 160 | None 161 | } 162 | } 163 | } 164 | 165 | impl<'i> Private<'i, char> for String<'i> { 166 | type TokenIter = str::Chars<'i>; 167 | type TokenIndicesIter = str::CharIndices<'i>; 168 | 169 | #[inline(always)] 170 | fn end(self) -> Self { 171 | Self { 172 | utf8: self.utf8.end(), 173 | } 174 | } 175 | 176 | #[inline(always)] 177 | fn tokens(self) -> Self::TokenIter { 178 | self.as_dangerous().chars() 179 | } 180 | 181 | #[inline(always)] 182 | fn tokens_indices(self) -> Self::TokenIndicesIter { 183 | self.as_dangerous().char_indices() 184 | } 185 | 186 | #[inline(always)] 187 | fn into_unbound_end(mut self) -> Self { 188 | self.utf8 = self.utf8.into_unbound_end(); 189 | self 190 | } 191 | 192 | #[inline(always)] 193 | fn verify_token_boundary(&self, index: usize) -> Result<(), CoreExpected> { 194 | if index > self.byte_len() { 195 | Err(CoreExpected::EnoughInputFor("char index")) 196 | } else if self.as_dangerous().is_char_boundary(index) { 197 | Ok(()) 198 | } else { 199 | Err(CoreExpected::Valid("char index")) 200 | } 201 | } 202 | 203 | #[inline(always)] 204 | unsafe fn split_at_byte_unchecked(self, mid: usize) -> (Self, Self) { 205 | let (head, tail) = slice::split_str_at_unchecked(self.as_dangerous(), mid); 206 | ( 207 | String::new(head, self.bound().close_end()), 208 | String::new(tail, self.bound()), 209 | ) 210 | } 211 | } 212 | 213 | /////////////////////////////////////////////////////////////////////////////// 214 | // Equality 215 | 216 | impl<'i> PartialEq for String<'i> { 217 | #[inline(always)] 218 | fn eq(&self, other: &Self) -> bool { 219 | self.as_dangerous() == other.as_dangerous() 220 | } 221 | } 222 | 223 | impl<'i> PartialEq for String<'i> { 224 | #[inline(always)] 225 | fn eq(&self, other: &str) -> bool { 226 | self.as_dangerous() == other 227 | } 228 | } 229 | 230 | impl<'i> PartialEq for &String<'i> { 231 | #[inline(always)] 232 | fn eq(&self, other: &str) -> bool { 233 | self.as_dangerous() == other 234 | } 235 | } 236 | 237 | impl<'i> PartialEq<&str> for String<'i> { 238 | #[inline(always)] 239 | fn eq(&self, other: &&str) -> bool { 240 | self.as_dangerous() == *other 241 | } 242 | } 243 | 244 | impl<'i> PartialEq> for str { 245 | #[inline(always)] 246 | fn eq(&self, other: &String<'i>) -> bool { 247 | self == other.as_dangerous() 248 | } 249 | } 250 | 251 | /////////////////////////////////////////////////////////////////////////////// 252 | // Formatting 253 | 254 | impl<'i> fmt::Debug for String<'i> { 255 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 256 | let display = self.display().with_formatter(f); 257 | f.debug_struct("String") 258 | .field("bound", &self.bound()) 259 | .field("value", &display) 260 | .finish() 261 | } 262 | } 263 | 264 | impl<'i> fmt::DisplayBase for String<'i> { 265 | fn fmt(&self, w: &mut dyn fmt::Write) -> fmt::Result { 266 | self.display().fmt(w) 267 | } 268 | } 269 | 270 | impl<'i> fmt::Display for String<'i> { 271 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 272 | self.display().with_formatter(f).fmt(f) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/input/string/pattern.rs: -------------------------------------------------------------------------------- 1 | // # Safety note about searching for substrings for `Pattern`. 2 | // 3 | // As we are looking for exact slice matches, the needle and haystack are both 4 | // UTF-8, we are searching forward and the fact the first byte of a UTF-8 5 | // character cannot clash with a continuation byte we can safely search as just 6 | // raw bytes and return those indexes. 7 | // 8 | // For searching within a string we care that we don't return an index that 9 | // isn't a char boundary, but with the above conditions this is impossible. 10 | // 11 | // Note in the below UTF-8 byte sequence table continuation bytes (bytes 2-4) 12 | // cannot clash with the first byte. This means when the first byte of the 13 | // needle string matches the first byte of the haystack string, we are at a 14 | // valid char boundary and if rest of the needle matches the indexes we return 15 | // are valid. 16 | // 17 | // | Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | 18 | // | ------ | -------- | -------- | -------- | -------- | 19 | // | 1 | 0xxxxxxx | | | | 20 | // | 2 | 110xxxxx | 10xxxxxx | | | 21 | // | 3 | 1110xxxx | 10xxxxxx | 10xxxxxx | | 22 | // | 4 | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 23 | 24 | use crate::input::{Pattern, String}; 25 | use crate::util::fast; 26 | 27 | /////////////////////////////////////////////////////////////////////////////// 28 | // Fn pattern 29 | 30 | unsafe impl<'i, F> Pattern> for F 31 | where 32 | F: FnMut(char) -> bool, 33 | { 34 | fn find_match(mut self, input: &String<'i>) -> Option<(usize, usize)> { 35 | for (i, c) in input.as_dangerous().char_indices() { 36 | if !(self)(c) { 37 | return Some((i, c.len_utf8())); 38 | } 39 | } 40 | None 41 | } 42 | 43 | fn find_reject(mut self, input: &String<'i>) -> Option { 44 | input.as_dangerous().char_indices().find_map( 45 | |(i, b)| { 46 | if (self)(b) { 47 | None 48 | } else { 49 | Some(i) 50 | } 51 | }, 52 | ) 53 | } 54 | } 55 | 56 | /////////////////////////////////////////////////////////////////////////////// 57 | // Token pattern 58 | 59 | unsafe impl<'i> Pattern> for char { 60 | fn find_match(self, input: &String<'i>) -> Option<(usize, usize)> { 61 | fast::find_char_match(self, input.as_dangerous().as_bytes()) 62 | .map(|index| (index, self.len_utf8())) 63 | } 64 | 65 | fn find_reject(self, input: &String<'i>) -> Option { 66 | fast::find_char_reject(self, input.as_dangerous().as_bytes()) 67 | } 68 | } 69 | 70 | /////////////////////////////////////////////////////////////////////////////// 71 | // Sub-slice pattern 72 | 73 | unsafe impl<'i> Pattern> for &str { 74 | #[inline] 75 | fn find_match(self, input: &String<'i>) -> Option<(usize, usize)> { 76 | fast::find_slice_match(self.as_bytes(), input.as_dangerous().as_bytes()) 77 | .map(|index| (index, self.len())) 78 | } 79 | 80 | #[inline] 81 | fn find_reject(self, input: &String<'i>) -> Option { 82 | fast::find_slice_reject(self.as_bytes(), input.as_dangerous().as_bytes()) 83 | } 84 | } 85 | 86 | /////////////////////////////////////////////////////////////////////////////// 87 | // Regex pattern 88 | 89 | #[cfg(feature = "regex")] 90 | unsafe impl<'i> Pattern> for ®ex::Regex { 91 | fn find_match(self, input: &String<'i>) -> Option<(usize, usize)> { 92 | regex::Regex::find(self, input.as_dangerous()).map(|m| (m.start(), m.end() - m.start())) 93 | } 94 | 95 | fn find_reject(self, input: &String<'i>) -> Option { 96 | let mut maybe_reject = 0; 97 | loop { 98 | match regex::Regex::find_at(self, input.as_dangerous(), maybe_reject) { 99 | Some(m) if input.as_dangerous().len() == m.end() => return None, 100 | Some(m) => { 101 | maybe_reject = m.end(); 102 | } 103 | None => return Some(maybe_reject), 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/input/string/prefix.rs: -------------------------------------------------------------------------------- 1 | use crate::input::{Prefix, String}; 2 | 3 | unsafe impl<'i> Prefix> for char { 4 | #[inline(always)] 5 | fn is_prefix_of(&self, input: &String<'i>) -> bool { 6 | match input.as_dangerous().chars().next() { 7 | Some(c) => c == *self, 8 | None => false, 9 | } 10 | } 11 | } 12 | 13 | unsafe impl<'i> Prefix> for str { 14 | #[inline(always)] 15 | fn is_prefix_of(&self, input: &String<'i>) -> bool { 16 | input.as_dangerous().starts_with(self) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/input/token.rs: -------------------------------------------------------------------------------- 1 | use super::ByteLength; 2 | 3 | /// Implemented for the smallest unit that can be consumed from an [`Input`]. 4 | /// 5 | /// [`Input`]: crate::Input 6 | pub trait Token: ByteLength + Copy + 'static { 7 | /// Returns the token type used in debugging. 8 | const TYPE: TokenType; 9 | } 10 | 11 | /// The token type. 12 | #[derive(Copy, Clone)] 13 | pub enum TokenType { 14 | /// A byte. 15 | Byte, 16 | /// A UTF-8 char. 17 | Char, 18 | } 19 | 20 | impl Token for u8 { 21 | const TYPE: TokenType = TokenType::Byte; 22 | } 23 | 24 | impl Token for char { 25 | const TYPE: TokenType = TokenType::Char; 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Safely and explicitly parse untrusted aka `dangerous` data. 2 | //! 3 | //! # Basic usage 4 | //! 5 | //! ``` 6 | //! use dangerous::{Input, Invalid}; 7 | //! 8 | //! let input = dangerous::input(b"hello"); 9 | //! let result: Result<_, Invalid> = input.read_partial(|r| { 10 | //! r.read() 11 | //! }); 12 | //! 13 | //! assert_eq!(result, Ok((b'h', dangerous::input(b"ello")))); 14 | //! ``` 15 | //! 16 | //! # Feature flags 17 | //! 18 | //! | Feature | Default | Description 19 | //! | ---------------- | ----------- | -------------------------------------------------- | 20 | //! | `std` | **Enabled** | Enables `std::error::Error` support and `alloc` | 21 | //! | `alloc` | **Enabled** | Enables allocations. | 22 | //! | `simd` | **Enabled** | Enables all supported SIMD optimisations. | 23 | //! | `unicode` | **Enabled** | Enables improved unicode printing support. | 24 | //! | `full-backtrace` | **Enabled** | Enables collection of all contexts for `Expected`. | 25 | //! | `zc` | _Disabled_ | Enables `zc` crate support. | 26 | //! | `nom` | _Disabled_ | Enables `nom` crate error support. | 27 | //! | `regex` | _Disabled_ | Enables `regex` pattern support. | 28 | 29 | /////////////////////////////////////////////////////////////////////////////// 30 | // Library quirks & hacks 31 | // 32 | // # `as_any` on selected traits when used with &dyn 33 | // 34 | // An ideal implementation wouldn't require this function and we would just lean 35 | // on the super trait requirement, but doesn't seem possible today with trait 36 | // objects. See: https://github.com/rust-lang/rfcs/issues/2035 37 | 38 | #![cfg_attr(not(any(feature = "std", test)), no_std)] 39 | #![cfg_attr(docsrs, feature(doc_cfg))] 40 | #![cfg_attr(doc, deny(rustdoc::all))] 41 | #![forbid(trivial_casts, trivial_numeric_casts, unstable_features)] 42 | #![deny( 43 | unused, 44 | missing_docs, 45 | rust_2018_idioms, 46 | future_incompatible, 47 | clippy::all, 48 | clippy::correctness, 49 | clippy::style, 50 | clippy::complexity, 51 | clippy::perf, 52 | clippy::pedantic, 53 | clippy::cargo 54 | )] 55 | #![allow( 56 | clippy::module_name_repetitions, 57 | clippy::type_repetition_in_bounds, 58 | clippy::inline_always, 59 | rustdoc::missing_doc_code_examples 60 | )] 61 | // FIXME: remove false positive, fixed in v1.59 62 | #![cfg_attr(doc, allow(rustdoc::private_doc_tests))] 63 | 64 | #[cfg(feature = "alloc")] 65 | extern crate alloc; 66 | 67 | mod reader; 68 | mod support; 69 | mod util; 70 | 71 | pub mod display; 72 | pub mod error; 73 | pub mod input; 74 | 75 | pub use self::error::{Error, Expected, Fatal, Invalid, ToRetryRequirement}; 76 | pub use self::input::{Bound, ByteArray, Bytes, Input, MaybeString, Span, String}; 77 | pub use self::reader::{BytesReader, Peek, Reader, StringReader}; 78 | 79 | // Re-exported types from core::fmt along with `DisplayBase` and `Write`. 80 | // This is used crate wide with the exception of crate::display. 81 | pub(crate) mod fmt { 82 | pub(crate) use crate::display::{DisplayBase, Write}; 83 | pub(crate) use core::fmt::{Debug, Display, Error, Formatter, Result}; 84 | } 85 | 86 | use crate::input::IntoInput; 87 | 88 | /// Creates a new `Input` from a byte or string slice. 89 | /// 90 | /// It is recommended to use this directly from the crate as `dangerous::input()`, 91 | /// not as an import via `use` as shown below, as you lose the discoverability. 92 | /// 93 | /// ``` 94 | /// use dangerous::input; // bad 95 | /// 96 | /// dangerous::input(b"hello"); // do this instead 97 | /// ``` 98 | #[inline(always)] 99 | pub fn input<'i, I>(input: I) -> I::Input 100 | where 101 | I: IntoInput<'i>, 102 | { 103 | input.into_input() 104 | } 105 | -------------------------------------------------------------------------------- /src/reader/bytes.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{CoreOperation, ExpectedLength, ExpectedValid, WithContext}; 2 | use crate::input::{ByteArray, Bytes, String}; 3 | 4 | use super::BytesReader; 5 | 6 | impl<'i, E> BytesReader<'i, E> { 7 | /// Read an array from input. 8 | /// 9 | /// # Integers 10 | /// 11 | /// This function can be used to read integers like so: 12 | /// 13 | /// ``` 14 | /// use dangerous::{Input, ByteArray, Invalid}; 15 | /// 16 | /// let result: Result<_, Invalid> = dangerous::input(&[1, 0, 0, 0]).read_all(|r| { 17 | /// r.take_array().map(ByteArray::into_dangerous).map(u32::from_le_bytes) 18 | /// }); 19 | /// 20 | /// assert_eq!(result.unwrap(), 1); 21 | /// ``` 22 | /// 23 | /// # Errors 24 | /// 25 | /// Returns an error if the length requirement to read could not be met. 26 | #[inline] 27 | pub fn take_array(&mut self) -> Result, E> 28 | where 29 | E: From>, 30 | { 31 | self.try_advance(|input| input.split_array(CoreOperation::TakeArray)) 32 | } 33 | 34 | /// Read an optional array. 35 | /// 36 | /// Returns `Some(ByteArray)` if there was enough input, `None` if not. 37 | #[inline] 38 | pub fn take_array_opt(&mut self) -> Option> { 39 | self.advance_opt(Bytes::split_array_opt) 40 | } 41 | 42 | /// Read the remaining string input. 43 | /// 44 | /// # Errors 45 | /// 46 | /// Returns [`ExpectedValid`] if the input could never be valid UTF-8 and 47 | /// [`ExpectedLength`] if a UTF-8 code point was cut short. 48 | pub fn take_remaining_str(&mut self) -> Result, E> 49 | where 50 | E: From>, 51 | E: From>, 52 | { 53 | self.try_advance(|input| input.split_str_while(|_| true, CoreOperation::TakeRemainingStr)) 54 | } 55 | 56 | /// Read a length of string input while a predicate check remains true. 57 | /// 58 | /// # Errors 59 | /// 60 | /// Returns [`ExpectedValid`] if the input could never be valid UTF-8 and 61 | /// [`ExpectedLength`] if a UTF-8 code point was cut short. 62 | pub fn take_str_while(&mut self, pred: F) -> Result, E> 63 | where 64 | E: From>, 65 | E: From>, 66 | F: FnMut(char) -> bool, 67 | { 68 | self.try_advance(|input| input.split_str_while(pred, CoreOperation::TakeStrWhile)) 69 | } 70 | 71 | /// Try read a length of string input while a predicate check remains true. 72 | /// 73 | /// # Errors 74 | /// 75 | /// Returns any error the provided function does, [`ExpectedValid`] if the 76 | /// the input could never be valid UTF-8 and [`ExpectedLength`] if a UTF-8 77 | /// code point was cut short. 78 | pub fn try_take_str_while(&mut self, pred: F) -> Result, E> 79 | where 80 | E: WithContext<'i>, 81 | E: From>, 82 | E: From>, 83 | F: FnMut(char) -> Result, 84 | { 85 | self.try_advance(|input| input.try_split_str_while(pred, CoreOperation::TakeStrWhile)) 86 | } 87 | 88 | /// Skip a length of string input while a predicate check remains true. 89 | /// 90 | /// # Errors 91 | /// 92 | /// Returns [`ExpectedValid`] if the input could never be valid UTF-8 and 93 | /// [`ExpectedLength`] if a UTF-8 code point was cut short. 94 | pub fn skip_str_while(&mut self, pred: F) -> Result<(), E> 95 | where 96 | E: From>, 97 | E: From>, 98 | F: FnMut(char) -> bool, 99 | { 100 | self.try_advance(|input| input.split_str_while(pred, CoreOperation::SkipStrWhile)) 101 | .map(drop) 102 | } 103 | 104 | /// Try skip a length of string input while a predicate check remains 105 | /// successful and true. 106 | /// 107 | /// # Errors 108 | /// 109 | /// Returns any error the provided function does, [`ExpectedValid`] if the 110 | /// the input could never be valid UTF-8 and [`ExpectedLength`] if a UTF-8 111 | /// code point was cut short. 112 | pub fn try_skip_str_while(&mut self, pred: F) -> Result<(), E> 113 | where 114 | E: WithContext<'i>, 115 | E: From>, 116 | E: From>, 117 | F: FnMut(char) -> Result, 118 | { 119 | self.try_advance(|input| input.try_split_str_while(pred, CoreOperation::SkipStrWhile)) 120 | .map(drop) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/reader/mod.rs: -------------------------------------------------------------------------------- 1 | mod bytes; 2 | mod input; 3 | mod peek; 4 | 5 | use core::marker::PhantomData; 6 | 7 | use crate::fmt; 8 | use crate::input::{Bytes, Input, String}; 9 | 10 | pub use self::peek::Peek; 11 | 12 | /// [`Bytes`] specific [`Reader`]. 13 | pub type BytesReader<'i, E> = Reader<'i, Bytes<'i>, E>; 14 | 15 | /// [`String`] specific [`Reader`]. 16 | pub type StringReader<'i, E> = Reader<'i, String<'i>, E>; 17 | 18 | /// Created from and consumes an [`Input`]. 19 | /// 20 | /// You can only create a [`Reader`] from [`Input`] via [`Input::read_all()`], 21 | /// [`Input::read_partial()`] or [`Input::read_infallible()`]. 22 | /// 23 | /// See [`BytesReader`] for [`Bytes`] specific functions and [`StringReader`] 24 | /// for [`String`] specific functions. 25 | /// 26 | /// # Conventions 27 | /// 28 | /// - `take_*` functions _take_ [`Input`] from the reader. 29 | /// - `read_*` functions _read_ a primitive type from the reader and take a 30 | /// description of what it is for. 31 | /// - `peek_*` functions _peek_ either [`Input`] or a primitive type from the 32 | /// reader without effecting its internal state. 33 | /// - `try_*` functions accept a function that can return an error as an 34 | /// argument. 35 | /// 36 | /// # Errors 37 | /// 38 | /// Functions on `Reader` are designed to provide a panic free interface and if 39 | /// applicable, clearly define the type of error that can can be thown. 40 | /// 41 | /// To verify input and optionally return a type from that verification, 42 | /// [`verify()`], [`try_verify()`], [`expect()`], [`try_expect()`] and 43 | /// [`try_external()`] is provided. These functions are the interface for 44 | /// creating errors based off what was expected. 45 | /// 46 | /// [`try_external()`] is provided for reading a custom type that does 47 | /// not support a `&mut Reader<'i, I, E>` interface, for example a type 48 | /// implementing `FromStr`. 49 | /// 50 | /// [`recover()`] and [`recover_if()`] are provided as an escape hatch when you 51 | /// wish to catch an error and try another branch. 52 | /// 53 | /// [`context()`] and [`peek_context()`] are provided to add a [`Context`] to 54 | /// any error thrown inside their scope. This is useful for debugging. 55 | /// 56 | /// # Peeking 57 | /// 58 | /// Peeking should be used to find the correct path to consume. Values read from 59 | /// peeking should not be used for the resulting type. 60 | /// 61 | /// ``` 62 | /// use dangerous::{Input, Invalid}; 63 | /// 64 | /// let input = dangerous::input(b"true"); 65 | /// let result: Result<_, Invalid> = input.read_all(|r| { 66 | /// // We use `peek_read` here because we expect at least one byte. 67 | /// // If we wanted to handle the case when there is no more input left, 68 | /// // for example to provide a default, we would use `peek_read_opt`. 69 | /// // The below allows us to handle a `RetryRequirement` if the 70 | /// // `Reader` is at the end of the input. 71 | /// r.try_expect("boolean", |r| match r.peek_read()? { 72 | /// b't' => r.consume(b"true").map(|()| Some(true)), 73 | /// b'f' => r.consume(b"false").map(|()| Some(false)), 74 | /// _ => Ok(None), 75 | /// }) 76 | /// }); 77 | /// assert!(matches!(result, Ok(true))); 78 | /// ``` 79 | /// 80 | /// [`Input`]: crate::Input 81 | /// [`Context`]: crate::error::Context 82 | /// [`Input::read_all()`]: crate::Input::read_all() 83 | /// [`Input::read_partial()`]: crate::Input::read_partial() 84 | /// [`Input::read_infallible()`]: crate::Input::read_infallible() 85 | /// [`context()`]: Reader::context() 86 | /// [`peek_context()`]: Reader::peek_context() 87 | /// [`verify()`]: Reader::verify() 88 | /// [`try_verify()`]: Reader::try_verify() 89 | /// [`expect()`]: Reader::expect() 90 | /// [`try_expect()`]: Reader::try_expect() 91 | /// [`try_external()`]: Reader::try_external() 92 | /// [`recover()`]: Reader::recover() 93 | /// [`recover_if()`]: Reader::recover_if() 94 | /// [`RetryRequirement`]: crate::error::RetryRequirement 95 | pub struct Reader<'i, I, E> { 96 | input: I, 97 | types: PhantomData<(&'i (), E)>, 98 | } 99 | 100 | impl<'i, I, E> Reader<'i, I, E> 101 | where 102 | I: Input<'i>, 103 | { 104 | /// Create a `Reader` given `Input`. 105 | pub(crate) fn new(input: I) -> Self { 106 | Self { 107 | input, 108 | types: PhantomData, 109 | } 110 | } 111 | 112 | /// Advances the reader's input given an operation. 113 | #[inline(always)] 114 | fn advance(&mut self, f: F) -> O 115 | where 116 | F: FnOnce(I) -> (O, I), 117 | { 118 | let (ok, next) = f(self.input.clone()); 119 | self.input = next; 120 | ok 121 | } 122 | 123 | /// Optionally advances the reader's input given an operation. 124 | #[inline(always)] 125 | fn advance_opt(&mut self, f: F) -> Option 126 | where 127 | F: FnOnce(I) -> Option<(O, I)>, 128 | { 129 | if let Some((ok, next)) = f(self.input.clone()) { 130 | self.input = next; 131 | Some(ok) 132 | } else { 133 | None 134 | } 135 | } 136 | 137 | /// Tries to advance the reader's input given an operation. 138 | #[inline(always)] 139 | fn try_advance(&mut self, f: F) -> Result 140 | where 141 | F: FnOnce(I) -> Result<(O, I), SE>, 142 | { 143 | match f(self.input.clone()) { 144 | Ok((ok, next)) => { 145 | self.input = next; 146 | Ok(ok) 147 | } 148 | Err(err) => Err(err), 149 | } 150 | } 151 | } 152 | 153 | impl<'i, I, E> fmt::Debug for Reader<'i, I, E> 154 | where 155 | I: Input<'i>, 156 | { 157 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 158 | f.debug_struct("Reader") 159 | .field("input", &self.input) 160 | .finish() 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/reader/peek.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use core::ops::Deref; 3 | 4 | use crate::input::Input; 5 | 6 | /// Peek of [`Input`]. 7 | /// 8 | /// Below is an example of what this structure prevents: 9 | /// 10 | /// ```compile_fail 11 | /// use dangerous::{BytesReader, Error}; 12 | /// 13 | /// fn parse<'i, E>(r: &mut BytesReader<'i, E>) -> Result<&'i [u8], E> 14 | /// where 15 | /// E: Error<'i> 16 | /// { 17 | /// let peeked = r.peek(2)?; 18 | /// Ok(peeked.as_dangerous()) 19 | /// } 20 | /// ``` 21 | pub struct Peek<'p, I> { 22 | input: I, 23 | lifetime: PhantomData<&'p ()>, 24 | } 25 | 26 | impl<'p, I> Peek<'p, I> { 27 | #[inline(always)] 28 | pub(super) fn new(input: I) -> Self { 29 | Self { 30 | input, 31 | lifetime: PhantomData, 32 | } 33 | } 34 | 35 | /// An escape hatch to persist the input beyond the peek lifetime `'p`, 36 | /// instead falling back to the lifetime associated with `I`. 37 | #[inline(always)] 38 | pub fn persist(self) -> I { 39 | self.input 40 | } 41 | } 42 | 43 | impl<'p, I> AsRef for Peek<'p, I> 44 | where 45 | I: Input<'p>, 46 | { 47 | #[inline(always)] 48 | fn as_ref(&self) -> &I { 49 | &self.input 50 | } 51 | } 52 | 53 | impl<'p, I> Deref for Peek<'p, I> 54 | where 55 | I: Input<'p>, 56 | { 57 | type Target = I; 58 | 59 | #[inline(always)] 60 | fn deref(&self) -> &Self::Target { 61 | self.as_ref() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/support/core.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{ExternalContext, WithContext}; 2 | 3 | impl<'i> crate::error::External<'i> for () {} 4 | 5 | impl<'i> crate::error::External<'i> for core::num::ParseFloatError { 6 | fn push_backtrace(self, error: E) -> E 7 | where 8 | E: WithContext<'i>, 9 | { 10 | error.with_context(ExternalContext { 11 | operation: Some("parse from string"), 12 | expected: Some("float"), 13 | }) 14 | } 15 | } 16 | 17 | impl<'i> crate::error::External<'i> for core::num::ParseIntError { 18 | fn push_backtrace(self, error: E) -> E 19 | where 20 | E: WithContext<'i>, 21 | { 22 | error.with_context(ExternalContext { 23 | operation: Some("parse from string"), 24 | expected: Some("integer"), 25 | }) 26 | } 27 | } 28 | 29 | impl<'i> crate::error::External<'i> for core::str::ParseBoolError { 30 | fn push_backtrace(self, error: E) -> E 31 | where 32 | E: WithContext<'i>, 33 | { 34 | error.with_context(ExternalContext { 35 | operation: Some("parse from string"), 36 | expected: Some("boolean"), 37 | }) 38 | } 39 | } 40 | 41 | impl<'i> crate::error::External<'i> for core::char::ParseCharError { 42 | fn push_backtrace(self, error: E) -> E 43 | where 44 | E: WithContext<'i>, 45 | { 46 | error.with_context(ExternalContext { 47 | operation: Some("parse from string"), 48 | expected: Some("char"), 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/support/mod.rs: -------------------------------------------------------------------------------- 1 | mod core; 2 | #[cfg(feature = "nom")] 3 | mod nom; 4 | #[cfg(feature = "std")] 5 | mod std; 6 | #[cfg(feature = "zc")] 7 | mod zc; 8 | -------------------------------------------------------------------------------- /src/support/nom.rs: -------------------------------------------------------------------------------- 1 | use core::any::Any; 2 | 3 | use nom::error::{Error, ErrorKind}; 4 | #[cfg(feature = "alloc")] 5 | use nom::error::{VerboseError, VerboseErrorKind}; 6 | use nom::{Err, Needed}; 7 | 8 | use crate::error::{Context, External, Operation, RetryRequirement, WithContext}; 9 | use crate::fmt; 10 | use crate::input::Span; 11 | 12 | pub trait AsBytes<'i> { 13 | fn as_bytes(&self) -> &'i [u8]; 14 | } 15 | 16 | impl<'i> AsBytes<'i> for &'i [u8] { 17 | fn as_bytes(&self) -> &'i [u8] { 18 | self 19 | } 20 | } 21 | 22 | impl<'i> AsBytes<'i> for &'i str { 23 | fn as_bytes(&self) -> &'i [u8] { 24 | str::as_bytes(self) 25 | } 26 | } 27 | 28 | #[cfg_attr(docsrs, doc(cfg(feature = "nom")))] 29 | impl<'i, Ex> External<'i> for Err 30 | where 31 | Ex: External<'i>, 32 | { 33 | fn span(&self) -> Option { 34 | match self { 35 | Err::Error(err) | Err::Failure(err) => err.span(), 36 | Err::Incomplete(_) => None, 37 | } 38 | } 39 | 40 | fn retry_requirement(&self) -> Option { 41 | match self { 42 | Err::Error(_) | Err::Failure(_) => None, 43 | Err::Incomplete(Needed::Unknown) => RetryRequirement::new(1), 44 | Err::Incomplete(Needed::Size(s)) => RetryRequirement::new(s.get()), 45 | } 46 | } 47 | 48 | fn push_backtrace(self, error: E) -> E 49 | where 50 | E: WithContext<'i>, 51 | { 52 | match self { 53 | Err::Error(err) | Err::Failure(err) => err.push_backtrace(error), 54 | Err::Incomplete(_) => error, 55 | } 56 | } 57 | } 58 | 59 | /////////////////////////////////////////////////////////////////////////////// 60 | // Basic 61 | 62 | struct NomContext { 63 | kind: ErrorKind, 64 | span: Span, 65 | } 66 | 67 | impl Context for NomContext { 68 | fn span(&self) -> Option { 69 | Some(self.span) 70 | } 71 | 72 | fn operation(&self) -> &dyn Operation { 73 | &self.kind 74 | } 75 | } 76 | 77 | #[cfg_attr(docsrs, doc(cfg(feature = "nom")))] 78 | impl Operation for ErrorKind { 79 | fn description(&self, w: &mut dyn fmt::Write) -> fmt::Result { 80 | w.write_str(self.description()) 81 | } 82 | 83 | fn as_any(&self) -> &dyn Any { 84 | self 85 | } 86 | } 87 | 88 | #[cfg_attr(docsrs, doc(cfg(feature = "nom")))] 89 | impl<'i, I> External<'i> for Error 90 | where 91 | I: AsBytes<'i>, 92 | { 93 | fn span(&self) -> Option { 94 | Some(self.input.as_bytes().into()) 95 | } 96 | 97 | fn push_backtrace(self, error: E) -> E 98 | where 99 | E: WithContext<'i>, 100 | { 101 | error.with_context(NomContext { 102 | span: self.input.as_bytes().into(), 103 | kind: self.code, 104 | }) 105 | } 106 | } 107 | 108 | /////////////////////////////////////////////////////////////////////////////// 109 | // Verbose 110 | 111 | #[cfg(feature = "alloc")] 112 | struct NomVerboseContext { 113 | kind: VerboseErrorKind, 114 | span: Span, 115 | } 116 | 117 | #[cfg(feature = "alloc")] 118 | impl Context for NomVerboseContext { 119 | fn span(&self) -> Option { 120 | Some(self.span) 121 | } 122 | 123 | fn operation(&self) -> &dyn Operation { 124 | &self.kind 125 | } 126 | 127 | fn has_expected(&self) -> bool { 128 | match self.kind { 129 | VerboseErrorKind::Char(_) | VerboseErrorKind::Context(_) => true, 130 | VerboseErrorKind::Nom(_) => false, 131 | } 132 | } 133 | 134 | fn expected(&self, w: &mut dyn fmt::Write) -> fmt::Result { 135 | match self.kind { 136 | VerboseErrorKind::Char(c) => { 137 | w.write_str("character '")?; 138 | w.write_char(c)?; 139 | w.write_char('\'') 140 | } 141 | VerboseErrorKind::Context(c) => w.write_str(c), 142 | VerboseErrorKind::Nom(_) => Err(fmt::Error), 143 | } 144 | } 145 | } 146 | 147 | #[cfg(feature = "alloc")] 148 | #[cfg_attr(docsrs, doc(cfg(all(feature = "nom", feature = "alloc"))))] 149 | impl Operation for VerboseErrorKind { 150 | fn description(&self, w: &mut dyn fmt::Write) -> fmt::Result { 151 | match self { 152 | VerboseErrorKind::Context(_) => w.write_str(""), 153 | VerboseErrorKind::Char(_) => w.write_str("consume input"), 154 | VerboseErrorKind::Nom(kind) => Operation::description(kind, w), 155 | } 156 | } 157 | 158 | fn as_any(&self) -> &dyn Any { 159 | self 160 | } 161 | } 162 | 163 | #[cfg(feature = "alloc")] 164 | #[cfg_attr(docsrs, doc(cfg(all(feature = "nom", feature = "alloc"))))] 165 | impl<'i, I> External<'i> for VerboseError 166 | where 167 | I: AsBytes<'i>, 168 | { 169 | fn span(&self) -> Option { 170 | self.errors.get(0).map(|(input, _)| input.as_bytes().into()) 171 | } 172 | 173 | fn push_backtrace(self, mut error: E) -> E 174 | where 175 | E: WithContext<'i>, 176 | { 177 | for (input, kind) in self.errors { 178 | error = error.with_context(NomVerboseContext { 179 | span: input.as_bytes().into(), 180 | kind, 181 | }); 182 | } 183 | error 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/support/std.rs: -------------------------------------------------------------------------------- 1 | use crate::error::WithContext; 2 | 3 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 4 | impl<'i> crate::error::External<'i> for std::net::AddrParseError { 5 | fn push_backtrace(self, error: E) -> E 6 | where 7 | E: WithContext<'i>, 8 | { 9 | error.with_context("IP address") 10 | } 11 | } 12 | 13 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 14 | impl std::error::Error for crate::error::Invalid {} 15 | 16 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 17 | impl std::error::Error for crate::error::Fatal {} 18 | 19 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 20 | impl<'i, S> std::error::Error for crate::error::Expected<'i, S> where S: crate::error::Backtrace {} 21 | -------------------------------------------------------------------------------- /src/support/zc.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 2 | unsafe impl<'o> zc::Dependant<'o> for crate::error::RootBacktrace { 3 | type Static = crate::error::RootBacktrace; 4 | } 5 | 6 | #[cfg(feature = "alloc")] 7 | #[cfg_attr(docsrs, doc(cfg(all(feature = "zc", feature = "alloc"))))] 8 | unsafe impl<'o> zc::Dependant<'o> for crate::error::FullBacktrace { 9 | type Static = crate::error::FullBacktrace; 10 | } 11 | 12 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 13 | unsafe impl<'o> zc::Dependant<'o> for crate::error::CoreContext { 14 | type Static = crate::error::CoreContext; 15 | } 16 | 17 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 18 | unsafe impl<'o> zc::Dependant<'o> for crate::error::Fatal { 19 | type Static = crate::error::Fatal; 20 | } 21 | 22 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 23 | unsafe impl<'o> zc::Dependant<'o> for crate::error::Invalid { 24 | type Static = crate::error::Invalid; 25 | } 26 | 27 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 28 | unsafe impl<'o> zc::Dependant<'o> for crate::error::RetryRequirement { 29 | type Static = crate::error::RetryRequirement; 30 | } 31 | 32 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 33 | unsafe impl<'o, S> zc::Dependant<'o> for crate::error::Expected<'o, S> 34 | where 35 | S: zc::Dependant<'o>, 36 | { 37 | type Static = crate::error::Expected<'static, S::Static>; 38 | } 39 | 40 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 41 | unsafe impl<'o> zc::Dependant<'o> for crate::error::ExpectedLength<'o> { 42 | type Static = crate::error::ExpectedLength<'static>; 43 | } 44 | 45 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 46 | unsafe impl<'o> zc::Dependant<'o> for crate::error::ExpectedValid<'o> { 47 | type Static = crate::error::ExpectedValid<'static>; 48 | } 49 | 50 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 51 | unsafe impl<'o> zc::Dependant<'o> for crate::error::ExpectedValue<'o> { 52 | type Static = crate::error::ExpectedValue<'static>; 53 | } 54 | 55 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 56 | unsafe impl<'o> zc::Dependant<'o> for crate::input::Span { 57 | type Static = crate::input::Span; 58 | } 59 | 60 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 61 | unsafe impl<'o> zc::Dependant<'o> for crate::input::Bytes<'o> { 62 | type Static = crate::input::Bytes<'static>; 63 | } 64 | 65 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 66 | unsafe impl<'o> zc::Dependant<'o> for crate::input::String<'o> { 67 | type Static = crate::input::String<'static>; 68 | } 69 | 70 | #[cfg_attr(docsrs, doc(cfg(feature = "zc")))] 71 | unsafe impl<'o> zc::Dependant<'o> for crate::input::MaybeString<'o> { 72 | type Static = crate::input::MaybeString<'static>; 73 | } 74 | -------------------------------------------------------------------------------- /src/util/fast.rs: -------------------------------------------------------------------------------- 1 | use crate::util::utf8::CharBytes; 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // u8 5 | 6 | #[cfg(feature = "bytecount")] 7 | #[inline(always)] 8 | pub(crate) fn count_u8(needle: u8, haystack: &[u8]) -> usize { 9 | bytecount::count(haystack, needle) 10 | } 11 | 12 | #[cfg(not(feature = "bytecount"))] 13 | pub(crate) fn count_u8(needle: u8, haystack: &[u8]) -> usize { 14 | haystack.iter().copied().filter(|b| *b == needle).count() 15 | } 16 | 17 | #[cfg(feature = "memchr")] 18 | #[inline(always)] 19 | pub(crate) fn find_u8_match(needle: u8, haystack: &[u8]) -> Option { 20 | memchr::memchr(needle, haystack) 21 | } 22 | 23 | #[cfg(not(feature = "memchr"))] 24 | pub(crate) fn find_u8_match(needle: u8, haystack: &[u8]) -> Option { 25 | haystack.iter().copied().position(|b| b == needle) 26 | } 27 | 28 | // FIXME: impl SIMD variant 29 | pub(crate) fn find_u8_reject(needle: u8, haystack: &[u8]) -> Option { 30 | haystack.iter().copied().position(|b| b != needle) 31 | } 32 | 33 | /////////////////////////////////////////////////////////////////////////////// 34 | // char 35 | 36 | #[cfg(feature = "bytecount")] 37 | #[inline(always)] 38 | pub(crate) fn num_chars(s: &str) -> usize { 39 | bytecount::num_chars(s.as_bytes()) 40 | } 41 | 42 | #[cfg(not(feature = "bytecount"))] 43 | #[inline(always)] 44 | pub(crate) fn num_chars(s: &str) -> usize { 45 | s.chars().count() 46 | } 47 | 48 | #[inline(always)] 49 | pub(crate) fn find_char_match(needle: char, haystack: &[u8]) -> Option { 50 | let needle = CharBytes::from(needle); 51 | find_slice_match(needle.as_bytes(), haystack) 52 | } 53 | 54 | #[inline(always)] 55 | pub(crate) fn find_char_reject(needle: char, haystack: &[u8]) -> Option { 56 | let needle = CharBytes::from(needle); 57 | find_slice_reject(needle.as_bytes(), haystack) 58 | } 59 | 60 | /////////////////////////////////////////////////////////////////////////////// 61 | // slice 62 | 63 | #[cfg(feature = "memchr")] 64 | pub(crate) fn find_slice_match(needle: &[u8], haystack: &[u8]) -> Option { 65 | if haystack.is_empty() || needle.is_empty() { 66 | return None; 67 | } 68 | match needle.len() { 69 | 1 => memchr::memchr(needle[0], haystack), 70 | _ => memchr::memmem::find(haystack, needle), 71 | } 72 | } 73 | 74 | #[cfg(not(feature = "memchr"))] 75 | pub(crate) fn find_slice_match(needle: &[u8], haystack: &[u8]) -> Option { 76 | if haystack.is_empty() || needle.is_empty() { 77 | return None; 78 | } 79 | haystack 80 | .windows(needle.len()) 81 | .enumerate() 82 | .find_map(|(i, w)| if w == needle { Some(i) } else { None }) 83 | } 84 | 85 | // FIXME: impl SIMD variant 86 | pub(crate) fn find_slice_reject(needle: &[u8], haystack: &[u8]) -> Option { 87 | if haystack.is_empty() || needle.is_empty() || haystack.len() < needle.len() { 88 | return Some(0); 89 | } 90 | haystack 91 | .chunks(needle.len()) 92 | .enumerate() 93 | .find_map(|(i, w)| { 94 | if w == needle { 95 | None 96 | } else { 97 | Some(i * needle.len()) 98 | } 99 | }) 100 | } 101 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod fast; 2 | pub(crate) mod slice; 3 | pub(crate) mod utf8; 4 | -------------------------------------------------------------------------------- /src/util/slice.rs: -------------------------------------------------------------------------------- 1 | /// Returns an end slice of the slice provided (always empty). 2 | #[inline(always)] 3 | pub(crate) fn end(slice: &[T]) -> &[T] { 4 | // SAFETY: This is always valid as we a getting a new slice from its own 5 | // length. 6 | unsafe { slice.get_unchecked(slice.len()..) } 7 | } 8 | 9 | /// Splits a slice at `mid`. 10 | /// 11 | /// Returns `Some` if `0 <= mid <= slice.len()` and `None` otherwise. 12 | #[inline(always)] 13 | pub(crate) fn split_at_opt(slice: &[T], mid: usize) -> Option<(&[T], &[T])> { 14 | if mid > slice.len() { 15 | None 16 | } else { 17 | // SAFETY: We have checked that 0 <= mid <= slice.len() 18 | unsafe { Some(split_at_unchecked(slice, mid)) } 19 | } 20 | } 21 | 22 | /// Returns the first item in a slice without bounds checking. 23 | #[inline(always)] 24 | pub(crate) unsafe fn first_unchecked(slice: &[T]) -> T { 25 | debug_assert!(!slice.is_empty()); 26 | *slice.get_unchecked(0) 27 | } 28 | 29 | /// Splits a slice at `mid` without bounds checking. 30 | /// 31 | /// # Safety 32 | /// 33 | /// Caller has to check that `0 <= mid <= slice.len()` 34 | #[inline(always)] 35 | pub(crate) unsafe fn split_at_unchecked(slice: &[T], mid: usize) -> (&[T], &[T]) { 36 | debug_assert!(mid <= slice.len()); 37 | (slice.get_unchecked(..mid), slice.get_unchecked(mid..)) 38 | } 39 | 40 | /// Splits a str slice at `mid` without bounds checking. 41 | /// 42 | /// # Safety 43 | /// 44 | /// Caller has to check that `0 <= mid <= slice.len()` and that `mid` is a valid 45 | /// char boundary. 46 | #[inline(always)] 47 | pub(crate) unsafe fn split_str_at_unchecked(slice: &str, mid: usize) -> (&str, &str) { 48 | debug_assert!(mid <= slice.len()); 49 | debug_assert!(slice.is_char_boundary(mid)); 50 | (slice.get_unchecked(..mid), slice.get_unchecked(mid..)) 51 | } 52 | 53 | /// Returns the slice as a reference to an array. 54 | /// 55 | /// # Safety 56 | /// 57 | /// Caller has to check that `slice.len() == N`. 58 | #[inline(always)] 59 | pub(crate) unsafe fn slice_to_array_unchecked(slice: &[T]) -> &[T; N] { 60 | debug_assert!(slice.len() == N); 61 | // Cast the slice pointer to an array pointer and reborrow. 62 | &*slice.as_ptr().cast::<[T; N]>() 63 | } 64 | -------------------------------------------------------------------------------- /src/util/utf8.rs: -------------------------------------------------------------------------------- 1 | use core::str; 2 | 3 | // Source: https://github.com/rust-lang/rust/blob/master/library/core/src/str/validations.rs 4 | // https://tools.ietf.org/html/rfc3629 5 | #[rustfmt::skip] 6 | static UTF8_CHAR_LENGTH: [u8; 256] = [ 7 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x0F 8 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x1F 9 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x2F 10 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x3F 11 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x4F 12 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x5F 13 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x6F 14 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x7F 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7F 16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x9F 17 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xAF 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xBF 19 | 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xCF 20 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xDF 21 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xEF 22 | 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xFF 23 | ]; 24 | 25 | /// Mask of the value bits of a continuation byte. 26 | const CONT_MASK: u8 = 0b0011_1111; 27 | /// Value of the tag bits (tag mask is !`CONT_MASK`) of a continuation byte. 28 | const TAG_CONT_U8: u8 = 0b1000_0000; 29 | 30 | /// Checks whether the byte is a UTF-8 continuation byte (i.e., starts with the 31 | /// bits `10`). 32 | #[inline] 33 | fn is_cont_byte(byte: u8) -> bool { 34 | (byte & !CONT_MASK) == TAG_CONT_U8 35 | } 36 | 37 | /// Returns a str slice from a byte slice without validation. 38 | #[inline] 39 | pub(crate) unsafe fn from_unchecked(bytes: &[u8]) -> &str { 40 | debug_assert!(str::from_utf8(bytes).is_ok()); 41 | str::from_utf8_unchecked(bytes) 42 | } 43 | 44 | /// Given a first byte, determines how many bytes are in this UTF-8 character. 45 | #[inline] 46 | pub(crate) fn char_len(b: u8) -> usize { 47 | UTF8_CHAR_LENGTH[b as usize] as usize 48 | } 49 | 50 | /// Returns the first UTF-8 codepoint if valid within bytes. 51 | #[inline(always)] 52 | fn first_codepoint(bytes: &[u8]) -> Result { 53 | if let Some(first_byte) = bytes.first() { 54 | let len = char_len(*first_byte); 55 | if bytes.len() >= len { 56 | return parse_char(&bytes[..len]); 57 | } 58 | } 59 | Err(InvalidChar { error_len: None }) 60 | } 61 | 62 | /// Returns the last UTF-8 codepoint if valid within bytes. 63 | #[inline(always)] 64 | fn last_codepoint(bytes: &[u8]) -> Result { 65 | if bytes.is_empty() { 66 | return Err(InvalidChar { error_len: None }); 67 | } 68 | for (i, byte) in (1..=4).zip(bytes.iter().rev().copied()) { 69 | if !is_cont_byte(byte) && char_len(byte) == i { 70 | let last_index = bytes.len() - i; 71 | return parse_char(&bytes[last_index..]); 72 | } 73 | } 74 | Err(InvalidChar { error_len: None }) 75 | } 76 | 77 | /// Parses a single char from bytes. 78 | #[inline(always)] 79 | fn parse_char(bytes: &[u8]) -> Result { 80 | match str::from_utf8(bytes) { 81 | Ok(s) => match s.chars().next() { 82 | Some(c) => Ok(c), 83 | None => Err(InvalidChar { error_len: None }), 84 | }, 85 | Err(e) => Err(InvalidChar { 86 | error_len: e.error_len(), 87 | }), 88 | } 89 | } 90 | 91 | /////////////////////////////////////////////////////////////////////////////// 92 | // CharBytes 93 | 94 | #[derive(Copy, Clone)] 95 | pub(crate) struct CharBytes([u8; 4]); 96 | 97 | impl CharBytes { 98 | #[inline(always)] 99 | pub fn as_bytes(&self) -> &[u8] { 100 | &self.0[..char_len(self.0[0])] 101 | } 102 | } 103 | 104 | impl From for CharBytes { 105 | #[inline(always)] 106 | fn from(c: char) -> Self { 107 | let mut bytes = [0_u8; 4]; 108 | c.encode_utf8(&mut bytes); 109 | Self(bytes) 110 | } 111 | } 112 | 113 | /////////////////////////////////////////////////////////////////////////////// 114 | // InvalidChar 115 | 116 | #[cfg_attr(test, derive(Debug))] 117 | pub(crate) struct InvalidChar { 118 | error_len: Option, 119 | } 120 | 121 | impl InvalidChar { 122 | pub(crate) fn error_len(&self) -> Option { 123 | self.error_len 124 | } 125 | } 126 | 127 | /////////////////////////////////////////////////////////////////////////////// 128 | // CharIter 129 | 130 | /// Char iterator over unvalidated bytes. 131 | pub(crate) struct CharIter<'i> { 132 | forward: usize, 133 | backward: usize, 134 | bytes: &'i [u8], 135 | } 136 | 137 | impl<'i> CharIter<'i> { 138 | /// Creates a new char iterator from a byte slice. 139 | pub(crate) fn new(bytes: &'i [u8]) -> Self { 140 | Self { 141 | bytes, 142 | forward: 0, 143 | backward: bytes.len(), 144 | } 145 | } 146 | 147 | /// Returns the remaining slice. 148 | pub(crate) fn as_slice(&self) -> &'i [u8] { 149 | &self.bytes[self.forward..self.backward] 150 | } 151 | 152 | /// Returns the `str` consumed from the front. 153 | pub(crate) fn as_forward(&self) -> &'i str { 154 | // SAFETY: bytes before this forward increasing index is valid UTF-8. 155 | unsafe { str::from_utf8_unchecked(&self.bytes[..self.forward]) } 156 | } 157 | 158 | fn is_done(&self) -> bool { 159 | self.forward == self.backward 160 | } 161 | 162 | fn head(&self) -> &'i [u8] { 163 | &self.bytes[self.forward..] 164 | } 165 | 166 | fn tail(&self) -> &'i [u8] { 167 | &self.bytes[..self.backward] 168 | } 169 | } 170 | 171 | impl<'i> Iterator for CharIter<'i> { 172 | type Item = Result; 173 | 174 | #[inline] 175 | fn next(&mut self) -> Option { 176 | if self.is_done() { 177 | None 178 | } else { 179 | let result = match first_codepoint(self.head()) { 180 | Ok(c) => { 181 | let forward = self.forward.saturating_add(c.len_utf8()); 182 | // If the parsing of the character goes over the reader's 183 | // backward bound raise an error. 184 | if forward > self.backward { 185 | Err(InvalidChar { error_len: None }) 186 | } else { 187 | self.forward = forward; 188 | Ok(c) 189 | } 190 | } 191 | Err(err) => Err(err), 192 | }; 193 | Some(result) 194 | } 195 | } 196 | } 197 | 198 | impl<'i> DoubleEndedIterator for CharIter<'i> { 199 | #[inline] 200 | fn next_back(&mut self) -> Option { 201 | if self.is_done() { 202 | None 203 | } else { 204 | let result = match last_codepoint(self.tail()) { 205 | Ok(c) => { 206 | let backward = self.backward.saturating_sub(c.len_utf8()); 207 | // If the parsing of the character goes over the reader's 208 | // forward bound raise an error. 209 | if backward < self.forward { 210 | Err(InvalidChar { error_len: None }) 211 | } else { 212 | self.backward = backward; 213 | Ok(c) 214 | } 215 | } 216 | Err(err) => Err(err), 217 | }; 218 | Some(result) 219 | } 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod tests { 225 | use super::*; 226 | 227 | #[test] 228 | fn test_char_iter() { 229 | let mut char_iter = CharIter::new("\u{10348}a\u{10347}".as_bytes()); 230 | assert_eq!(char_iter.next().unwrap().unwrap(), '\u{10348}'); 231 | assert_eq!(char_iter.next_back().unwrap().unwrap(), '\u{10347}'); 232 | assert_eq!(char_iter.next().unwrap().unwrap(), 'a'); 233 | } 234 | 235 | #[test] 236 | fn test_last_codepoint() { 237 | assert!(last_codepoint(b"").is_err()); 238 | assert!(last_codepoint(b"\xFF").is_err()); 239 | assert!(last_codepoint(b"a\xFF").is_err()); 240 | assert_eq!(last_codepoint(b"a").unwrap(), 'a'); 241 | assert_eq!(last_codepoint(b"ab").unwrap(), 'b'); 242 | assert_eq!( 243 | last_codepoint("a\u{10348}".as_bytes()).unwrap(), 244 | '\u{10348}' 245 | ); 246 | assert_eq!(last_codepoint("\u{10348}".as_bytes()).unwrap(), '\u{10348}'); 247 | } 248 | 249 | #[test] 250 | fn test_first_codepoint() { 251 | assert!(first_codepoint(b"").is_err()); 252 | assert!(first_codepoint(b"\xFF").is_err()); 253 | assert!(first_codepoint(b"\xFFa").is_err()); 254 | assert_eq!(first_codepoint(b"a").unwrap(), 'a'); 255 | assert_eq!(first_codepoint(b"ab").unwrap(), 'a'); 256 | assert_eq!( 257 | first_codepoint("\u{10348}a".as_bytes()).unwrap(), 258 | '\u{10348}' 259 | ); 260 | assert_eq!( 261 | first_codepoint("\u{10348}".as_bytes()).unwrap(), 262 | '\u{10348}' 263 | ); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /tests/common.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_macros)] 2 | 3 | pub use dangerous::{error::*, *}; 4 | pub use indoc::indoc; 5 | pub use paste::paste; 6 | 7 | macro_rules! assert_str_eq { 8 | ($actual:expr, $expected:expr) => {{ 9 | let actual = &$actual; 10 | let expected = &$expected; 11 | if actual != expected { 12 | panic!( 13 | indoc! {" 14 | string not expected value: 15 | ============================EXPECTED========================== 16 | {} 17 | =============================ACTUAL=========================== 18 | {} 19 | ==============================DIFF============================ 20 | {} 21 | ============================================================== 22 | "}, 23 | expected, 24 | actual, 25 | colored_diff::PrettyDifference { expected, actual }, 26 | ); 27 | } 28 | }}; 29 | } 30 | 31 | macro_rules! input { 32 | ($input:expr) => { 33 | dangerous::input(&$input) 34 | }; 35 | } 36 | 37 | macro_rules! read_all { 38 | ($input:expr, $read_fn:expr) => { 39 | input!($input).read_all::<_, _, dangerous::Expected>($read_fn) 40 | }; 41 | } 42 | 43 | macro_rules! read_partial { 44 | ($input:expr, $read_fn:expr) => { 45 | input!($input).read_partial::<_, _, dangerous::Expected>($read_fn) 46 | }; 47 | } 48 | 49 | macro_rules! read_infallible { 50 | ($input:expr, $read_fn:expr) => { 51 | input!($input).read_infallible($read_fn) 52 | }; 53 | } 54 | 55 | macro_rules! read_all_ok { 56 | ($input:expr, $read_fn:expr) => { 57 | read_all!($input, $read_fn).unwrap() 58 | }; 59 | } 60 | 61 | macro_rules! read_all_err { 62 | ($input:expr, $read_fn:expr) => { 63 | read_all!($input, $read_fn).unwrap_err() 64 | }; 65 | } 66 | 67 | macro_rules! read_partial_ok { 68 | ($input:expr, $read_fn:expr) => { 69 | read_partial!($input, $read_fn).unwrap() 70 | }; 71 | } 72 | 73 | macro_rules! read_partial_err { 74 | ($input:expr, $read_fn:expr) => { 75 | read_partial!($input, $read_fn).unwrap_err() 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /tests/test_display.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod common; 3 | 4 | use common::*; 5 | use std::fmt; 6 | 7 | macro_rules! assert_input_display_eq { 8 | ($input:expr, $format:expr, $expected:expr) => { 9 | assert_eq!( 10 | format!($format, input!(<&[u8]>::from($input.as_ref()))), 11 | $expected 12 | ); 13 | }; 14 | } 15 | 16 | #[test] 17 | fn test_valid_utf8() { 18 | assert_input_display_eq!("hello ♥", "{}", "[68 65 6c 6c 6f 20 e2 99 a5]"); 19 | assert_input_display_eq!("hello ♥", "{:#}", r#""hello ♥""#); 20 | assert_input_display_eq!("hello ♥", "{:.18}", "[68 65 .. 99 a5]"); 21 | assert_input_display_eq!("hello ♥", "{:#.18}", r#""hello ♥""#); 22 | assert_input_display_eq!("oh, hello world! ♥", "{:#.16}", r#""oh, " .. "d! ♥""#); 23 | } 24 | 25 | #[test] 26 | fn test_high_range_utf8() { 27 | assert_input_display_eq!("♥♥♥", "{}", "[e2 99 a5 e2 99 a5 e2 99 a5]"); 28 | assert_input_display_eq!("♥♥♥", "{:#}", r#""♥♥♥""#); 29 | assert_input_display_eq!("♥♥♥", "{:.16}", "[e2 99 .. 99 a5]"); 30 | assert_input_display_eq!("♥♥♥", "{:#.16}", r#""♥♥♥""#); 31 | assert_input_display_eq!("♥♥♥", "{:.19}", "[e2 99 a5 .. 99 a5]"); 32 | assert_input_display_eq!("♥♥♥", "{:#.19}", r#""♥♥♥""#); 33 | } 34 | 35 | #[test] 36 | fn test_invalid_utf8() { 37 | assert_input_display_eq!(&[0xFF, 0xFF, b'a'], "{}", "[ff ff 61]"); 38 | assert_input_display_eq!(&[0xFF, 0xFF, b'a'], "{:#}", "[ff ff 'a']"); 39 | assert_input_display_eq!( 40 | &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, b'a'], 41 | "{:.18}", 42 | "[ff ff .. ff 61]" 43 | ); 44 | assert_input_display_eq!( 45 | &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, b'a'], 46 | "{:#.18}", 47 | "[ff ff .. ff 'a']" 48 | ); 49 | } 50 | 51 | #[test] 52 | fn test_invalid_span_does_nothing() { 53 | let display = input!(b"hello").display().span("world".into(), 16); 54 | assert_eq!(display.to_string(), "[68 65 6c 6c 6f]"); 55 | assert_eq!(display.underline().to_string(), " "); 56 | let display = input!(b"hello").display().span("".into(), 16); 57 | assert_eq!(display.to_string(), "[68 65 6c 6c 6f]"); 58 | assert_eq!(display.underline().to_string(), " "); 59 | } 60 | 61 | #[test] 62 | fn test_format_with_mut_ref_write() { 63 | use dangerous::display::{DisplayBase, Write}; 64 | 65 | struct Helper; 66 | 67 | impl DisplayBase for Helper { 68 | fn fmt(&self, w: &mut dyn Write) -> fmt::Result { 69 | Write::write_str(w, "a")?; 70 | Write::write_char(w, ',')?; 71 | Write::write_usize(w, 1)?; 72 | Write::write_char(w, ',')?; 73 | Write::write_hex(w, 1)?; 74 | Write::write_char(w, ',')?; 75 | Write::write_hex(w, 128)?; 76 | Write::write_char(w, ',')?; 77 | Write::write_hex(w, 129) 78 | } 79 | } 80 | 81 | impl fmt::Display for Helper { 82 | fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result { 83 | DisplayBase::fmt(&&self, &mut f) 84 | } 85 | } 86 | 87 | assert_eq!(Helper.to_string(), "a,1,01,80,81"); 88 | } 89 | 90 | #[test] 91 | fn test_preferred_format_debug() { 92 | use dangerous::display::PreferredFormat; 93 | assert_eq!(format!("{:?}", PreferredFormat::Str), "Str"); 94 | assert_eq!(format!("{:?}", PreferredFormat::StrCjk), "StrCjk"); 95 | assert_eq!(format!("{:?}", PreferredFormat::Bytes), "Bytes"); 96 | assert_eq!(format!("{:?}", PreferredFormat::BytesAscii), "BytesAscii"); 97 | } 98 | -------------------------------------------------------------------------------- /tests/test_input.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unit_cmp)] 2 | 3 | #[macro_use] 4 | mod common; 5 | 6 | use common::*; 7 | 8 | #[test] 9 | fn test_as_dangerous() { 10 | assert_eq!(input!(b"").as_dangerous(), b""); 11 | assert_eq!(input!(b"hello").as_dangerous(), b"hello"); 12 | } 13 | 14 | #[test] 15 | fn test_into_non_empty() { 16 | // Valid 17 | assert_eq!( 18 | input!(b"hello").into_non_empty::().unwrap(), 19 | b"hello"[..], 20 | ); 21 | // Invalid 22 | let _ = input!(b"").into_non_empty::().unwrap_err(); 23 | } 24 | 25 | #[test] 26 | fn test_to_dangerous_str() { 27 | // Valid 28 | assert_eq!(input!(b"").to_dangerous_str::().unwrap(), ""); 29 | assert_eq!( 30 | input!(b"hello").to_dangerous_str::().unwrap(), 31 | "hello" 32 | ); 33 | // Invalid 34 | let _ = input!(b"\xff").to_dangerous_str::().unwrap_err(); 35 | } 36 | 37 | #[test] 38 | fn test_to_dangerous_str_expected_length() { 39 | // Length 1 40 | input!(&[0b0111_1111]) 41 | .to_dangerous_str::() 42 | .unwrap(); 43 | // Length 2 44 | let err = input!(&[0b1101_1111]) 45 | .to_dangerous_str::() 46 | .unwrap_err(); 47 | assert_eq!(err.to_retry_requirement(), RetryRequirement::new(1)); 48 | // Length 3 49 | let err = input!(&[0b1110_1111]) 50 | .to_dangerous_str::() 51 | .unwrap_err(); 52 | assert_eq!(err.to_retry_requirement(), RetryRequirement::new(2)); 53 | // Invalid 54 | let err = input!(&[0b1111_0111]) 55 | .to_dangerous_str::() 56 | .unwrap_err(); 57 | assert_eq!(err.to_retry_requirement(), None); 58 | } 59 | 60 | #[test] 61 | fn test_read_all() { 62 | // Valid 63 | assert_eq!(read_all_ok!(b"hello", |r| { r.consume(b"hello") }), ()); 64 | assert_eq!(read_all_ok!(b"hello", |r| { r.take(5) }), input!(b"hello")); 65 | // Invalid 66 | assert_eq!( 67 | read_all_err!(b"hello", |r| { r.consume(b"hell") }).to_retry_requirement(), 68 | None 69 | ); 70 | assert_eq!( 71 | read_all_err!(b"hello", |r| { r.take(4) }).to_retry_requirement(), 72 | None 73 | ); 74 | assert_eq!( 75 | read_all_err!(b"hello", |r| { r.take(10) }).to_retry_requirement(), 76 | RetryRequirement::new(5) 77 | ); 78 | } 79 | 80 | #[test] 81 | fn test_read_partial() { 82 | // Valid 83 | assert_eq!( 84 | read_partial_ok!(b"hello", |r| { r.consume(b"hello") }), 85 | ((), input!(b"")) 86 | ); 87 | assert_eq!( 88 | read_partial_ok!(b"hello", |r| { r.take(5) }), 89 | (input!(b"hello"), input!(b"")) 90 | ); 91 | assert_eq!( 92 | read_partial_ok!(b"hello", |r| { r.consume(b"hell") }), 93 | ((), input!(b"o")) 94 | ); 95 | // Invalid 96 | assert_eq!( 97 | read_partial_err!(b"hello", |r| { r.take(10) }).to_retry_requirement(), 98 | RetryRequirement::new(5) 99 | ); 100 | } 101 | 102 | #[test] 103 | fn test_read_infallible() { 104 | assert_eq!( 105 | read_infallible!(b"hello", |r| { 106 | r.take_while(|b: u8| b.is_ascii_alphabetic()) 107 | }), 108 | (input!(b"hello"), input!(b"")) 109 | ); 110 | assert_eq!( 111 | read_infallible!(b"hello1", |r| { 112 | r.take_while(|b: u8| b.is_ascii_alphabetic()) 113 | }), 114 | (input!(b"hello"), input!(b"1")) 115 | ); 116 | } 117 | -------------------------------------------------------------------------------- /tests/test_nom.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod common; 3 | 4 | use common::*; 5 | 6 | /////////////////////////////////////////////////////////////////////////////// 7 | 8 | mod color { 9 | use nom::{ 10 | bytes::complete::{tag, take_while_m_n}, 11 | combinator::map_res, 12 | sequence::tuple, 13 | IResult, 14 | }; 15 | 16 | #[derive(Debug, PartialEq)] 17 | pub struct Value { 18 | pub red: u8, 19 | pub green: u8, 20 | pub blue: u8, 21 | } 22 | 23 | pub fn parse(input: &str) -> IResult<&str, Value> { 24 | let (input, _) = tag("#")(input)?; 25 | let (input, (red, green, blue)) = tuple((hex_primary, hex_primary, hex_primary))(input)?; 26 | 27 | Ok((input, Value { red, green, blue })) 28 | } 29 | 30 | fn from_hex(input: &str) -> Result { 31 | u8::from_str_radix(input, 16) 32 | } 33 | 34 | fn is_hex_digit(c: char) -> bool { 35 | c.is_digit(16) 36 | } 37 | 38 | fn hex_primary(input: &str) -> IResult<&str, u8> { 39 | map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(input) 40 | } 41 | } 42 | 43 | mod verbose { 44 | use nom::{ 45 | bytes::streaming::tag, character::streaming::char, error::context, error::VerboseError, 46 | IResult, 47 | }; 48 | 49 | pub fn parse(i: &str) -> IResult<&str, &str, VerboseError<&str>> { 50 | context("a", |i| { 51 | context("b", |i| { 52 | let (i, _) = char('f')(i)?; 53 | tag("oobar")(i) 54 | })(i) 55 | })(i) 56 | } 57 | } 58 | 59 | /////////////////////////////////////////////////////////////////////////////// 60 | 61 | fn parse_color<'i, E>(r: &mut StringReader<'i, E>) -> Result 62 | where 63 | E: Error<'i>, 64 | { 65 | r.take_remaining().read_all(|r| { 66 | r.try_external("hex color", |i| { 67 | color::parse(i.as_dangerous()) 68 | .map(|(remaining, response)| (i.byte_len() - remaining.len(), response)) 69 | }) 70 | }) 71 | } 72 | 73 | fn parse_verbose<'i, E>(r: &mut StringReader<'i, E>) -> Result<&'i str, E> 74 | where 75 | E: Error<'i>, 76 | { 77 | r.take_remaining().read_all(|r| { 78 | r.try_external("value", |i| { 79 | verbose::parse(i.as_dangerous()) 80 | .map(|(remaining, response)| (i.byte_len() - remaining.len(), response)) 81 | }) 82 | }) 83 | } 84 | 85 | /////////////////////////////////////////////////////////////////////////////// 86 | 87 | #[test] 88 | fn test_parse_color_ok() { 89 | assert_eq!( 90 | read_all_ok!("#2F14DF", parse_color), 91 | color::Value { 92 | red: 47, 93 | green: 20, 94 | blue: 223, 95 | } 96 | ); 97 | } 98 | 99 | #[test] 100 | fn test_parse_color_trailing() { 101 | let _ = read_all_err!("#2F14DFF", parse_color); 102 | } 103 | 104 | #[test] 105 | fn test_parse_verbose_ok() { 106 | let value = read_all_ok!("foobar", parse_verbose); 107 | assert_eq!(value, "oobar"); 108 | } 109 | 110 | #[test] 111 | fn test_parse_color_too_short() { 112 | let error = read_all_err!("#2F14D", parse_color); 113 | assert!(error.is_fatal()); 114 | assert_str_eq!( 115 | format!("{:#}\n", error), 116 | indoc! {r##" 117 | failed to read and expect an external value: expected hex color 118 | > "#2F14D" 119 | ^ 120 | additional: 121 | error line: 1, error offset: 5, input length: 6 122 | backtrace: 123 | 1. `read all input` 124 | 2. `read all input` 125 | 3. `read and expect an external value` (expected hex color) 126 | 1. `TakeWhileMN` 127 | "##} 128 | ); 129 | } 130 | 131 | #[test] 132 | fn test_parse_verbose_err() { 133 | let error = read_all_err!("err", parse_verbose); 134 | assert!(error.is_fatal()); 135 | assert_str_eq!( 136 | format!("{:#}\n", error), 137 | indoc! {r##" 138 | failed to read and expect an external value: expected value 139 | > "err" 140 | ^^^ 141 | additional: 142 | error line: 1, error offset: 0, input length: 3 143 | backtrace: 144 | 1. `read all input` 145 | 2. `read all input` 146 | 3. `read and expect an external value` (expected value) 147 | 1. `` (expected a) 148 | 2. `` (expected b) 149 | 3. `consume input` (expected character 'f') 150 | "##} 151 | ); 152 | } 153 | 154 | #[test] 155 | fn test_parse_verbose_err_retry() { 156 | let error = read_all_err!("f", parse_verbose); 157 | assert!(!error.is_fatal()); 158 | assert_eq!(error.to_retry_requirement(), RetryRequirement::new(5)); 159 | assert_str_eq!( 160 | format!("{:#}\n", error), 161 | indoc! {r##" 162 | failed to read and expect an external value: expected value 163 | > "f" 164 | ^ 165 | additional: 166 | error line: 1, error offset: 0, input length: 1 167 | backtrace: 168 | 1. `read all input` 169 | 2. `read all input` 170 | 3. `read and expect an external value` (expected value) 171 | "##} 172 | ); 173 | } 174 | -------------------------------------------------------------------------------- /tests/test_reader_bytes.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod common; 3 | 4 | use common::*; 5 | 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // Test debug 8 | 9 | #[test] 10 | fn test_bytes_input_debug() { 11 | read_all_ok!(b"hello", |r| { 12 | assert_eq!( 13 | format!("{:?}", r), 14 | "Reader { input: Bytes { bound: Start, value: [68 65 6c 6c 6f] } }" 15 | ); 16 | r.consume(b"hello") 17 | }); 18 | } 19 | 20 | #[test] 21 | fn test_bytes_input_pretty_debug() { 22 | read_all_ok!(b"hello", |r| { 23 | assert_eq!( 24 | format!("{:#?}\n", r), 25 | indoc! {r#" 26 | Reader { 27 | input: Bytes { 28 | bound: Start, 29 | value: "hello", 30 | }, 31 | } 32 | "#} 33 | ); 34 | r.consume(b"hello") 35 | }); 36 | } 37 | 38 | /////////////////////////////////////////////////////////////////////////////// 39 | // Reader::consume (u8) 40 | 41 | #[test] 42 | fn test_consume_u8_exact_same() { 43 | read_all_ok!(b"1", |r| { r.consume(b'1') }); 44 | } 45 | 46 | #[test] 47 | fn test_consume_u8_same_len_different_value() { 48 | assert_eq!( 49 | read_all_err!(b"1", |r| { r.consume(b'2') }).to_retry_requirement(), 50 | None 51 | ); 52 | } 53 | 54 | #[test] 55 | fn test_consume_u8_different_len_and_value() { 56 | assert_eq!( 57 | read_all_err!(b"", |r| { r.consume(b'1') }).to_retry_requirement(), 58 | RetryRequirement::new(1) 59 | ); 60 | } 61 | 62 | /////////////////////////////////////////////////////////////////////////////// 63 | // Reader::consume_opt (u8) 64 | 65 | #[test] 66 | fn test_consume_opt_u8_true() { 67 | assert!(read_all_ok!(b"1", |r| { Ok(r.consume_opt(b'1')) })); 68 | } 69 | 70 | #[test] 71 | fn test_consume_opt_u8_false() { 72 | assert!(!read_all_ok!(b"1", |r| { 73 | let v = r.consume_opt(b'2'); 74 | r.skip(1)?; 75 | Ok(v) 76 | })); 77 | } 78 | 79 | /////////////////////////////////////////////////////////////////////////////// 80 | // Reader::take_array 81 | 82 | #[test] 83 | fn test_array() { 84 | assert_eq!( 85 | read_all_ok!(&[0, 1, 2], |r| { r.take_array::<3>() }).into_dangerous(), 86 | [0, 1, 2] 87 | ); 88 | } 89 | 90 | #[test] 91 | fn test_array_err() { 92 | let _ = read_all_err!(&[0, 1], |r| { r.take_array::<3>() }); 93 | } 94 | 95 | /////////////////////////////////////////////////////////////////////////////// 96 | // Reader::take_array_opt 97 | 98 | #[test] 99 | fn test_array_opt() { 100 | assert_eq!( 101 | read_all_ok!(&[0, 1, 2], |r| { Ok(r.take_array_opt::<3>()) }) 102 | .unwrap() 103 | .into_dangerous(), 104 | [0, 1, 2] 105 | ); 106 | } 107 | 108 | #[test] 109 | fn test_array_opt_none() { 110 | assert_eq!( 111 | read_partial_ok!(&[0, 1], |r| { Ok(r.take_array_opt::<3>()) }), 112 | (None, input(&[0, 1])) 113 | ); 114 | } 115 | 116 | /////////////////////////////////////////////////////////////////////////////// 117 | // Reader::peek_read 118 | 119 | #[test] 120 | fn test_peek_read() { 121 | assert!(read_all_ok!(b"hello", |r| { 122 | let v = r.peek_read()? == b'h'; 123 | r.skip(5)?; 124 | Ok(v) 125 | })); 126 | } 127 | 128 | /////////////////////////////////////////////////////////////////////////////// 129 | // Reader::peek_read_opt 130 | 131 | #[test] 132 | fn test_peek_read_opt() { 133 | assert!(read_all_ok!(b"hello", |r| { 134 | let v = r.peek_read_opt().map_or(false, |v| v == b'h'); 135 | r.skip(5)?; 136 | Ok(v) 137 | })); 138 | } 139 | 140 | /////////////////////////////////////////////////////////////////////////////// 141 | // Reader::skip_str_while 142 | 143 | #[test] 144 | fn test_skip_str_while() { 145 | read_all_ok!(b"hello!", |r| { 146 | r.skip_str_while(|c| c.is_ascii_alphabetic())?; 147 | r.skip(1)?; 148 | Ok(()) 149 | }) 150 | } 151 | 152 | /////////////////////////////////////////////////////////////////////////////// 153 | // Reader::take_str_while 154 | 155 | #[test] 156 | fn test_take_str_while() { 157 | assert_eq!( 158 | read_all_ok!(b"hello!", |r| { 159 | let v = r.take_str_while(|c| c.is_ascii_alphabetic())?; 160 | r.skip(1)?; 161 | Ok(v) 162 | }), 163 | "hello"[..] 164 | ); 165 | } 166 | 167 | #[test] 168 | fn test_take_str_while_utf8_retry() { 169 | // Length 1 170 | assert_eq!( 171 | read_all_ok!(&[0b0111_1111], |r| r.take_str_while(|_| true)), 172 | input!(core::str::from_utf8(&[0b0111_1111]).unwrap()) 173 | ); 174 | // Length 2 175 | let err = read_all_err!(&[0b1101_1111], |r| r.take_str_while(|_| true)); 176 | assert_eq!(err.to_retry_requirement(), RetryRequirement::new(1)); 177 | // Length 3 178 | let err = read_all_err!(&[0b1110_1111], |r| r.take_str_while(|_| true)); 179 | assert_eq!(err.to_retry_requirement(), RetryRequirement::new(2)); 180 | // Invalid 181 | let err = read_all_err!(&[0b1111_0111], |r| r.take_str_while(|_| true)); 182 | assert_eq!(err.to_retry_requirement(), None); 183 | } 184 | 185 | /////////////////////////////////////////////////////////////////////////////// 186 | // Reader::try_skip_str_while 187 | 188 | #[test] 189 | fn test_try_skip_str_while() { 190 | read_all_ok!(b"hello!", |r| { 191 | r.try_skip_str_while(|c| Ok(c.is_ascii_alphabetic()))?; 192 | r.skip(1)?; 193 | Ok(()) 194 | }) 195 | } 196 | 197 | /////////////////////////////////////////////////////////////////////////////// 198 | // Reader::try_take_str_while 199 | 200 | #[test] 201 | fn test_try_take_str_while() { 202 | assert_eq!( 203 | read_all_ok!(b"hello!", |r| { 204 | let v = r.try_take_str_while(|c| Ok(c.is_ascii_alphabetic()))?; 205 | r.skip(1)?; 206 | Ok(v) 207 | }), 208 | "hello"[..] 209 | ); 210 | } 211 | -------------------------------------------------------------------------------- /tests/test_reader_string.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod common; 3 | 4 | use common::*; 5 | 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // Test debug 8 | 9 | #[test] 10 | fn test_string_input_debug() { 11 | read_all_ok!("hello", |r| { 12 | assert_eq!( 13 | format!("{:?}", r), 14 | r#"Reader { input: String { bound: Start, value: "hello" } }"# 15 | ); 16 | r.consume("hello") 17 | }) 18 | } 19 | 20 | #[test] 21 | fn test_string_input_pretty_debug() { 22 | read_all_ok!("hello", |r| { 23 | assert_eq!( 24 | format!("{:#?}\n", r), 25 | indoc! {r#" 26 | Reader { 27 | input: String { 28 | bound: Start, 29 | value: "hello", 30 | }, 31 | } 32 | "#} 33 | ); 34 | r.consume("hello") 35 | }) 36 | } 37 | 38 | /////////////////////////////////////////////////////////////////////////////// 39 | // Reader::consume (char) 40 | 41 | #[test] 42 | fn test_consume_char_exact_same() { 43 | read_all_ok!("1", |r| { r.consume('1') }); 44 | } 45 | 46 | #[test] 47 | fn test_consume_char_same_len_different_value() { 48 | assert_eq!( 49 | read_all_err!("1", |r| { r.consume('2') }).to_retry_requirement(), 50 | None 51 | ); 52 | } 53 | 54 | #[test] 55 | fn test_consume_char_different_len_and_value() { 56 | assert_eq!( 57 | read_all_err!("", |r| { r.consume('1') }).to_retry_requirement(), 58 | RetryRequirement::new(1) 59 | ); 60 | } 61 | 62 | /////////////////////////////////////////////////////////////////////////////// 63 | // Reader::consume_opt (char) 64 | 65 | #[test] 66 | fn test_consume_opt_char_true() { 67 | assert!(read_all_ok!("1", |r| { Ok(r.consume_opt('1')) })); 68 | } 69 | 70 | #[test] 71 | fn test_consume_opt_char_false() { 72 | assert!(!read_all_ok!("1", |r| { 73 | let v = r.consume_opt('2'); 74 | r.skip(1)?; 75 | Ok(v) 76 | })); 77 | } 78 | 79 | /////////////////////////////////////////////////////////////////////////////// 80 | // Reader::peek_read 81 | 82 | #[test] 83 | fn test_peek_read() { 84 | assert!(read_all_ok!("hello", |r| { 85 | let v = r.peek_read()? == 'h'; 86 | r.skip(5)?; 87 | Ok(v) 88 | })); 89 | } 90 | 91 | /////////////////////////////////////////////////////////////////////////////// 92 | // Reader::peek_read_opt 93 | 94 | #[test] 95 | fn test_peek_read_opt() { 96 | assert!(read_all_ok!("hello", |r| { 97 | let v = r.peek_read_opt().map_or(false, |v| v == 'h'); 98 | r.skip(5)?; 99 | Ok(v) 100 | })); 101 | } 102 | -------------------------------------------------------------------------------- /tests/test_span.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod common; 3 | 4 | use common::*; 5 | 6 | #[test] 7 | fn test_of_bytes() { 8 | let parent = &[1, 2, 3, 4][..]; 9 | let sub = &parent[1..2]; 10 | assert_eq!(Span::from(sub).of(parent).unwrap(), sub); 11 | 12 | let non_span = Span::from(&[1, 2, 2, 4][..]); 13 | assert_eq!(non_span.of(parent), None); 14 | } 15 | 16 | #[test] 17 | fn test_of_str() { 18 | let parent = "1234"; 19 | 20 | let sub = &parent[1..2]; 21 | assert_eq!(Span::from(sub).of(parent).unwrap(), sub); 22 | 23 | let non_span = Span::from("1224"); 24 | assert_eq!(non_span.of(parent), None); 25 | } 26 | 27 | #[test] 28 | fn test_of_str_invalid() { 29 | let parent = "♥♥"; 30 | 31 | let sub = &parent[0..3]; 32 | assert_eq!(Span::from(sub).of(parent).unwrap(), sub); 33 | 34 | let non_span = Span::from(&parent.as_bytes()[0..1]); 35 | assert_eq!(non_span.of(parent), None); 36 | } 37 | 38 | #[test] 39 | fn test_of_input_bytes() { 40 | let parent = dangerous::input(&[1, 2, 3, 4]); 41 | let sub = &parent.as_dangerous()[1..2]; 42 | assert_eq!(Span::from(sub).of(parent.clone()).unwrap(), sub); 43 | 44 | let non_span = Span::from(&[1, 2, 2, 4][..]); 45 | assert_eq!(non_span.of(parent), None); 46 | } 47 | 48 | #[test] 49 | fn test_of_input_string() { 50 | let parent = dangerous::input("1234"); 51 | 52 | let sub = &parent.as_dangerous()[1..2]; 53 | assert_eq!(Span::from(sub).of(parent.clone()).unwrap(), sub); 54 | 55 | let non_span = Span::from("1224"); 56 | assert_eq!(non_span.of(parent), None); 57 | } 58 | 59 | #[test] 60 | fn test_of_input_string_invalid() { 61 | let parent = dangerous::input("♥♥"); 62 | 63 | let sub = &parent.as_dangerous()[0..3]; 64 | assert_eq!(Span::from(sub).of(parent.clone()).unwrap(), sub); 65 | 66 | let non_span = Span::from(&parent.as_dangerous().as_bytes()[0..1]); 67 | assert_eq!(non_span.of(parent), None); 68 | } 69 | -------------------------------------------------------------------------------- /tests/test_usage.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_usage_with_fn() { 3 | use dangerous::error::{Expected, RetryRequirement, ToRetryRequirement}; 4 | use dangerous::{BytesReader, Input}; 5 | 6 | let input = dangerous::input(b"a"); 7 | 8 | fn do_thing<'i>(r: &mut BytesReader<'i, Expected<'i>>) -> Result<(), Expected<'i>> { 9 | let a = r.read()?; 10 | assert_eq!(a, b'a'); 11 | r.read()?; 12 | Ok(()) 13 | } 14 | 15 | assert_eq!( 16 | input.read_all(do_thing).unwrap_err().to_retry_requirement(), 17 | RetryRequirement::new(1) 18 | ); 19 | } 20 | 21 | #[test] 22 | fn test_streaming_usage_with_fatal_requirement() { 23 | use dangerous::{BytesReader, Input, Invalid}; 24 | 25 | let input = dangerous::input(b"blah"); 26 | let result: Result<_, Invalid> = input.read_all(|r| { 27 | r.take(2)? 28 | .read_all(|r: &mut BytesReader| r.skip(4))?; 29 | r.consume(b"ah") 30 | }); 31 | 32 | assert_eq!(result, Err(Invalid::fatal())); 33 | } 34 | 35 | #[test] 36 | fn test_streaming_usage_with_valid_requirement() { 37 | use dangerous::error::{Invalid, RetryRequirement, ToRetryRequirement}; 38 | use dangerous::Input; 39 | 40 | let input = dangerous::input(b"blah"); 41 | let result: Result<_, Invalid> = input.read_all(|r| { 42 | r.skip(2)?; 43 | r.take(4) 44 | }); 45 | 46 | assert_eq!( 47 | result.unwrap_err().to_retry_requirement(), 48 | RetryRequirement::new(2) 49 | ); 50 | } 51 | 52 | #[test] 53 | fn test_retry_unbound_spent_reader() { 54 | use dangerous::error::{RetryRequirement, ToRetryRequirement}; 55 | use dangerous::{BytesReader, Error, Input, Invalid}; 56 | 57 | fn parse<'i, E: Error<'i>>(r: &mut BytesReader<'i, E>) -> Result<(), E> { 58 | // This may have not finished so the input is unbound. 59 | let _ = r.take_while(|_| true); 60 | // This may have not finished so the input is unbound. 61 | let unbound = r.take_while(|_| true); 62 | // We try to take more and get a retry error. 63 | unbound.read_all(|r| r.skip(1)) 64 | } 65 | 66 | let input = dangerous::input(b"blah"); 67 | let result: Result<_, Invalid> = input.read_all(parse); 68 | 69 | assert_eq!( 70 | result.unwrap_err().to_retry_requirement(), 71 | RetryRequirement::new(1), 72 | ); 73 | } 74 | 75 | #[test] 76 | fn test_retry_footgun_with_take_consumed() { 77 | use dangerous::{BytesReader, Error, Input, Invalid}; 78 | 79 | fn parse<'i, E: Error<'i>>(r: &mut BytesReader<'i, E>) -> Result<(), E> { 80 | let (_, consumed) = r.try_take_consumed(|r| { 81 | // We take a exact length of input 82 | r.consume(b"blah") 83 | })?; 84 | consumed.read_all(|r| r.consume(b"blah1")) 85 | } 86 | 87 | let input = dangerous::input(b"blah"); 88 | let result: Result<_, Invalid> = input.read_all(parse); 89 | 90 | assert_eq!(result, Err(Invalid::fatal())); 91 | } 92 | 93 | #[test] 94 | fn test_retry_footgun_from_unbound_spent_reader_take_0() { 95 | use dangerous::{BytesReader, Error, Input, Invalid}; 96 | 97 | fn parse<'i, E: Error<'i>>(r: &mut BytesReader<'i, E>) -> Result<(), E> { 98 | let _ = r.take_while(|_| true); 99 | // We read input with a length of zero from the spent Reader. 100 | let bound = r.take(0)?; 101 | // This should result in a fatal error. 102 | bound.read_all(|r| r.skip(1)) 103 | } 104 | 105 | let input = dangerous::input(b"blah"); 106 | let result: Result<_, Invalid> = input.read_all(parse); 107 | 108 | assert_eq!(result, Err(Invalid::fatal())); 109 | } 110 | --------------------------------------------------------------------------------