diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/sfv/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/sfv/Cargo.toml | 60 | ||||
-rw-r--r-- | third_party/rust/sfv/LICENSE-APACHE | 201 | ||||
-rw-r--r-- | third_party/rust/sfv/LICENSE-MIT | 27 | ||||
-rw-r--r-- | third_party/rust/sfv/README.md | 20 | ||||
-rw-r--r-- | third_party/rust/sfv/benches/bench.rs | 171 | ||||
-rw-r--r-- | third_party/rust/sfv/src/lib.rs | 440 | ||||
-rw-r--r-- | third_party/rust/sfv/src/parser.rs | 473 | ||||
-rw-r--r-- | third_party/rust/sfv/src/ref_serializer.rs | 310 | ||||
-rw-r--r-- | third_party/rust/sfv/src/serializer.rs | 319 | ||||
-rw-r--r-- | third_party/rust/sfv/src/test_parser.rs | 850 | ||||
-rw-r--r-- | third_party/rust/sfv/src/test_serializer.rs | 531 | ||||
-rw-r--r-- | third_party/rust/sfv/src/utils.rs | 44 |
13 files changed, 3447 insertions, 0 deletions
diff --git a/third_party/rust/sfv/.cargo-checksum.json b/third_party/rust/sfv/.cargo-checksum.json new file mode 100644 index 0000000000..b34463089b --- /dev/null +++ b/third_party/rust/sfv/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"cf545d5d3384e757f96bffb58f35f1df6236a340e9aac13ae3398a8b9127fe30","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"5318787a14e32720b1652f08a24408aaea67fdb5154ceda0f46a6069a0c5e5e3","README.md":"50a8b83ce705a34030957a3502c768674541220a5c2952a965f70c31faaed559","benches/bench.rs":"bbc60db4b542abb3738eba80f5c7c54ac39301ed5e48e2ae2a94cecfdb42e33f","src/lib.rs":"d6c579a4b35078c2f86d6515c957040eb54d9b7ecd65e74f90186d977986b358","src/parser.rs":"1c54f855a5bdd270b9b83b8cb95de670fdf14bce2154f683fba920b447adc94f","src/ref_serializer.rs":"8806ee50e2b2ae466a49788d7e972a47329c0e2c842d669673a152286e81c5d9","src/serializer.rs":"937541e0bdda7f3b043c55eb44ef2c431bdbf636498ce99af4ca47d773790178","src/test_parser.rs":"7a2728e7cbdcb1f3bb42e009045ec0dcfca241316a2aee4905925d4b1ce0bb3a","src/test_serializer.rs":"2419279c9a9a4f48952836d63f3822281c18691d86c146749a573c52a41d6ff0","src/utils.rs":"94c8f79f4747973819b9da2c1a9f6246bf3b5ea7450b376a98eb055f6acf8e73"},"package":"b4f1641177943b4e6faf7622463ae6dfe0f143eb88799e91e2e2e68ede568af5"}
\ No newline at end of file diff --git a/third_party/rust/sfv/Cargo.toml b/third_party/rust/sfv/Cargo.toml new file mode 100644 index 0000000000..1827d69d5d --- /dev/null +++ b/third_party/rust/sfv/Cargo.toml @@ -0,0 +1,60 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "sfv" +version = "0.9.3" +authors = ["Tania Batieva <yalyna.ts@gmail.com>"] +exclude = [ + "tests/**", + ".github/*", +] +description = """ +Structured Field Values for HTTP parser. +Implementation of RFC 8941.""" +documentation = "https://docs.rs/sfv" +readme = "README.md" +keywords = [ + "http-header", + "structured-header", +] +license = "MIT/Apache-2.0" +repository = "https://github.com/undef1nd/sfv" + +[[bench]] +name = "bench" +harness = false + +[dependencies.data-encoding] +version = "2.3.2" + +[dependencies.indexmap] +version = "1.8.0" + +[dependencies.rust_decimal] +version = "1.20.0" +default-features = false + +[dev-dependencies.criterion] +version = "0.4.0" + +[dev-dependencies.rust_decimal] +version = "1.20.0" +features = ["std"] +default-features = false + +[dev-dependencies.serde] +version = "1.0" +features = ["derive"] + +[dev-dependencies.serde_json] +version = "1.0" diff --git a/third_party/rust/sfv/LICENSE-APACHE b/third_party/rust/sfv/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/sfv/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/sfv/LICENSE-MIT b/third_party/rust/sfv/LICENSE-MIT new file mode 100644 index 0000000000..1fd78f4072 --- /dev/null +++ b/third_party/rust/sfv/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2020 Tania Batieva + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/sfv/README.md b/third_party/rust/sfv/README.md new file mode 100644 index 0000000000..2a781b611f --- /dev/null +++ b/third_party/rust/sfv/README.md @@ -0,0 +1,20 @@ +[![License](https://img.shields.io/crates/l/sfv)](LICENSE-MIT) +![Build Status](https://img.shields.io/github/actions/workflow/status/undef1nd/sfv/ci-workflow.yml) +[![Version](https://img.shields.io/crates/v/sfv)](https://crates.io/crates/sfv) +[![Docs](https://img.shields.io/docsrs/sfv?color=white)](https://docs.rs/sfv) + + +# Structured Field Values for HTTP + +`sfv` crate is an implementation of *Structured Field Values for HTTP* as specified in [RFC 8941](https://httpwg.org/specs/rfc8941.html) for parsing and serializing HTTP field values (also known as "structured headers" or "structured trailers"). + +It also exposes a set of types that might be useful for defining new structured fields. + +<br> + +#### License + +<sup> +Licensed under either of <a href="LICENSE-APACHE">Apache License, Version +2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. +</sup> diff --git a/third_party/rust/sfv/benches/bench.rs b/third_party/rust/sfv/benches/bench.rs new file mode 100644 index 0000000000..3e924c1f32 --- /dev/null +++ b/third_party/rust/sfv/benches/bench.rs @@ -0,0 +1,171 @@ +#[macro_use] +extern crate criterion; + +use criterion::{BenchmarkId, Criterion}; +use rust_decimal::prelude::FromPrimitive; +use sfv::{BareItem, Decimal, Parser, SerializeValue}; +use sfv::{RefBareItem, RefDictSerializer, RefItemSerializer, RefListSerializer}; + +criterion_main!(parsing, serializing, ref_serializing); + +criterion_group!(parsing, parsing_item, parsing_list, parsing_dict); + +fn parsing_item(c: &mut Criterion) { + let fixture = + "c29tZXZlcnlsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXNhbnNvbWVvdGhlcmxvbmdsaW5l"; + c.bench_with_input( + BenchmarkId::new("parsing_item", fixture), + &fixture, + move |bench, &input| { + bench.iter(|| Parser::parse_item(input.as_bytes()).unwrap()); + }, + ); +} + +fn parsing_list(c: &mut Criterion) { + let fixture = "a, abcdefghigklmnoprst, 123456785686457, 99999999999.999, (), (\"somelongstringvalue\" \"anotherlongstringvalue\";key=:c29tZXZlciBsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXM: 145)"; + c.bench_with_input( + BenchmarkId::new("parsing_list", fixture), + &fixture, + move |bench, &input| { + bench.iter(|| Parser::parse_list(input.as_bytes()).unwrap()); + }, + ); +} + +fn parsing_dict(c: &mut Criterion) { + let fixture = "a, dict_key2=abcdefghigklmnoprst, dict_key3=123456785686457, dict_key4=(\"inner-list-member\" :aW5uZXItbGlzdC1tZW1iZXI=:);key=aW5uZXItbGlzdC1wYXJhbWV0ZXJz"; + c.bench_with_input( + BenchmarkId::new("parsing_dict", fixture), + &fixture, + move |bench, &input| { + bench.iter(|| Parser::parse_dictionary(input.as_bytes()).unwrap()); + }, + ); +} + +criterion_group!( + serializing, + serializing_item, + serializing_list, + serializing_dict +); + +fn serializing_item(c: &mut Criterion) { + let fixture = + "c29tZXZlcnlsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXNhbnNvbWVvdGhlcmxvbmdsaW5l"; + c.bench_with_input( + BenchmarkId::new("serializing_item", fixture), + &fixture, + move |bench, &input| { + let parsed_item = Parser::parse_item(input.as_bytes()).unwrap(); + bench.iter(|| parsed_item.serialize_value().unwrap()); + }, + ); +} + +fn serializing_list(c: &mut Criterion) { + let fixture = "a, abcdefghigklmnoprst, 123456785686457, 99999999999.999, (), (\"somelongstringvalue\" \"anotherlongstringvalue\";key=:c29tZXZlciBsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXM: 145)"; + c.bench_with_input( + BenchmarkId::new("serializing_list", fixture), + &fixture, + move |bench, &input| { + let parsed_list = Parser::parse_list(input.as_bytes()).unwrap(); + bench.iter(|| parsed_list.serialize_value().unwrap()); + }, + ); +} + +fn serializing_dict(c: &mut Criterion) { + let fixture = "a, dict_key2=abcdefghigklmnoprst, dict_key3=123456785686457, dict_key4=(\"inner-list-member\" :aW5uZXItbGlzdC1tZW1iZXI=:);key=aW5uZXItbGlzdC1wYXJhbWV0ZXJz"; + c.bench_with_input( + BenchmarkId::new("serializing_dict", fixture), + &fixture, + move |bench, &input| { + let parsed_dict = Parser::parse_dictionary(input.as_bytes()).unwrap(); + bench.iter(|| parsed_dict.serialize_value().unwrap()); + }, + ); +} + +criterion_group!( + ref_serializing, + serializing_ref_item, + serializing_ref_list, + serializing_ref_dict +); + +fn serializing_ref_item(c: &mut Criterion) { + let fixture = + "c29tZXZlcnlsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXNhbnNvbWVvdGhlcmxvbmdsaW5l"; + c.bench_with_input( + BenchmarkId::new("serializing_ref_item", fixture), + &fixture, + move |bench, &input| { + bench.iter(|| { + let mut output = String::new(); + let ser = RefItemSerializer::new(&mut output); + ser.bare_item(&RefBareItem::ByteSeq(input.as_bytes())) + .unwrap(); + }); + }, + ); +} + +fn serializing_ref_list(c: &mut Criterion) { + c.bench_function("serializing_ref_list", move |bench| { + bench.iter(|| { + let mut output = String::new(); + let ser = RefListSerializer::new(&mut output); + ser.bare_item(&RefBareItem::Token("a")) + .unwrap() + .bare_item(&RefBareItem::Token("abcdefghigklmnoprst")) + .unwrap() + .bare_item(&RefBareItem::Integer(123456785686457)) + .unwrap() + .bare_item(&RefBareItem::Decimal( + Decimal::from_f64(99999999999.999).unwrap(), + )) + .unwrap() + .open_inner_list() + .close_inner_list() + .open_inner_list() + .inner_list_bare_item(&RefBareItem::String("somelongstringvalue")) + .unwrap() + .inner_list_bare_item(&RefBareItem::String("anotherlongstringvalue")) + .unwrap() + .inner_list_parameter( + "key", + &RefBareItem::ByteSeq("somever longstringvaluerepresentedasbytes".as_bytes()), + ) + .unwrap() + .inner_list_bare_item(&RefBareItem::Integer(145)) + .unwrap() + .close_inner_list(); + }); + }); +} + +fn serializing_ref_dict(c: &mut Criterion) { + c.bench_function("serializing_ref_dict", move |bench| { + bench.iter(|| { + let mut output = String::new(); + RefDictSerializer::new(&mut output) + .bare_item_member("a", &RefBareItem::Boolean(true)) + .unwrap() + .bare_item_member("dict_key2", &RefBareItem::Token("abcdefghigklmnoprst")) + .unwrap() + .bare_item_member("dict_key3", &RefBareItem::Integer(123456785686457)) + .unwrap() + .open_inner_list("dict_key4") + .unwrap() + .inner_list_bare_item(&RefBareItem::String("inner-list-member")) + .unwrap() + .inner_list_bare_item(&RefBareItem::ByteSeq("inner-list-member".as_bytes())) + .unwrap() + .close_inner_list() + .parameter("key", &RefBareItem::Token("aW5uZXItbGlzdC1wYXJhbWV0ZXJz")) + .unwrap(); + }); + }); +} diff --git a/third_party/rust/sfv/src/lib.rs b/third_party/rust/sfv/src/lib.rs new file mode 100644 index 0000000000..f7aebfcdd2 --- /dev/null +++ b/third_party/rust/sfv/src/lib.rs @@ -0,0 +1,440 @@ +/*! +`sfv` crate is an implementation of *Structured Field Values for HTTP* as specified in [RFC 8941](https://httpwg.org/specs/rfc8941.html) for parsing and serializing HTTP field values. +It also exposes a set of types that might be useful for defining new structured fields. + +# Data Structures + +There are three types of structured fields: + +- `Item` - can be an `Integer`, `Decimal`, `String`, `Token`, `Byte Sequence`, or `Boolean`. It can have associated `Parameters`. +- `List` - array of zero or more members, each of which can be an `Item` or an `InnerList`, both of which can be `Parameterized`. +- `Dictionary` - ordered map of name-value pairs, where the names are short textual strings and the values are `Items` or arrays of `Items` (represented with `InnerList`), both of which can be `Parameterized`. There can be zero or more members, and their names are unique in the scope of the `Dictionary` they occur within. + +There's also a few primitive types used to construct structured field values: +- `BareItem` used as `Item`'s value or as a parameter value in `Parameters`. +- `Parameters` are an ordered map of key-value pairs that are associated with an `Item` or `InnerList`. The keys are unique within the scope the `Parameters` they occur within, and the values are `BareItem`. +- `InnerList` is an array of zero or more `Items`. Can have `Parameters`. +- `ListEntry` represents either `Item` or `InnerList` as a member of `List` or as member-value in `Dictionary`. + +# Examples + +### Parsing + +``` +use sfv::Parser; + +// Parsing structured field value of Item type. +let item_header_input = "12.445;foo=bar"; +let item = Parser::parse_item(item_header_input.as_bytes()); +assert!(item.is_ok()); +println!("{:#?}", item); + +// Parsing structured field value of List type. +let list_header_input = "1;a=tok, (\"foo\" \"bar\");baz, ()"; +let list = Parser::parse_list(list_header_input.as_bytes()); +assert!(list.is_ok()); +println!("{:#?}", list); + +// Parsing structured field value of Dictionary type. +let dict_header_input = "a=?0, b, c; foo=bar, rating=1.5, fruits=(apple pear)"; +let dict = Parser::parse_dictionary(dict_header_input.as_bytes()); +assert!(dict.is_ok()); +println!("{:#?}", dict); +``` + +### Getting Parsed Value Members +``` +use sfv::*; + +let dict_header = "u=2, n=(* foo 2)"; + let dict = Parser::parse_dictionary(dict_header.as_bytes()).unwrap(); + + // Case 1 - handling value if it's an Item of Integer type + let u_val = match dict.get("u") { + Some(ListEntry::Item(item)) => item.bare_item.as_int(), + _ => None, + }; + + if let Some(u_val) = u_val { + println!("{}", u_val); + } + + // Case 2 - matching on all possible types + match dict.get("u") { + Some(ListEntry::Item(item)) => match &item.bare_item { + BareItem::Token(val) => { + // do something if it's a Token + println!("{}", val); + } + BareItem::Integer(val) => { + // do something if it's an Integer + println!("{}", val); + } + BareItem::Boolean(val) => { + // do something if it's a Boolean + println!("{}", val); + } + BareItem::Decimal(val) => { + // do something if it's a Decimal + println!("{}", val); + } + BareItem::String(val) => { + // do something if it's a String + println!("{}", val); + } + BareItem::ByteSeq(val) => { + // do something if it's a ByteSeq + println!("{:?}", val); + } + }, + Some(ListEntry::InnerList(inner_list)) => { + // do something if it's an InnerList + println!("{:?}", inner_list.items); + } + None => panic!("key not found"), + } +``` + +### Structured Field Value Construction and Serialization +Creates `Item` with empty parameters: +``` +use sfv::{Item, BareItem, SerializeValue}; + +let str_item = Item::new(BareItem::String(String::from("foo"))); +assert_eq!(str_item.serialize_value().unwrap(), "\"foo\""); +``` + + +Creates `Item` field value with parameters: +``` +use sfv::{Item, BareItem, SerializeValue, Parameters, Decimal, FromPrimitive}; + +let mut params = Parameters::new(); +let decimal = Decimal::from_f64(13.45655).unwrap(); +params.insert("key".into(), BareItem::Decimal(decimal)); +let int_item = Item::with_params(BareItem::Integer(99), params); +assert_eq!(int_item.serialize_value().unwrap(), "99;key=13.457"); +``` + +Creates `List` field value with `Item` and parametrized `InnerList` as members: +``` +use sfv::{Item, BareItem, InnerList, List, SerializeValue, Parameters}; + +let tok_item = BareItem::Token("tok".into()); + +// Creates Item. +let str_item = Item::new(BareItem::String(String::from("foo"))); + +// Creates InnerList members. +let mut int_item_params = Parameters::new(); +int_item_params.insert("key".into(), BareItem::Boolean(false)); +let int_item = Item::with_params(BareItem::Integer(99), int_item_params); + +// Creates InnerList. +let mut inner_list_params = Parameters::new(); +inner_list_params.insert("bar".into(), BareItem::Boolean(true)); +let inner_list = InnerList::with_params(vec![int_item, str_item], inner_list_params); + + +let list: List = vec![Item::new(tok_item).into(), inner_list.into()]; +assert_eq!( + list.serialize_value().unwrap(), + "tok, (99;key=?0 \"foo\");bar" +); +``` + +Creates `Dictionary` field value: +``` +use sfv::{Parser, Item, BareItem, SerializeValue, ParseValue, Dictionary}; + +let member_value1 = Item::new(BareItem::String(String::from("apple"))); +let member_value2 = Item::new(BareItem::Boolean(true)); +let member_value3 = Item::new(BareItem::Boolean(false)); + +let mut dict = Dictionary::new(); +dict.insert("key1".into(), member_value1.into()); +dict.insert("key2".into(), member_value2.into()); +dict.insert("key3".into(), member_value3.into()); + +assert_eq!( + dict.serialize_value().unwrap(), + "key1=\"apple\", key2, key3=?0" +); + +``` +*/ + +mod parser; +mod ref_serializer; +mod serializer; +mod utils; + +#[cfg(test)] +mod test_parser; +#[cfg(test)] +mod test_serializer; +use indexmap::IndexMap; + +pub use rust_decimal::{ + prelude::{FromPrimitive, FromStr}, + Decimal, +}; + +pub use parser::{ParseMore, ParseValue, Parser}; +pub use ref_serializer::{RefDictSerializer, RefItemSerializer, RefListSerializer}; +pub use serializer::SerializeValue; + +type SFVResult<T> = std::result::Result<T, &'static str>; + +/// Represents `Item` type structured field value. +/// Can be used as a member of `List` or `Dictionary`. +// sf-item = bare-item parameters +// bare-item = sf-integer / sf-decimal / sf-string / sf-token +// / sf-binary / sf-boolean +#[derive(Debug, PartialEq, Clone)] +pub struct Item { + /// Value of `Item`. + pub bare_item: BareItem, + /// `Item`'s associated parameters. Can be empty. + pub params: Parameters, +} + +impl Item { + /// Returns new `Item` with empty `Parameters`. + pub fn new(bare_item: BareItem) -> Item { + Item { + bare_item, + params: Parameters::new(), + } + } + /// Returns new `Item` with specified `Parameters`. + pub fn with_params(bare_item: BareItem, params: Parameters) -> Item { + Item { bare_item, params } + } +} + +/// Represents `Dictionary` type structured field value. +// sf-dictionary = dict-member *( OWS "," OWS dict-member ) +// dict-member = member-name [ "=" member-value ] +// member-name = key +// member-value = sf-item / inner-list +pub type Dictionary = IndexMap<String, ListEntry>; + +/// Represents `List` type structured field value. +// sf-list = list-member *( OWS "," OWS list-member ) +// list-member = sf-item / inner-list +pub type List = Vec<ListEntry>; + +/// Parameters of `Item` or `InnerList`. +// parameters = *( ";" *SP parameter ) +// parameter = param-name [ "=" param-value ] +// param-name = key +// key = ( lcalpha / "*" ) +// *( lcalpha / DIGIT / "_" / "-" / "." / "*" ) +// lcalpha = %x61-7A ; a-z +// param-value = bare-item +pub type Parameters = IndexMap<String, BareItem>; + +/// Represents a member of `List` or `Dictionary` structured field value. +#[derive(Debug, PartialEq, Clone)] +pub enum ListEntry { + /// Member of `Item` type. + Item(Item), + /// Member of `InnerList` (array of `Items`) type. + InnerList(InnerList), +} + +impl From<Item> for ListEntry { + fn from(item: Item) -> Self { + ListEntry::Item(item) + } +} + +impl From<InnerList> for ListEntry { + fn from(item: InnerList) -> Self { + ListEntry::InnerList(item) + } +} + +/// Array of `Items` with associated `Parameters`. +// inner-list = "(" *SP [ sf-item *( 1*SP sf-item ) *SP ] ")" +// parameters +#[derive(Debug, PartialEq, Clone)] +pub struct InnerList { + /// `Items` that `InnerList` contains. Can be empty. + pub items: Vec<Item>, + /// `InnerList`'s associated parameters. Can be empty. + pub params: Parameters, +} + +impl InnerList { + /// Returns new `InnerList` with empty `Parameters`. + pub fn new(items: Vec<Item>) -> InnerList { + InnerList { + items, + params: Parameters::new(), + } + } + + /// Returns new `InnerList` with specified `Parameters`. + pub fn with_params(items: Vec<Item>, params: Parameters) -> InnerList { + InnerList { items, params } + } +} + +/// `BareItem` type is used to construct `Items` or `Parameters` values. +#[derive(Debug, PartialEq, Clone)] +pub enum BareItem { + /// Decimal number + // sf-decimal = ["-"] 1*12DIGIT "." 1*3DIGIT + Decimal(Decimal), + /// Integer number + // sf-integer = ["-"] 1*15DIGIT + Integer(i64), + // sf-string = DQUOTE *chr DQUOTE + // chr = unescaped / escaped + // unescaped = %x20-21 / %x23-5B / %x5D-7E + // escaped = "\" ( DQUOTE / "\" ) + String(String), + // ":" *(base64) ":" + // base64 = ALPHA / DIGIT / "+" / "/" / "=" + ByteSeq(Vec<u8>), + // sf-boolean = "?" boolean + // boolean = "0" / "1" + Boolean(bool), + // sf-token = ( ALPHA / "*" ) *( tchar / ":" / "/" ) + Token(String), +} + +impl BareItem { + /// If `BareItem` is a decimal, returns `Decimal`, otherwise returns `None`. + /// ``` + /// # use sfv::{BareItem, Decimal, FromPrimitive}; + /// let decimal_number = Decimal::from_f64(415.566).unwrap(); + /// let bare_item: BareItem = decimal_number.into(); + /// assert_eq!(bare_item.as_decimal().unwrap(), decimal_number); + /// ``` + pub fn as_decimal(&self) -> Option<Decimal> { + match *self { + BareItem::Decimal(val) => Some(val), + _ => None, + } + } + /// If `BareItem` is an integer, returns `i64`, otherwise returns `None`. + /// ``` + /// # use sfv::BareItem; + /// let bare_item: BareItem = 100.into(); + /// assert_eq!(bare_item.as_int().unwrap(), 100); + /// ``` + pub fn as_int(&self) -> Option<i64> { + match *self { + BareItem::Integer(val) => Some(val), + _ => None, + } + } + /// If `BareItem` is `String`, returns `&str`, otherwise returns `None`. + /// ``` + /// # use sfv::BareItem; + /// let bare_item = BareItem::String("foo".into()); + /// assert_eq!(bare_item.as_str().unwrap(), "foo"); + /// ``` + pub fn as_str(&self) -> Option<&str> { + match *self { + BareItem::String(ref val) => Some(val), + _ => None, + } + } + /// If `BareItem` is a `ByteSeq`, returns `&Vec<u8>`, otherwise returns `None`. + /// ``` + /// # use sfv::BareItem; + /// let bare_item = BareItem::ByteSeq("foo".to_owned().into_bytes()); + /// assert_eq!(bare_item.as_byte_seq().unwrap().as_slice(), "foo".as_bytes()); + /// ``` + pub fn as_byte_seq(&self) -> Option<&Vec<u8>> { + match *self { + BareItem::ByteSeq(ref val) => Some(val), + _ => None, + } + } + /// If `BareItem` is a `Boolean`, returns `bool`, otherwise returns `None`. + /// ``` + /// # use sfv::{BareItem, Decimal, FromPrimitive}; + /// let bare_item = BareItem::Boolean(true); + /// assert_eq!(bare_item.as_bool().unwrap(), true); + /// ``` + pub fn as_bool(&self) -> Option<bool> { + match *self { + BareItem::Boolean(val) => Some(val), + _ => None, + } + } + /// If `BareItem` is a `Token`, returns `&str`, otherwise returns `None`. + /// ``` + /// use sfv::BareItem; + /// + /// let bare_item = BareItem::Token("*bar".into()); + /// assert_eq!(bare_item.as_token().unwrap(), "*bar"); + /// ``` + pub fn as_token(&self) -> Option<&str> { + match *self { + BareItem::Token(ref val) => Some(val), + _ => None, + } + } +} + +impl From<i64> for BareItem { + /// Converts `i64` into `BareItem::Integer`. + /// ``` + /// # use sfv::BareItem; + /// let bare_item: BareItem = 456.into(); + /// assert_eq!(bare_item.as_int().unwrap(), 456); + /// ``` + fn from(item: i64) -> Self { + BareItem::Integer(item) + } +} + +impl From<Decimal> for BareItem { + /// Converts `Decimal` into `BareItem::Decimal`. + /// ``` + /// # use sfv::{BareItem, Decimal, FromPrimitive}; + /// let decimal_number = Decimal::from_f64(48.01).unwrap(); + /// let bare_item: BareItem = decimal_number.into(); + /// assert_eq!(bare_item.as_decimal().unwrap(), decimal_number); + /// ``` + fn from(item: Decimal) -> Self { + BareItem::Decimal(item) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) enum Num { + Decimal(Decimal), + Integer(i64), +} + +/// Similar to `BareItem`, but used to serialize values via `RefItemSerializer`, `RefListSerializer`, `RefDictSerializer`. +#[derive(Debug, PartialEq, Clone)] +pub enum RefBareItem<'a> { + Integer(i64), + Decimal(Decimal), + String(&'a str), + ByteSeq(&'a [u8]), + Boolean(bool), + Token(&'a str), +} + +impl BareItem { + /// Converts `BareItem` into `RefBareItem`. + fn to_ref_bare_item(&self) -> RefBareItem { + match self { + BareItem::Integer(val) => RefBareItem::Integer(*val), + BareItem::Decimal(val) => RefBareItem::Decimal(*val), + BareItem::String(val) => RefBareItem::String(val), + BareItem::ByteSeq(val) => RefBareItem::ByteSeq(val.as_slice()), + BareItem::Boolean(val) => RefBareItem::Boolean(*val), + BareItem::Token(val) => RefBareItem::Token(val), + } + } +} diff --git a/third_party/rust/sfv/src/parser.rs b/third_party/rust/sfv/src/parser.rs new file mode 100644 index 0000000000..943380f279 --- /dev/null +++ b/third_party/rust/sfv/src/parser.rs @@ -0,0 +1,473 @@ +use crate::utils; +use crate::{ + BareItem, Decimal, Dictionary, FromStr, InnerList, Item, List, ListEntry, Num, Parameters, + SFVResult, +}; +use std::iter::Peekable; +use std::str::{from_utf8, Chars}; + +/// Implements parsing logic for each structured field value type. +pub trait ParseValue { + /// This method should not be used for parsing input into structured field value. + /// Use `Parser::parse_item`, `Parser::parse_list` or `Parsers::parse_dictionary` for that. + fn parse(input_chars: &mut Peekable<Chars>) -> SFVResult<Self> + where + Self: Sized; +} + +/// If structured field value of List or Dictionary type is split into multiple lines, +/// allows to parse more lines and merge them into already existing structure field value. +pub trait ParseMore { + /// If structured field value is split across lines, + /// parses and merges next line into a single structured field value. + /// # Examples + /// ``` + /// # use sfv::{Parser, SerializeValue, ParseMore}; + /// + /// let mut list_field = Parser::parse_list("11, (12 13)".as_bytes()).unwrap(); + /// list_field.parse_more("\"foo\", \"bar\"".as_bytes()).unwrap(); + /// + /// assert_eq!(list_field.serialize_value().unwrap(), "11, (12 13), \"foo\", \"bar\""); + fn parse_more(&mut self, input_bytes: &[u8]) -> SFVResult<()> + where + Self: Sized; +} + +impl ParseValue for Item { + fn parse(input_chars: &mut Peekable<Chars>) -> SFVResult<Item> { + // https://httpwg.org/specs/rfc8941.html#parse-item + let bare_item = Parser::parse_bare_item(input_chars)?; + let params = Parser::parse_parameters(input_chars)?; + + Ok(Item { bare_item, params }) + } +} + +impl ParseValue for List { + fn parse(input_chars: &mut Peekable<Chars>) -> SFVResult<List> { + // https://httpwg.org/specs/rfc8941.html#parse-list + // List represents an array of (item_or_inner_list, parameters) + + let mut members = vec![]; + + while input_chars.peek().is_some() { + members.push(Parser::parse_list_entry(input_chars)?); + + utils::consume_ows_chars(input_chars); + + if input_chars.peek().is_none() { + return Ok(members); + } + + if let Some(c) = input_chars.next() { + if c != ',' { + return Err("parse_list: trailing characters after list member"); + } + } + + utils::consume_ows_chars(input_chars); + + if input_chars.peek().is_none() { + return Err("parse_list: trailing comma"); + } + } + + Ok(members) + } +} + +impl ParseValue for Dictionary { + fn parse(input_chars: &mut Peekable<Chars>) -> SFVResult<Dictionary> { + let mut dict = Dictionary::new(); + + while input_chars.peek().is_some() { + let this_key = Parser::parse_key(input_chars)?; + + if let Some('=') = input_chars.peek() { + input_chars.next(); + let member = Parser::parse_list_entry(input_chars)?; + dict.insert(this_key, member); + } else { + let value = true; + let params = Parser::parse_parameters(input_chars)?; + let member = Item { + bare_item: BareItem::Boolean(value), + params, + }; + dict.insert(this_key, member.into()); + } + + utils::consume_ows_chars(input_chars); + + if input_chars.peek().is_none() { + return Ok(dict); + } + + if let Some(c) = input_chars.next() { + if c != ',' { + return Err("parse_dict: trailing characters after dictionary member"); + } + } + + utils::consume_ows_chars(input_chars); + + if input_chars.peek().is_none() { + return Err("parse_dict: trailing comma"); + } + } + Ok(dict) + } +} + +impl ParseMore for List { + fn parse_more(&mut self, input_bytes: &[u8]) -> SFVResult<()> { + let parsed_list = Parser::parse_list(input_bytes)?; + self.extend(parsed_list); + Ok(()) + } +} + +impl ParseMore for Dictionary { + fn parse_more(&mut self, input_bytes: &[u8]) -> SFVResult<()> { + let parsed_dict = Parser::parse_dictionary(input_bytes)?; + self.extend(parsed_dict); + Ok(()) + } +} + +/// Exposes methods for parsing input into structured field value. +pub struct Parser; + +impl Parser { + /// Parses input into structured field value of Dictionary type + pub fn parse_dictionary(input_bytes: &[u8]) -> SFVResult<Dictionary> { + Self::parse::<Dictionary>(input_bytes) + } + + /// Parses input into structured field value of List type + pub fn parse_list(input_bytes: &[u8]) -> SFVResult<List> { + Self::parse::<List>(input_bytes) + } + + /// Parses input into structured field value of Item type + pub fn parse_item(input_bytes: &[u8]) -> SFVResult<Item> { + Self::parse::<Item>(input_bytes) + } + + // Generic parse method for checking input before parsing + // and handling trailing text error + fn parse<T: ParseValue>(input_bytes: &[u8]) -> SFVResult<T> { + // https://httpwg.org/specs/rfc8941.html#text-parse + if !input_bytes.is_ascii() { + return Err("parse: non-ascii characters in input"); + } + + let mut input_chars = from_utf8(input_bytes) + .map_err(|_| "parse: conversion from bytes to str failed")? + .chars() + .peekable(); + utils::consume_sp_chars(&mut input_chars); + + let output = T::parse(&mut input_chars)?; + + utils::consume_sp_chars(&mut input_chars); + + if input_chars.next().is_some() { + return Err("parse: trailing characters after parsed value"); + }; + Ok(output) + } + + fn parse_list_entry(input_chars: &mut Peekable<Chars>) -> SFVResult<ListEntry> { + // https://httpwg.org/specs/rfc8941.html#parse-item-or-list + // ListEntry represents a tuple (item_or_inner_list, parameters) + + match input_chars.peek() { + Some('(') => { + let parsed = Self::parse_inner_list(input_chars)?; + Ok(ListEntry::InnerList(parsed)) + } + _ => { + let parsed = Item::parse(input_chars)?; + Ok(ListEntry::Item(parsed)) + } + } + } + + pub(crate) fn parse_inner_list(input_chars: &mut Peekable<Chars>) -> SFVResult<InnerList> { + // https://httpwg.org/specs/rfc8941.html#parse-innerlist + + if Some('(') != input_chars.next() { + return Err("parse_inner_list: input does not start with '('"); + } + + let mut inner_list = Vec::new(); + while input_chars.peek().is_some() { + utils::consume_sp_chars(input_chars); + + if Some(&')') == input_chars.peek() { + input_chars.next(); + let params = Self::parse_parameters(input_chars)?; + return Ok(InnerList { + items: inner_list, + params, + }); + } + + let parsed_item = Item::parse(input_chars)?; + inner_list.push(parsed_item); + + if let Some(c) = input_chars.peek() { + if c != &' ' && c != &')' { + return Err("parse_inner_list: bad delimitation"); + } + } + } + + Err("parse_inner_list: the end of the inner list was not found") + } + + pub(crate) fn parse_bare_item(input_chars: &mut Peekable<Chars>) -> SFVResult<BareItem> { + // https://httpwg.org/specs/rfc8941.html#parse-bare-item + if input_chars.peek().is_none() { + return Err("parse_bare_item: empty item"); + } + + match input_chars.peek() { + Some(&'?') => Ok(BareItem::Boolean(Self::parse_bool(input_chars)?)), + Some(&'"') => Ok(BareItem::String(Self::parse_string(input_chars)?)), + Some(&':') => Ok(BareItem::ByteSeq(Self::parse_byte_sequence(input_chars)?)), + Some(&c) if c == '*' || c.is_ascii_alphabetic() => { + Ok(BareItem::Token(Self::parse_token(input_chars)?)) + } + Some(&c) if c == '-' || c.is_ascii_digit() => match Self::parse_number(input_chars)? { + Num::Decimal(val) => Ok(BareItem::Decimal(val)), + Num::Integer(val) => Ok(BareItem::Integer(val)), + }, + _ => Err("parse_bare_item: item type can't be identified"), + } + } + + pub(crate) fn parse_bool(input_chars: &mut Peekable<Chars>) -> SFVResult<bool> { + // https://httpwg.org/specs/rfc8941.html#parse-boolean + + if input_chars.next() != Some('?') { + return Err("parse_bool: first character is not '?'"); + } + + match input_chars.next() { + Some('0') => Ok(false), + Some('1') => Ok(true), + _ => Err("parse_bool: invalid variant"), + } + } + + pub(crate) fn parse_string(input_chars: &mut Peekable<Chars>) -> SFVResult<String> { + // https://httpwg.org/specs/rfc8941.html#parse-string + + if input_chars.next() != Some('\"') { + return Err("parse_string: first character is not '\"'"); + } + + let mut output_string = String::from(""); + while let Some(curr_char) = input_chars.next() { + match curr_char { + '\"' => return Ok(output_string), + '\x7f' | '\x00'..='\x1f' => return Err("parse_string: not a visible character"), + '\\' => match input_chars.next() { + Some(c) if c == '\\' || c == '\"' => { + output_string.push(c); + } + None => return Err("parse_string: last input character is '\\'"), + _ => return Err("parse_string: disallowed character after '\\'"), + }, + _ => output_string.push(curr_char), + } + } + Err("parse_string: no closing '\"'") + } + + pub(crate) fn parse_token(input_chars: &mut Peekable<Chars>) -> SFVResult<String> { + // https://httpwg.org/specs/rfc8941.html#parse-token + + if let Some(first_char) = input_chars.peek() { + if !first_char.is_ascii_alphabetic() && first_char != &'*' { + return Err("parse_token: first character is not ALPHA or '*'"); + } + } else { + return Err("parse_token: empty input string"); + } + + let mut output_string = String::from(""); + while let Some(curr_char) = input_chars.peek() { + if !utils::is_tchar(*curr_char) && curr_char != &':' && curr_char != &'/' { + return Ok(output_string); + } + + match input_chars.next() { + Some(c) => output_string.push(c), + None => return Err("parse_token: end of the string"), + } + } + Ok(output_string) + } + + pub(crate) fn parse_byte_sequence(input_chars: &mut Peekable<Chars>) -> SFVResult<Vec<u8>> { + // https://httpwg.org/specs/rfc8941.html#parse-binary + + if input_chars.next() != Some(':') { + return Err("parse_byte_seq: first char is not ':'"); + } + + if !input_chars.clone().any(|c| c == ':') { + return Err("parse_byte_seq: no closing ':'"); + } + + let b64_content = input_chars.take_while(|c| c != &':').collect::<String>(); + if !b64_content.chars().all(utils::is_allowed_b64_content) { + return Err("parse_byte_seq: invalid char in byte sequence"); + } + match utils::base64()?.decode(b64_content.as_bytes()) { + Ok(content) => Ok(content), + Err(_) => Err("parse_byte_seq: decoding error"), + } + } + + pub(crate) fn parse_number(input_chars: &mut Peekable<Chars>) -> SFVResult<Num> { + // https://httpwg.org/specs/rfc8941.html#parse-number + + let mut sign = 1; + if let Some('-') = input_chars.peek() { + sign = -1; + input_chars.next(); + } + + match input_chars.peek() { + Some(c) if !c.is_ascii_digit() => { + return Err("parse_number: input number does not start with a digit") + } + None => return Err("parse_number: input number lacks a digit"), + _ => (), + } + + // Get number from input as a string and identify whether it's a decimal or integer + let (is_integer, input_number) = Self::extract_digits(input_chars)?; + + // Parse input_number from string into integer + if is_integer { + let output_number = input_number + .parse::<i64>() + .map_err(|_err| "parse_number: parsing i64 failed")? + * sign; + + let (min_int, max_int) = (-999_999_999_999_999_i64, 999_999_999_999_999_i64); + if !(min_int <= output_number && output_number <= max_int) { + return Err("parse_number: integer number is out of range"); + } + + return Ok(Num::Integer(output_number)); + } + + // Parse input_number from string into decimal + let chars_after_dot = input_number + .find('.') + .map(|dot_pos| input_number.len() - dot_pos - 1); + + match chars_after_dot { + Some(0) => Err("parse_number: decimal ends with '.'"), + Some(1..=3) => { + let mut output_number = Decimal::from_str(&input_number) + .map_err(|_err| "parse_number: parsing f64 failed")?; + + if sign == -1 { + output_number.set_sign_negative(true) + } + + Ok(Num::Decimal(output_number)) + } + _ => Err("parse_number: invalid decimal fraction length"), + } + } + + fn extract_digits(input_chars: &mut Peekable<Chars>) -> SFVResult<(bool, String)> { + let mut is_integer = true; + let mut input_number = String::from(""); + while let Some(curr_char) = input_chars.peek() { + if curr_char.is_ascii_digit() { + input_number.push(*curr_char); + input_chars.next(); + } else if curr_char == &'.' && is_integer { + if input_number.len() > 12 { + return Err( + "parse_number: decimal too long, illegal position for decimal point", + ); + } + input_number.push(*curr_char); + is_integer = false; + input_chars.next(); + } else { + break; + } + + if is_integer && input_number.len() > 15 { + return Err("parse_number: integer too long, length > 15"); + } + + if !is_integer && input_number.len() > 16 { + return Err("parse_number: decimal too long, length > 16"); + } + } + Ok((is_integer, input_number)) + } + + pub(crate) fn parse_parameters(input_chars: &mut Peekable<Chars>) -> SFVResult<Parameters> { + // https://httpwg.org/specs/rfc8941.html#parse-param + + let mut params = Parameters::new(); + + while let Some(curr_char) = input_chars.peek() { + if curr_char == &';' { + input_chars.next(); + } else { + break; + } + + utils::consume_sp_chars(input_chars); + + let param_name = Self::parse_key(input_chars)?; + let param_value = match input_chars.peek() { + Some('=') => { + input_chars.next(); + Self::parse_bare_item(input_chars)? + } + _ => BareItem::Boolean(true), + }; + params.insert(param_name, param_value); + } + + // If parameters already contains a name param_name (comparing character-for-character), overwrite its value. + // Note that when duplicate Parameter keys are encountered, this has the effect of ignoring all but the last instance. + Ok(params) + } + + pub(crate) fn parse_key(input_chars: &mut Peekable<Chars>) -> SFVResult<String> { + match input_chars.peek() { + Some(c) if c == &'*' || c.is_ascii_lowercase() => (), + _ => return Err("parse_key: first character is not lcalpha or '*'"), + } + + let mut output = String::new(); + while let Some(curr_char) = input_chars.peek() { + if !curr_char.is_ascii_lowercase() + && !curr_char.is_ascii_digit() + && !"_-*.".contains(*curr_char) + { + return Ok(output); + } + + output.push(*curr_char); + input_chars.next(); + } + Ok(output) + } +} diff --git a/third_party/rust/sfv/src/ref_serializer.rs b/third_party/rust/sfv/src/ref_serializer.rs new file mode 100644 index 0000000000..4bcd29ff3a --- /dev/null +++ b/third_party/rust/sfv/src/ref_serializer.rs @@ -0,0 +1,310 @@ +use crate::serializer::Serializer; +use crate::{RefBareItem, SFVResult}; +use std::marker::PhantomData; + +/// Serializes `Item` field value components incrementally. +/// ``` +/// use sfv::{RefBareItem, RefItemSerializer}; +/// +/// let mut serialized_item = String::new(); +/// let serializer = RefItemSerializer::new(&mut serialized_item); +/// serializer +/// .bare_item(&RefBareItem::Integer(11)) +/// .unwrap() +/// .parameter("foo", &RefBareItem::Boolean(true)) +/// .unwrap(); +/// assert_eq!(serialized_item, "11;foo"); +/// ``` +#[derive(Debug)] +pub struct RefItemSerializer<'a> { + pub buffer: &'a mut String, +} + +impl<'a> RefItemSerializer<'a> { + pub fn new(buffer: &'a mut String) -> Self { + RefItemSerializer { buffer } + } + + pub fn bare_item(self, bare_item: &RefBareItem) -> SFVResult<RefParameterSerializer<'a>> { + Serializer::serialize_ref_bare_item(bare_item, self.buffer)?; + Ok(RefParameterSerializer { + buffer: self.buffer, + }) + } +} + +/// Used by `RefItemSerializer`, `RefListSerializer`, `RefDictSerializer` to serialize a single `Parameter`. +#[derive(Debug)] +pub struct RefParameterSerializer<'a> { + buffer: &'a mut String, +} + +impl<'a> RefParameterSerializer<'a> { + pub fn parameter(self, name: &str, value: &RefBareItem) -> SFVResult<Self> { + Serializer::serialize_ref_parameter(name, value, self.buffer)?; + Ok(self) + } +} + +/// Serializes `List` field value components incrementally. +/// ``` +/// use sfv::{RefBareItem, RefListSerializer}; +/// +/// let mut serialized_item = String::new(); +/// let serializer = RefListSerializer::new(&mut serialized_item); +/// serializer +/// .bare_item(&RefBareItem::Integer(11)) +/// .unwrap() +/// .parameter("foo", &RefBareItem::Boolean(true)) +/// .unwrap() +/// .open_inner_list() +/// .inner_list_bare_item(&RefBareItem::Token("abc")) +/// .unwrap() +/// .inner_list_parameter("abc_param", &RefBareItem::Boolean(false)) +/// .unwrap() +/// .inner_list_bare_item(&RefBareItem::Token("def")) +/// .unwrap() +/// .close_inner_list() +/// .parameter("bar", &RefBareItem::String("val")) +/// .unwrap(); +/// assert_eq!( +/// serialized_item, +/// "11;foo, (abc;abc_param=?0 def);bar=\"val\"" +/// ); +/// ``` +#[derive(Debug)] +pub struct RefListSerializer<'a> { + buffer: &'a mut String, +} + +impl<'a> RefListSerializer<'a> { + pub fn new(buffer: &'a mut String) -> Self { + RefListSerializer { buffer } + } + + pub fn bare_item(self, bare_item: &RefBareItem) -> SFVResult<Self> { + if !self.buffer.is_empty() { + self.buffer.push_str(", "); + } + Serializer::serialize_ref_bare_item(bare_item, self.buffer)?; + Ok(RefListSerializer { + buffer: self.buffer, + }) + } + + pub fn parameter(self, name: &str, value: &RefBareItem) -> SFVResult<Self> { + if self.buffer.is_empty() { + return Err("parameters must be serialized after bare item or inner list"); + } + Serializer::serialize_ref_parameter(name, value, self.buffer)?; + Ok(RefListSerializer { + buffer: self.buffer, + }) + } + pub fn open_inner_list(self) -> RefInnerListSerializer<'a, Self> { + if !self.buffer.is_empty() { + self.buffer.push_str(", "); + } + self.buffer.push('('); + RefInnerListSerializer::<RefListSerializer> { + buffer: self.buffer, + caller_type: PhantomData, + } + } +} + +/// Serializes `Dictionary` field value components incrementally. +/// ``` +/// use sfv::{RefBareItem, RefDictSerializer, Decimal, FromPrimitive}; +/// +/// let mut serialized_item = String::new(); +/// let serializer = RefDictSerializer::new(&mut serialized_item); +/// serializer +/// .bare_item_member("member1", &RefBareItem::Integer(11)) +/// .unwrap() +/// .parameter("foo", &RefBareItem::Boolean(true)) +/// .unwrap() +/// .open_inner_list("member2") +/// .unwrap() +/// .inner_list_bare_item(&RefBareItem::Token("abc")) +/// .unwrap() +/// .inner_list_parameter("abc_param", &RefBareItem::Boolean(false)) +/// .unwrap() +/// .inner_list_bare_item(&RefBareItem::Token("def")) +/// .unwrap() +/// .close_inner_list() +/// .parameter("bar", &RefBareItem::String("val")) +/// .unwrap() +/// .bare_item_member( +/// "member3", +/// &RefBareItem::Decimal(Decimal::from_f64(12.34566).unwrap()), +/// ) +/// .unwrap(); +/// assert_eq!( +/// serialized_item, +/// "member1=11;foo, member2=(abc;abc_param=?0 def);bar=\"val\", member3=12.346" +/// ); +/// ``` +#[derive(Debug)] +pub struct RefDictSerializer<'a> { + buffer: &'a mut String, +} + +impl<'a> RefDictSerializer<'a> { + pub fn new(buffer: &'a mut String) -> Self { + RefDictSerializer { buffer } + } + + pub fn bare_item_member(self, name: &str, value: &RefBareItem) -> SFVResult<Self> { + if !self.buffer.is_empty() { + self.buffer.push_str(", "); + } + Serializer::serialize_key(name, self.buffer)?; + if value != &RefBareItem::Boolean(true) { + self.buffer.push('='); + Serializer::serialize_ref_bare_item(value, self.buffer)?; + } + Ok(self) + } + + pub fn parameter(self, name: &str, value: &RefBareItem) -> SFVResult<Self> { + if self.buffer.is_empty() { + return Err("parameters must be serialized after bare item or inner list"); + } + Serializer::serialize_ref_parameter(name, value, self.buffer)?; + Ok(RefDictSerializer { + buffer: self.buffer, + }) + } + + pub fn open_inner_list(self, name: &str) -> SFVResult<RefInnerListSerializer<'a, Self>> { + if !self.buffer.is_empty() { + self.buffer.push_str(", "); + } + Serializer::serialize_key(name, self.buffer)?; + self.buffer.push_str("=("); + Ok(RefInnerListSerializer::<RefDictSerializer> { + buffer: self.buffer, + caller_type: PhantomData, + }) + } +} + +/// Used by `RefItemSerializer`, `RefListSerializer`, `RefDictSerializer` to serialize `InnerList`. +#[derive(Debug)] +pub struct RefInnerListSerializer<'a, T> { + buffer: &'a mut String, + caller_type: PhantomData<T>, +} + +impl<'a, T: Container<'a>> RefInnerListSerializer<'a, T> { + pub fn inner_list_bare_item(self, bare_item: &RefBareItem) -> SFVResult<Self> { + if !self.buffer.is_empty() & !self.buffer.ends_with('(') { + self.buffer.push(' '); + } + Serializer::serialize_ref_bare_item(bare_item, self.buffer)?; + Ok(RefInnerListSerializer { + buffer: self.buffer, + caller_type: PhantomData, + }) + } + + pub fn inner_list_parameter(self, name: &str, value: &RefBareItem) -> SFVResult<Self> { + if self.buffer.is_empty() { + return Err("parameters must be serialized after bare item or inner list"); + } + Serializer::serialize_ref_parameter(name, value, self.buffer)?; + Ok(RefInnerListSerializer { + buffer: self.buffer, + caller_type: PhantomData, + }) + } + + pub fn close_inner_list(self) -> T { + self.buffer.push(')'); + T::new(self.buffer) + } +} + +pub trait Container<'a> { + fn new(buffer: &'a mut String) -> Self; +} + +impl<'a> Container<'a> for RefListSerializer<'a> { + fn new(buffer: &mut String) -> RefListSerializer { + RefListSerializer { buffer } + } +} + +impl<'a> Container<'a> for RefDictSerializer<'a> { + fn new(buffer: &mut String) -> RefDictSerializer { + RefDictSerializer { buffer } + } +} + +#[cfg(test)] +mod alternative_serializer_tests { + use super::*; + use crate::{Decimal, FromPrimitive}; + + #[test] + fn test_fast_serialize_item() -> SFVResult<()> { + let mut output = String::new(); + let ser = RefItemSerializer::new(&mut output); + ser.bare_item(&RefBareItem::Token("hello"))? + .parameter("abc", &RefBareItem::Boolean(true))?; + assert_eq!("hello;abc", output); + Ok(()) + } + + #[test] + fn test_fast_serialize_list() -> SFVResult<()> { + let mut output = String::new(); + let ser = RefListSerializer::new(&mut output); + ser.bare_item(&RefBareItem::Token("hello"))? + .parameter("key1", &RefBareItem::Boolean(true))? + .parameter("key2", &RefBareItem::Boolean(false))? + .open_inner_list() + .inner_list_bare_item(&RefBareItem::String("some_string"))? + .inner_list_bare_item(&RefBareItem::Integer(12))? + .inner_list_parameter("inner-member-key", &RefBareItem::Boolean(true))? + .close_inner_list() + .parameter("inner-list-param", &RefBareItem::Token("*"))?; + assert_eq!( + "hello;key1;key2=?0, (\"some_string\" 12;inner-member-key);inner-list-param=*", + output + ); + Ok(()) + } + + #[test] + fn test_fast_serialize_dict() -> SFVResult<()> { + let mut output = String::new(); + let ser = RefDictSerializer::new(&mut output); + ser.bare_item_member("member1", &RefBareItem::Token("hello"))? + .parameter("key1", &RefBareItem::Boolean(true))? + .parameter("key2", &RefBareItem::Boolean(false))? + .bare_item_member("member2", &RefBareItem::Boolean(true))? + .parameter( + "key3", + &RefBareItem::Decimal(Decimal::from_f64(45.4586).unwrap()), + )? + .parameter("key4", &RefBareItem::String("str"))? + .open_inner_list("key5")? + .inner_list_bare_item(&RefBareItem::Integer(45))? + .inner_list_bare_item(&RefBareItem::Integer(0))? + .close_inner_list() + .bare_item_member("key6", &RefBareItem::String("foo"))? + .open_inner_list("key7")? + .inner_list_bare_item(&RefBareItem::ByteSeq("some_string".as_bytes()))? + .inner_list_bare_item(&RefBareItem::ByteSeq("other_string".as_bytes()))? + .close_inner_list() + .parameter("lparam", &RefBareItem::Integer(10))? + .bare_item_member("key8", &RefBareItem::Boolean(true))?; + assert_eq!( + "member1=hello;key1;key2=?0, member2;key3=45.459;key4=\"str\", key5=(45 0), key6=\"foo\", key7=(:c29tZV9zdHJpbmc=: :b3RoZXJfc3RyaW5n:);lparam=10, key8", + output + ); + Ok(()) + } +} diff --git a/third_party/rust/sfv/src/serializer.rs b/third_party/rust/sfv/src/serializer.rs new file mode 100644 index 0000000000..b0ed021766 --- /dev/null +++ b/third_party/rust/sfv/src/serializer.rs @@ -0,0 +1,319 @@ +use crate::utils; +use crate::{ + BareItem, Decimal, Dictionary, InnerList, Item, List, ListEntry, Parameters, RefBareItem, + SFVResult, +}; +use data_encoding::BASE64; + +/// Serializes structured field value into String. +pub trait SerializeValue { + /// Serializes structured field value into String. + /// # Examples + /// ``` + /// # use sfv::{Parser, SerializeValue, ParseValue}; + /// + /// let parsed_list_field = Parser::parse_list("\"london\", \t\t\"berlin\"".as_bytes()); + /// assert!(parsed_list_field.is_ok()); + /// + /// assert_eq!( + /// parsed_list_field.unwrap().serialize_value().unwrap(), + /// "\"london\", \"berlin\"" + /// ); + /// ``` + fn serialize_value(&self) -> SFVResult<String>; +} + +impl SerializeValue for Dictionary { + fn serialize_value(&self) -> SFVResult<String> { + let mut output = String::new(); + Serializer::serialize_dict(self, &mut output)?; + Ok(output) + } +} + +impl SerializeValue for List { + fn serialize_value(&self) -> SFVResult<String> { + let mut output = String::new(); + Serializer::serialize_list(self, &mut output)?; + Ok(output) + } +} + +impl SerializeValue for Item { + fn serialize_value(&self) -> SFVResult<String> { + let mut output = String::new(); + Serializer::serialize_item(self, &mut output)?; + Ok(output) + } +} + +/// Container serialization functions +pub(crate) struct Serializer; + +impl Serializer { + pub(crate) fn serialize_item(input_item: &Item, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-item + + Self::serialize_bare_item(&input_item.bare_item, output)?; + Self::serialize_parameters(&input_item.params, output)?; + Ok(()) + } + + #[allow(clippy::ptr_arg)] + pub(crate) fn serialize_list(input_list: &List, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-list + if input_list.is_empty() { + return Err("serialize_list: serializing empty field is not allowed"); + } + + for (idx, member) in input_list.iter().enumerate() { + match member { + ListEntry::Item(item) => { + Self::serialize_item(item, output)?; + } + ListEntry::InnerList(inner_list) => { + Self::serialize_inner_list(inner_list, output)?; + } + }; + + // If more items remain in input_list: + // Append “,” to output. + // Append a single SP to output. + if idx < input_list.len() - 1 { + output.push_str(", "); + } + } + Ok(()) + } + + pub(crate) fn serialize_dict(input_dict: &Dictionary, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-dictionary + if input_dict.is_empty() { + return Err("serialize_dictionary: serializing empty field is not allowed"); + } + + for (idx, (member_name, member_value)) in input_dict.iter().enumerate() { + Serializer::serialize_key(member_name, output)?; + + match member_value { + ListEntry::Item(ref item) => { + // If dict member is boolean true, no need to serialize it: only its params must be serialized + // Otherwise serialize entire item with its params + if item.bare_item == BareItem::Boolean(true) { + Self::serialize_parameters(&item.params, output)?; + } else { + output.push('='); + Self::serialize_item(item, output)?; + } + } + ListEntry::InnerList(inner_list) => { + output.push('='); + Self::serialize_inner_list(inner_list, output)?; + } + } + + // If more items remain in input_dictionary: + // Append “,” to output. + // Append a single SP to output. + if idx < input_dict.len() - 1 { + output.push_str(", "); + } + } + Ok(()) + } + + fn serialize_inner_list(input_inner_list: &InnerList, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-innerlist + + let items = &input_inner_list.items; + let inner_list_parameters = &input_inner_list.params; + + output.push('('); + for (idx, item) in items.iter().enumerate() { + Self::serialize_item(item, output)?; + + // If more values remain in inner_list, append a single SP to output + if idx < items.len() - 1 { + output.push(' '); + } + } + output.push(')'); + Self::serialize_parameters(inner_list_parameters, output)?; + Ok(()) + } + + pub(crate) fn serialize_bare_item( + input_bare_item: &BareItem, + output: &mut String, + ) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-bare-item + + let ref_bare_item = input_bare_item.to_ref_bare_item(); + Self::serialize_ref_bare_item(&ref_bare_item, output) + } + + pub(crate) fn serialize_ref_bare_item( + value: &RefBareItem, + output: &mut String, + ) -> SFVResult<()> { + match value { + RefBareItem::Boolean(value) => Self::serialize_bool(*value, output)?, + RefBareItem::String(value) => Self::serialize_string(value, output)?, + RefBareItem::ByteSeq(value) => Self::serialize_byte_sequence(value, output)?, + RefBareItem::Token(value) => Self::serialize_token(value, output)?, + RefBareItem::Integer(value) => Self::serialize_integer(*value, output)?, + RefBareItem::Decimal(value) => Self::serialize_decimal(*value, output)?, + }; + Ok(()) + } + + pub(crate) fn serialize_parameters( + input_params: &Parameters, + output: &mut String, + ) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-params + + for (param_name, param_value) in input_params.iter() { + Self::serialize_ref_parameter(param_name, ¶m_value.to_ref_bare_item(), output)?; + } + Ok(()) + } + + pub(crate) fn serialize_ref_parameter( + name: &str, + value: &RefBareItem, + output: &mut String, + ) -> SFVResult<()> { + output.push(';'); + Self::serialize_key(name, output)?; + + if value != &RefBareItem::Boolean(true) { + output.push('='); + Self::serialize_ref_bare_item(value, output)?; + } + Ok(()) + } + + pub(crate) fn serialize_key(input_key: &str, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-key + + let disallowed_chars = + |c: char| !(c.is_ascii_lowercase() || c.is_ascii_digit() || "_-*.".contains(c)); + + if input_key.chars().any(disallowed_chars) { + return Err("serialize_key: disallowed character in input"); + } + + if let Some(char) = input_key.chars().next() { + if !(char.is_ascii_lowercase() || char == '*') { + return Err("serialize_key: first character is not lcalpha or '*'"); + } + } + output.push_str(input_key); + Ok(()) + } + + pub(crate) fn serialize_integer(value: i64, output: &mut String) -> SFVResult<()> { + //https://httpwg.org/specs/rfc8941.html#ser-integer + + let (min_int, max_int) = (-999_999_999_999_999_i64, 999_999_999_999_999_i64); + if !(min_int <= value && value <= max_int) { + return Err("serialize_integer: integer is out of range"); + } + output.push_str(&value.to_string()); + Ok(()) + } + + pub(crate) fn serialize_decimal(value: Decimal, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-decimal + + let integer_comp_length = 12; + let fraction_length = 3; + + let decimal = value.round_dp(fraction_length); + let int_comp = decimal.trunc(); + let fract_comp = decimal.fract(); + + // TODO: Replace with > 999_999_999_999_u64 + if int_comp.abs().to_string().len() > integer_comp_length { + return Err("serialize_decimal: integer component > 12 digits"); + } + + if fract_comp.is_zero() { + output.push_str(&int_comp.to_string()); + output.push('.'); + output.push('0'); + } else { + output.push_str(&decimal.to_string()); + } + + Ok(()) + } + + pub(crate) fn serialize_string(value: &str, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-integer + + if !value.is_ascii() { + return Err("serialize_string: non-ascii character"); + } + + let vchar_or_sp = |char| char == '\x7f' || ('\x00'..='\x1f').contains(&char); + if value.chars().any(vchar_or_sp) { + return Err("serialize_string: not a visible character"); + } + + output.push('\"'); + for char in value.chars() { + if char == '\\' || char == '\"' { + output.push('\\'); + } + output.push(char); + } + output.push('\"'); + + Ok(()) + } + + pub(crate) fn serialize_token(value: &str, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-token + + if !value.is_ascii() { + return Err("serialize_string: non-ascii character"); + } + + let mut chars = value.chars(); + if let Some(char) = chars.next() { + if !(char.is_ascii_alphabetic() || char == '*') { + return Err("serialise_token: first character is not ALPHA or '*'"); + } + } + + if chars + .clone() + .any(|c| !(utils::is_tchar(c) || c == ':' || c == '/')) + { + return Err("serialise_token: disallowed character"); + } + + output.push_str(value); + Ok(()) + } + + pub(crate) fn serialize_byte_sequence(value: &[u8], output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-binary + + output.push(':'); + let encoded = BASE64.encode(value.as_ref()); + output.push_str(&encoded); + output.push(':'); + Ok(()) + } + + pub(crate) fn serialize_bool(value: bool, output: &mut String) -> SFVResult<()> { + // https://httpwg.org/specs/rfc8941.html#ser-boolean + + let val = if value { "?1" } else { "?0" }; + output.push_str(val); + Ok(()) + } +} diff --git a/third_party/rust/sfv/src/test_parser.rs b/third_party/rust/sfv/src/test_parser.rs new file mode 100644 index 0000000000..97404eb951 --- /dev/null +++ b/third_party/rust/sfv/src/test_parser.rs @@ -0,0 +1,850 @@ +use crate::FromStr; +use crate::{BareItem, Decimal, Dictionary, InnerList, Item, List, Num, Parameters}; +use crate::{ParseMore, ParseValue, Parser}; +use std::error::Error; +use std::iter::FromIterator; + +#[test] +fn parse() -> Result<(), Box<dyn Error>> { + let input = "\"some_value\"".as_bytes(); + let parsed_item = Item::new(BareItem::String("some_value".to_owned())); + let expected = parsed_item; + assert_eq!(expected, Parser::parse_item(input)?); + + let input = "12.35;a ".as_bytes(); + let params = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true))]); + let expected = Item::with_params(Decimal::from_str("12.35")?.into(), params); + + assert_eq!(expected, Parser::parse_item(input)?); + Ok(()) +} + +#[test] +fn parse_errors() -> Result<(), Box<dyn Error>> { + let input = "\"some_value¢\"".as_bytes(); + assert_eq!( + Err("parse: non-ascii characters in input"), + Parser::parse_item(input) + ); + let input = "\"some_value\" trailing_text".as_bytes(); + assert_eq!( + Err("parse: trailing characters after parsed value"), + Parser::parse_item(input) + ); + assert_eq!( + Err("parse_bare_item: empty item"), + Parser::parse_item("".as_bytes()) + ); + Ok(()) +} + +#[test] +fn parse_list_of_numbers() -> Result<(), Box<dyn Error>> { + let mut input = "1,42".chars().peekable(); + let item1 = Item::new(1.into()); + let item2 = Item::new(42.into()); + let expected_list: List = vec![item1.into(), item2.into()]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_with_multiple_spaces() -> Result<(), Box<dyn Error>> { + let mut input = "1 , 42".chars().peekable(); + let item1 = Item::new(1.into()); + let item2 = Item::new(42.into()); + let expected_list: List = vec![item1.into(), item2.into()]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_of_lists() -> Result<(), Box<dyn Error>> { + let mut input = "(1 2), (42 43)".chars().peekable(); + let item1 = Item::new(1.into()); + let item2 = Item::new(2.into()); + let item3 = Item::new(42.into()); + let item4 = Item::new(43.into()); + let inner_list_1 = InnerList::new(vec![item1, item2]); + let inner_list_2 = InnerList::new(vec![item3, item4]); + let expected_list: List = vec![inner_list_1.into(), inner_list_2.into()]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_empty_inner_list() -> Result<(), Box<dyn Error>> { + let mut input = "()".chars().peekable(); + let inner_list = InnerList::new(vec![]); + let expected_list: List = vec![inner_list.into()]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_empty() -> Result<(), Box<dyn Error>> { + let mut input = "".chars().peekable(); + let expected_list: List = vec![]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_of_lists_with_param_and_spaces() -> Result<(), Box<dyn Error>> { + let mut input = "( 1 42 ); k=*".chars().peekable(); + let item1 = Item::new(1.into()); + let item2 = Item::new(42.into()); + let inner_list_param = + Parameters::from_iter(vec![("k".to_owned(), BareItem::Token("*".to_owned()))]); + let inner_list = InnerList::with_params(vec![item1, item2], inner_list_param); + let expected_list: List = vec![inner_list.into()]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_of_items_and_lists_with_param() -> Result<(), Box<dyn Error>> { + let mut input = "12, 14, (a b); param=\"param_value_1\", ()" + .chars() + .peekable(); + let item1 = Item::new(12.into()); + let item2 = Item::new(14.into()); + let item3 = Item::new(BareItem::Token("a".to_owned())); + let item4 = Item::new(BareItem::Token("b".to_owned())); + let inner_list_param = Parameters::from_iter(vec![( + "param".to_owned(), + BareItem::String("param_value_1".to_owned()), + )]); + let inner_list = InnerList::with_params(vec![item3, item4], inner_list_param); + let empty_inner_list = InnerList::new(vec![]); + let expected_list: List = vec![ + item1.into(), + item2.into(), + inner_list.into(), + empty_inner_list.into(), + ]; + assert_eq!(expected_list, List::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_list_errors() -> Result<(), Box<dyn Error>> { + let mut input = ",".chars().peekable(); + assert_eq!( + Err("parse_bare_item: item type can't be identified"), + List::parse(&mut input) + ); + + let mut input = "a, b c".chars().peekable(); + assert_eq!( + Err("parse_list: trailing characters after list member"), + List::parse(&mut input) + ); + + let mut input = "a,".chars().peekable(); + assert_eq!(Err("parse_list: trailing comma"), List::parse(&mut input)); + + let mut input = "a , ".chars().peekable(); + assert_eq!(Err("parse_list: trailing comma"), List::parse(&mut input)); + + let mut input = "a\t \t ,\t ".chars().peekable(); + assert_eq!(Err("parse_list: trailing comma"), List::parse(&mut input)); + + let mut input = "a\t\t,\t\t\t".chars().peekable(); + assert_eq!(Err("parse_list: trailing comma"), List::parse(&mut input)); + + let mut input = "(a b),".chars().peekable(); + assert_eq!(Err("parse_list: trailing comma"), List::parse(&mut input)); + + let mut input = "(1, 2, (a b)".chars().peekable(); + assert_eq!( + Err("parse_inner_list: bad delimitation"), + List::parse(&mut input) + ); + + Ok(()) +} + +#[test] +fn parse_inner_list_errors() -> Result<(), Box<dyn Error>> { + let mut input = "c b); a=1".chars().peekable(); + assert_eq!( + Err("parse_inner_list: input does not start with '('"), + Parser::parse_inner_list(&mut input) + ); + Ok(()) +} + +#[test] +fn parse_inner_list_with_param_and_spaces() -> Result<(), Box<dyn Error>> { + let mut input = "(c b); a=1".chars().peekable(); + let inner_list_param = Parameters::from_iter(vec![("a".to_owned(), 1.into())]); + + let item1 = Item::new(BareItem::Token("c".to_owned())); + let item2 = Item::new(BareItem::Token("b".to_owned())); + let expected = InnerList::with_params(vec![item1, item2], inner_list_param); + assert_eq!(expected, Parser::parse_inner_list(&mut input)?); + Ok(()) +} + +#[test] +fn parse_item_int_with_space() -> Result<(), Box<dyn Error>> { + let mut input = "12 ".chars().peekable(); + assert_eq!(Item::new(12.into()), Item::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_item_decimal_with_bool_param_and_space() -> Result<(), Box<dyn Error>> { + let mut input = "12.35;a ".chars().peekable(); + let param = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true))]); + assert_eq!( + Item::with_params(Decimal::from_str("12.35")?.into(), param), + Item::parse(&mut input)? + ); + Ok(()) +} + +#[test] +fn parse_item_number_with_param() -> Result<(), Box<dyn Error>> { + let param = Parameters::from_iter(vec![("a1".to_owned(), BareItem::Token("*".to_owned()))]); + assert_eq!( + Item::with_params(BareItem::String("12.35".to_owned()), param), + Item::parse(&mut "\"12.35\";a1=*".chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_item_errors() -> Result<(), Box<dyn Error>> { + assert_eq!( + Err("parse_bare_item: empty item"), + Item::parse(&mut "".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_dict_empty() -> Result<(), Box<dyn Error>> { + assert_eq!( + Dictionary::new(), + Dictionary::parse(&mut "".chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_dict_errors() -> Result<(), Box<dyn Error>> { + let mut input = "abc=123;a=1;b=2 def".chars().peekable(); + assert_eq!( + Err("parse_dict: trailing characters after dictionary member"), + Dictionary::parse(&mut input) + ); + let mut input = "abc=123;a=1,".chars().peekable(); + assert_eq!( + Err("parse_dict: trailing comma"), + Dictionary::parse(&mut input) + ); + Ok(()) +} + +#[test] +fn parse_dict_with_spaces_and_params() -> Result<(), Box<dyn Error>> { + let mut input = "abc=123;a=1;b=2, def=456, ghi=789;q=9;r=\"+w\"" + .chars() + .peekable(); + let item1_params = + Parameters::from_iter(vec![("a".to_owned(), 1.into()), ("b".to_owned(), 2.into())]); + let item3_params = Parameters::from_iter(vec![ + ("q".to_owned(), 9.into()), + ("r".to_owned(), BareItem::String("+w".to_owned())), + ]); + + let item1 = Item::with_params(123.into(), item1_params); + let item2 = Item::new(456.into()); + let item3 = Item::with_params(789.into(), item3_params); + + let expected_dict = Dictionary::from_iter(vec![ + ("abc".to_owned(), item1.into()), + ("def".to_owned(), item2.into()), + ("ghi".to_owned(), item3.into()), + ]); + assert_eq!(expected_dict, Dictionary::parse(&mut input)?); + + Ok(()) +} + +#[test] +fn parse_dict_empty_value() -> Result<(), Box<dyn Error>> { + let mut input = "a=()".chars().peekable(); + let inner_list = InnerList::new(vec![]); + let expected_dict = Dictionary::from_iter(vec![("a".to_owned(), inner_list.into())]); + assert_eq!(expected_dict, Dictionary::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_dict_with_token_param() -> Result<(), Box<dyn Error>> { + let mut input = "a=1, b;foo=*, c=3".chars().peekable(); + let item2_params = + Parameters::from_iter(vec![("foo".to_owned(), BareItem::Token("*".to_owned()))]); + let item1 = Item::new(1.into()); + let item2 = Item::with_params(BareItem::Boolean(true), item2_params); + let item3 = Item::new(3.into()); + let expected_dict = Dictionary::from_iter(vec![ + ("a".to_owned(), item1.into()), + ("b".to_owned(), item2.into()), + ("c".to_owned(), item3.into()), + ]); + assert_eq!(expected_dict, Dictionary::parse(&mut input)?); + Ok(()) +} + +#[test] +fn parse_dict_multiple_spaces() -> Result<(), Box<dyn Error>> { + // input1, input2, input3 must be parsed into the same structure + let item1 = Item::new(1.into()); + let item2 = Item::new(2.into()); + let expected_dict = Dictionary::from_iter(vec![ + ("a".to_owned(), item1.into()), + ("b".to_owned(), item2.into()), + ]); + + let mut input1 = "a=1 , b=2".chars().peekable(); + let mut input2 = "a=1\t,\tb=2".chars().peekable(); + let mut input3 = "a=1, b=2".chars().peekable(); + assert_eq!(expected_dict, Dictionary::parse(&mut input1)?); + assert_eq!(expected_dict, Dictionary::parse(&mut input2)?); + assert_eq!(expected_dict, Dictionary::parse(&mut input3)?); + + Ok(()) +} + +#[test] +fn parse_bare_item() -> Result<(), Box<dyn Error>> { + assert_eq!( + BareItem::Boolean(false), + Parser::parse_bare_item(&mut "?0".chars().peekable())? + ); + assert_eq!( + BareItem::String("test string".to_owned()), + Parser::parse_bare_item(&mut "\"test string\"".chars().peekable())? + ); + assert_eq!( + BareItem::Token("*token".to_owned()), + Parser::parse_bare_item(&mut "*token".chars().peekable())? + ); + assert_eq!( + BareItem::ByteSeq("base_64 encoding test".to_owned().into_bytes()), + Parser::parse_bare_item(&mut ":YmFzZV82NCBlbmNvZGluZyB0ZXN0:".chars().peekable())? + ); + assert_eq!( + BareItem::Decimal(Decimal::from_str("-3.55")?), + Parser::parse_bare_item(&mut "-3.55".chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_bare_item_errors() -> Result<(), Box<dyn Error>> { + assert_eq!( + Err("parse_bare_item: item type can't be identified"), + Parser::parse_bare_item(&mut "!?0".chars().peekable()) + ); + assert_eq!( + Err("parse_bare_item: item type can't be identified"), + Parser::parse_bare_item(&mut "_11abc".chars().peekable()) + ); + assert_eq!( + Err("parse_bare_item: item type can't be identified"), + Parser::parse_bare_item(&mut " ".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_bool() -> Result<(), Box<dyn Error>> { + let mut input = "?0gk".chars().peekable(); + assert_eq!(false, Parser::parse_bool(&mut input)?); + assert_eq!(input.collect::<String>(), "gk"); + + assert_eq!(false, Parser::parse_bool(&mut "?0".chars().peekable())?); + assert_eq!(true, Parser::parse_bool(&mut "?1".chars().peekable())?); + Ok(()) +} + +#[test] +fn parse_bool_errors() -> Result<(), Box<dyn Error>> { + assert_eq!( + Err("parse_bool: first character is not '?'"), + Parser::parse_bool(&mut "".chars().peekable()) + ); + assert_eq!( + Err("parse_bool: invalid variant"), + Parser::parse_bool(&mut "?".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_string() -> Result<(), Box<dyn Error>> { + let mut input = "\"some string\" ;not string".chars().peekable(); + assert_eq!("some string".to_owned(), Parser::parse_string(&mut input)?); + assert_eq!(input.collect::<String>(), " ;not string"); + + assert_eq!( + "test".to_owned(), + Parser::parse_string(&mut "\"test\"".chars().peekable())? + ); + assert_eq!( + r#"te\st"#.to_owned(), + Parser::parse_string(&mut "\"te\\\\st\"".chars().peekable())? + ); + assert_eq!( + "".to_owned(), + Parser::parse_string(&mut "\"\"".chars().peekable())? + ); + assert_eq!( + "some string".to_owned(), + Parser::parse_string(&mut "\"some string\"".chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_string_errors() -> Result<(), Box<dyn Error>> { + assert_eq!( + Err("parse_string: first character is not '\"'"), + Parser::parse_string(&mut "test".chars().peekable()) + ); + assert_eq!( + Err("parse_string: last input character is '\\'"), + Parser::parse_string(&mut "\"\\".chars().peekable()) + ); + assert_eq!( + Err("parse_string: disallowed character after '\\'"), + Parser::parse_string(&mut "\"\\l\"".chars().peekable()) + ); + assert_eq!( + Err("parse_string: not a visible character"), + Parser::parse_string(&mut "\"\u{1f}\"".chars().peekable()) + ); + assert_eq!( + Err("parse_string: no closing '\"'"), + Parser::parse_string(&mut "\"smth".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_token() -> Result<(), Box<dyn Error>> { + let mut input = "*some:token}not token".chars().peekable(); + assert_eq!("*some:token".to_owned(), Parser::parse_token(&mut input)?); + assert_eq!(input.collect::<String>(), "}not token"); + + assert_eq!( + "token".to_owned(), + Parser::parse_token(&mut "token".chars().peekable())? + ); + assert_eq!( + "a_b-c.d3:f%00/*".to_owned(), + Parser::parse_token(&mut "a_b-c.d3:f%00/*".chars().peekable())? + ); + assert_eq!( + "TestToken".to_owned(), + Parser::parse_token(&mut "TestToken".chars().peekable())? + ); + assert_eq!( + "some".to_owned(), + Parser::parse_token(&mut "some@token".chars().peekable())? + ); + assert_eq!( + "*TestToken*".to_owned(), + Parser::parse_token(&mut "*TestToken*".chars().peekable())? + ); + assert_eq!( + "*".to_owned(), + Parser::parse_token(&mut "*[@:token".chars().peekable())? + ); + assert_eq!( + "test".to_owned(), + Parser::parse_token(&mut "test token".chars().peekable())? + ); + + Ok(()) +} + +#[test] +fn parse_token_errors() -> Result<(), Box<dyn Error>> { + let mut input = "765token".chars().peekable(); + assert_eq!( + Err("parse_token: first character is not ALPHA or '*'"), + Parser::parse_token(&mut input) + ); + assert_eq!(input.collect::<String>(), "765token"); + + assert_eq!( + Err("parse_token: first character is not ALPHA or '*'"), + Parser::parse_token(&mut "7token".chars().peekable()) + ); + assert_eq!( + Err("parse_token: empty input string"), + Parser::parse_token(&mut "".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_byte_sequence() -> Result<(), Box<dyn Error>> { + let mut input = ":aGVsbG8:rest_of_str".chars().peekable(); + assert_eq!( + "hello".to_owned().into_bytes(), + Parser::parse_byte_sequence(&mut input)? + ); + assert_eq!("rest_of_str", input.collect::<String>()); + + assert_eq!( + "hello".to_owned().into_bytes(), + Parser::parse_byte_sequence(&mut ":aGVsbG8:".chars().peekable())? + ); + assert_eq!( + "test_encode".to_owned().into_bytes(), + Parser::parse_byte_sequence(&mut ":dGVzdF9lbmNvZGU:".chars().peekable())? + ); + assert_eq!( + "new:year tree".to_owned().into_bytes(), + Parser::parse_byte_sequence(&mut ":bmV3OnllYXIgdHJlZQ==:".chars().peekable())? + ); + assert_eq!( + "".to_owned().into_bytes(), + Parser::parse_byte_sequence(&mut "::".chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_byte_sequence_errors() -> Result<(), Box<dyn Error>> { + assert_eq!( + Err("parse_byte_seq: first char is not ':'"), + Parser::parse_byte_sequence(&mut "aGVsbG8".chars().peekable()) + ); + assert_eq!( + Err("parse_byte_seq: invalid char in byte sequence"), + Parser::parse_byte_sequence(&mut ":aGVsb G8=:".chars().peekable()) + ); + assert_eq!( + Err("parse_byte_seq: no closing ':'"), + Parser::parse_byte_sequence(&mut ":aGVsbG8=".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_number_int() -> Result<(), Box<dyn Error>> { + let mut input = "-733333333332d.14".chars().peekable(); + assert_eq!( + Num::Integer(-733333333332), + Parser::parse_number(&mut input)? + ); + assert_eq!("d.14", input.collect::<String>()); + + assert_eq!( + Num::Integer(42), + Parser::parse_number(&mut "42".chars().peekable())? + ); + assert_eq!( + Num::Integer(-42), + Parser::parse_number(&mut "-42".chars().peekable())? + ); + assert_eq!( + Num::Integer(-42), + Parser::parse_number(&mut "-042".chars().peekable())? + ); + assert_eq!( + Num::Integer(0), + Parser::parse_number(&mut "0".chars().peekable())? + ); + assert_eq!( + Num::Integer(0), + Parser::parse_number(&mut "00".chars().peekable())? + ); + assert_eq!( + Num::Integer(123456789012345), + Parser::parse_number(&mut "123456789012345".chars().peekable())? + ); + assert_eq!( + Num::Integer(-123456789012345), + Parser::parse_number(&mut "-123456789012345".chars().peekable())? + ); + assert_eq!( + Num::Integer(2), + Parser::parse_number(&mut "2,3".chars().peekable())? + ); + assert_eq!( + Num::Integer(4), + Parser::parse_number(&mut "4-2".chars().peekable())? + ); + assert_eq!( + Num::Integer(-999999999999999), + Parser::parse_number(&mut "-999999999999999".chars().peekable())? + ); + assert_eq!( + Num::Integer(999999999999999), + Parser::parse_number(&mut "999999999999999".chars().peekable())? + ); + + Ok(()) +} + +#[test] +fn parse_number_decimal() -> Result<(), Box<dyn Error>> { + let mut input = "00.42 test string".chars().peekable(); + assert_eq!( + Num::Decimal(Decimal::from_str("0.42")?), + Parser::parse_number(&mut input)? + ); + assert_eq!(" test string", input.collect::<String>()); + + assert_eq!( + Num::Decimal(Decimal::from_str("1.5")?), + Parser::parse_number(&mut "1.5.4.".chars().peekable())? + ); + assert_eq!( + Num::Decimal(Decimal::from_str("1.8")?), + Parser::parse_number(&mut "1.8.".chars().peekable())? + ); + assert_eq!( + Num::Decimal(Decimal::from_str("1.7")?), + Parser::parse_number(&mut "1.7.0".chars().peekable())? + ); + assert_eq!( + Num::Decimal(Decimal::from_str("3.14")?), + Parser::parse_number(&mut "3.14".chars().peekable())? + ); + assert_eq!( + Num::Decimal(Decimal::from_str("-3.14")?), + Parser::parse_number(&mut "-3.14".chars().peekable())? + ); + assert_eq!( + Num::Decimal(Decimal::from_str("123456789012.1")?), + Parser::parse_number(&mut "123456789012.1".chars().peekable())? + ); + assert_eq!( + Num::Decimal(Decimal::from_str("1234567890.112")?), + Parser::parse_number(&mut "1234567890.112".chars().peekable())? + ); + + Ok(()) +} + +#[test] +fn parse_number_errors() -> Result<(), Box<dyn Error>> { + let mut input = ":aGVsbG8:rest".chars().peekable(); + assert_eq!( + Err("parse_number: input number does not start with a digit"), + Parser::parse_number(&mut input) + ); + assert_eq!(":aGVsbG8:rest", input.collect::<String>()); + + let mut input = "-11.5555 test string".chars().peekable(); + assert_eq!( + Err("parse_number: invalid decimal fraction length"), + Parser::parse_number(&mut input) + ); + assert_eq!(" test string", input.collect::<String>()); + + assert_eq!( + Err("parse_number: input number does not start with a digit"), + Parser::parse_number(&mut "--0".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal too long, illegal position for decimal point"), + Parser::parse_number(&mut "1999999999999.1".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal ends with '.'"), + Parser::parse_number(&mut "19888899999.".chars().peekable()) + ); + assert_eq!( + Err("parse_number: integer too long, length > 15"), + Parser::parse_number(&mut "1999999999999999".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal too long, length > 16"), + Parser::parse_number(&mut "19999999999.99991".chars().peekable()) + ); + assert_eq!( + Err("parse_number: input number does not start with a digit"), + Parser::parse_number(&mut "- 42".chars().peekable()) + ); + assert_eq!( + Err("parse_number: input number does not start with a digit"), + Parser::parse_number(&mut "- 42".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal ends with '.'"), + Parser::parse_number(&mut "1..4".chars().peekable()) + ); + assert_eq!( + Err("parse_number: input number lacks a digit"), + Parser::parse_number(&mut "-".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal ends with '.'"), + Parser::parse_number(&mut "-5. 14".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal ends with '.'"), + Parser::parse_number(&mut "7. 1".chars().peekable()) + ); + assert_eq!( + Err("parse_number: invalid decimal fraction length"), + Parser::parse_number(&mut "-7.3333333333".chars().peekable()) + ); + assert_eq!( + Err("parse_number: decimal too long, illegal position for decimal point"), + Parser::parse_number(&mut "-7333333333323.12".chars().peekable()) + ); + + Ok(()) +} + +#[test] +fn parse_params_string() -> Result<(), Box<dyn Error>> { + let mut input = ";b=\"param_val\"".chars().peekable(); + let expected = Parameters::from_iter(vec![( + "b".to_owned(), + BareItem::String("param_val".to_owned()), + )]); + assert_eq!(expected, Parser::parse_parameters(&mut input)?); + Ok(()) +} + +#[test] +fn parse_params_bool() -> Result<(), Box<dyn Error>> { + let mut input = ";b;a".chars().peekable(); + let expected = Parameters::from_iter(vec![ + ("b".to_owned(), BareItem::Boolean(true)), + ("a".to_owned(), BareItem::Boolean(true)), + ]); + assert_eq!(expected, Parser::parse_parameters(&mut input)?); + Ok(()) +} + +#[test] +fn parse_params_mixed_types() -> Result<(), Box<dyn Error>> { + let mut input = ";key1=?0;key2=746.15".chars().peekable(); + let expected = Parameters::from_iter(vec![ + ("key1".to_owned(), BareItem::Boolean(false)), + ("key2".to_owned(), Decimal::from_str("746.15")?.into()), + ]); + assert_eq!(expected, Parser::parse_parameters(&mut input)?); + Ok(()) +} + +#[test] +fn parse_params_with_spaces() -> Result<(), Box<dyn Error>> { + let mut input = "; key1=?0; key2=11111".chars().peekable(); + let expected = Parameters::from_iter(vec![ + ("key1".to_owned(), BareItem::Boolean(false)), + ("key2".to_owned(), 11111.into()), + ]); + assert_eq!(expected, Parser::parse_parameters(&mut input)?); + Ok(()) +} + +#[test] +fn parse_params_empty() -> Result<(), Box<dyn Error>> { + assert_eq!( + Parameters::new(), + Parser::parse_parameters(&mut " key1=?0; key2=11111".chars().peekable())? + ); + assert_eq!( + Parameters::new(), + Parser::parse_parameters(&mut "".chars().peekable())? + ); + assert_eq!( + Parameters::new(), + Parser::parse_parameters(&mut "[;a=1".chars().peekable())? + ); + assert_eq!( + Parameters::new(), + Parser::parse_parameters(&mut String::new().chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_key() -> Result<(), Box<dyn Error>> { + assert_eq!( + "a".to_owned(), + Parser::parse_key(&mut "a=1".chars().peekable())? + ); + assert_eq!( + "a1".to_owned(), + Parser::parse_key(&mut "a1=10".chars().peekable())? + ); + assert_eq!( + "*1".to_owned(), + Parser::parse_key(&mut "*1=10".chars().peekable())? + ); + assert_eq!( + "f".to_owned(), + Parser::parse_key(&mut "f[f=10".chars().peekable())? + ); + Ok(()) +} + +#[test] +fn parse_key_errors() -> Result<(), Box<dyn Error>> { + assert_eq!( + Err("parse_key: first character is not lcalpha or '*'"), + Parser::parse_key(&mut "[*f=10".chars().peekable()) + ); + Ok(()) +} + +#[test] +fn parse_more_list() -> Result<(), Box<dyn Error>> { + let item1 = Item::new(1.into()); + let item2 = Item::new(2.into()); + let item3 = Item::new(42.into()); + let inner_list_1 = InnerList::new(vec![item1, item2]); + let expected_list: List = vec![inner_list_1.into(), item3.into()]; + + let mut parsed_header = Parser::parse_list("(1 2)".as_bytes())?; + let _ = parsed_header.parse_more("42".as_bytes())?; + assert_eq!(expected_list, parsed_header); + Ok(()) +} + +#[test] +fn parse_more_dict() -> Result<(), Box<dyn Error>> { + let item2_params = + Parameters::from_iter(vec![("foo".to_owned(), BareItem::Token("*".to_owned()))]); + let item1 = Item::new(1.into()); + let item2 = Item::with_params(BareItem::Boolean(true), item2_params); + let item3 = Item::new(3.into()); + let expected_dict = Dictionary::from_iter(vec![ + ("a".to_owned(), item1.into()), + ("b".to_owned(), item2.into()), + ("c".to_owned(), item3.into()), + ]); + + let mut parsed_header = Parser::parse_dictionary("a=1, b;foo=*\t\t".as_bytes())?; + let _ = parsed_header.parse_more(" c=3".as_bytes())?; + assert_eq!(expected_dict, parsed_header); + Ok(()) +} + +#[test] +fn parse_more_errors() -> Result<(), Box<dyn Error>> { + let parsed_dict_header = + Parser::parse_dictionary("a=1, b;foo=*".as_bytes())?.parse_more(",a".as_bytes()); + assert!(parsed_dict_header.is_err()); + + let parsed_list_header = + Parser::parse_list("a, b;foo=*".as_bytes())?.parse_more("(a, 2)".as_bytes()); + assert!(parsed_list_header.is_err()); + Ok(()) +} diff --git a/third_party/rust/sfv/src/test_serializer.rs b/third_party/rust/sfv/src/test_serializer.rs new file mode 100644 index 0000000000..edcc79ce2a --- /dev/null +++ b/third_party/rust/sfv/src/test_serializer.rs @@ -0,0 +1,531 @@ +use crate::serializer::Serializer; +use crate::FromStr; +use crate::SerializeValue; +use crate::{BareItem, Decimal, Dictionary, InnerList, Item, List, Parameters}; +use std::error::Error; +use std::iter::FromIterator; + +#[test] +fn serialize_value_empty_dict() -> Result<(), Box<dyn Error>> { + let dict_field_value = Dictionary::new(); + assert_eq!( + Err("serialize_dictionary: serializing empty field is not allowed"), + dict_field_value.serialize_value() + ); + Ok(()) +} + +#[test] +fn serialize_value_empty_list() -> Result<(), Box<dyn Error>> { + let list_field_value = List::new(); + assert_eq!( + Err("serialize_list: serializing empty field is not allowed"), + list_field_value.serialize_value() + ); + Ok(()) +} + +#[test] +fn serialize_value_list_mixed_members_with_params() -> Result<(), Box<dyn Error>> { + let item1 = Item::new(Decimal::from_str("42.4568")?.into()); + let item2_param = Parameters::from_iter(vec![("itm2_p".to_owned(), BareItem::Boolean(true))]); + let item2 = Item::with_params(17.into(), item2_param); + + let inner_list_item1_param = + Parameters::from_iter(vec![("in1_p".to_owned(), BareItem::Boolean(false))]); + let inner_list_item1 = + Item::with_params(BareItem::String("str1".to_owned()), inner_list_item1_param); + let inner_list_item2_param = Parameters::from_iter(vec![( + "in2_p".to_owned(), + BareItem::String("valu\\e".to_owned()), + )]); + let inner_list_item2 = + Item::with_params(BareItem::Token("str2".to_owned()), inner_list_item2_param); + let inner_list_param = Parameters::from_iter(vec![( + "inner_list_param".to_owned(), + BareItem::ByteSeq("weather".as_bytes().to_vec()), + )]); + let inner_list = + InnerList::with_params(vec![inner_list_item1, inner_list_item2], inner_list_param); + + let list_field_value: List = vec![item1.into(), item2.into(), inner_list.into()]; + let expected = "42.457, 17;itm2_p, (\"str1\";in1_p=?0 str2;in2_p=\"valu\\\\e\");inner_list_param=:d2VhdGhlcg==:"; + assert_eq!(expected, list_field_value.serialize_value()?); + Ok(()) +} + +#[test] +fn serialize_value_errors() -> Result<(), Box<dyn Error>> { + let disallowed_item = Item::new(BareItem::String("non-ascii text 🐹".into())); + assert_eq!( + Err("serialize_string: non-ascii character"), + disallowed_item.serialize_value() + ); + + let disallowed_item = Item::new(Decimal::from_str("12345678912345.123")?.into()); + assert_eq!( + Err("serialize_decimal: integer component > 12 digits"), + disallowed_item.serialize_value() + ); + + let param_with_disallowed_key = Parameters::from_iter(vec![("_key".to_owned(), 13.into())]); + let disallowed_item = Item::with_params(12.into(), param_with_disallowed_key); + assert_eq!( + Err("serialize_key: first character is not lcalpha or '*'"), + disallowed_item.serialize_value() + ); + Ok(()) +} + +#[test] +fn serialize_item_byteseq_with_param() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let item_param = ("a".to_owned(), BareItem::Token("*ab_1".into())); + let item_param = Parameters::from_iter(vec![item_param]); + let item = Item::with_params(BareItem::ByteSeq("parser".as_bytes().to_vec()), item_param); + Serializer::serialize_item(&item, &mut buf)?; + assert_eq!(":cGFyc2Vy:;a=*ab_1", &buf); + Ok(()) +} + +#[test] +fn serialize_item_without_params() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + let item = Item::new(1.into()); + Serializer::serialize_item(&item, &mut buf)?; + assert_eq!("1", &buf); + Ok(()) +} + +#[test] +fn serialize_item_with_bool_true_param() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + let param = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true))]); + let item = Item::with_params(Decimal::from_str("12.35")?.into(), param); + Serializer::serialize_item(&item, &mut buf)?; + assert_eq!("12.35;a", &buf); + Ok(()) +} + +#[test] +fn serialize_item_with_token_param() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + let param = Parameters::from_iter(vec![("a1".to_owned(), BareItem::Token("*tok".to_owned()))]); + let item = Item::with_params(BareItem::String("12.35".to_owned()), param); + Serializer::serialize_item(&item, &mut buf)?; + assert_eq!("\"12.35\";a1=*tok", &buf); + Ok(()) +} + +#[test] +fn serialize_integer() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_integer(-12, &mut buf)?; + assert_eq!("-12", &buf); + + buf.clear(); + Serializer::serialize_integer(0, &mut buf)?; + assert_eq!("0", &buf); + + buf.clear(); + Serializer::serialize_integer(999_999_999_999_999, &mut buf)?; + assert_eq!("999999999999999", &buf); + + buf.clear(); + Serializer::serialize_integer(-999_999_999_999_999, &mut buf)?; + assert_eq!("-999999999999999", &buf); + Ok(()) +} + +#[test] +fn serialize_integer_errors() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + assert_eq!( + Err("serialize_integer: integer is out of range"), + Serializer::serialize_integer(1_000_000_000_000_000, &mut buf) + ); + + buf.clear(); + assert_eq!( + Err("serialize_integer: integer is out of range"), + Serializer::serialize_integer(-1_000_000_000_000_000, &mut buf) + ); + Ok(()) +} + +#[test] +fn serialize_decimal() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_decimal(Decimal::from_str("-99.1346897")?, &mut buf)?; + assert_eq!("-99.135", &buf); + + buf.clear(); + Serializer::serialize_decimal(Decimal::from_str("-1.00")?, &mut buf)?; + assert_eq!("-1.0", &buf); + + buf.clear(); + Serializer::serialize_decimal( + Decimal::from_str("-00000000000000000000000099.1346897")?, + &mut buf, + )?; + assert_eq!("-99.135", &buf); + + buf.clear(); + Serializer::serialize_decimal(Decimal::from_str("100.13")?, &mut buf)?; + assert_eq!("100.13", &buf); + + buf.clear(); + Serializer::serialize_decimal(Decimal::from_str("-100.130")?, &mut buf)?; + assert_eq!("-100.130", &buf); + + buf.clear(); + Serializer::serialize_decimal(Decimal::from_str("-137.0")?, &mut buf)?; + assert_eq!("-137.0", &buf); + + buf.clear(); + Serializer::serialize_decimal(Decimal::from_str("137121212112.123")?, &mut buf)?; + assert_eq!("137121212112.123", &buf); + + buf.clear(); + Serializer::serialize_decimal(Decimal::from_str("137121212112.1238")?, &mut buf)?; + assert_eq!("137121212112.124", &buf); + Ok(()) +} + +#[test] +fn serialize_decimal_errors() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + assert_eq!( + Err("serialize_decimal: integer component > 12 digits"), + Serializer::serialize_decimal(Decimal::from_str("1371212121121.1")?, &mut buf) + ); + Ok(()) +} + +#[test] +fn serialize_string() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_string("1.1 text", &mut buf)?; + assert_eq!("\"1.1 text\"", &buf); + + buf.clear(); + Serializer::serialize_string("hello \"name\"", &mut buf)?; + assert_eq!("\"hello \\\"name\\\"\"", &buf); + + buf.clear(); + Serializer::serialize_string("something\\nothing", &mut buf)?; + assert_eq!("\"something\\\\nothing\"", &buf); + + buf.clear(); + Serializer::serialize_string("", &mut buf)?; + assert_eq!("\"\"", &buf); + + buf.clear(); + Serializer::serialize_string(" ", &mut buf)?; + assert_eq!("\" \"", &buf); + + buf.clear(); + Serializer::serialize_string(" ", &mut buf)?; + assert_eq!("\" \"", &buf); + Ok(()) +} + +#[test] +fn serialize_string_errors() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + assert_eq!( + Err("serialize_string: not a visible character"), + Serializer::serialize_string("text \x00", &mut buf) + ); + + assert_eq!( + Err("serialize_string: not a visible character"), + Serializer::serialize_string("text \x1f", &mut buf) + ); + assert_eq!( + Err("serialize_string: not a visible character"), + Serializer::serialize_string("text \x7f", &mut buf) + ); + assert_eq!( + Err("serialize_string: non-ascii character"), + Serializer::serialize_string("рядок", &mut buf) + ); + Ok(()) +} + +#[test] +fn serialize_token() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_token("*", &mut buf)?; + assert_eq!("*", &buf); + + buf.clear(); + Serializer::serialize_token("abc", &mut buf)?; + assert_eq!("abc", &buf); + + buf.clear(); + Serializer::serialize_token("abc:de", &mut buf)?; + assert_eq!("abc:de", &buf); + + buf.clear(); + Serializer::serialize_token("smth/#!else", &mut buf)?; + assert_eq!("smth/#!else", &buf); + Ok(()) +} + +#[test] +fn serialize_token_errors() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + assert_eq!( + Err("serialise_token: first character is not ALPHA or '*'"), + Serializer::serialize_token("#some", &mut buf) + ); + assert_eq!( + Err("serialise_token: disallowed character"), + Serializer::serialize_token("s ", &mut buf) + ); + assert_eq!( + Err("serialise_token: disallowed character"), + Serializer::serialize_token("abc:de\t", &mut buf) + ); + Ok(()) +} + +#[test] +fn serialize_byte_sequence() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_byte_sequence("hello".as_bytes(), &mut buf)?; + assert_eq!(":aGVsbG8=:", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("test_encode".as_bytes(), &mut buf)?; + assert_eq!(":dGVzdF9lbmNvZGU=:", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("".as_bytes(), &mut buf)?; + assert_eq!("::", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("pleasure.".as_bytes(), &mut buf)?; + assert_eq!(":cGxlYXN1cmUu:", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("leasure.".as_bytes(), &mut buf)?; + assert_eq!(":bGVhc3VyZS4=:", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("easure.".as_bytes(), &mut buf)?; + assert_eq!(":ZWFzdXJlLg==:", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("asure.".as_bytes(), &mut buf)?; + assert_eq!(":YXN1cmUu:", &buf); + + buf.clear(); + Serializer::serialize_byte_sequence("sure.".as_bytes(), &mut buf)?; + assert_eq!(":c3VyZS4=:", &buf); + + Ok(()) +} + +#[test] +fn serialize_bool() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_bool(true, &mut buf)?; + assert_eq!("?1", &buf); + + buf.clear(); + Serializer::serialize_bool(false, &mut buf)?; + assert_eq!("?0", &buf); + Ok(()) +} + +#[test] +fn serialize_params_bool() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let input = Parameters::from_iter(vec![ + ("*b".to_owned(), BareItem::Boolean(true)), + ("a.a".to_owned(), BareItem::Boolean(true)), + ]); + + Serializer::serialize_parameters(&input, &mut buf)?; + assert_eq!(";*b;a.a", &buf); + Ok(()) +} + +#[test] +fn serialize_params_string() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let input = Parameters::from_iter(vec![( + "b".to_owned(), + BareItem::String("param_val".to_owned()), + )]); + Serializer::serialize_parameters(&input, &mut buf)?; + assert_eq!(";b=\"param_val\"", &buf); + Ok(()) +} + +#[test] +fn serialize_params_numbers() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let input = Parameters::from_iter(vec![ + ("key1".to_owned(), Decimal::from_str("746.15")?.into()), + ("key2".to_owned(), 11111.into()), + ]); + Serializer::serialize_parameters(&input, &mut buf)?; + assert_eq!(";key1=746.15;key2=11111", &buf); + Ok(()) +} + +#[test] +fn serialize_params_mixed_types() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let input = Parameters::from_iter(vec![ + ("key1".to_owned(), BareItem::Boolean(false)), + ("key2".to_owned(), Decimal::from_str("1354.091878")?.into()), + ]); + Serializer::serialize_parameters(&input, &mut buf)?; + assert_eq!(";key1=?0;key2=1354.092", &buf); + Ok(()) +} + +#[test] +fn serialize_key() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + Serializer::serialize_key("*a_fg", &mut buf)?; + assert_eq!("*a_fg", &buf); + + buf.clear(); + Serializer::serialize_key("*a_fg*", &mut buf)?; + assert_eq!("*a_fg*", &buf); + + buf.clear(); + Serializer::serialize_key("key1", &mut buf)?; + assert_eq!("key1", &buf); + + buf.clear(); + Serializer::serialize_key("ke-y.1", &mut buf)?; + assert_eq!("ke-y.1", &buf); + + Ok(()) +} + +#[test] +fn serialize_key_erros() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + assert_eq!( + Err("serialize_key: disallowed character in input"), + Serializer::serialize_key("AND", &mut buf) + ); + assert_eq!( + Err("serialize_key: first character is not lcalpha or '*'"), + Serializer::serialize_key("_key", &mut buf) + ); + assert_eq!( + Err("serialize_key: first character is not lcalpha or '*'"), + Serializer::serialize_key("7key", &mut buf) + ); + Ok(()) +} + +#[test] +fn serialize_list_of_items_and_inner_list() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let item1 = Item::new(12.into()); + let item2 = Item::new(14.into()); + let item3 = Item::new(BareItem::Token("a".to_owned())); + let item4 = Item::new(BareItem::Token("b".to_owned())); + let inner_list_param = Parameters::from_iter(vec![( + "param".to_owned(), + BareItem::String("param_value_1".to_owned()), + )]); + let inner_list = InnerList::with_params(vec![item3, item4], inner_list_param); + let input: List = vec![item1.into(), item2.into(), inner_list.into()]; + + Serializer::serialize_list(&input, &mut buf)?; + assert_eq!("12, 14, (a b);param=\"param_value_1\"", &buf); + Ok(()) +} + +#[test] +fn serialize_list_of_lists() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let item1 = Item::new(1.into()); + let item2 = Item::new(2.into()); + let item3 = Item::new(42.into()); + let item4 = Item::new(43.into()); + let inner_list_1 = InnerList::new(vec![item1, item2]); + let inner_list_2 = InnerList::new(vec![item3, item4]); + let input: List = vec![inner_list_1.into(), inner_list_2.into()]; + + Serializer::serialize_list(&input, &mut buf)?; + assert_eq!("(1 2), (42 43)", &buf); + Ok(()) +} + +#[test] +fn serialize_list_with_bool_item_and_bool_params() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let item1_params = Parameters::from_iter(vec![ + ("a".to_owned(), BareItem::Boolean(true)), + ("b".to_owned(), BareItem::Boolean(false)), + ]); + let item1 = Item::with_params(BareItem::Boolean(false), item1_params); + let item2 = Item::new(BareItem::Token("cde_456".to_owned())); + + let input: List = vec![item1.into(), item2.into()]; + Serializer::serialize_list(&input, &mut buf)?; + assert_eq!("?0;a;b=?0, cde_456", &buf); + Ok(()) +} + +#[test] +fn serialize_dictionary_with_params() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let item1_params = Parameters::from_iter(vec![ + ("a".to_owned(), 1.into()), + ("b".to_owned(), BareItem::Boolean(true)), + ]); + let item2_params = Parameters::new(); + let item3_params = Parameters::from_iter(vec![ + ("q".to_owned(), BareItem::Boolean(false)), + ("r".to_owned(), BareItem::String("+w".to_owned())), + ]); + + let item1 = Item::with_params(123.into(), item1_params); + let item2 = Item::with_params(456.into(), item2_params); + let item3 = Item::with_params(789.into(), item3_params); + + let input = Dictionary::from_iter(vec![ + ("abc".to_owned(), item1.into()), + ("def".to_owned(), item2.into()), + ("ghi".to_owned(), item3.into()), + ]); + + Serializer::serialize_dict(&input, &mut buf)?; + assert_eq!("abc=123;a=1;b, def=456, ghi=789;q=?0;r=\"+w\"", &buf); + Ok(()) +} + +#[test] +fn serialize_dict_empty_member_value() -> Result<(), Box<dyn Error>> { + let mut buf = String::new(); + + let inner_list = InnerList::new(vec![]); + let input = Dictionary::from_iter(vec![("a".to_owned(), inner_list.into())]); + Serializer::serialize_dict(&input, &mut buf)?; + assert_eq!("a=()", &buf); + Ok(()) +} diff --git a/third_party/rust/sfv/src/utils.rs b/third_party/rust/sfv/src/utils.rs new file mode 100644 index 0000000000..7eb79340ba --- /dev/null +++ b/third_party/rust/sfv/src/utils.rs @@ -0,0 +1,44 @@ +use data_encoding::{Encoding, Specification}; +use std::iter::Peekable; +use std::str::Chars; + +pub(crate) fn base64() -> Result<Encoding, &'static str> { + let mut spec = Specification::new(); + spec.check_trailing_bits = false; + spec.symbols + .push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + spec.padding = None; + spec.ignore = "=".to_owned(); + spec.encoding() + .map_err(|_err| "invalid base64 specification") +} + +pub(crate) fn is_tchar(c: char) -> bool { + // See tchar values list in https://tools.ietf.org/html/rfc7230#section-3.2.6 + let tchars = "!#$%&'*+-.^_`|~"; + tchars.contains(c) || c.is_ascii_alphanumeric() +} + +pub(crate) fn is_allowed_b64_content(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '+' || c == '=' || c == '/' +} + +pub(crate) fn consume_ows_chars(input_chars: &mut Peekable<Chars>) { + while let Some(c) = input_chars.peek() { + if c == &' ' || c == &'\t' { + input_chars.next(); + } else { + break; + } + } +} + +pub(crate) fn consume_sp_chars(input_chars: &mut Peekable<Chars>) { + while let Some(c) = input_chars.peek() { + if c == &' ' { + input_chars.next(); + } else { + break; + } + } +} |