[![Crate](https://img.shields.io/crates/v/cborpath.svg)](https://crates.io/crates/cborpath) [![docs.rs](https://docs.rs/cborpath/badge.svg)](https://docs.rs/cborpath) [![Build](https://github.com/dahomey-technologies/cborpath-rs/actions/workflows/compile_and_test.yml/badge.svg)](https://github.com/dahomey-technologies/cborpath-rs/actions/workflows/compile_and_test.yml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) # cborpath-rs cborpath is a CBORPath engine written in Rust. # CBORPath CBORPath is an adaptation of JSONPath to [CBOR](https://www.rfc-editor.org/rfc/rfc8949.html) based on the [JSONPath Internet Draft](https://www.ietf.org/archive/id/draft-ietf-jsonpath-base-09.html) ## Syntax summary ### Path A `path` expression is a `CBOR Array` which, when applied to a `CBOR` value, the *argument*, selects zero or more nodes of the argument and output these nodes as a nodelist. A `path` always begins by an identifier * a root identifier (`$`) for absolute paths, * a current node identifier (`@`) for relative paths. relative path are always used in a filter context. A `path` is then followed by one or more `segments`. | Syntax | Description | |-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | `["$", ]` | an absolute path composed by an array of segments
and which always begins by a root identifier (`$`) | | `["@", ]` | a relative path composed by an array of segments
and which always begins by a current node identifier (`@`) | ### Segment `Segments` apply one or more `selectors` to an input value and concatenate the results into a single nodelist. | Syntax | Description | |-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | `[]` | a `child segment`, composed by one ore more `selectors` | | `` | shortcut for a `child segment`, composed by a unique `selector` | | `{"..": []}` | a `descendant segment`, composed by one ore more `selectors` | | `{"..": }` | shortcut for a `descendant segment`, composed by a unique `selector` | ### Selector A selector produces a nodelist consisting of zero or more children of the input value. | Syntax | Description | |-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | ``
``
``
``
``
`` | `key selector`: selects a child of a CBOR Map based on the child key | | `{"*": 1}` | `wildcard selector`: selects all children of a node | | `{"#": }` | `index selector`: selects an indexed child of an array (from 0) | | `{":": [, , ]}` | `array slice selector`: selects a subset of the elements of an array
(between `start` and `end` with a `step`) | | `{"?": }` | `filter selector`: selects particular children using a boolean expression | ### Boolean expression A boolean expression returns `true` or `false` and is used by a `filter selector` to filter array elements or map items. | Syntax | Description | |-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | `{"&&": [, ]}` | logical `AND` | | `{"\|\|": [, ]}` | logical `OR` | | `{"!": }` | logical `NOT` | | `{"<=": [, ]}` | comparison `lesser than or equal | | `{"<": [, ]}` | comparison `lesser than` | | `{"==": [, ]}` | comparison `equal` | | `{"!=": [, ]}` | comparison `not equal` | | `{">": [, ]}` | comparison `greater than` | | `{">=": [, ]}` | comparison `greater than or equal` | | `{"match": [, ]}` | match function to compute a regular expression full match.
returns a boolean | | `{"search": [, ]}` | length function to compute a regular expression substring match.
returns a boolean | ### Comparable A `comparable` is an operand of a `filter` comparison or an argument of a function. | Syntax | Description | |-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | ``
``
``
``
``
`` | a `CBOR` value | | `["$", ]`
`["@", ]` | a singular path (path which procudes a nodelist containing at most one node) | | `{"length": }` | length function to compute the length of a value.
returns an unsigned integer | | `{"count": }` | count function to compute the number of nodes in a path.
returns an unsigned integer | | `{"value": }` | value function to get the number of a single node path.
returns a `CBOR` value | ### Singular Segment A `singular segment` produces a nodelist containing at most one node. | Syntax | Description | |-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | ``
``
``
``
``
`` | `key selector`: selects a child of a CBOR Map based on the child key | | `{"#": }` | `index selector`: selects an indexed child of an array (from 0) | ## Examples This section is informative. It provides examples of CBORPath expressions. The examples are based on the simple CBOR value representing a bookstore (that also has a bicycle). ```json { "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 399 } } } ``` This table shows some CBORPath queries that might be applied to this example and their intended results. | Syntax | Intended result | |---------------------------------------------------------------------------------------------|-----------------------------------------------------------------| | `["$", "store", "book", {"*": 1}, "author"]` | the authors of all books in the store | | `["$", {"..": "author"}]` | all authors | | `["$", "store", {"*": 1}]` | all things in store, which are some books
and a red bicycle | | `["$", "store", {"..": "price"}]` | the prices of everything in the store | | `["$", {"..": "book"}, {"#": 2}] ` | the third book | | `["$", {"..": "book"}, {"#": -1}]` | the last book in order | | `["$", {"..": "book"}, [{"#": 0}, {"#": 1}]]`
or
`["$", {"..": "book"}, {":": [0, 2, 1]}]` | the first two books | | `["$", {"..": "book"}, {"?": ["@", "isbn"]}]` | all books with an ISBN number | | `["$", {"..": "book"}, {"?": {"<": [["@", "price"], 10.0]}}]` | all books cheaper than 10 | | `["$", {"..": {"*": 1}}]` | all map item values and array elements
contained in input value | # Library Usage These are a few samples of code based on the examples of the previous section. ```rust use cborpath::{CborPath, builder, Error}; use cbor_diag::parse_diag; pub fn diag_to_bytes(cbor_diag_str: &str) -> Vec { parse_diag(cbor_diag_str).unwrap().to_bytes() } fn main() -> Result<(), Error> { let value = diag_to_bytes( r#"{ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 399 } } }"#, ); // the authors of all books in the store // ["$", "store", "book", {"*": 1}, "author"] let cbor_path = CborPath::builder() .key("store") .key("book") .wildcard() .key("author") .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ]"# ), results ); // all authors // ["$", {"..": "author"}] let cbor_path = CborPath::builder() .descendant(builder::segment().key("author")) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ]"# ), results ); // all things in store, which are some books and a red bicycle // ["$", "store", {"*": 1}] let cbor_path = CborPath::builder().key("store").wildcard().build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[[ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], { "color": "red", "price": 399 }]"# ), results ); // the prices of everything in the store // ["$", "store", {"..": "price"}] let cbor_path = CborPath::builder() .key("store") .descendant(builder::segment().key("price")) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ 399, 8.95, 12.99, 8.99, 22.99 ]"# ), results ); // the third book // ["$", {"..": "book"}, {"#": 2}] let cbor_path = CborPath::builder() .descendant(builder::segment().key("book")) .index(2) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[{ "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }]"# ), results ); // the last book in order // ["$", {"..": "book"}, {"#": -1}] let cbor_path = CborPath::builder() .descendant(builder::segment().key("book")) .index(-1) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[{ "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 }]"# ), results ); // the first two books // ["$", {"..": "book"}, [{"#": 0}, {"#": 1}]] let cbor_path = CborPath::builder() .descendant(builder::segment().key("book")) .child(builder::segment().index(0).index(1)) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }]"# ), results ); // the first two books // ["$", {"..": "book"}, {":": [0, 2, 1]}] let cbor_path = CborPath::builder() .descendant(builder::segment().key("book")) .slice(0, 2, 1) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }]"# ), results ); // all books with an ISBN number // ["$", {"..": "book"}, {"?": ["@", "isbn"]}] let cbor_path = CborPath::builder() .descendant(builder::segment().key("book")) .filter(builder::rel_path().key("isbn")) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 }]"# ), results ); // all books cheaper than 10 // ["$", {"..": "book"}, {"?": {"<": [["@", "price"], 10.0]}}] let cbor_path = CborPath::builder() .descendant(builder::segment().key("book")) .filter(builder::lt( builder::sing_rel_path().key("price"), builder::val(10.), )) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }]"# ), results ); // all map item values and array elements contained in input value // ["$", {"..": {"*": 1}}] let cbor_path = CborPath::builder() .descendant(builder::segment().wildcard()) .build(); let results = cbor_path.read_from_bytes(&value)?; assert_eq!( diag_to_bytes( r#"[{ "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 399 } }, [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], { "color": "red", "price": 399 }, { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 }, "red", 399, "reference", "Nigel Rees", "Sayings of the Century", 8.95, "fiction", "Evelyn Waugh", "Sword of Honour", 12.99, "fiction", "Herman Melville", "Moby Dick", "0-553-21311-3", 8.99, "fiction", "J. R. R. Tolkien", "The Lord of the Rings", "0-395-19395-8", 22.99 ]"# ), results ); Ok(()) } ```