Parser Combinators
id_effect_parse brings small, composable parsers to the workspace — the same functional pattern as schema combinators in Part IV, but oriented toward text and byte streams rather than JSON Unknown values.
The Parser type
A Parser<I, O, E> wraps a function I -> Result<(O, I), E>: parsed output plus remaining input.
#![allow(unused)] fn main() { use id_effect_parse::{char, int, parse_str, Parser}; let number = char('(') .and_then(|_| int()) .and_then(|n| char(')').map(move |_| n)); let (value, rest) = parse_str(&number, "(42) rest").unwrap(); assert_eq!(value, 42); assert_eq!(rest, " rest"); }
Core combinators:
| Combinator | Role |
|---|---|
map | transform parsed output |
and_then | sequence dependent parsers |
alt | try another parser on failure |
many | repeat until the inner parser fails |
Built-ins such as char, tag, int, and ws cover common text needs.
Pretty printing
The Pretty trait builds Doc values — a Wadler-style document tree rendered with a line width budget:
#![allow(unused)] fn main() { use id_effect_parse::{Doc, Pretty}; let doc = Doc::text("users") .cat(Doc::line()) .cat(["alice", "bob"].pretty()) .group(); println!("{}", doc.render(40)); }
Use pretty printers for debug output, REPLs, and human-readable config — not for wire formats (use Codec instead).
Invertible codecs
Codec pairs parse and print so formats round-trip:
#![allow(unused)] fn main() { use id_effect_parse::codec::quoted_string; let codec = quoted_string(); let wire = codec.print(&"hello".to_string()); let (parsed, _) = codec.parse(wire).unwrap(); assert_eq!(parsed, "hello"); }
When you need lossless serialization with a parser-shaped API, start with Codec::new.
Diffs
Diff<T> describes value-level changes (Unchanged, Added, Removed, Changed). Helpers like diff_values and diff_option support config drift and snapshot tests.
Parsing Stream chunks
Collect stream chunks, flatten, then parse — parse_stream for typed buffers, parse_text_stream for UTF-8 text:
#![allow(unused)] fn main() { use id_effect::{Chunk, Stream, run_blocking}; use id_effect_parse::{parse_text_stream, tag}; let parser = tag("ping"); let stream = Stream::from_iterable(vec![Chunk::from_vec(b"ping".to_vec())]); let value = run_blocking(parse_text_stream(&parser, stream), ()).unwrap(); assert_eq!(value, "ping"); }
Schema bridge
SchemaBridge connects Schema values to text parsers:
parser_for_json— parse JSON text, thendecode_unknownparser_for_string_wire— when the wire type isStringparser_for::<T>()— for types implementingHasSchema
Boundary validation for external data still belongs to id_effect::schema (Part IV). Use schema at the edge; use SchemaBridge when a text protocol should reuse the same schema.
#![allow(unused)] fn main() { use id_effect::schema::{HasSchema, i64, string, struct_}; use id_effect_parse::SchemaBridge; let schema = struct_("name", string(), "age", i64()); let parser = SchemaBridge::parser_for_json(schema); let (person, _) = parser.parse(r#"{"name":"Ada","age":36}"#.to_string()).unwrap(); assert_eq!(person, ("Ada".to_string(), 36)); }
#[derive(SchemaParser)]
The proc macro generates schema(), parser(), and HasSchema for structs with named fields (see Part V chapter 24).