├── .github └── workflows │ ├── audit.yml │ └── ci-workflow.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bench.rs ├── examples └── priority.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── input.rs │ ├── parse_dictionary.rs │ ├── parse_item.rs │ ├── parse_list.rs │ ├── roundtrip_dictionary.rs │ ├── roundtrip_item.rs │ └── roundtrip_list.rs ├── rustfmt.toml ├── src ├── date.rs ├── decimal.rs ├── error.rs ├── integer.rs ├── key.rs ├── lib.rs ├── parsed.rs ├── parser.rs ├── ref_serializer.rs ├── serializer.rs ├── string.rs ├── test_decimal.rs ├── test_integer.rs ├── test_key.rs ├── test_parser.rs ├── test_ref_serializer.rs ├── test_serializer.rs ├── test_string.rs ├── test_token.rs ├── token.rs ├── utils.rs └── visitor.rs └── tests └── specification_tests.rs /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * 0' 5 | jobs: 6 | audit: 7 | name: Security audit 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions-rs/audit-check@v1 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/ci-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | name: Run check and clippy 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: 'true' 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | - run: cargo check --all-targets 26 | - run: cargo check --all-targets --no-default-features 27 | - run: cargo check --all-targets --all-features 28 | - run: cargo clippy --all-targets --all-features 29 | 30 | msrv: 31 | name: Check MSRV 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | with: 36 | submodules: 'true' 37 | - uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: 1.77 40 | override: true 41 | - name: Build with MSRV 42 | run: cargo build 43 | 44 | test: 45 | name: Run tests 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v4 49 | with: 50 | submodules: 'true' 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | profile: minimal 54 | toolchain: stable 55 | override: true 56 | - run: cargo test 57 | - run: cargo run --example priority 58 | 59 | format: 60 | name: Run fmt 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@v4 64 | with: 65 | submodules: 'true' 66 | - uses: actions-rs/toolchain@v1 67 | with: 68 | profile: minimal 69 | toolchain: nightly 70 | components: rustfmt 71 | - run: cargo fmt -- --check 72 | 73 | fuzz-check: 74 | name: Run fuzz check 75 | runs-on: ubuntu-latest 76 | steps: 77 | - uses: actions/checkout@v4 78 | - uses: actions-rs/toolchain@v1 79 | with: 80 | profile: minimal 81 | toolchain: nightly 82 | override: true 83 | - run: cargo install cargo-fuzz 84 | - run: cargo fuzz check 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /target 3 | Cargo.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/spec_tests"] 2 | path = tests/spec_tests 3 | url = https://github.com/httpwg/structured-header-tests.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sfv" 3 | version = "0.13.0" 4 | authors = ["Tania Batieva "] 5 | edition = "2021" 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | documentation = "https://docs.rs/sfv" 9 | description = """Structured Field Values for HTTP parser. 10 | Implementation of RFC 8941 and RFC 9651.""" 11 | repository = "https://github.com/undef1nd/sfv" 12 | keywords = ["http-header", "structured-header", ] 13 | exclude = ["tests/**", ".github/*", "benches/**", "fuzz/**"] 14 | rust-version = "1.77" 15 | 16 | [dependencies] 17 | arbitrary = { version = "1.4.1", optional = true, features = ["derive"] } 18 | base64 = "0.22.1" 19 | indexmap = { version = "2", optional = true } 20 | ref-cast = "1.0.23" 21 | 22 | [dev-dependencies] 23 | serde_json = { version = "1.0" } 24 | serde = { version = "1.0", features = ["derive"] } 25 | criterion = "0.5.1" 26 | base32 = "0.5.1" 27 | 28 | [lints.clippy] 29 | pedantic = "deny" 30 | 31 | [[bench]] 32 | name = "bench" 33 | harness = false 34 | required-features = ["parsed-types"] 35 | 36 | [features] 37 | default = ["parsed-types"] 38 | arbitrary = ["dep:arbitrary", "indexmap?/arbitrary"] 39 | parsed-types = ["dep:indexmap"] 40 | 41 | [[example]] 42 | name = "priority" 43 | required-features = ["parsed-types"] 44 | 45 | [[test]] 46 | name = "specification_tests" 47 | required-features = ["parsed-types"] 48 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tania Batieva 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/crates/l/sfv)](LICENSE-MIT) 2 | ![Build Status](https://img.shields.io/github/actions/workflow/status/undef1nd/sfv/ci-workflow.yml) 3 | [![Version](https://img.shields.io/crates/v/sfv)](https://crates.io/crates/sfv) 4 | [![Docs](https://img.shields.io/docsrs/sfv?color=white)](https://docs.rs/sfv) 5 | 6 | 7 | # Structured Field Values for HTTP 8 | 9 | `sfv` is an implementation of *Structured Field Values for HTTP* as specified in [RFC 9651](https://httpwg.org/specs/rfc9651.html) for parsing and serializing HTTP field values (also known as "structured headers" or "structured trailers"). 10 | 11 | It also exposes a set of types that might be useful for defining new structured fields. 12 | 13 |
14 | 15 | #### License 16 | 17 | 18 | Licensed under either of Apache License, Version 19 | 2.0 or MIT license at your option. 20 | 21 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | use criterion::{BenchmarkId, Criterion}; 5 | use sfv::{ 6 | integer, key_ref, string_ref, token_ref, Decimal, DictSerializer, Dictionary, FieldType, Item, 7 | ItemSerializer, List, ListSerializer, Parser, 8 | }; 9 | 10 | criterion_main!(parsing, serializing, ref_serializing); 11 | 12 | criterion_group!(parsing, parsing_item, parsing_list, parsing_dict); 13 | 14 | fn parsing_item(c: &mut Criterion) { 15 | let fixture = 16 | "c29tZXZlcnlsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXNhbnNvbWVvdGhlcmxvbmdsaW5l"; 17 | c.bench_with_input( 18 | BenchmarkId::new("parsing_item", fixture), 19 | &fixture, 20 | move |bench, &input| { 21 | bench.iter(|| Parser::new(input).parse::().unwrap()); 22 | }, 23 | ); 24 | } 25 | 26 | fn parsing_list(c: &mut Criterion) { 27 | let fixture = r#"a, abcdefghigklmnoprst, 123456785686457, 99999999999.999, (), ("somelongstringvalue" "anotherlongstringvalue";key=:c29tZXZlciBsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXM: 145)"#; 28 | c.bench_with_input( 29 | BenchmarkId::new("parsing_list", fixture), 30 | &fixture, 31 | move |bench, &input| { 32 | bench.iter(|| Parser::new(input).parse::().unwrap()); 33 | }, 34 | ); 35 | } 36 | 37 | fn parsing_dict(c: &mut Criterion) { 38 | let fixture = r#"a, dict_key2=abcdefghigklmnoprst, dict_key3=123456785686457, dict_key4=("inner-list-member" :aW5uZXItbGlzdC1tZW1iZXI=:);key=aW5uZXItbGlzdC1wYXJhbWV0ZXJz"#; 39 | c.bench_with_input( 40 | BenchmarkId::new("parsing_dict", fixture), 41 | &fixture, 42 | move |bench, &input| { 43 | bench.iter(|| Parser::new(input).parse::().unwrap()); 44 | }, 45 | ); 46 | } 47 | 48 | criterion_group!( 49 | serializing, 50 | serializing_item, 51 | serializing_list, 52 | serializing_dict 53 | ); 54 | 55 | fn serializing_item(c: &mut Criterion) { 56 | let fixture = 57 | "c29tZXZlcnlsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXNhbnNvbWVvdGhlcmxvbmdsaW5l"; 58 | c.bench_with_input( 59 | BenchmarkId::new("serializing_item", fixture), 60 | &fixture, 61 | move |bench, &input| { 62 | let parsed_item: Item = Parser::new(input).parse().unwrap(); 63 | bench.iter(|| parsed_item.serialize()); 64 | }, 65 | ); 66 | } 67 | 68 | fn serializing_list(c: &mut Criterion) { 69 | let fixture = r#"a, abcdefghigklmnoprst, 123456785686457, 99999999999.999, (), ("somelongstringvalue" "anotherlongstringvalue";key=:c29tZXZlciBsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXM: 145)"#; 70 | c.bench_with_input( 71 | BenchmarkId::new("serializing_list", fixture), 72 | &fixture, 73 | move |bench, &input| { 74 | let parsed_list: List = Parser::new(input).parse().unwrap(); 75 | bench.iter(|| parsed_list.serialize().unwrap()); 76 | }, 77 | ); 78 | } 79 | 80 | fn serializing_dict(c: &mut Criterion) { 81 | let fixture = r#"a, dict_key2=abcdefghigklmnoprst, dict_key3=123456785686457, dict_key4=("inner-list-member" :aW5uZXItbGlzdC1tZW1iZXI=:);key=aW5uZXItbGlzdC1wYXJhbWV0ZXJz"#; 82 | c.bench_with_input( 83 | BenchmarkId::new("serializing_dict", fixture), 84 | &fixture, 85 | move |bench, &input| { 86 | let parsed_dict: Dictionary = Parser::new(input).parse().unwrap(); 87 | bench.iter(|| parsed_dict.serialize().unwrap()); 88 | }, 89 | ); 90 | } 91 | 92 | criterion_group!( 93 | ref_serializing, 94 | serializing_ref_item, 95 | serializing_ref_list, 96 | serializing_ref_dict 97 | ); 98 | 99 | fn serializing_ref_item(c: &mut Criterion) { 100 | let fixture = 101 | "c29tZXZlcnlsb25nc3RyaW5ndmFsdWVyZXByZXNlbnRlZGFzYnl0ZXNhbnNvbWVvdGhlcmxvbmdsaW5l"; 102 | c.bench_with_input( 103 | BenchmarkId::new("serializing_ref_item", fixture), 104 | &fixture, 105 | move |bench, &input| { 106 | bench.iter(|| { 107 | let ser = ItemSerializer::new(); 108 | ser.bare_item(input.as_bytes()).finish() 109 | }); 110 | }, 111 | ); 112 | } 113 | 114 | fn serializing_ref_list(c: &mut Criterion) { 115 | c.bench_function("serializing_ref_list", move |bench| { 116 | bench.iter(|| { 117 | let mut ser = ListSerializer::new(); 118 | _ = ser.bare_item(token_ref("a")); 119 | _ = ser.bare_item(token_ref("abcdefghigklmnoprst")); 120 | _ = ser.bare_item(integer(123_456_785_686_457)); 121 | _ = ser.bare_item(Decimal::try_from(99_999_999_999.999).unwrap()); 122 | _ = ser.inner_list(); 123 | { 124 | let mut ser = ser.inner_list(); 125 | _ = ser.bare_item(string_ref("somelongstringvalue")); 126 | _ = ser 127 | .bare_item(string_ref("anotherlongstringvalue")) 128 | .parameter( 129 | key_ref("key"), 130 | "somever longstringvaluerepresentedasbytes".as_bytes(), 131 | ); 132 | _ = ser.bare_item(145); 133 | } 134 | ser.finish() 135 | }); 136 | }); 137 | } 138 | 139 | fn serializing_ref_dict(c: &mut Criterion) { 140 | c.bench_function("serializing_ref_dict", move |bench| { 141 | bench.iter(|| { 142 | let mut ser = DictSerializer::new(); 143 | _ = ser.bare_item(key_ref("a"), true); 144 | _ = ser.bare_item(key_ref("dict_key2"), token_ref("abcdefghigklmnoprst")); 145 | _ = ser.bare_item(key_ref("dict_key3"), integer(123_456_785_686_457)); 146 | { 147 | let mut ser = ser.inner_list(key_ref("dict_key4")); 148 | _ = ser.bare_item(string_ref("inner-list-member")); 149 | _ = ser.bare_item("inner-list-member".as_bytes()); 150 | _ = ser 151 | .finish() 152 | .parameter(key_ref("key"), token_ref("aW5uZXItbGlzdC1wYXJhbWV0ZXJz")); 153 | } 154 | ser.finish() 155 | }); 156 | }); 157 | } 158 | -------------------------------------------------------------------------------- /examples/priority.rs: -------------------------------------------------------------------------------- 1 | // This example demonstrates both dictionary- and visitor-based parsing of the 2 | // Priority header according to https://httpwg.org/specs/rfc9218.html. 3 | 4 | const KEY_U: &str = "u"; 5 | const KEY_I: &str = "i"; 6 | 7 | const DEFAULT_URGENCY: u8 = 3; 8 | const DEFAULT_INCREMENTAL: bool = false; 9 | 10 | #[derive(Debug, PartialEq)] 11 | struct Priority { 12 | urgency: u8, 13 | incremental: bool, 14 | } 15 | 16 | impl Default for Priority { 17 | fn default() -> Self { 18 | Self { 19 | urgency: DEFAULT_URGENCY, 20 | incremental: DEFAULT_INCREMENTAL, 21 | } 22 | } 23 | } 24 | 25 | fn parse_urgency(v: Option) -> u8 { 26 | v.and_then(|v| u8::try_from(v).ok()) 27 | .filter(|v| *v <= 7) 28 | .unwrap_or(DEFAULT_URGENCY) 29 | } 30 | 31 | fn parse_incremental(v: Option) -> bool { 32 | v.unwrap_or(DEFAULT_INCREMENTAL) 33 | } 34 | 35 | impl<'de> sfv::visitor::DictionaryVisitor<'de> for Priority { 36 | type Error = std::convert::Infallible; 37 | 38 | fn entry( 39 | &mut self, 40 | key: &'de sfv::KeyRef, 41 | ) -> Result, Self::Error> { 42 | Ok(match key.as_str() { 43 | KEY_U => Some(PriorityParameter::U(&mut self.urgency)), 44 | KEY_I => Some(PriorityParameter::I(&mut self.incremental)), 45 | // Per https://httpwg.org/specs/rfc9218.html#parameters unknown 46 | // dictionary keys are ignored. 47 | _ => None, 48 | }) 49 | } 50 | } 51 | 52 | enum PriorityParameter<'a> { 53 | U(&'a mut u8), 54 | I(&'a mut bool), 55 | } 56 | 57 | impl<'de> sfv::visitor::ItemVisitor<'de> for PriorityParameter<'_> { 58 | type Error = std::convert::Infallible; 59 | 60 | fn bare_item( 61 | self, 62 | bare_item: sfv::BareItemFromInput<'de>, 63 | ) -> Result, Self::Error> { 64 | // Per https://httpwg.org/specs/rfc9218.html#parameters values of 65 | // unexpected types and out-of-range values are ignored. Since the same 66 | // dictionary key can appear multiple times in the input, and only the 67 | // last value may be considered per Structured Field semantics, we 68 | // overwrite any existing value with the default on error. 69 | match self { 70 | Self::U(urgency) => *urgency = parse_urgency(bare_item.as_integer()), 71 | Self::I(incremental) => *incremental = parse_incremental(bare_item.as_boolean()), 72 | } 73 | // Neither https://httpwg.org/specs/rfc9218.html#urgency nor 74 | // https://httpwg.org/specs/rfc9218.html#incremental defines parameters. 75 | Ok(sfv::visitor::Ignored) 76 | } 77 | } 78 | 79 | impl<'de> sfv::visitor::EntryVisitor<'de> for PriorityParameter<'_> { 80 | fn inner_list(self) -> Result, Self::Error> { 81 | // Per https://httpwg.org/specs/rfc9218.html#parameters values of 82 | // unexpected types are ignored. Since the same dictionary key can 83 | // appear multiple times in the input, and only the last value may be 84 | // considered per Structured Field semantics, we overwrite any existing 85 | // value with the default. 86 | match self { 87 | Self::U(urgency) => *urgency = DEFAULT_URGENCY, 88 | Self::I(incremental) => *incremental = DEFAULT_INCREMENTAL, 89 | } 90 | // Per https://httpwg.org/specs/rfc9218.html#parameters values of 91 | // unexpected types are ignored. 92 | Ok(sfv::visitor::Ignored) 93 | } 94 | } 95 | 96 | impl From<&sfv::Dictionary> for Priority { 97 | fn from(dict: &sfv::Dictionary) -> Self { 98 | Self { 99 | urgency: parse_urgency(match dict.get(KEY_U) { 100 | Some(sfv::ListEntry::Item(sfv::Item { bare_item, .. })) => bare_item.as_integer(), 101 | _ => None, 102 | }), 103 | incremental: parse_incremental(match dict.get(KEY_I) { 104 | Some(sfv::ListEntry::Item(sfv::Item { bare_item, .. })) => bare_item.as_boolean(), 105 | _ => None, 106 | }), 107 | } 108 | } 109 | } 110 | 111 | #[allow(clippy::too_many_lines)] 112 | fn main() -> Result<(), sfv::Error> { 113 | let examples = [ 114 | // From https://httpwg.org/specs/rfc9218.html#parameters: "When 115 | // receiving an HTTP request that does not carry these priority 116 | // parameters, a server SHOULD act as if their default values were 117 | // specified." 118 | ( 119 | "", 120 | Priority { 121 | urgency: 3, 122 | incremental: false, 123 | }, 124 | ), 125 | // https://httpwg.org/specs/rfc9218.html#incremental 126 | ( 127 | "u=5, i", 128 | Priority { 129 | urgency: 5, 130 | incremental: true, 131 | }, 132 | ), 133 | // Unknown key 134 | ( 135 | "x;a, u=5, i", 136 | Priority { 137 | urgency: 5, 138 | incremental: true, 139 | }, 140 | ), 141 | // Unexpected type for urgency 142 | ( 143 | "u=(), i", 144 | Priority { 145 | urgency: 3, 146 | incremental: true, 147 | }, 148 | ), 149 | // Unexpected type for urgency 150 | ( 151 | "u=6.5, i", 152 | Priority { 153 | urgency: 3, 154 | incremental: true, 155 | }, 156 | ), 157 | // Urgency below minimum 158 | ( 159 | "u=-1, i", 160 | Priority { 161 | urgency: 3, 162 | incremental: true, 163 | }, 164 | ), 165 | // Urgency above maximum 166 | ( 167 | "u=8, i", 168 | Priority { 169 | urgency: 3, 170 | incremental: true, 171 | }, 172 | ), 173 | // Unexpected type for incremental 174 | ( 175 | "i=(), u=5", 176 | Priority { 177 | urgency: 5, 178 | incremental: false, 179 | }, 180 | ), 181 | // Unexpected type for incremental 182 | ( 183 | "i=1, u=5", 184 | Priority { 185 | urgency: 5, 186 | incremental: false, 187 | }, 188 | ), 189 | // Parameters are ignored 190 | ( 191 | "u=5;x, i;y", 192 | Priority { 193 | urgency: 5, 194 | incremental: true, 195 | }, 196 | ), 197 | // When duplicate keys are encountered, only last's value is used 198 | ( 199 | "u=6, i, u=5, i=?0", 200 | Priority { 201 | urgency: 5, 202 | incremental: false, 203 | }, 204 | ), 205 | // When duplicate keys are encountered, only last's value is used 206 | ( 207 | "u=6, i, u=(), i=()", 208 | Priority { 209 | urgency: 3, 210 | incremental: false, 211 | }, 212 | ), 213 | ]; 214 | 215 | for (input, expected) in examples { 216 | assert_eq!( 217 | Priority::from( 218 | &sfv::Parser::new(input) 219 | .with_version(sfv::Version::Rfc8941) 220 | .parse()? 221 | ), 222 | expected, 223 | "{input}" 224 | ); 225 | 226 | assert_eq!( 227 | { 228 | let mut priority = Priority::default(); 229 | sfv::Parser::new(input) 230 | .with_version(sfv::Version::Rfc8941) 231 | .parse_dictionary_with_visitor(&mut priority)?; 232 | priority 233 | }, 234 | expected, 235 | "{input}" 236 | ); 237 | } 238 | 239 | Ok(()) 240 | } 241 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sfv-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | arbitrary = { version = "1.4.1", features = ["derive"] } 12 | libfuzzer-sys = "0.4" 13 | 14 | [dependencies.sfv] 15 | path = ".." 16 | features = ["arbitrary"] 17 | 18 | [[bin]] 19 | name = "parse_dictionary" 20 | path = "fuzz_targets/parse_dictionary.rs" 21 | test = false 22 | doc = false 23 | bench = false 24 | 25 | [[bin]] 26 | name = "parse_list" 27 | path = "fuzz_targets/parse_list.rs" 28 | test = false 29 | doc = false 30 | bench = false 31 | 32 | [[bin]] 33 | name = "parse_item" 34 | path = "fuzz_targets/parse_item.rs" 35 | test = false 36 | doc = false 37 | bench = false 38 | 39 | [[bin]] 40 | name = "roundtrip_item" 41 | path = "fuzz_targets/roundtrip_item.rs" 42 | test = false 43 | doc = false 44 | bench = false 45 | 46 | [[bin]] 47 | name = "roundtrip_list" 48 | path = "fuzz_targets/roundtrip_list.rs" 49 | test = false 50 | doc = false 51 | bench = false 52 | 53 | [[bin]] 54 | name = "roundtrip_dictionary" 55 | path = "fuzz_targets/roundtrip_dictionary.rs" 56 | test = false 57 | doc = false 58 | bench = false 59 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/input.rs: -------------------------------------------------------------------------------- 1 | #[derive(arbitrary::Arbitrary, Debug)] 2 | pub struct Input<'a> { 3 | pub data: &'a [u8], 4 | pub version: sfv::Version, 5 | } 6 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_dictionary.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | mod input; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | fuzz_target!(|input: input::Input| { 8 | let _ = sfv::Parser::new(input.data) 9 | .with_version(input.version) 10 | .parse::(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_item.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | mod input; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | fuzz_target!(|input: input::Input| { 8 | let _ = sfv::Parser::new(input.data) 9 | .with_version(input.version) 10 | .parse::(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_list.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | mod input; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | fuzz_target!(|input: input::Input| { 8 | let _ = sfv::Parser::new(input.data) 9 | .with_version(input.version) 10 | .parse::(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_dictionary.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use sfv::FieldType as _; 5 | 6 | fuzz_target!(|dict: sfv::Dictionary| { 7 | let serialized = dict.serialize(); 8 | if dict.is_empty() { 9 | assert!(serialized.is_none()); 10 | } else { 11 | assert_eq!( 12 | sfv::Parser::new(&serialized.unwrap()) 13 | .parse::() 14 | .unwrap(), 15 | dict 16 | ); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_item.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use sfv::FieldType as _; 5 | 6 | fuzz_target!(|item: sfv::Item| { 7 | let serialized = item.serialize(); 8 | assert_eq!( 9 | sfv::Parser::new(&serialized).parse::().unwrap(), 10 | item 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_list.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use sfv::FieldType as _; 5 | 6 | fuzz_target!(|list: sfv::List| { 7 | let serialized = list.serialize(); 8 | if list.is_empty() { 9 | assert!(serialized.is_none()); 10 | } else { 11 | assert_eq!( 12 | sfv::Parser::new(&serialized.unwrap()) 13 | .parse::() 14 | .unwrap(), 15 | list, 16 | ); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | -------------------------------------------------------------------------------- /src/date.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::Integer; 4 | 5 | /// A structured field value [date]. 6 | /// 7 | /// Dates represent an integer number of seconds from the Unix epoch. 8 | /// 9 | /// [`Version::Rfc9651`][`crate::Version::Rfc9651`] supports bare items of this 10 | /// type; [`Version::Rfc8941`][`crate::Version::Rfc8941`] does not. 11 | /// 12 | /// [date]: 13 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 15 | pub struct Date(Integer); 16 | 17 | impl Date { 18 | /// The minimum value for a parsed or serialized date, corresponding to 19 | /// [`Integer::MIN`] seconds from the Unix epoch. 20 | pub const MIN: Self = Self::from_unix_seconds(Integer::MIN); 21 | 22 | /// The maximum value for a parsed or serialized date, corresponding to 23 | /// [`Integer::MAX`] seconds from the Unix epoch. 24 | pub const MAX: Self = Self::from_unix_seconds(Integer::MAX); 25 | 26 | /// The Unix epoch: `1970-01-01T00:00:00Z`. 27 | pub const UNIX_EPOCH: Self = Self::from_unix_seconds(Integer::ZERO); 28 | 29 | /// Returns the date as an integer number of seconds from the Unix epoch. 30 | #[must_use] 31 | pub fn unix_seconds(&self) -> Integer { 32 | self.0 33 | } 34 | 35 | /// Creates a date from an integer number of seconds from the Unix epoch. 36 | #[must_use] 37 | pub const fn from_unix_seconds(v: Integer) -> Self { 38 | Self(v) 39 | } 40 | } 41 | 42 | impl fmt::Display for Date { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | write!(f, "@{}", self.unix_seconds()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/decimal.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{Error, Integer}; 4 | 5 | /// A structured field value [decimal]. 6 | /// 7 | /// Decimals have 12 digits of integer precision and 3 digits of fractional precision. 8 | /// 9 | /// [decimal]: 10 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 12 | pub struct Decimal(Integer); 13 | 14 | impl Decimal { 15 | /// The minimum value for a parsed or serialized decimal: `-999_999_999_999.999`. 16 | pub const MIN: Self = Self::from_integer_scaled_1000(Integer::MIN); 17 | 18 | /// The maximum value for a parsed or serialized decimal: `999_999_999_999.999`. 19 | pub const MAX: Self = Self::from_integer_scaled_1000(Integer::MAX); 20 | 21 | /// `0.0`. 22 | pub const ZERO: Self = Self(Integer::ZERO); 23 | 24 | /// Returns the decimal as an integer multiplied by 1000. 25 | /// 26 | /// The conversion is guaranteed to be precise. 27 | /// 28 | /// # Example 29 | /// 30 | /// ``` 31 | /// let decimal = sfv::Decimal::try_from(1.234).unwrap(); 32 | /// assert_eq!(i64::try_from(decimal.as_integer_scaled_1000()).unwrap(), 1234); 33 | /// ``` 34 | #[must_use] 35 | pub fn as_integer_scaled_1000(&self) -> Integer { 36 | self.0 37 | } 38 | 39 | /// Creates a decimal from an integer multiplied by 1000. 40 | /// 41 | /// The conversion is guaranteed to be precise. 42 | /// 43 | /// # Example 44 | /// 45 | /// ``` 46 | /// let decimal = sfv::Decimal::from_integer_scaled_1000(sfv::integer(1234)); 47 | /// #[allow(clippy::float_cmp)] 48 | /// assert_eq!(f64::try_from(decimal).unwrap(), 1.234); 49 | /// ``` 50 | #[must_use] 51 | pub const fn from_integer_scaled_1000(v: Integer) -> Self { 52 | Self(v) 53 | } 54 | } 55 | 56 | impl fmt::Display for Decimal { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | let v = i64::from(self.as_integer_scaled_1000()); 59 | 60 | if v == 0 { 61 | return f.write_str("0.0"); 62 | } 63 | 64 | let sign = if v < 0 { "-" } else { "" }; 65 | let v = v.abs(); 66 | let i_part = v / 1000; 67 | let f_part = v % 1000; 68 | 69 | if f_part % 100 == 0 { 70 | write!(f, "{}{}.{}", sign, i_part, f_part / 100) 71 | } else if f_part % 10 == 0 { 72 | write!(f, "{}{}.{:02}", sign, i_part, f_part / 10) 73 | } else { 74 | write!(f, "{sign}{i_part}.{f_part:03}") 75 | } 76 | } 77 | } 78 | 79 | impl From for Decimal { 80 | fn from(v: i8) -> Decimal { 81 | Self(Integer::from(i16::from(v) * 1000)) 82 | } 83 | } 84 | 85 | impl From for Decimal { 86 | fn from(v: i16) -> Decimal { 87 | Self(Integer::from(i32::from(v) * 1000)) 88 | } 89 | } 90 | 91 | impl From for Decimal { 92 | fn from(v: i32) -> Decimal { 93 | Self(Integer::try_from(i64::from(v) * 1000).unwrap()) 94 | } 95 | } 96 | 97 | macro_rules! impl_try_from_integer { 98 | ($($from: ty,)+) => { 99 | $( 100 | impl TryFrom<$from> for Decimal { 101 | type Error = Error; 102 | 103 | fn try_from(v: $from) -> Result { 104 | match v.checked_mul(1000) { 105 | None => Err(Error::out_of_range()), 106 | Some(v) => Integer::try_from(v).map(Decimal), 107 | } 108 | } 109 | } 110 | )+ 111 | } 112 | } 113 | 114 | impl_try_from_integer! { 115 | i64, 116 | i128, 117 | isize, 118 | u64, 119 | u128, 120 | usize, 121 | } 122 | 123 | impl From for Decimal { 124 | fn from(v: u8) -> Decimal { 125 | Self(Integer::from(u16::from(v) * 1000)) 126 | } 127 | } 128 | 129 | impl From for Decimal { 130 | fn from(v: u16) -> Decimal { 131 | Self(Integer::from(u32::from(v) * 1000)) 132 | } 133 | } 134 | 135 | impl From for Decimal { 136 | fn from(v: u32) -> Decimal { 137 | Self(Integer::try_from(u64::from(v) * 1000).unwrap()) 138 | } 139 | } 140 | 141 | impl From for f64 { 142 | #[allow(clippy::cast_precision_loss)] 143 | fn from(v: Decimal) -> Self { 144 | let v = i64::from(v.as_integer_scaled_1000()); 145 | (v as f64) / 1000.0 146 | } 147 | } 148 | 149 | impl TryFrom for Decimal { 150 | type Error = Error; 151 | 152 | fn try_from(v: f32) -> Result { 153 | Self::try_from(f64::from(v)) 154 | } 155 | } 156 | 157 | impl TryFrom for Decimal { 158 | type Error = Error; 159 | 160 | fn try_from(v: f64) -> Result { 161 | if v.is_nan() { 162 | return Err(Error::new("NaN")); 163 | } 164 | 165 | let v = (v * 1000.0).round_ties_even(); 166 | // Only excessively clever options exist for this conversion, so use "as" 167 | // Note that this relies on saturating casts for values > i64::MAX 168 | // See https://github.com/rust-lang/rust/issues/10184 169 | #[allow(clippy::cast_possible_truncation)] 170 | Integer::try_from(v as i64).map(Decimal) 171 | } 172 | } 173 | 174 | impl TryFrom for Decimal { 175 | type Error = Error; 176 | 177 | fn try_from(v: Integer) -> Result { 178 | i64::from(v).try_into() 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | 4 | /// An error that can occur in this crate. 5 | /// 6 | /// The most common type of error is invalid input during parsing, but others 7 | /// exist as well: 8 | /// 9 | /// - Conversion to or from bare-item types such as [`Integer`][crate::Integer] 10 | /// - Attempting to serialize an empty [list][crate::ListSerializer::finish] or 11 | /// [dictionary][crate::DictSerializer::finish] 12 | /// 13 | /// Other than implementing the [`std::error::Error`], [`std::fmt::Debug`], and 14 | /// [`std::fmt::Display`] traits, this error type currently provides no 15 | /// introspection capabilities. 16 | #[derive(Debug)] 17 | #[cfg_attr(test, derive(PartialEq))] 18 | pub struct Error { 19 | msg: Cow<'static, str>, 20 | index: Option, 21 | } 22 | 23 | impl Error { 24 | pub(crate) fn new(msg: &'static str) -> Self { 25 | Self { 26 | msg: Cow::Borrowed(msg), 27 | index: None, 28 | } 29 | } 30 | 31 | pub(crate) fn with_index(msg: &'static str, index: usize) -> Self { 32 | Self { 33 | msg: Cow::Borrowed(msg), 34 | index: Some(index), 35 | } 36 | } 37 | 38 | pub(crate) fn out_of_range() -> Self { 39 | Self::new("out of range") 40 | } 41 | 42 | pub(crate) fn custom(msg: impl fmt::Display) -> Self { 43 | Self { 44 | msg: Cow::Owned(msg.to_string()), 45 | index: None, 46 | } 47 | } 48 | } 49 | 50 | impl fmt::Display for Error { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | match self.index { 53 | None => f.write_str(&self.msg), 54 | Some(index) => write!(f, "{} at index {}", self.msg, index), 55 | } 56 | } 57 | } 58 | 59 | impl std::error::Error for Error {} 60 | 61 | pub(crate) struct NonEmptyStringError { 62 | byte_index: Option, 63 | } 64 | 65 | impl NonEmptyStringError { 66 | pub(crate) const fn empty() -> Self { 67 | Self { byte_index: None } 68 | } 69 | 70 | pub(crate) const fn invalid_character(byte_index: usize) -> Self { 71 | Self { 72 | byte_index: Some(byte_index), 73 | } 74 | } 75 | 76 | pub(crate) const fn msg(&self) -> &'static str { 77 | match self.byte_index { 78 | None => "cannot be empty", 79 | Some(_) => "invalid character", 80 | } 81 | } 82 | } 83 | 84 | impl From for Error { 85 | fn from(err: NonEmptyStringError) -> Error { 86 | Error { 87 | msg: Cow::Borrowed(err.msg()), 88 | index: err.byte_index, 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/integer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{Error, GenericBareItem}; 4 | 5 | const RANGE_I64: std::ops::RangeInclusive = -999_999_999_999_999..=999_999_999_999_999; 6 | 7 | /// A structured field value [integer]. 8 | /// 9 | /// [integer]: 10 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 12 | pub struct Integer( 13 | #[cfg_attr( 14 | feature = "arbitrary", 15 | arbitrary(with = |u: &mut arbitrary::Unstructured| u.int_in_range(RANGE_I64)) 16 | )] 17 | i64, 18 | ); 19 | 20 | impl Integer { 21 | /// The minimum value for a parsed or serialized integer: `-999_999_999_999_999`. 22 | pub const MIN: Self = Self(*RANGE_I64.start()); 23 | 24 | /// The maximum value for a parsed or serialized integer: `999_999_999_999_999`. 25 | pub const MAX: Self = Self(*RANGE_I64.end()); 26 | 27 | /// `0`. 28 | /// 29 | /// Equivalent to `Integer::constant(0)`. 30 | pub const ZERO: Self = Self(0); 31 | 32 | /// Creates an `Integer`, panicking if the value is out of range. 33 | /// 34 | /// This method is intended to be called from `const` contexts in which the 35 | /// value is known to be valid. Use [`TryFrom::try_from`] for non-panicking 36 | /// conversions. 37 | #[must_use] 38 | pub const fn constant(v: i64) -> Self { 39 | if v >= Self::MIN.0 && v <= Self::MAX.0 { 40 | Self(v) 41 | } else { 42 | panic!("out of range for Integer") 43 | } 44 | } 45 | } 46 | 47 | impl fmt::Display for Integer { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 49 | fmt::Display::fmt(&self.0, f) 50 | } 51 | } 52 | 53 | macro_rules! impl_conversions { 54 | ($($t: ty: $from:ident => $into:ident,)+) => { 55 | $( 56 | impl_conversion!($from<$t>); 57 | impl_conversion!($into<$t>); 58 | )+ 59 | } 60 | } 61 | 62 | macro_rules! impl_conversion { 63 | (From<$t: ty>) => { 64 | impl From<$t> for Integer { 65 | fn from(v: $t) -> Integer { 66 | Integer(v.into()) 67 | } 68 | } 69 | impl From<$t> for GenericBareItem { 70 | fn from(v: $t) -> Self { 71 | Self::Integer(v.into()) 72 | } 73 | } 74 | }; 75 | (TryFrom<$t: ty>) => { 76 | impl TryFrom<$t> for Integer { 77 | type Error = Error; 78 | 79 | fn try_from(v: $t) -> Result { 80 | match i64::try_from(v) { 81 | Ok(v) if RANGE_I64.contains(&v) => Ok(Integer(v)), 82 | _ => Err(Error::out_of_range()), 83 | } 84 | } 85 | } 86 | impl TryFrom<$t> for GenericBareItem { 87 | type Error = Error; 88 | 89 | fn try_from(v: $t) -> Result { 90 | Integer::try_from(v).map(Self::Integer) 91 | } 92 | } 93 | }; 94 | (Into<$t: ty>) => { 95 | impl From for $t { 96 | fn from(v: Integer) -> $t { 97 | v.0.into() 98 | } 99 | } 100 | }; 101 | (TryInto<$t: ty>) => { 102 | impl TryFrom for $t { 103 | type Error = Error; 104 | 105 | fn try_from(v: Integer) -> Result<$t, Error> { 106 | v.0.try_into().map_err(|_| Error::out_of_range()) 107 | } 108 | } 109 | }; 110 | } 111 | 112 | impl_conversions! { 113 | i8: From => TryInto, 114 | i16: From => TryInto, 115 | i32: From => TryInto, 116 | i64: TryFrom => Into, 117 | i128: TryFrom => Into, 118 | isize: TryFrom => TryInto, 119 | 120 | u8: From => TryInto, 121 | u16: From => TryInto, 122 | u32: From => TryInto, 123 | u64: TryFrom => TryInto, 124 | u128: TryFrom => TryInto, 125 | usize: TryFrom => TryInto, 126 | } 127 | 128 | /// Creates an `Integer`, panicking if the value is out of range. 129 | /// 130 | /// This is a convenience free function for [`Integer::constant`]. 131 | /// 132 | /// This method is intended to be called from `const` contexts in which the 133 | /// value is known to be valid. Use [`TryFrom::try_from`] for non-panicking 134 | /// conversions. 135 | #[must_use] 136 | pub const fn integer(v: i64) -> Integer { 137 | Integer::constant(v) 138 | } 139 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Borrow, fmt}; 2 | 3 | use crate::{ 4 | error::{Error, NonEmptyStringError}, 5 | utils, 6 | }; 7 | 8 | /// An owned structured field value [key]. 9 | /// 10 | /// Keys must match the following regular expression: 11 | /// 12 | /// ```re 13 | /// ^[A-Za-z*][A-Za-z*0-9!#$%&'+\-.^_`|~]*$ 14 | /// ``` 15 | /// 16 | /// [key]: 17 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub struct Key(String); 19 | 20 | /// A borrowed structured field value [key]. 21 | /// 22 | /// Keys must match the following regular expression: 23 | /// 24 | /// ```re 25 | /// ^[A-Za-z*][A-Za-z*0-9!#$%&'+\-.^_`|~]*$ 26 | /// ``` 27 | /// 28 | /// This type is to [`Key`] as [`str`] is to [`String`]. 29 | /// 30 | /// [key]: 31 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, ref_cast::RefCastCustom)] 32 | #[repr(transparent)] 33 | pub struct KeyRef(str); 34 | 35 | const fn validate(v: &[u8]) -> Result<(), NonEmptyStringError> { 36 | if v.is_empty() { 37 | return Err(NonEmptyStringError::empty()); 38 | } 39 | 40 | if !utils::is_allowed_start_key_char(v[0]) { 41 | return Err(NonEmptyStringError::invalid_character(0)); 42 | } 43 | 44 | let mut index = 1; 45 | 46 | while index < v.len() { 47 | if !utils::is_allowed_inner_key_char(v[index]) { 48 | return Err(NonEmptyStringError::invalid_character(index)); 49 | } 50 | index += 1; 51 | } 52 | 53 | Ok(()) 54 | } 55 | 56 | impl KeyRef { 57 | #[ref_cast::ref_cast_custom] 58 | const fn cast(v: &str) -> &Self; 59 | 60 | /// Creates a `&KeyRef` from a `&str`. 61 | /// 62 | /// # Errors 63 | /// If the input string validation fails. 64 | #[allow(clippy::should_implement_trait)] 65 | pub fn from_str(v: &str) -> Result<&Self, Error> { 66 | validate(v.as_bytes())?; 67 | Ok(Self::cast(v)) 68 | } 69 | 70 | // Like `from_str`, but assumes that the contents of the string have already 71 | // been validated as a key. 72 | pub(crate) fn from_validated_str(v: &str) -> &Self { 73 | debug_assert!(validate(v.as_bytes()).is_ok()); 74 | Self::cast(v) 75 | } 76 | 77 | /// Creates a `&KeyRef`, panicking if the value is invalid. 78 | /// 79 | /// This method is intended to be called from `const` contexts in which the 80 | /// value is known to be valid. Use [`KeyRef::from_str`] for non-panicking 81 | /// conversions. 82 | /// 83 | /// # Errors 84 | /// If the input string validation fails. 85 | #[must_use] 86 | pub const fn constant(v: &str) -> &Self { 87 | match validate(v.as_bytes()) { 88 | Ok(()) => Self::cast(v), 89 | Err(err) => panic!("{}", err.msg()), 90 | } 91 | } 92 | 93 | /// Returns the key as a `&str`. 94 | #[must_use] 95 | pub fn as_str(&self) -> &str { 96 | &self.0 97 | } 98 | } 99 | 100 | impl ToOwned for KeyRef { 101 | type Owned = Key; 102 | 103 | fn to_owned(&self) -> Key { 104 | Key(self.0.to_owned()) 105 | } 106 | 107 | fn clone_into(&self, target: &mut Key) { 108 | self.0.clone_into(&mut target.0); 109 | } 110 | } 111 | 112 | impl Borrow for Key { 113 | fn borrow(&self) -> &KeyRef { 114 | self 115 | } 116 | } 117 | 118 | impl std::ops::Deref for Key { 119 | type Target = KeyRef; 120 | 121 | fn deref(&self) -> &KeyRef { 122 | KeyRef::cast(&self.0) 123 | } 124 | } 125 | 126 | impl From for String { 127 | fn from(v: Key) -> String { 128 | v.0 129 | } 130 | } 131 | 132 | impl TryFrom for Key { 133 | type Error = Error; 134 | 135 | fn try_from(v: String) -> Result { 136 | validate(v.as_bytes())?; 137 | Ok(Key(v)) 138 | } 139 | } 140 | 141 | impl Key { 142 | /// Creates a `Key` from a `String`. 143 | /// 144 | /// Returns the original value if the conversion failed. 145 | /// 146 | /// # Errors 147 | /// If the input string validation fails. 148 | pub fn from_string(v: String) -> Result { 149 | match validate(v.as_bytes()) { 150 | Ok(()) => Ok(Self(v)), 151 | Err(err) => Err((err.into(), v)), 152 | } 153 | } 154 | } 155 | 156 | /// Creates a `&KeyRef`, panicking if the value is invalid. 157 | /// 158 | /// This is a convenience free function for [`KeyRef::constant`]. 159 | /// 160 | /// This method is intended to be called from `const` contexts in which the 161 | /// value is known to be valid. Use [`KeyRef::from_str`] for non-panicking 162 | /// conversions. 163 | #[must_use] 164 | pub const fn key_ref(v: &str) -> &KeyRef { 165 | KeyRef::constant(v) 166 | } 167 | 168 | impl fmt::Display for KeyRef { 169 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 170 | f.write_str(self.as_str()) 171 | } 172 | } 173 | 174 | impl fmt::Display for Key { 175 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 176 | ::fmt(self, f) 177 | } 178 | } 179 | 180 | macro_rules! impl_eq { 181 | ($a: ty, $b: ty) => { 182 | impl PartialEq<$a> for $b { 183 | fn eq(&self, other: &$a) -> bool { 184 | ::eq(self, other) 185 | } 186 | } 187 | impl PartialEq<$b> for $a { 188 | fn eq(&self, other: &$b) -> bool { 189 | ::eq(self, other) 190 | } 191 | } 192 | }; 193 | } 194 | 195 | impl_eq!(Key, KeyRef); 196 | impl_eq!(Key, &KeyRef); 197 | 198 | impl<'a> TryFrom<&'a str> for &'a KeyRef { 199 | type Error = Error; 200 | 201 | fn try_from(v: &'a str) -> Result<&'a KeyRef, Error> { 202 | KeyRef::from_str(v) 203 | } 204 | } 205 | 206 | impl Borrow for Key { 207 | fn borrow(&self) -> &str { 208 | self.as_str() 209 | } 210 | } 211 | 212 | impl Borrow for KeyRef { 213 | fn borrow(&self) -> &str { 214 | self.as_str() 215 | } 216 | } 217 | 218 | impl AsRef for Key { 219 | fn as_ref(&self) -> &KeyRef { 220 | self 221 | } 222 | } 223 | 224 | impl AsRef for KeyRef { 225 | fn as_ref(&self) -> &KeyRef { 226 | self 227 | } 228 | } 229 | 230 | #[cfg(feature = "arbitrary")] 231 | impl<'a> arbitrary::Arbitrary<'a> for &'a KeyRef { 232 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 233 | KeyRef::from_str(<&str>::arbitrary(u)?).map_err(|_| arbitrary::Error::IncorrectFormat) 234 | } 235 | 236 | fn size_hint(_depth: usize) -> (usize, Option) { 237 | (1, None) 238 | } 239 | } 240 | 241 | #[cfg(feature = "arbitrary")] 242 | impl<'a> arbitrary::Arbitrary<'a> for Key { 243 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 244 | <&KeyRef>::arbitrary(u).map(ToOwned::to_owned) 245 | } 246 | 247 | fn size_hint(_depth: usize) -> (usize, Option) { 248 | (1, None) 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | `sfv` is an implementation of *Structured Field Values for HTTP*, as specified in [RFC 9651](https://httpwg.org/specs/rfc9651.html) for parsing and serializing HTTP field values. 3 | It also exposes a set of types that might be useful for defining new structured fields. 4 | 5 | # Data Structures 6 | 7 | There are three types of structured fields: 8 | 9 | - `Item` -- an `Integer`, `Decimal`, `String`, `Token`, `Byte Sequence`, `Boolean`, `Date`, or `Display String`. It can have associated `Parameters`. 10 | - `List` -- an array of zero or more members, each of which can be an `Item` or an `InnerList`, both of which can have `Parameters`. 11 | - `Dictionary` -- an ordered map of name-value pairs, where the names are short textual strings and the values are `Item`s or arrays of `Items` (represented with `InnerList`), both of which can have associated parameters. There can be zero or more members, and their names are unique in the scope of the `Dictionary` they occur within. 12 | 13 | There are also a few lower-level types used to construct structured field values: 14 | - `BareItem` is used as `Item`'s value or as a parameter value in `Parameters`. 15 | - `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`. 16 | - `InnerList` is an array of zero or more `Items`. Can have associated `Parameters`. 17 | - `ListEntry` represents either `Item` or `InnerList` as a member of `List` or as member-value in `Dictionary`. 18 | 19 | # Examples 20 | 21 | */ 22 | #![cfg_attr( 23 | feature = "parsed-types", 24 | doc = r##" 25 | ### Parsing 26 | 27 | ``` 28 | # use sfv::{Dictionary, Item, List, Parser}; 29 | # fn main() -> Result<(), sfv::Error> { 30 | // Parsing a structured field value of Item type. 31 | let input = "12.445;foo=bar"; 32 | let item: Item = Parser::new(input).parse()?; 33 | println!("{:#?}", item); 34 | 35 | // Parsing a structured field value of List type. 36 | let input = r#"1;a=tok, ("foo" "bar");baz, ()"#; 37 | let list: List = Parser::new(input).parse()?; 38 | println!("{:#?}", list); 39 | 40 | // Parsing a structured field value of Dictionary type. 41 | let input = "a=?0, b, c; foo=bar, rating=1.5, fruits=(apple pear)"; 42 | let dict: Dictionary = Parser::new(input).parse()?; 43 | println!("{:#?}", dict); 44 | # Ok(()) 45 | # } 46 | ``` 47 | 48 | ### Getting Parsed Value Members 49 | ``` 50 | # use sfv::*; 51 | # fn main() -> Result<(), sfv::Error> { 52 | let input = "u=2, n=(* foo 2)"; 53 | let dict: Dictionary = Parser::new(input).parse()?; 54 | 55 | match dict.get("u") { 56 | Some(ListEntry::Item(item)) => match &item.bare_item { 57 | BareItem::Token(val) => { /* ... */ } 58 | BareItem::Integer(val) => { /* ... */ } 59 | BareItem::Boolean(val) => { /* ... */ } 60 | BareItem::Decimal(val) => { /* ... */ } 61 | BareItem::String(val) => { /* ... */ } 62 | BareItem::ByteSequence(val) => { /* ... */ } 63 | BareItem::Date(val) => { /* ... */ } 64 | BareItem::DisplayString(val) => { /* ... */ } 65 | }, 66 | Some(ListEntry::InnerList(inner_list)) => { /* ... */ } 67 | None => { /* ... */ } 68 | } 69 | # Ok(()) 70 | # } 71 | ``` 72 | "## 73 | )] 74 | /*! 75 | ### Serialization 76 | Serializes an `Item`: 77 | ``` 78 | use sfv::{Decimal, ItemSerializer, KeyRef, StringRef}; 79 | 80 | # fn main() -> Result<(), sfv::Error> { 81 | let serialized_item = ItemSerializer::new() 82 | .bare_item(StringRef::from_str("foo")?) 83 | .parameter(KeyRef::from_str("key")?, Decimal::try_from(13.45655)?) 84 | .finish(); 85 | 86 | assert_eq!(serialized_item, r#""foo";key=13.457"#); 87 | # Ok(()) 88 | # } 89 | ``` 90 | 91 | Serializes a `List`: 92 | ``` 93 | use sfv::{KeyRef, ListSerializer, StringRef, TokenRef}; 94 | 95 | # fn main() -> Result<(), sfv::Error> { 96 | let mut ser = ListSerializer::new(); 97 | 98 | ser.bare_item(TokenRef::from_str("tok")?); 99 | 100 | { 101 | let mut ser = ser.inner_list(); 102 | 103 | ser.bare_item(99).parameter(KeyRef::from_str("key")?, false); 104 | 105 | ser.bare_item(StringRef::from_str("foo")?); 106 | 107 | ser.finish().parameter(KeyRef::from_str("bar")?, true); 108 | } 109 | 110 | assert_eq!( 111 | ser.finish().as_deref(), 112 | Some(r#"tok, (99;key=?0 "foo");bar"#), 113 | ); 114 | # Ok(()) 115 | # } 116 | ``` 117 | 118 | Serializes a `Dictionary`: 119 | ``` 120 | use sfv::{DictSerializer, KeyRef, StringRef}; 121 | 122 | # fn main() -> Result<(), sfv::Error> { 123 | let mut ser = DictSerializer::new(); 124 | 125 | ser.bare_item(KeyRef::from_str("key1")?, StringRef::from_str("apple")?); 126 | 127 | ser.bare_item(KeyRef::from_str("key2")?, true); 128 | 129 | ser.bare_item(KeyRef::from_str("key3")?, false); 130 | 131 | assert_eq!( 132 | ser.finish().as_deref(), 133 | Some(r#"key1="apple", key2, key3=?0"#), 134 | ); 135 | # Ok(()) 136 | # } 137 | ``` 138 | 139 | # Crate features 140 | 141 | - `parsed-types` (enabled by default) -- When enabled, exposes fully owned types 142 | `Item`, `Dictionary`, `List`, and their components, which can be obtained from 143 | `Parser::parse_item`, etc. These types are implemented using the 144 | [`indexmap`](https://crates.io/crates/indexmap) crate, so disabling this 145 | feature can avoid that dependency if parsing using a visitor 146 | ([`Parser::parse_item_with_visitor`], etc.) is sufficient. 147 | 148 | - `arbitrary` -- Implements the 149 | [`Arbitrary`](https://docs.rs/arbitrary/1.4.1/arbitrary/trait.Arbitrary.html) 150 | trait for this crate's types, making them easier to use with fuzzing. 151 | */ 152 | 153 | #![deny(missing_docs)] 154 | 155 | mod date; 156 | mod decimal; 157 | mod error; 158 | mod integer; 159 | mod key; 160 | #[cfg(feature = "parsed-types")] 161 | mod parsed; 162 | mod parser; 163 | mod ref_serializer; 164 | mod serializer; 165 | mod string; 166 | mod token; 167 | mod utils; 168 | pub mod visitor; 169 | 170 | #[cfg(test)] 171 | mod test_decimal; 172 | #[cfg(test)] 173 | mod test_integer; 174 | #[cfg(test)] 175 | mod test_key; 176 | #[cfg(test)] 177 | mod test_parser; 178 | #[cfg(test)] 179 | mod test_ref_serializer; 180 | #[cfg(test)] 181 | mod test_serializer; 182 | #[cfg(test)] 183 | mod test_string; 184 | #[cfg(test)] 185 | mod test_token; 186 | 187 | use std::borrow::{Borrow, Cow}; 188 | use std::fmt; 189 | use std::string::String as StdString; 190 | 191 | pub use date::Date; 192 | pub use decimal::Decimal; 193 | pub use error::Error; 194 | pub use integer::{integer, Integer}; 195 | pub use key::{key_ref, Key, KeyRef}; 196 | #[cfg(feature = "parsed-types")] 197 | pub use parsed::{Dictionary, FieldType, InnerList, Item, List, ListEntry, Parameters}; 198 | pub use parser::Parser; 199 | pub use ref_serializer::{ 200 | DictSerializer, InnerListSerializer, ItemSerializer, ListSerializer, ParameterSerializer, 201 | }; 202 | pub use string::{string_ref, String, StringRef}; 203 | pub use token::{token_ref, Token, TokenRef}; 204 | 205 | type SFVResult = std::result::Result; 206 | 207 | /// An abstraction over multiple kinds of ownership of a [bare item]. 208 | /// 209 | /// In general most users will be interested in: 210 | /// - [`BareItem`], for completely owned data 211 | /// - [`RefBareItem`], for completely borrowed data 212 | /// - [`BareItemFromInput`], for data borrowed from input when possible 213 | /// 214 | /// [bare item]: 215 | #[derive(Debug, Clone, Copy)] 216 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 217 | pub enum GenericBareItem { 218 | /// A [decimal](https://httpwg.org/specs/rfc9651.html#decimal). 219 | // sf-decimal = ["-"] 1*12DIGIT "." 1*3DIGIT 220 | Decimal(Decimal), 221 | /// An [integer](https://httpwg.org/specs/rfc9651.html#integer). 222 | // sf-integer = ["-"] 1*15DIGIT 223 | Integer(Integer), 224 | /// A [string](https://httpwg.org/specs/rfc9651.html#string). 225 | // sf-string = DQUOTE *chr DQUOTE 226 | // chr = unescaped / escaped 227 | // unescaped = %x20-21 / %x23-5B / %x5D-7E 228 | // escaped = "\" ( DQUOTE / "\" ) 229 | String(S), 230 | /// A [byte sequence](https://httpwg.org/specs/rfc9651.html#binary). 231 | // ":" *(base64) ":" 232 | // base64 = ALPHA / DIGIT / "+" / "/" / "=" 233 | ByteSequence(B), 234 | /// A [boolean](https://httpwg.org/specs/rfc9651.html#boolean). 235 | // sf-boolean = "?" boolean 236 | // boolean = "0" / "1" 237 | Boolean(bool), 238 | /// A [token](https://httpwg.org/specs/rfc9651.html#token). 239 | // sf-token = ( ALPHA / "*" ) *( tchar / ":" / "/" ) 240 | Token(T), 241 | /// A [date](https://httpwg.org/specs/rfc9651.html#date). 242 | /// 243 | /// [`Parser`] will never produce this variant when used with 244 | /// [`Version::Rfc8941`]. 245 | // sf-date = "@" sf-integer 246 | Date(Date), 247 | /// A [display string](https://httpwg.org/specs/rfc9651.html#displaystring). 248 | /// 249 | /// Display Strings are similar to [`String`]s, in that they consist of zero 250 | /// or more characters, but they allow Unicode scalar values (i.e., all 251 | /// Unicode code points except for surrogates), unlike [`String`]s. 252 | /// 253 | /// [`Parser`] will never produce this variant when used with 254 | /// [`Version::Rfc8941`]. 255 | /// 256 | /// [display string]: 257 | // sf-displaystring = "%" DQUOTE *( unescaped / "\" / pct-encoded ) DQUOTE 258 | // pct-encoded = "%" lc-hexdig lc-hexdig 259 | // lc-hexdig = DIGIT / %x61-66 ; 0-9, a-f 260 | DisplayString(D), 261 | } 262 | 263 | impl GenericBareItem { 264 | /// If the bare item is a decimal, returns it; otherwise returns `None`. 265 | #[must_use] 266 | pub fn as_decimal(&self) -> Option { 267 | match *self { 268 | Self::Decimal(val) => Some(val), 269 | _ => None, 270 | } 271 | } 272 | 273 | /// If the bare item is an integer, returns it; otherwise returns `None`. 274 | #[must_use] 275 | pub fn as_integer(&self) -> Option { 276 | match *self { 277 | Self::Integer(val) => Some(val), 278 | _ => None, 279 | } 280 | } 281 | 282 | /// If the bare item is a string, returns a reference to it; otherwise returns `None`. 283 | #[must_use] 284 | pub fn as_string(&self) -> Option<&StringRef> 285 | where 286 | S: Borrow, 287 | { 288 | match *self { 289 | Self::String(ref val) => Some(val.borrow()), 290 | _ => None, 291 | } 292 | } 293 | 294 | /// If the bare item is a byte sequence, returns a reference to it; otherwise returns `None`. 295 | #[must_use] 296 | pub fn as_byte_sequence(&self) -> Option<&[u8]> 297 | where 298 | B: Borrow<[u8]>, 299 | { 300 | match *self { 301 | Self::ByteSequence(ref val) => Some(val.borrow()), 302 | _ => None, 303 | } 304 | } 305 | 306 | /// If the bare item is a boolean, returns it; otherwise returns `None`. 307 | #[must_use] 308 | pub fn as_boolean(&self) -> Option { 309 | match *self { 310 | Self::Boolean(val) => Some(val), 311 | _ => None, 312 | } 313 | } 314 | 315 | /// If the bare item is a token, returns a reference to it; otherwise returns `None`. 316 | #[must_use] 317 | pub fn as_token(&self) -> Option<&TokenRef> 318 | where 319 | T: Borrow, 320 | { 321 | match *self { 322 | Self::Token(ref val) => Some(val.borrow()), 323 | _ => None, 324 | } 325 | } 326 | 327 | /// If the bare item is a date, returns it; otherwise returns `None`. 328 | #[must_use] 329 | pub fn as_date(&self) -> Option { 330 | match *self { 331 | Self::Date(val) => Some(val), 332 | _ => None, 333 | } 334 | } 335 | 336 | /// If the bare item is a display string, returns a reference to it; otherwise returns `None`. 337 | #[must_use] 338 | pub fn as_display_string(&self) -> Option<&D> { 339 | match *self { 340 | Self::DisplayString(ref val) => Some(val), 341 | _ => None, 342 | } 343 | } 344 | } 345 | 346 | impl From for GenericBareItem { 347 | fn from(val: Integer) -> Self { 348 | Self::Integer(val) 349 | } 350 | } 351 | 352 | impl From for GenericBareItem { 353 | fn from(val: bool) -> Self { 354 | Self::Boolean(val) 355 | } 356 | } 357 | 358 | impl From for GenericBareItem { 359 | fn from(val: Decimal) -> Self { 360 | Self::Decimal(val) 361 | } 362 | } 363 | 364 | impl From for GenericBareItem { 365 | fn from(val: Date) -> Self { 366 | Self::Date(val) 367 | } 368 | } 369 | 370 | impl TryFrom for GenericBareItem { 371 | type Error = Error; 372 | 373 | fn try_from(val: f32) -> Result { 374 | Decimal::try_from(val).map(Self::Decimal) 375 | } 376 | } 377 | 378 | impl TryFrom for GenericBareItem { 379 | type Error = Error; 380 | 381 | fn try_from(val: f64) -> Result { 382 | Decimal::try_from(val).map(Self::Decimal) 383 | } 384 | } 385 | 386 | impl From> for GenericBareItem, T, D> { 387 | fn from(val: Vec) -> Self { 388 | Self::ByteSequence(val) 389 | } 390 | } 391 | 392 | impl From for GenericBareItem { 393 | fn from(val: Token) -> Self { 394 | Self::Token(val) 395 | } 396 | } 397 | 398 | impl From for GenericBareItem { 399 | fn from(val: String) -> Self { 400 | Self::String(val) 401 | } 402 | } 403 | 404 | impl<'a, S, T, D> From<&'a [u8]> for GenericBareItem, T, D> { 405 | fn from(val: &'a [u8]) -> Self { 406 | Self::ByteSequence(val.to_owned()) 407 | } 408 | } 409 | 410 | impl<'a, S, B, D> From<&'a TokenRef> for GenericBareItem { 411 | fn from(val: &'a TokenRef) -> Self { 412 | Self::Token(val.to_owned()) 413 | } 414 | } 415 | 416 | impl<'a, B, T, D> From<&'a StringRef> for GenericBareItem { 417 | fn from(val: &'a StringRef) -> Self { 418 | Self::String(val.to_owned()) 419 | } 420 | } 421 | 422 | #[derive(Debug, PartialEq)] 423 | pub(crate) enum Num { 424 | Decimal(Decimal), 425 | Integer(Integer), 426 | } 427 | 428 | /// A [bare item] that owns its data. 429 | /// 430 | /// [bare item]: 431 | #[cfg_attr( 432 | feature = "parsed-types", 433 | doc = "Used to construct an [`Item`] or [`Parameters`] values." 434 | )] 435 | /// 436 | /// Note: This type deliberately does not implement `From` as a 437 | /// shorthand for [`BareItem::DisplayString`] because it is too easy to confuse 438 | /// with conversions from [`String`]: 439 | /// 440 | /// ```compile_fail 441 | /// # use sfv::BareItem; 442 | /// let _: BareItem = "x".to_owned().into(); 443 | /// ``` 444 | /// 445 | /// Instead, use: 446 | /// 447 | /// ``` 448 | /// # use sfv::BareItem; 449 | /// let _ = BareItem::DisplayString("x".to_owned()); 450 | /// ``` 451 | pub type BareItem = GenericBareItem, Token, StdString>; 452 | 453 | /// A [bare item] that borrows its data. 454 | /// 455 | /// Used to serialize values via [`ItemSerializer`], [`ListSerializer`], and [`DictSerializer`]. 456 | /// 457 | /// [bare item]: 458 | /// 459 | /// Note: This type deliberately does not implement `From<&str>` as a shorthand 460 | /// for [`RefBareItem::DisplayString`] because it is too easy to confuse with 461 | /// conversions from [`StringRef`]: 462 | /// 463 | /// ```compile_fail 464 | /// # use sfv::RefBareItem; 465 | /// let _: RefBareItem = "x".into(); 466 | /// ``` 467 | /// 468 | /// Instead, use: 469 | /// 470 | /// ``` 471 | /// # use sfv::RefBareItem; 472 | /// let _ = RefBareItem::DisplayString("x"); 473 | /// ``` 474 | pub type RefBareItem<'a> = GenericBareItem<&'a StringRef, &'a [u8], &'a TokenRef, &'a str>; 475 | 476 | /// A [bare item] that borrows data from input when possible. 477 | /// 478 | /// Used to parse input incrementally in the [`visitor`] module. 479 | /// 480 | /// [bare item]: 481 | /// 482 | /// Note: This type deliberately does not implement `From>` as a 483 | /// shorthand for [`BareItemFromInput::DisplayString`] because it is too easy to 484 | /// confuse with conversions from [`Cow`]: 485 | /// 486 | /// ```compile_fail 487 | /// # use sfv::BareItemFromInput; 488 | /// # use std::borrow::Cow; 489 | /// let _: BareItemFromInput = "x".to_owned().into(); 490 | /// ``` 491 | /// 492 | /// Instead, use: 493 | /// 494 | /// ``` 495 | /// # use sfv::BareItemFromInput; 496 | /// # use std::borrow::Cow; 497 | /// let _ = BareItemFromInput::DisplayString(Cow::Borrowed("x")); 498 | /// ``` 499 | pub type BareItemFromInput<'a> = 500 | GenericBareItem, Vec, &'a TokenRef, Cow<'a, str>>; 501 | 502 | impl<'a, S, B, T, D> From<&'a GenericBareItem> for RefBareItem<'a> 503 | where 504 | S: Borrow, 505 | B: Borrow<[u8]>, 506 | T: Borrow, 507 | D: Borrow, 508 | { 509 | fn from(val: &'a GenericBareItem) -> RefBareItem<'a> { 510 | match val { 511 | GenericBareItem::Integer(val) => RefBareItem::Integer(*val), 512 | GenericBareItem::Decimal(val) => RefBareItem::Decimal(*val), 513 | GenericBareItem::String(val) => RefBareItem::String(val.borrow()), 514 | GenericBareItem::ByteSequence(val) => RefBareItem::ByteSequence(val.borrow()), 515 | GenericBareItem::Boolean(val) => RefBareItem::Boolean(*val), 516 | GenericBareItem::Token(val) => RefBareItem::Token(val.borrow()), 517 | GenericBareItem::Date(val) => RefBareItem::Date(*val), 518 | GenericBareItem::DisplayString(val) => RefBareItem::DisplayString(val.borrow()), 519 | } 520 | } 521 | } 522 | 523 | impl<'a> From> for BareItem { 524 | fn from(val: BareItemFromInput<'a>) -> BareItem { 525 | match val { 526 | BareItemFromInput::Integer(val) => BareItem::Integer(val), 527 | BareItemFromInput::Decimal(val) => BareItem::Decimal(val), 528 | BareItemFromInput::String(val) => BareItem::String(val.into_owned()), 529 | BareItemFromInput::ByteSequence(val) => BareItem::ByteSequence(val), 530 | BareItemFromInput::Boolean(val) => BareItem::Boolean(val), 531 | BareItemFromInput::Token(val) => BareItem::Token(val.to_owned()), 532 | BareItemFromInput::Date(val) => BareItem::Date(val), 533 | BareItemFromInput::DisplayString(val) => BareItem::DisplayString(val.into_owned()), 534 | } 535 | } 536 | } 537 | 538 | impl<'a> From> for BareItem { 539 | fn from(val: RefBareItem<'a>) -> BareItem { 540 | match val { 541 | RefBareItem::Integer(val) => BareItem::Integer(val), 542 | RefBareItem::Decimal(val) => BareItem::Decimal(val), 543 | RefBareItem::String(val) => BareItem::String(val.to_owned()), 544 | RefBareItem::ByteSequence(val) => BareItem::ByteSequence(val.to_owned()), 545 | RefBareItem::Boolean(val) => BareItem::Boolean(val), 546 | RefBareItem::Token(val) => BareItem::Token(val.to_owned()), 547 | RefBareItem::Date(val) => BareItem::Date(val), 548 | RefBareItem::DisplayString(val) => BareItem::DisplayString(val.to_owned()), 549 | } 550 | } 551 | } 552 | 553 | impl<'a, S, T, D> From<&'a [u8]> for GenericBareItem { 554 | fn from(val: &'a [u8]) -> Self { 555 | Self::ByteSequence(val) 556 | } 557 | } 558 | 559 | impl<'a, S, B, D> From<&'a Token> for GenericBareItem { 560 | fn from(val: &'a Token) -> Self { 561 | Self::Token(val) 562 | } 563 | } 564 | 565 | impl<'a, S, B, D> From<&'a TokenRef> for GenericBareItem { 566 | fn from(val: &'a TokenRef) -> Self { 567 | Self::Token(val) 568 | } 569 | } 570 | 571 | impl<'a, B, T, D> From<&'a String> for GenericBareItem<&'a StringRef, B, T, D> { 572 | fn from(val: &'a String) -> Self { 573 | Self::String(val) 574 | } 575 | } 576 | 577 | impl<'a, B, T, D> From<&'a StringRef> for GenericBareItem<&'a StringRef, B, T, D> { 578 | fn from(val: &'a StringRef) -> Self { 579 | Self::String(val) 580 | } 581 | } 582 | 583 | impl PartialEq> 584 | for GenericBareItem 585 | where 586 | for<'a> RefBareItem<'a>: From<&'a Self>, 587 | for<'a> RefBareItem<'a>: From<&'a GenericBareItem>, 588 | { 589 | fn eq(&self, other: &GenericBareItem) -> bool { 590 | match (RefBareItem::from(self), RefBareItem::from(other)) { 591 | (RefBareItem::Integer(a), RefBareItem::Integer(b)) => a == b, 592 | (RefBareItem::Decimal(a), RefBareItem::Decimal(b)) => a == b, 593 | (RefBareItem::String(a), RefBareItem::String(b)) => a == b, 594 | (RefBareItem::ByteSequence(a), RefBareItem::ByteSequence(b)) => a == b, 595 | (RefBareItem::Boolean(a), RefBareItem::Boolean(b)) => a == b, 596 | (RefBareItem::Token(a), RefBareItem::Token(b)) => a == b, 597 | (RefBareItem::Date(a), RefBareItem::Date(b)) => a == b, 598 | (RefBareItem::DisplayString(a), RefBareItem::DisplayString(b)) => a == b, 599 | _ => false, 600 | } 601 | } 602 | } 603 | 604 | /// A version for serialized structured field values. 605 | /// 606 | /// Each HTTP specification that uses structured field values must indicate 607 | /// which version it uses. See [the guidance from RFC 9651] for details. 608 | /// 609 | /// [RFC 9651]: 610 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 611 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 612 | pub enum Version { 613 | /// [RFC 8941], which does not support dates or display strings. 614 | /// 615 | /// [RFC 8941]: 616 | Rfc8941, 617 | /// [RFC 9651], which supports dates and display strings. 618 | /// 619 | /// [RFC 9651]: 620 | Rfc9651, 621 | } 622 | 623 | impl fmt::Display for Version { 624 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 625 | f.write_str(match self { 626 | Self::Rfc8941 => "RFC 8941", 627 | Self::Rfc9651 => "RFC 9651", 628 | }) 629 | } 630 | } 631 | 632 | mod private { 633 | #[allow(unused)] 634 | pub trait Sealed {} 635 | } 636 | -------------------------------------------------------------------------------- /src/parsed.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use indexmap::IndexMap; 4 | 5 | use crate::{ 6 | private::Sealed, 7 | visitor::{ 8 | DictionaryVisitor, EntryVisitor, InnerListVisitor, ItemVisitor, ListVisitor, 9 | ParameterVisitor, 10 | }, 11 | BareItem, BareItemFromInput, Error, Key, KeyRef, Parser, 12 | }; 13 | 14 | /// An [item]-type structured field value. 15 | /// 16 | /// Can be used as a member of `List` or `Dictionary`. 17 | /// 18 | /// [item]: 19 | // sf-item = bare-item parameters 20 | // bare-item = sf-integer / sf-decimal / sf-string / sf-token 21 | // / sf-binary / sf-boolean 22 | #[derive(Debug, PartialEq, Clone)] 23 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 24 | pub struct Item { 25 | /// The item's value. 26 | pub bare_item: BareItem, 27 | /// The item's parameters, which can be empty. 28 | pub params: Parameters, 29 | } 30 | 31 | impl Item { 32 | /// Returns a new `Item` with empty `Parameters`. 33 | #[must_use] 34 | pub fn new(bare_item: impl Into) -> Self { 35 | Self { 36 | bare_item: bare_item.into(), 37 | params: Parameters::new(), 38 | } 39 | } 40 | 41 | /// Returns a new `Item` with the given `Parameters`. 42 | #[must_use] 43 | pub fn with_params(bare_item: impl Into, params: Parameters) -> Self { 44 | Self { 45 | bare_item: bare_item.into(), 46 | params, 47 | } 48 | } 49 | } 50 | 51 | /// A [dictionary]-type structured field value. 52 | /// 53 | /// [dictionary]: 54 | // sf-dictionary = dict-member *( OWS "," OWS dict-member ) 55 | // dict-member = member-name [ "=" member-value ] 56 | // member-name = key 57 | // member-value = sf-item / inner-list 58 | pub type Dictionary = IndexMap; 59 | 60 | /// A [list]-type structured field value. 61 | /// 62 | /// [list]: 63 | // sf-list = list-member *( OWS "," OWS list-member ) 64 | // list-member = sf-item / inner-list 65 | pub type List = Vec; 66 | 67 | /// [Parameters] of an [`Item`] or [`InnerList`]. 68 | /// 69 | /// [parameters]: 70 | // parameters = *( ";" *SP parameter ) 71 | // parameter = param-name [ "=" param-value ] 72 | // param-name = key 73 | // key = ( lcalpha / "*" ) 74 | // *( lcalpha / DIGIT / "_" / "-" / "." / "*" ) 75 | // lcalpha = %x61-7A ; a-z 76 | // param-value = bare-item 77 | pub type Parameters = IndexMap; 78 | 79 | /// A member of a [`List`] or [`Dictionary`]. 80 | #[derive(Debug, PartialEq, Clone)] 81 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 82 | pub enum ListEntry { 83 | /// An item. 84 | Item(Item), 85 | /// An inner list. 86 | InnerList(InnerList), 87 | } 88 | 89 | impl From for ListEntry { 90 | fn from(item: Item) -> Self { 91 | ListEntry::Item(item) 92 | } 93 | } 94 | 95 | impl From for ListEntry { 96 | fn from(inner_list: InnerList) -> Self { 97 | ListEntry::InnerList(inner_list) 98 | } 99 | } 100 | 101 | /// An [array] of [`Item`]s with associated [`Parameters`]. 102 | /// 103 | /// [array]: 104 | // inner-list = "(" *SP [ sf-item *( 1*SP sf-item ) *SP ] ")" 105 | // parameters 106 | #[derive(Debug, Default, PartialEq, Clone)] 107 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 108 | pub struct InnerList { 109 | /// The inner list's items, which can be empty. 110 | pub items: Vec, 111 | /// The inner list's parameters, which can be empty. 112 | pub params: Parameters, 113 | } 114 | 115 | impl InnerList { 116 | /// Returns a new `InnerList` with empty `Parameters`. 117 | #[must_use] 118 | pub fn new(items: Vec) -> Self { 119 | Self { 120 | items, 121 | params: Parameters::new(), 122 | } 123 | } 124 | 125 | /// Returns a new `InnerList` with the given `Parameters`. 126 | #[must_use] 127 | pub fn with_params(items: Vec, params: Parameters) -> Self { 128 | Self { items, params } 129 | } 130 | } 131 | 132 | impl<'de> ParameterVisitor<'de> for &mut Parameters { 133 | type Error = Infallible; 134 | 135 | fn parameter( 136 | &mut self, 137 | key: &'de KeyRef, 138 | value: BareItemFromInput<'de>, 139 | ) -> Result<(), Self::Error> { 140 | self.insert(key.to_owned(), value.into()); 141 | Ok(()) 142 | } 143 | } 144 | 145 | impl<'de> ItemVisitor<'de> for &mut Item { 146 | type Error = Infallible; 147 | 148 | fn bare_item( 149 | self, 150 | bare_item: BareItemFromInput<'de>, 151 | ) -> Result, Self::Error> { 152 | self.bare_item = bare_item.into(); 153 | Ok(&mut self.params) 154 | } 155 | } 156 | 157 | impl<'de> ItemVisitor<'de> for &mut InnerList { 158 | type Error = Infallible; 159 | fn bare_item( 160 | self, 161 | bare_item: BareItemFromInput<'de>, 162 | ) -> Result, Self::Error> { 163 | self.items.push(Item::new(bare_item)); 164 | match self.items.last_mut() { 165 | Some(item) => Ok(&mut item.params), 166 | None => unreachable!(), 167 | } 168 | } 169 | } 170 | 171 | impl<'de> InnerListVisitor<'de> for &mut InnerList { 172 | type Error = Infallible; 173 | 174 | fn item(&mut self) -> Result, Self::Error> { 175 | Ok(&mut **self) 176 | } 177 | 178 | fn finish(self) -> Result, Self::Error> { 179 | Ok(&mut self.params) 180 | } 181 | } 182 | 183 | impl<'de> DictionaryVisitor<'de> for Dictionary { 184 | type Error = Infallible; 185 | 186 | fn entry(&mut self, key: &'de KeyRef) -> Result, Self::Error> { 187 | Ok(self.entry(key.to_owned())) 188 | } 189 | } 190 | 191 | type Entry<'a> = indexmap::map::Entry<'a, Key, ListEntry>; 192 | 193 | impl<'de> ItemVisitor<'de> for Entry<'_> { 194 | type Error = Infallible; 195 | 196 | fn bare_item( 197 | self, 198 | bare_item: BareItemFromInput<'de>, 199 | ) -> Result, Self::Error> { 200 | match self.insert_entry(Item::new(bare_item).into()).into_mut() { 201 | ListEntry::Item(item) => Ok(&mut item.params), 202 | ListEntry::InnerList(_) => unreachable!(), 203 | } 204 | } 205 | } 206 | 207 | impl<'de> EntryVisitor<'de> for Entry<'_> { 208 | fn inner_list(self) -> Result, Self::Error> { 209 | match self.insert_entry(InnerList::default().into()).into_mut() { 210 | ListEntry::InnerList(inner_list) => Ok(inner_list), 211 | ListEntry::Item(_) => unreachable!(), 212 | } 213 | } 214 | } 215 | 216 | impl<'de> ItemVisitor<'de> for &mut List { 217 | type Error = Infallible; 218 | 219 | fn bare_item( 220 | self, 221 | bare_item: BareItemFromInput<'de>, 222 | ) -> Result, Self::Error> { 223 | self.push(Item::new(bare_item).into()); 224 | match self.last_mut() { 225 | Some(ListEntry::Item(item)) => Ok(&mut item.params), 226 | _ => unreachable!(), 227 | } 228 | } 229 | } 230 | 231 | impl<'de> EntryVisitor<'de> for &mut List { 232 | fn inner_list(self) -> Result, Self::Error> { 233 | self.push(InnerList::default().into()); 234 | match self.last_mut() { 235 | Some(ListEntry::InnerList(inner_list)) => Ok(inner_list), 236 | _ => unreachable!(), 237 | } 238 | } 239 | } 240 | 241 | impl<'de> ListVisitor<'de> for List { 242 | type Error = Infallible; 243 | 244 | fn entry(&mut self) -> Result, Self::Error> { 245 | Ok(self) 246 | } 247 | } 248 | 249 | /// A structured-field type, supporting parsing and serialization. 250 | pub trait FieldType: Sealed { 251 | /// The result of serializing the value into a string. 252 | /// 253 | /// [`Item`] serialization is infallible; [`List`] and [`Dictionary`] 254 | /// serialization is not. 255 | type SerializeResult: Into>; 256 | 257 | /// Serializes a structured field value into a string. 258 | /// 259 | /// Note: The serialization conforms to [RFC 9651], meaning that 260 | /// [`Dates`][crate::Date] and [`Display Strings`][RefBareItem::DisplayString], 261 | /// which cause parsing errors under [RFC 8941], will be serialized 262 | /// unconditionally. The consumer of this API is responsible for determining 263 | /// whether it is valid to serialize these bare items for any specific field. 264 | /// 265 | /// [RFC 8941]: 266 | /// [RFC 9651]: 267 | /// 268 | /// Use [`crate::ItemSerializer`], [`crate::ListSerializer`], or 269 | /// [`crate::DictSerializer`] to serialize components incrementally without 270 | /// having to create an [`Item`], [`List`], or [`Dictionary`]. 271 | #[must_use] 272 | fn serialize(&self) -> Self::SerializeResult; 273 | 274 | /// Parses a structured-field value from the given parser. 275 | /// 276 | /// # Errors 277 | /// When the parsing process is unsuccessful. 278 | fn parse(parser: Parser<'_>) -> Result 279 | where 280 | Self: Sized; 281 | } 282 | 283 | impl Sealed for Item {} 284 | 285 | impl FieldType for Item { 286 | type SerializeResult = String; 287 | 288 | fn serialize(&self) -> String { 289 | crate::ItemSerializer::new() 290 | .bare_item(&self.bare_item) 291 | .parameters(&self.params) 292 | .finish() 293 | } 294 | 295 | fn parse(parser: Parser<'_>) -> Result { 296 | let mut item = Self::new(false); 297 | parser.parse_item_with_visitor(&mut item)?; 298 | Ok(item) 299 | } 300 | } 301 | 302 | impl Sealed for List {} 303 | 304 | impl FieldType for List { 305 | type SerializeResult = Option; 306 | 307 | fn serialize(&self) -> Option { 308 | let mut ser = crate::ListSerializer::new(); 309 | ser.members(self); 310 | ser.finish() 311 | } 312 | 313 | fn parse(parser: Parser<'_>) -> Result { 314 | let mut list = Self::new(); 315 | parser.parse_list_with_visitor(&mut list)?; 316 | Ok(list) 317 | } 318 | } 319 | 320 | impl Sealed for Dictionary {} 321 | 322 | impl FieldType for Dictionary { 323 | type SerializeResult = Option; 324 | 325 | fn serialize(&self) -> Option { 326 | let mut ser = crate::DictSerializer::new(); 327 | ser.members(self); 328 | ser.finish() 329 | } 330 | 331 | fn parse(parser: Parser<'_>) -> Result { 332 | let mut dict = Self::new(); 333 | parser.parse_dictionary_with_visitor(&mut dict)?; 334 | Ok(dict) 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, string::String as StdString}; 2 | 3 | use crate::{ 4 | utils, 5 | visitor::{ 6 | DictionaryVisitor, EntryVisitor, InnerListVisitor, ItemVisitor, ListVisitor, 7 | ParameterVisitor, 8 | }, 9 | BareItemFromInput, Date, Decimal, Error, Integer, KeyRef, Num, SFVResult, String, StringRef, 10 | TokenRef, Version, 11 | }; 12 | 13 | fn parse_item<'de>(parser: &mut Parser<'de>, visitor: impl ItemVisitor<'de>) -> SFVResult<()> { 14 | // https://httpwg.org/specs/rfc9651.html#parse-item 15 | let param_visitor = visitor 16 | .bare_item(parser.parse_bare_item()?) 17 | .map_err(Error::custom)?; 18 | parser.parse_parameters(param_visitor) 19 | } 20 | 21 | fn parse_comma_separated<'de>( 22 | parser: &mut Parser<'de>, 23 | mut parse_member: impl FnMut(&mut Parser<'de>) -> SFVResult<()>, 24 | ) -> SFVResult<()> { 25 | while parser.peek().is_some() { 26 | parse_member(parser)?; 27 | 28 | parser.consume_ows_chars(); 29 | 30 | if parser.peek().is_none() { 31 | return Ok(()); 32 | } 33 | 34 | let comma_index = parser.index; 35 | 36 | if let Some(c) = parser.peek() { 37 | if c != b',' { 38 | return parser.error("trailing characters after member"); 39 | } 40 | parser.next(); 41 | } 42 | 43 | parser.consume_ows_chars(); 44 | 45 | if parser.peek().is_none() { 46 | // Report the error at the position of the comma itself, rather 47 | // than at the end of input. 48 | return Err(Error::with_index("trailing comma", comma_index)); 49 | } 50 | } 51 | 52 | Ok(()) 53 | } 54 | 55 | /// Exposes methods for parsing input into a structured field value. 56 | #[must_use] 57 | pub struct Parser<'de> { 58 | input: &'de [u8], 59 | index: usize, 60 | version: Version, 61 | } 62 | 63 | impl<'de> Parser<'de> { 64 | /// Creates a parser from the given input with [`Version::Rfc9651`]. 65 | pub fn new(input: &'de (impl ?Sized + AsRef<[u8]>)) -> Self { 66 | Self { 67 | input: input.as_ref(), 68 | index: 0, 69 | version: Version::Rfc9651, 70 | } 71 | } 72 | 73 | /// Sets the parser's version and returns it. 74 | pub fn with_version(mut self, version: Version) -> Self { 75 | self.version = version; 76 | self 77 | } 78 | 79 | /// Parses a structured field value. 80 | /// 81 | /// # Errors 82 | /// When the parsing process is unsuccessful. 83 | #[cfg(feature = "parsed-types")] 84 | pub fn parse(self) -> SFVResult { 85 | T::parse(self) 86 | } 87 | 88 | /// Parses input into a structured field value of `Dictionary` type, using 89 | /// the given visitor. 90 | #[cfg_attr( 91 | feature = "parsed-types", 92 | doc = r#" 93 | 94 | This can also be used to parse a dictionary that is split into multiple lines by merging 95 | them into an existing structure: 96 | 97 | ``` 98 | # use sfv::{Dictionary, FieldType, Parser}; 99 | # fn main() -> Result<(), sfv::Error> { 100 | let mut dict: Dictionary = Parser::new("a=1").parse()?; 101 | 102 | Parser::new("b=2").parse_dictionary_with_visitor(&mut dict)?; 103 | 104 | assert_eq!( 105 | dict.serialize().as_deref(), 106 | Some("a=1, b=2"), 107 | ); 108 | # Ok(()) 109 | # } 110 | ``` 111 | "# 112 | )] 113 | /// 114 | /// # Errors 115 | /// When the parsing process is unsuccessful, including any error raised by a visitor. 116 | pub fn parse_dictionary_with_visitor( 117 | self, 118 | visitor: &mut (impl ?Sized + DictionaryVisitor<'de>), 119 | ) -> SFVResult<()> { 120 | // https://httpwg.org/specs/rfc9651.html#parse-dictionary 121 | self.parse_internal(move |parser| { 122 | parse_comma_separated(parser, |parser| { 123 | // Note: It is up to the visitor to properly handle duplicate keys. 124 | let entry_visitor = visitor.entry(parser.parse_key()?).map_err(Error::custom)?; 125 | 126 | if let Some(b'=') = parser.peek() { 127 | parser.next(); 128 | parser.parse_list_entry(entry_visitor) 129 | } else { 130 | let param_visitor = entry_visitor 131 | .bare_item(BareItemFromInput::from(true)) 132 | .map_err(Error::custom)?; 133 | parser.parse_parameters(param_visitor) 134 | } 135 | }) 136 | }) 137 | } 138 | 139 | /// Parses input into a structured field value of `List` type, using the 140 | /// given visitor. 141 | #[allow(clippy::needless_raw_string_hashes)] // false positive: https://github.com/rust-lang/rust-clippy/issues/11737 142 | #[cfg_attr( 143 | feature = "parsed-types", 144 | doc = r##" 145 | 146 | This can also be used to parse a list that is split into multiple lines by merging them 147 | into an existing structure: 148 | ``` 149 | # use sfv::{FieldType, List, Parser}; 150 | # fn main() -> Result<(), sfv::Error> { 151 | let mut list: List = Parser::new("11, (12 13)").parse()?; 152 | 153 | Parser::new(r#""foo", "bar""#).parse_list_with_visitor(&mut list)?; 154 | 155 | assert_eq!( 156 | list.serialize().as_deref(), 157 | Some(r#"11, (12 13), "foo", "bar""#), 158 | ); 159 | # Ok(()) 160 | # } 161 | ``` 162 | "## 163 | )] 164 | /// 165 | /// # Errors 166 | /// When the parsing process is unsuccessful, including any error raised by a visitor. 167 | pub fn parse_list_with_visitor( 168 | self, 169 | visitor: &mut (impl ?Sized + ListVisitor<'de>), 170 | ) -> SFVResult<()> { 171 | // https://httpwg.org/specs/rfc9651.html#parse-list 172 | self.parse_internal(|parser| { 173 | parse_comma_separated(parser, |parser| { 174 | parser.parse_list_entry(visitor.entry().map_err(Error::custom)?) 175 | }) 176 | }) 177 | } 178 | 179 | /// Parses input into a structured field value of `Item` type, using the 180 | /// given visitor. 181 | /// 182 | /// # Errors 183 | /// When the parsing process is unsuccessful, including any error raised by a visitor. 184 | pub fn parse_item_with_visitor(self, visitor: impl ItemVisitor<'de>) -> SFVResult<()> { 185 | self.parse_internal(|parser| parse_item(parser, visitor)) 186 | } 187 | 188 | fn peek(&self) -> Option { 189 | self.input.get(self.index).copied() 190 | } 191 | 192 | fn next(&mut self) -> Option { 193 | self.peek().inspect(|_| self.index += 1) 194 | } 195 | 196 | fn error(&self, msg: &'static str) -> SFVResult { 197 | Err(Error::with_index(msg, self.index)) 198 | } 199 | 200 | // Generic parse method for checking input before parsing 201 | // and handling trailing text error 202 | fn parse_internal(mut self, f: impl FnOnce(&mut Self) -> SFVResult<()>) -> SFVResult<()> { 203 | // https://httpwg.org/specs/rfc9651.html#text-parse 204 | 205 | self.consume_sp_chars(); 206 | 207 | f(&mut self)?; 208 | 209 | self.consume_sp_chars(); 210 | 211 | if self.peek().is_some() { 212 | self.error("trailing characters after parsed value") 213 | } else { 214 | Ok(()) 215 | } 216 | } 217 | 218 | fn parse_list_entry(&mut self, visitor: impl EntryVisitor<'de>) -> SFVResult<()> { 219 | // https://httpwg.org/specs/rfc9651.html#parse-item-or-list 220 | // ListEntry represents a tuple (item_or_inner_list, parameters) 221 | 222 | match self.peek() { 223 | Some(b'(') => self.parse_inner_list(visitor.inner_list().map_err(Error::custom)?), 224 | _ => parse_item(self, visitor), 225 | } 226 | } 227 | 228 | pub(crate) fn parse_inner_list( 229 | &mut self, 230 | mut visitor: impl InnerListVisitor<'de>, 231 | ) -> SFVResult<()> { 232 | // https://httpwg.org/specs/rfc9651.html#parse-innerlist 233 | 234 | if Some(b'(') != self.peek() { 235 | return self.error("expected start of inner list"); 236 | } 237 | 238 | self.next(); 239 | 240 | while self.peek().is_some() { 241 | self.consume_sp_chars(); 242 | 243 | if Some(b')') == self.peek() { 244 | self.next(); 245 | let param_visitor = visitor.finish().map_err(Error::custom)?; 246 | return self.parse_parameters(param_visitor); 247 | } 248 | 249 | parse_item(self, visitor.item().map_err(Error::custom)?)?; 250 | 251 | if let Some(c) = self.peek() { 252 | if c != b' ' && c != b')' { 253 | return self.error("expected inner list delimiter (' ' or ')')"); 254 | } 255 | } 256 | } 257 | 258 | self.error("unterminated inner list") 259 | } 260 | 261 | pub(crate) fn parse_bare_item(&mut self) -> SFVResult> { 262 | // https://httpwg.org/specs/rfc9651.html#parse-bare-item 263 | 264 | match self.peek() { 265 | Some(b'?') => Ok(BareItemFromInput::Boolean(self.parse_bool()?)), 266 | Some(b'"') => Ok(BareItemFromInput::String(self.parse_string()?)), 267 | Some(b':') => Ok(BareItemFromInput::ByteSequence(self.parse_byte_sequence()?)), 268 | Some(b'@') => Ok(BareItemFromInput::Date(self.parse_date()?)), 269 | Some(b'%') => Ok(BareItemFromInput::DisplayString( 270 | self.parse_display_string()?, 271 | )), 272 | Some(c) if utils::is_allowed_start_token_char(c) => { 273 | Ok(BareItemFromInput::Token(self.parse_token()?)) 274 | } 275 | Some(c) if c == b'-' || c.is_ascii_digit() => match self.parse_number()? { 276 | Num::Decimal(val) => Ok(BareItemFromInput::Decimal(val)), 277 | Num::Integer(val) => Ok(BareItemFromInput::Integer(val)), 278 | }, 279 | _ => self.error("expected start of bare item"), 280 | } 281 | } 282 | 283 | pub(crate) fn parse_bool(&mut self) -> SFVResult { 284 | // https://httpwg.org/specs/rfc9651.html#parse-boolean 285 | 286 | if self.peek() != Some(b'?') { 287 | return self.error("expected start of boolean ('?')"); 288 | } 289 | 290 | self.next(); 291 | 292 | match self.peek() { 293 | Some(b'0') => { 294 | self.next(); 295 | Ok(false) 296 | } 297 | Some(b'1') => { 298 | self.next(); 299 | Ok(true) 300 | } 301 | _ => self.error("expected boolean ('0' or '1')"), 302 | } 303 | } 304 | 305 | pub(crate) fn parse_string(&mut self) -> SFVResult> { 306 | // https://httpwg.org/specs/rfc9651.html#parse-string 307 | 308 | if self.peek() != Some(b'"') { 309 | return self.error(r#"expected start of string ('"')"#); 310 | } 311 | 312 | self.next(); 313 | 314 | let start = self.index; 315 | let mut output = Cow::Borrowed(&[] as &[u8]); 316 | 317 | while let Some(curr_char) = self.peek() { 318 | match curr_char { 319 | b'"' => { 320 | self.next(); 321 | // TODO: The UTF-8 validation is redundant with the preceding character checks, but 322 | // its removal is only possible with unsafe code. 323 | return Ok(match output { 324 | Cow::Borrowed(output) => { 325 | let output = std::str::from_utf8(output).unwrap(); 326 | Cow::Borrowed(StringRef::from_str(output).unwrap()) 327 | } 328 | Cow::Owned(output) => { 329 | let output = StdString::from_utf8(output).unwrap(); 330 | Cow::Owned(String::from_string(output).unwrap()) 331 | } 332 | }); 333 | } 334 | 0x00..=0x1f | 0x7f..=0xff => { 335 | return self.error("invalid string character"); 336 | } 337 | b'\\' => { 338 | self.next(); 339 | match self.peek() { 340 | Some(c @ (b'\\' | b'"')) => { 341 | self.next(); 342 | output.to_mut().push(c); 343 | } 344 | None => return self.error("unterminated escape sequence"), 345 | Some(_) => return self.error("invalid escape sequence"), 346 | } 347 | } 348 | _ => { 349 | self.next(); 350 | match output { 351 | Cow::Borrowed(ref mut output) => *output = &self.input[start..self.index], 352 | Cow::Owned(ref mut output) => output.push(curr_char), 353 | } 354 | } 355 | } 356 | } 357 | self.error("unterminated string") 358 | } 359 | 360 | fn parse_non_empty_str( 361 | &mut self, 362 | is_allowed_start_char: impl FnOnce(u8) -> bool, 363 | is_allowed_inner_char: impl Fn(u8) -> bool, 364 | ) -> Option<&'de str> { 365 | let start = self.index; 366 | 367 | match self.peek() { 368 | Some(c) if is_allowed_start_char(c) => { 369 | self.next(); 370 | } 371 | _ => return None, 372 | } 373 | 374 | loop { 375 | match self.peek() { 376 | Some(c) if is_allowed_inner_char(c) => { 377 | self.next(); 378 | } 379 | // TODO: The UTF-8 validation is redundant with the preceding character checks, but 380 | // its removal is only possible with unsafe code. 381 | _ => return Some(std::str::from_utf8(&self.input[start..self.index]).unwrap()), 382 | } 383 | } 384 | } 385 | 386 | pub(crate) fn parse_token(&mut self) -> SFVResult<&'de TokenRef> { 387 | // https://httpwg.org/specs/9651.html#parse-token 388 | 389 | match self.parse_non_empty_str( 390 | utils::is_allowed_start_token_char, 391 | utils::is_allowed_inner_token_char, 392 | ) { 393 | None => self.error("expected start of token"), 394 | Some(str) => Ok(TokenRef::from_validated_str(str)), 395 | } 396 | } 397 | 398 | pub(crate) fn parse_byte_sequence(&mut self) -> SFVResult> { 399 | // https://httpwg.org/specs/rfc9651.html#parse-binary 400 | 401 | if self.peek() != Some(b':') { 402 | return self.error("expected start of byte sequence (':')"); 403 | } 404 | 405 | self.next(); 406 | let start = self.index; 407 | 408 | loop { 409 | match self.next() { 410 | Some(b':') => break, 411 | Some(_) => {} 412 | None => return self.error("unterminated byte sequence"), 413 | } 414 | } 415 | 416 | let colon_index = self.index - 1; 417 | 418 | match base64::Engine::decode(&utils::BASE64, &self.input[start..colon_index]) { 419 | Ok(content) => Ok(content), 420 | Err(err) => { 421 | let index = match err { 422 | base64::DecodeError::InvalidByte(offset, _) 423 | | base64::DecodeError::InvalidLastSymbol(offset, _) => start + offset, 424 | // Report these two at the position of the last base64 425 | // character, since they correspond to errors in the input 426 | // as a whole. 427 | base64::DecodeError::InvalidLength(_) | base64::DecodeError::InvalidPadding => { 428 | colon_index - 1 429 | } 430 | }; 431 | 432 | Err(Error::with_index("invalid byte sequence", index)) 433 | } 434 | } 435 | } 436 | 437 | pub(crate) fn parse_number(&mut self) -> SFVResult { 438 | // https://httpwg.org/specs/rfc9651.html#parse-number 439 | 440 | fn char_to_i64(c: u8) -> i64 { 441 | i64::from(c - b'0') 442 | } 443 | 444 | let sign = if let Some(b'-') = self.peek() { 445 | self.next(); 446 | -1 447 | } else { 448 | 1 449 | }; 450 | 451 | let mut magnitude = match self.peek() { 452 | Some(c @ b'0'..=b'9') => { 453 | self.next(); 454 | char_to_i64(c) 455 | } 456 | _ => return self.error("expected digit"), 457 | }; 458 | 459 | let mut digits = 1; 460 | 461 | loop { 462 | match self.peek() { 463 | Some(b'.') => { 464 | if digits > 12 { 465 | return self.error("too many digits before decimal point"); 466 | } 467 | self.next(); 468 | break; 469 | } 470 | Some(c @ b'0'..=b'9') => { 471 | digits += 1; 472 | if digits > 15 { 473 | return self.error("too many digits"); 474 | } 475 | self.next(); 476 | magnitude = magnitude * 10 + char_to_i64(c); 477 | } 478 | _ => return Ok(Num::Integer(Integer::try_from(sign * magnitude).unwrap())), 479 | } 480 | } 481 | 482 | magnitude *= 1000; 483 | let mut scale = 100; 484 | 485 | while let Some(c @ b'0'..=b'9') = self.peek() { 486 | if scale == 0 { 487 | return self.error("too many digits after decimal point"); 488 | } 489 | 490 | self.next(); 491 | magnitude += char_to_i64(c) * scale; 492 | scale /= 10; 493 | } 494 | 495 | if scale == 100 { 496 | // Report the error at the position of the decimal itself, rather 497 | // than the next position. 498 | Err(Error::with_index("trailing decimal point", self.index - 1)) 499 | } else { 500 | Ok(Num::Decimal(Decimal::from_integer_scaled_1000( 501 | Integer::try_from(sign * magnitude).unwrap(), 502 | ))) 503 | } 504 | } 505 | 506 | pub(crate) fn parse_date(&mut self) -> SFVResult { 507 | // https://httpwg.org/specs/rfc9651.html#parse-date 508 | 509 | if self.peek() != Some(b'@') { 510 | return self.error("expected start of date ('@')"); 511 | } 512 | 513 | match self.version { 514 | Version::Rfc8941 => return self.error("RFC 8941 does not support dates"), 515 | Version::Rfc9651 => {} 516 | } 517 | 518 | let start = self.index; 519 | self.next(); 520 | 521 | match self.parse_number()? { 522 | Num::Integer(seconds) => Ok(Date::from_unix_seconds(seconds)), 523 | Num::Decimal(_) => Err(Error::with_index( 524 | "date must be an integer number of seconds", 525 | start, 526 | )), 527 | } 528 | } 529 | 530 | pub(crate) fn parse_display_string(&mut self) -> SFVResult> { 531 | // https://httpwg.org/specs/rfc9651.html#parse-display 532 | 533 | if self.peek() != Some(b'%') { 534 | return self.error("expected start of display string ('%')"); 535 | } 536 | 537 | match self.version { 538 | Version::Rfc8941 => return self.error("RFC 8941 does not support display strings"), 539 | Version::Rfc9651 => {} 540 | } 541 | 542 | self.next(); 543 | 544 | if self.peek() != Some(b'"') { 545 | return self.error(r#"expected '"'"#); 546 | } 547 | 548 | self.next(); 549 | 550 | let start = self.index; 551 | let mut output = Cow::Borrowed(&[] as &[u8]); 552 | 553 | while let Some(curr_char) = self.peek() { 554 | match curr_char { 555 | b'"' => { 556 | self.next(); 557 | return match output { 558 | Cow::Borrowed(output) => match std::str::from_utf8(output) { 559 | Ok(output) => Ok(Cow::Borrowed(output)), 560 | Err(err) => Err(Error::with_index( 561 | "invalid UTF-8 in display string", 562 | start + err.valid_up_to(), 563 | )), 564 | }, 565 | Cow::Owned(output) => match StdString::from_utf8(output) { 566 | Ok(output) => Ok(Cow::Owned(output)), 567 | Err(err) => Err(Error::with_index( 568 | "invalid UTF-8 in display string", 569 | start + err.utf8_error().valid_up_to(), 570 | )), 571 | }, 572 | }; 573 | } 574 | 0x00..=0x1f | 0x7f..=0xff => { 575 | return self.error("invalid display string character"); 576 | } 577 | b'%' => { 578 | self.next(); 579 | 580 | let mut octet = 0; 581 | 582 | for _ in 0..2 { 583 | octet = (octet << 4) 584 | + match self.peek() { 585 | Some(c @ b'0'..=b'9') => { 586 | self.next(); 587 | c - b'0' 588 | } 589 | Some(c @ b'a'..=b'f') => { 590 | self.next(); 591 | c - b'a' + 10 592 | } 593 | None => return self.error("unterminated escape sequence"), 594 | Some(_) => return self.error("invalid escape sequence"), 595 | }; 596 | } 597 | 598 | output.to_mut().push(octet); 599 | } 600 | _ => { 601 | self.next(); 602 | match output { 603 | Cow::Borrowed(ref mut output) => *output = &self.input[start..self.index], 604 | Cow::Owned(ref mut output) => output.push(curr_char), 605 | } 606 | } 607 | } 608 | } 609 | self.error("unterminated display string") 610 | } 611 | 612 | pub(crate) fn parse_parameters( 613 | &mut self, 614 | mut visitor: impl ParameterVisitor<'de>, 615 | ) -> SFVResult<()> { 616 | // https://httpwg.org/specs/rfc9651.html#parse-param 617 | 618 | while let Some(b';') = self.peek() { 619 | self.next(); 620 | self.consume_sp_chars(); 621 | 622 | let param_name = self.parse_key()?; 623 | let param_value = match self.peek() { 624 | Some(b'=') => { 625 | self.next(); 626 | self.parse_bare_item()? 627 | } 628 | _ => BareItemFromInput::Boolean(true), 629 | }; 630 | // Note: It is up to the visitor to properly handle duplicate keys. 631 | visitor 632 | .parameter(param_name, param_value) 633 | .map_err(Error::custom)?; 634 | } 635 | 636 | visitor.finish().map_err(Error::custom) 637 | } 638 | 639 | pub(crate) fn parse_key(&mut self) -> SFVResult<&'de KeyRef> { 640 | // https://httpwg.org/specs/rfc9651.html#parse-key 641 | 642 | match self.parse_non_empty_str( 643 | utils::is_allowed_start_key_char, 644 | utils::is_allowed_inner_key_char, 645 | ) { 646 | None => self.error("expected start of key ('a'-'z' or '*')"), 647 | Some(str) => Ok(KeyRef::from_validated_str(str)), 648 | } 649 | } 650 | 651 | fn consume_ows_chars(&mut self) { 652 | while let Some(b' ' | b'\t') = self.peek() { 653 | self.next(); 654 | } 655 | } 656 | 657 | fn consume_sp_chars(&mut self) { 658 | while let Some(b' ') = self.peek() { 659 | self.next(); 660 | } 661 | } 662 | 663 | #[cfg(test)] 664 | pub(crate) fn remaining(&self) -> &[u8] { 665 | &self.input[self.index..] 666 | } 667 | } 668 | -------------------------------------------------------------------------------- /src/ref_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | 3 | use crate::{serializer::Serializer, KeyRef, RefBareItem}; 4 | #[cfg(feature = "parsed-types")] 5 | use crate::{Item, ListEntry}; 6 | 7 | /// Serializes `Item` field value components incrementally. 8 | /// 9 | /// Note: The serialization conforms to [RFC 9651], meaning that 10 | /// [`Dates`][crate::Date] and [`Display Strings`][RefBareItem::DisplayString], 11 | /// which cause parsing errors under [RFC 8941], will be serialized 12 | /// unconditionally. The consumer of this API is responsible for determining 13 | /// whether it is valid to serialize these bare items for any specific field. 14 | /// 15 | /// [RFC 8941]: 16 | /// [RFC 9651]: 17 | /// ``` 18 | /// use sfv::{KeyRef, ItemSerializer}; 19 | /// 20 | /// # fn main() -> Result<(), sfv::Error> { 21 | /// let serialized_item = ItemSerializer::new() 22 | /// .bare_item(11) 23 | /// .parameter(KeyRef::from_str("foo")?, true) 24 | /// .finish(); 25 | /// 26 | /// assert_eq!(serialized_item, "11;foo"); 27 | /// # Ok(()) 28 | /// # } 29 | /// ``` 30 | // https://httpwg.org/specs/rfc9651.html#ser-item 31 | #[derive(Debug)] 32 | #[must_use] 33 | pub struct ItemSerializer { 34 | buffer: W, 35 | } 36 | 37 | impl Default for ItemSerializer { 38 | fn default() -> Self { 39 | Self::new() 40 | } 41 | } 42 | 43 | impl ItemSerializer { 44 | /// Creates a serializer that writes into a new string. 45 | pub fn new() -> Self { 46 | Self { 47 | buffer: String::new(), 48 | } 49 | } 50 | } 51 | 52 | impl<'a> ItemSerializer<&'a mut String> { 53 | /// Creates a serializer that writes into the given string. 54 | pub fn with_buffer(buffer: &'a mut String) -> Self { 55 | Self { buffer } 56 | } 57 | } 58 | 59 | impl> ItemSerializer { 60 | /// Serializes the given bare item. 61 | /// 62 | /// Returns a serializer for the item's parameters. 63 | pub fn bare_item<'b>( 64 | mut self, 65 | bare_item: impl Into>, 66 | ) -> ParameterSerializer { 67 | Serializer::serialize_bare_item(bare_item, self.buffer.borrow_mut()); 68 | ParameterSerializer { 69 | buffer: self.buffer, 70 | } 71 | } 72 | } 73 | 74 | /// Serializes parameters incrementally. 75 | #[derive(Debug)] 76 | #[must_use] 77 | pub struct ParameterSerializer { 78 | buffer: W, 79 | } 80 | 81 | impl> ParameterSerializer { 82 | /// Serializes a parameter with the given name and value. 83 | /// 84 | /// Returns the serializer. 85 | pub fn parameter<'b>(mut self, name: &KeyRef, value: impl Into>) -> Self { 86 | Serializer::serialize_parameter(name, value, self.buffer.borrow_mut()); 87 | self 88 | } 89 | 90 | /// Serializes the given parameters. 91 | /// 92 | /// Returns the serializer. 93 | pub fn parameters<'b>( 94 | mut self, 95 | params: impl IntoIterator, impl Into>)>, 96 | ) -> Self { 97 | for (name, value) in params { 98 | Serializer::serialize_parameter(name.as_ref(), value, self.buffer.borrow_mut()); 99 | } 100 | self 101 | } 102 | 103 | /// Finishes parameter serialization and returns the serializer's output. 104 | #[must_use] 105 | pub fn finish(self) -> W { 106 | self.buffer 107 | } 108 | } 109 | 110 | fn maybe_write_separator(buffer: &mut String, first: &mut bool) { 111 | if *first { 112 | *first = false; 113 | } else { 114 | buffer.push_str(", "); 115 | } 116 | } 117 | 118 | /// Serializes `List` field value components incrementally. 119 | /// 120 | /// Note: The serialization conforms to [RFC 9651], meaning that 121 | /// [`Dates`][crate::Date] and [`Display Strings`][RefBareItem::DisplayString], 122 | /// which cause parsing errors under [RFC 8941], will be serialized 123 | /// unconditionally. The consumer of this API is responsible for determining 124 | /// whether it is valid to serialize these bare items for any specific field. 125 | /// 126 | /// [RFC 8941]: 127 | /// [RFC 9651]: 128 | /// ``` 129 | /// use sfv::{KeyRef, StringRef, TokenRef, ListSerializer}; 130 | /// 131 | /// # fn main() -> Result<(), sfv::Error> { 132 | /// let mut ser = ListSerializer::new(); 133 | /// 134 | /// ser.bare_item(11) 135 | /// .parameter(KeyRef::from_str("foo")?, true); 136 | /// 137 | /// { 138 | /// let mut ser = ser.inner_list(); 139 | /// 140 | /// ser.bare_item(TokenRef::from_str("abc")?) 141 | /// .parameter(KeyRef::from_str("abc_param")?, false); 142 | /// 143 | /// ser.bare_item(TokenRef::from_str("def")?); 144 | /// 145 | /// ser.finish() 146 | /// .parameter(KeyRef::from_str("bar")?, StringRef::from_str("val")?); 147 | /// } 148 | /// 149 | /// assert_eq!( 150 | /// ser.finish().as_deref(), 151 | /// Some(r#"11;foo, (abc;abc_param=?0 def);bar="val""#), 152 | /// ); 153 | /// # Ok(()) 154 | /// # } 155 | /// ``` 156 | // https://httpwg.org/specs/rfc9651.html#ser-list 157 | #[derive(Debug)] 158 | #[must_use] 159 | pub struct ListSerializer { 160 | buffer: W, 161 | first: bool, 162 | } 163 | 164 | impl Default for ListSerializer { 165 | fn default() -> Self { 166 | Self::new() 167 | } 168 | } 169 | 170 | impl ListSerializer { 171 | /// Creates a serializer that writes into a new string. 172 | pub fn new() -> Self { 173 | Self { 174 | buffer: String::new(), 175 | first: true, 176 | } 177 | } 178 | } 179 | 180 | impl<'a> ListSerializer<&'a mut String> { 181 | /// Creates a serializer that writes into the given string. 182 | pub fn with_buffer(buffer: &'a mut String) -> Self { 183 | Self { 184 | buffer, 185 | first: true, 186 | } 187 | } 188 | } 189 | 190 | impl> ListSerializer { 191 | /// Serializes the given bare item as a member of the list. 192 | /// 193 | /// Returns a serializer for the item's parameters. 194 | pub fn bare_item<'b>( 195 | &mut self, 196 | bare_item: impl Into>, 197 | ) -> ParameterSerializer<&mut String> { 198 | let buffer = self.buffer.borrow_mut(); 199 | maybe_write_separator(buffer, &mut self.first); 200 | Serializer::serialize_bare_item(bare_item, buffer); 201 | ParameterSerializer { buffer } 202 | } 203 | 204 | /// Opens an inner list, returning a serializer to be used for its items and 205 | /// parameters. 206 | pub fn inner_list(&mut self) -> InnerListSerializer { 207 | let buffer = self.buffer.borrow_mut(); 208 | maybe_write_separator(buffer, &mut self.first); 209 | buffer.push('('); 210 | InnerListSerializer { 211 | buffer: Some(buffer), 212 | } 213 | } 214 | 215 | /// Serializes the given members of the list. 216 | #[cfg(feature = "parsed-types")] 217 | pub fn members<'b>(&mut self, members: impl IntoIterator) { 218 | for value in members { 219 | match value { 220 | ListEntry::Item(value) => { 221 | _ = self.bare_item(&value.bare_item).parameters(&value.params); 222 | } 223 | ListEntry::InnerList(value) => { 224 | let mut ser = self.inner_list(); 225 | ser.items(&value.items); 226 | _ = ser.finish().parameters(&value.params); 227 | } 228 | } 229 | } 230 | } 231 | 232 | /// Finishes serialization of the list and returns the underlying output. 233 | /// 234 | /// Returns `None` if and only if no members were serialized, as [empty 235 | /// lists are not meant to be serialized at 236 | /// all](https://httpwg.org/specs/rfc9651.html#text-serialize). 237 | #[must_use] 238 | pub fn finish(self) -> Option { 239 | if self.first { 240 | None 241 | } else { 242 | Some(self.buffer) 243 | } 244 | } 245 | } 246 | 247 | /// Serializes `Dictionary` field value components incrementally. 248 | /// 249 | /// Note: The serialization conforms to [RFC 9651], meaning that 250 | /// [`Dates`][crate::Date] and [`Display Strings`][RefBareItem::DisplayString], 251 | /// which cause parsing errors under [RFC 8941], will be serialized 252 | /// unconditionally. The consumer of this API is responsible for determining 253 | /// whether it is valid to serialize these bare items for any specific field. 254 | /// 255 | /// [RFC 8941]: 256 | /// [RFC 9651]: 257 | /// 258 | /// ``` 259 | /// use sfv::{KeyRef, StringRef, TokenRef, DictSerializer, Decimal}; 260 | /// 261 | /// # fn main() -> Result<(), sfv::Error> { 262 | /// let mut ser = DictSerializer::new(); 263 | /// 264 | /// ser.bare_item(KeyRef::from_str("member1")?, 11) 265 | /// .parameter(KeyRef::from_str("foo")?, true); 266 | /// 267 | /// { 268 | /// let mut ser = ser.inner_list(KeyRef::from_str("member2")?); 269 | /// 270 | /// ser.bare_item(TokenRef::from_str("abc")?) 271 | /// .parameter(KeyRef::from_str("abc_param")?, false); 272 | /// 273 | /// ser.bare_item(TokenRef::from_str("def")?); 274 | /// 275 | /// ser.finish() 276 | /// .parameter(KeyRef::from_str("bar")?, StringRef::from_str("val")?); 277 | /// } 278 | /// 279 | /// ser.bare_item(KeyRef::from_str("member3")?, Decimal::try_from(12.34566)?); 280 | /// 281 | /// assert_eq!( 282 | /// ser.finish().as_deref(), 283 | /// Some(r#"member1=11;foo, member2=(abc;abc_param=?0 def);bar="val", member3=12.346"#), 284 | /// ); 285 | /// # Ok(()) 286 | /// # } 287 | /// ``` 288 | // https://httpwg.org/specs/rfc9651.html#ser-dictionary 289 | #[derive(Debug)] 290 | #[must_use] 291 | pub struct DictSerializer { 292 | buffer: W, 293 | first: bool, 294 | } 295 | 296 | impl Default for DictSerializer { 297 | fn default() -> Self { 298 | Self::new() 299 | } 300 | } 301 | 302 | impl DictSerializer { 303 | /// Creates a serializer that writes into a new string. 304 | pub fn new() -> Self { 305 | Self { 306 | buffer: String::new(), 307 | first: true, 308 | } 309 | } 310 | } 311 | 312 | impl<'a> DictSerializer<&'a mut String> { 313 | /// Creates a serializer that writes into the given string. 314 | pub fn with_buffer(buffer: &'a mut String) -> Self { 315 | Self { 316 | buffer, 317 | first: true, 318 | } 319 | } 320 | } 321 | 322 | impl> DictSerializer { 323 | /// Serializes the given bare item as a member of the dictionary with the 324 | /// given key. 325 | /// 326 | /// Returns a serializer for the item's parameters. 327 | pub fn bare_item<'b>( 328 | &mut self, 329 | name: &KeyRef, 330 | value: impl Into>, 331 | ) -> ParameterSerializer<&mut String> { 332 | let buffer = self.buffer.borrow_mut(); 333 | maybe_write_separator(buffer, &mut self.first); 334 | Serializer::serialize_key(name, buffer); 335 | let value = value.into(); 336 | if value != RefBareItem::Boolean(true) { 337 | buffer.push('='); 338 | Serializer::serialize_bare_item(value, buffer); 339 | } 340 | ParameterSerializer { buffer } 341 | } 342 | 343 | /// Opens an inner list with the given key, returning a serializer to be 344 | /// used for its items and parameters. 345 | pub fn inner_list(&mut self, name: &KeyRef) -> InnerListSerializer { 346 | let buffer = self.buffer.borrow_mut(); 347 | maybe_write_separator(buffer, &mut self.first); 348 | Serializer::serialize_key(name, buffer); 349 | buffer.push_str("=("); 350 | InnerListSerializer { 351 | buffer: Some(buffer), 352 | } 353 | } 354 | 355 | /// Serializes the given members of the dictionary. 356 | #[cfg(feature = "parsed-types")] 357 | pub fn members<'b>( 358 | &mut self, 359 | members: impl IntoIterator, &'b ListEntry)>, 360 | ) { 361 | for (name, value) in members { 362 | match value { 363 | ListEntry::Item(value) => { 364 | _ = self 365 | .bare_item(name.as_ref(), &value.bare_item) 366 | .parameters(&value.params); 367 | } 368 | ListEntry::InnerList(value) => { 369 | let mut ser = self.inner_list(name.as_ref()); 370 | ser.items(&value.items); 371 | _ = ser.finish().parameters(&value.params); 372 | } 373 | } 374 | } 375 | } 376 | 377 | /// Finishes serialization of the dictionary and returns the underlying output. 378 | /// 379 | /// Returns `None` if and only if no members were serialized, as [empty 380 | /// dictionaries are not meant to be serialized at 381 | /// all](https://httpwg.org/specs/rfc9651.html#text-serialize). 382 | #[must_use] 383 | pub fn finish(self) -> Option { 384 | if self.first { 385 | None 386 | } else { 387 | Some(self.buffer) 388 | } 389 | } 390 | } 391 | 392 | /// Serializes inner lists incrementally. 393 | /// 394 | /// The inner list will be closed automatically when the serializer is dropped. 395 | /// To set the inner list's parameters, call [`InnerListSerializer::finish`]. 396 | /// 397 | /// Failing to drop the serializer or call its `finish` method will result in 398 | /// an invalid serialization that lacks a closing `)` character. 399 | // https://httpwg.org/specs/rfc9651.html#ser-innerlist 400 | #[derive(Debug)] 401 | #[must_use] 402 | pub struct InnerListSerializer<'a> { 403 | buffer: Option<&'a mut String>, 404 | } 405 | 406 | impl Drop for InnerListSerializer<'_> { 407 | fn drop(&mut self) { 408 | if let Some(ref mut buffer) = self.buffer { 409 | buffer.push(')'); 410 | } 411 | } 412 | } 413 | 414 | impl<'a> InnerListSerializer<'a> { 415 | /// Serializes the given bare item as a member of the inner list. 416 | /// 417 | /// Returns a serializer for the item's parameters. 418 | #[allow(clippy::missing_panics_doc)] // The unwrap is safe by construction. 419 | pub fn bare_item<'b>( 420 | &mut self, 421 | bare_item: impl Into>, 422 | ) -> ParameterSerializer<&mut String> { 423 | let buffer = self.buffer.as_mut().unwrap(); 424 | if !buffer.is_empty() && !buffer.ends_with('(') { 425 | buffer.push(' '); 426 | } 427 | Serializer::serialize_bare_item(bare_item, buffer); 428 | ParameterSerializer { buffer } 429 | } 430 | 431 | /// Serializes the given items as members of the inner list. 432 | #[cfg(feature = "parsed-types")] 433 | pub fn items<'b>(&mut self, items: impl IntoIterator) { 434 | for item in items { 435 | _ = self.bare_item(&item.bare_item).parameters(&item.params); 436 | } 437 | } 438 | 439 | /// Closes the inner list and returns a serializer for its parameters. 440 | #[allow(clippy::missing_panics_doc)] 441 | pub fn finish(mut self) -> ParameterSerializer<&'a mut String> { 442 | let buffer = self.buffer.take().unwrap(); 443 | buffer.push(')'); 444 | ParameterSerializer { buffer } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/serializer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write as _; 2 | 3 | use crate::{utils, Date, Decimal, Integer, KeyRef, RefBareItem, StringRef, TokenRef}; 4 | 5 | pub(crate) struct Serializer; 6 | 7 | impl Serializer { 8 | pub(crate) fn serialize_bare_item<'b>(value: impl Into>, output: &mut String) { 9 | // https://httpwg.org/specs/rfc9651.html#ser-bare-item 10 | 11 | match value.into() { 12 | RefBareItem::Boolean(value) => Self::serialize_bool(value, output), 13 | RefBareItem::String(value) => Self::serialize_string(value, output), 14 | RefBareItem::ByteSequence(value) => Self::serialize_byte_sequence(value, output), 15 | RefBareItem::Token(value) => Self::serialize_token(value, output), 16 | RefBareItem::Integer(value) => Self::serialize_integer(value, output), 17 | RefBareItem::Decimal(value) => Self::serialize_decimal(value, output), 18 | RefBareItem::Date(value) => Self::serialize_date(value, output), 19 | RefBareItem::DisplayString(value) => Self::serialize_display_string(value, output), 20 | } 21 | } 22 | 23 | pub(crate) fn serialize_parameter<'b>( 24 | name: &KeyRef, 25 | value: impl Into>, 26 | output: &mut String, 27 | ) { 28 | // https://httpwg.org/specs/rfc9651.html#ser-params 29 | output.push(';'); 30 | Self::serialize_key(name, output); 31 | 32 | let value = value.into(); 33 | if value != RefBareItem::Boolean(true) { 34 | output.push('='); 35 | Self::serialize_bare_item(value, output); 36 | } 37 | } 38 | 39 | pub(crate) fn serialize_key(input_key: &KeyRef, output: &mut String) { 40 | // https://httpwg.org/specs/rfc9651.html#ser-key 41 | 42 | output.push_str(input_key.as_str()); 43 | } 44 | 45 | pub(crate) fn serialize_integer(value: Integer, output: &mut String) { 46 | //https://httpwg.org/specs/rfc9651.html#ser-integer 47 | 48 | write!(output, "{value}").unwrap(); 49 | } 50 | 51 | pub(crate) fn serialize_decimal(value: Decimal, output: &mut String) { 52 | // https://httpwg.org/specs/rfc9651.html#ser-decimal 53 | 54 | write!(output, "{value}").unwrap(); 55 | } 56 | 57 | pub(crate) fn serialize_string(value: &StringRef, output: &mut String) { 58 | // https://httpwg.org/specs/rfc9651.html#ser-string 59 | 60 | output.push('"'); 61 | for char in value.as_str().chars() { 62 | if char == '\\' || char == '"' { 63 | output.push('\\'); 64 | } 65 | output.push(char); 66 | } 67 | output.push('"'); 68 | } 69 | 70 | pub(crate) fn serialize_token(value: &TokenRef, output: &mut String) { 71 | // https://httpwg.org/specs/rfc9651.html#ser-token 72 | 73 | output.push_str(value.as_str()); 74 | } 75 | 76 | pub(crate) fn serialize_byte_sequence(value: &[u8], output: &mut String) { 77 | // https://httpwg.org/specs/rfc9651.html#ser-binary 78 | 79 | output.push(':'); 80 | base64::Engine::encode_string(&utils::BASE64, value, output); 81 | output.push(':'); 82 | } 83 | 84 | pub(crate) fn serialize_bool(value: bool, output: &mut String) { 85 | // https://httpwg.org/specs/rfc9651.html#ser-boolean 86 | 87 | output.push_str(if value { "?1" } else { "?0" }); 88 | } 89 | 90 | pub(crate) fn serialize_date(value: Date, output: &mut String) { 91 | // https://httpwg.org/specs/rfc9651.html#ser-date 92 | 93 | write!(output, "{value}").unwrap(); 94 | } 95 | 96 | pub(crate) fn serialize_display_string(value: &str, output: &mut String) { 97 | // https://httpwg.org/specs/rfc9651.html#ser-display 98 | 99 | output.push_str(r#"%""#); 100 | for c in value.bytes() { 101 | match c { 102 | b'%' | b'"' | 0x00..=0x1f | 0x7f..=0xff => { 103 | output.push('%'); 104 | output.push(char::from_digit((u32::from(c) >> 4) & 0xf, 16).unwrap()); 105 | output.push(char::from_digit(u32::from(c) & 0xf, 16).unwrap()); 106 | } 107 | _ => output.push(c as char), 108 | } 109 | } 110 | output.push('"'); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::{Borrow, Cow}, 3 | fmt, 4 | string::String as StdString, 5 | }; 6 | 7 | use crate::Error; 8 | 9 | /// An owned structured field value [string]. 10 | /// 11 | /// Strings may only contain printable ASCII characters (i.e. the range 12 | /// `0x20 ..= 0x7e`). 13 | /// 14 | /// [string]: 15 | #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 | pub struct String(StdString); 17 | 18 | /// A borrowed structured field value [string]. 19 | /// 20 | /// Strings may only contain printable ASCII characters (i.e. the range 21 | /// `0x20 ..= 0x7e`). 22 | /// 23 | /// This type is to [`String`] as [`str`] is to [`std::string::String`]. 24 | /// 25 | /// [string]: 26 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, ref_cast::RefCastCustom)] 27 | #[repr(transparent)] 28 | pub struct StringRef(str); 29 | 30 | struct StringError { 31 | byte_index: usize, 32 | } 33 | 34 | impl From for Error { 35 | fn from(err: StringError) -> Error { 36 | Error::with_index("invalid character", err.byte_index) 37 | } 38 | } 39 | 40 | const fn validate(v: &[u8]) -> Result<(), StringError> { 41 | let mut index = 0; 42 | 43 | while index < v.len() { 44 | if v[index] < 0x20 || v[index] > 0x7e { 45 | return Err(StringError { byte_index: index }); 46 | } 47 | index += 1; 48 | } 49 | 50 | Ok(()) 51 | } 52 | 53 | impl StringRef { 54 | /// An empty `&StringRef`. 55 | pub const EMPTY: &Self = Self::cast(""); 56 | 57 | #[ref_cast::ref_cast_custom] 58 | const fn cast(v: &str) -> &Self; 59 | 60 | /// Creates a `&StringRef` from a `&str`. 61 | /// 62 | /// # Errors 63 | /// The error reports the cause of any failed string validation. 64 | #[allow(clippy::should_implement_trait)] 65 | pub fn from_str(v: &str) -> Result<&Self, Error> { 66 | validate(v.as_bytes())?; 67 | Ok(Self::cast(v)) 68 | } 69 | 70 | /// Creates a `&StringRef`, panicking if the value is invalid. 71 | /// 72 | /// This method is intended to be called from `const` contexts in which the 73 | /// value is known to be valid. Use [`StringRef::from_str`] for non-panicking 74 | /// conversions. 75 | #[must_use] 76 | pub const fn constant(v: &str) -> &Self { 77 | match validate(v.as_bytes()) { 78 | Ok(()) => Self::cast(v), 79 | Err(_) => panic!("invalid character for string"), 80 | } 81 | } 82 | 83 | /// Returns the string as a `&str`. 84 | #[must_use] 85 | pub fn as_str(&self) -> &str { 86 | &self.0 87 | } 88 | } 89 | 90 | impl ToOwned for StringRef { 91 | type Owned = String; 92 | 93 | fn to_owned(&self) -> String { 94 | String(self.0.to_owned()) 95 | } 96 | 97 | fn clone_into(&self, target: &mut String) { 98 | self.0.clone_into(&mut target.0); 99 | } 100 | } 101 | 102 | impl Borrow for String { 103 | fn borrow(&self) -> &StringRef { 104 | self 105 | } 106 | } 107 | 108 | impl std::ops::Deref for String { 109 | type Target = StringRef; 110 | 111 | fn deref(&self) -> &StringRef { 112 | StringRef::cast(&self.0) 113 | } 114 | } 115 | 116 | impl From for StdString { 117 | fn from(v: String) -> StdString { 118 | v.0 119 | } 120 | } 121 | 122 | impl TryFrom for String { 123 | type Error = Error; 124 | 125 | fn try_from(v: StdString) -> Result { 126 | validate(v.as_bytes())?; 127 | Ok(String(v)) 128 | } 129 | } 130 | 131 | impl String { 132 | /// Creates a `String` from a `std::string::String`. 133 | /// 134 | /// Returns the original value if the conversion failed. 135 | /// 136 | /// # Errors 137 | /// The error reports any problems from failed validation. 138 | pub fn from_string(v: StdString) -> Result { 139 | match validate(v.as_bytes()) { 140 | Ok(()) => Ok(Self(v)), 141 | Err(err) => Err((err.into(), v)), 142 | } 143 | } 144 | } 145 | 146 | /// Creates a `&StringRef`, panicking if the value is invalid. 147 | /// 148 | /// This is a convenience free function for [`StringRef::constant`]. 149 | /// 150 | /// This method is intended to be called from `const` contexts in which the 151 | /// value is known to be valid. Use [`StringRef::from_str`] for non-panicking 152 | /// conversions. 153 | #[must_use] 154 | pub const fn string_ref(v: &str) -> &StringRef { 155 | StringRef::constant(v) 156 | } 157 | 158 | impl fmt::Display for StringRef { 159 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 160 | fmt::Display::fmt(self.as_str(), f) 161 | } 162 | } 163 | 164 | impl fmt::Display for String { 165 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 166 | ::fmt(self, f) 167 | } 168 | } 169 | 170 | macro_rules! impl_eq { 171 | ($a: ty, $b: ty) => { 172 | impl PartialEq<$a> for $b { 173 | fn eq(&self, other: &$a) -> bool { 174 | ::eq(self, other) 175 | } 176 | } 177 | impl PartialEq<$b> for $a { 178 | fn eq(&self, other: &$b) -> bool { 179 | ::eq(self, other) 180 | } 181 | } 182 | }; 183 | } 184 | 185 | impl_eq!(String, StringRef); 186 | impl_eq!(String, &StringRef); 187 | impl_eq!(Cow<'_, StringRef>, StringRef); 188 | impl_eq!(Cow<'_, StringRef>, &StringRef); 189 | 190 | impl<'a> TryFrom<&'a str> for &'a StringRef { 191 | type Error = Error; 192 | 193 | fn try_from(v: &'a str) -> Result<&'a StringRef, Error> { 194 | StringRef::from_str(v) 195 | } 196 | } 197 | 198 | impl Borrow for String { 199 | fn borrow(&self) -> &str { 200 | self.as_str() 201 | } 202 | } 203 | 204 | impl Borrow for StringRef { 205 | fn borrow(&self) -> &str { 206 | self.as_str() 207 | } 208 | } 209 | 210 | #[cfg(feature = "arbitrary")] 211 | impl<'a> arbitrary::Arbitrary<'a> for &'a StringRef { 212 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 213 | StringRef::from_str(<&str>::arbitrary(u)?).map_err(|_| arbitrary::Error::IncorrectFormat) 214 | } 215 | } 216 | 217 | #[cfg(feature = "arbitrary")] 218 | impl<'a> arbitrary::Arbitrary<'a> for String { 219 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 220 | <&StringRef>::arbitrary(u).map(ToOwned::to_owned) 221 | } 222 | } 223 | 224 | impl Default for &StringRef { 225 | fn default() -> Self { 226 | StringRef::EMPTY 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/test_decimal.rs: -------------------------------------------------------------------------------- 1 | use crate::{Decimal, Error, Integer}; 2 | 3 | // Expect a floating point error less than the smallest allowed value of 0.001 4 | const ABSERR: f64 = 0.000_1_f64; 5 | 6 | #[test] 7 | fn test_display() { 8 | for (expected, input) in [ 9 | ("0.1", 100), 10 | ("-0.1", -100), 11 | ("0.01", 10), 12 | ("-0.01", -10), 13 | ("0.001", 1), 14 | ("-0.001", -1), 15 | ("0.12", 120), 16 | ("-0.12", -120), 17 | ("0.124", 124), 18 | ("-0.124", -124), 19 | ("0.125", 125), 20 | ("-0.125", -125), 21 | ("0.126", 126), 22 | ("-0.126", -126), 23 | ] { 24 | let decimal = Decimal::from_integer_scaled_1000(Integer::constant(input)); 25 | assert_eq!(expected, decimal.to_string()); 26 | } 27 | 28 | assert_eq!("0.0", Decimal::ZERO.to_string()); 29 | assert_eq!("-999999999999.999", Decimal::MIN.to_string()); 30 | assert_eq!("999999999999.999", Decimal::MAX.to_string()); 31 | } 32 | 33 | #[test] 34 | fn test_into_f64() { 35 | for (expected, input) in [ 36 | (0.0, 0), 37 | (0.001, 1), 38 | (0.01, 10), 39 | (0.1, 100), 40 | (1.0, 1000), 41 | (10.0, 10000), 42 | (0.123, 123), 43 | (-0.001, -1), 44 | (-0.01, -10), 45 | (-0.1, -100), 46 | (-1.0, -1000), 47 | (-10.0, -10000), 48 | (-0.123, -123), 49 | ] { 50 | assert!( 51 | (expected - f64::from(Decimal::from_integer_scaled_1000(input.into()))).abs() < ABSERR 52 | ); 53 | } 54 | 55 | assert!((-999_999_999_999.999 - f64::from(Decimal::MIN)).abs() < ABSERR); 56 | assert!((999_999_999_999.999 - f64::from(Decimal::MAX)).abs() < ABSERR); 57 | } 58 | 59 | #[test] 60 | fn test_try_from_f64() { 61 | for (expected, input) in [ 62 | (Err(Error::new("NaN")), f64::NAN), 63 | (Err(Error::out_of_range()), f64::INFINITY), 64 | (Err(Error::out_of_range()), f64::NEG_INFINITY), 65 | (Err(Error::out_of_range()), 2_f64.powi(65)), 66 | (Err(Error::out_of_range()), -(2_f64.powi(65))), 67 | (Err(Error::out_of_range()), -1_000_000_000_000.0), 68 | (Err(Error::out_of_range()), 1_000_000_000_000.0), 69 | (Ok(Decimal::MIN), -999_999_999_999.999), 70 | (Ok(Decimal::MIN), -999_999_999_999.999_1), 71 | (Err(Error::out_of_range()), -999_999_999_999.999_5), 72 | (Ok(Decimal::MAX), 999_999_999_999.999), 73 | (Ok(Decimal::MAX), 999_999_999_999.999_1), 74 | (Err(Error::out_of_range()), 999_999_999_999.999_5), 75 | (Ok(Decimal::ZERO), 0.0), 76 | (Ok(Decimal::ZERO), -0.0), 77 | ( 78 | Ok(Decimal::from_integer_scaled_1000(Integer::constant(123))), 79 | 0.1234, 80 | ), 81 | ( 82 | Ok(Decimal::from_integer_scaled_1000(Integer::constant(124))), 83 | 0.1235, 84 | ), 85 | ( 86 | Ok(Decimal::from_integer_scaled_1000(Integer::constant(124))), 87 | 0.1236, 88 | ), 89 | ( 90 | Ok(Decimal::from_integer_scaled_1000(Integer::constant(-123))), 91 | -0.1234, 92 | ), 93 | ( 94 | Ok(Decimal::from_integer_scaled_1000(Integer::constant(-124))), 95 | -0.1235, 96 | ), 97 | ( 98 | Ok(Decimal::from_integer_scaled_1000(Integer::constant(-124))), 99 | -0.1236, 100 | ), 101 | ] { 102 | assert_eq!(expected, Decimal::try_from(input)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test_integer.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Integer}; 2 | 3 | #[test] 4 | #[should_panic = "out of range"] 5 | fn test_constant_too_small() { 6 | let _ = Integer::constant(-1_000_000_000_000_000); 7 | } 8 | 9 | #[test] 10 | #[should_panic = "out of range"] 11 | fn test_constant_too_large() { 12 | let _ = Integer::constant(1_000_000_000_000_000); 13 | } 14 | 15 | #[test] 16 | fn test_conversions() -> Result<(), Error> { 17 | assert_eq!(Integer::MIN, Integer::constant(-999_999_999_999_999)); 18 | assert_eq!(Integer::MAX, Integer::constant(999_999_999_999_999)); 19 | 20 | assert!(Integer::try_from(-1_000_000_000_000_000_i64).is_err()); 21 | assert!(Integer::try_from(1_000_000_000_000_000_i64).is_err()); 22 | 23 | assert_eq!(i8::try_from(Integer::from(123_i8)), Ok(123)); 24 | assert_eq!(i16::try_from(Integer::from(123_i16)), Ok(123)); 25 | assert_eq!(i32::try_from(Integer::from(123_i32)), Ok(123)); 26 | assert_eq!(i64::from(Integer::try_from(123_i64)?), 123); 27 | assert_eq!(i128::from(Integer::try_from(123_i128)?), 123); 28 | assert_eq!(isize::try_from(Integer::try_from(123_isize)?), Ok(123)); 29 | 30 | assert_eq!(u8::try_from(Integer::from(123_u8)), Ok(123)); 31 | assert_eq!(u16::try_from(Integer::from(123_u16)), Ok(123)); 32 | assert_eq!(u32::try_from(Integer::from(123_u32)), Ok(123)); 33 | assert_eq!(u64::try_from(Integer::try_from(123_u64)?), Ok(123)); 34 | assert_eq!(u128::try_from(Integer::try_from(123_u128)?), Ok(123)); 35 | assert_eq!(usize::try_from(Integer::try_from(123_usize)?), Ok(123)); 36 | 37 | assert_eq!(i8::try_from(Integer::from(-123_i8)), Ok(-123)); 38 | assert_eq!(i16::try_from(Integer::from(-123_i16)), Ok(-123)); 39 | assert_eq!(i32::try_from(Integer::from(-123_i32)), Ok(-123)); 40 | assert_eq!(i64::from(Integer::try_from(-123_i64)?), -123); 41 | assert_eq!(i128::from(Integer::try_from(-123_i128)?), -123); 42 | assert_eq!(isize::try_from(Integer::try_from(-123_isize)?), Ok(-123)); 43 | 44 | assert!(u8::try_from(Integer::constant(-123)).is_err()); 45 | assert!(u16::try_from(Integer::constant(-123)).is_err()); 46 | assert!(u32::try_from(Integer::constant(-123)).is_err()); 47 | assert!(u64::try_from(Integer::constant(-123)).is_err()); 48 | assert!(u128::try_from(Integer::constant(-123)).is_err()); 49 | assert!(usize::try_from(Integer::constant(-123)).is_err()); 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /src/test_key.rs: -------------------------------------------------------------------------------- 1 | use crate::KeyRef; 2 | 3 | #[test] 4 | #[should_panic = "cannot be empty"] 5 | fn test_constant_empty() { 6 | let _ = KeyRef::constant(""); 7 | } 8 | 9 | #[test] 10 | #[should_panic = "invalid character"] 11 | fn test_constant_invalid_start_char() { 12 | let _ = KeyRef::constant("_key"); 13 | } 14 | 15 | #[test] 16 | #[should_panic = "invalid character"] 17 | fn test_constant_invalid_inner_char() { 18 | let _ = KeyRef::constant("aND"); 19 | } 20 | 21 | #[test] 22 | fn test_conversions() { 23 | assert!(KeyRef::from_str("").is_err()); 24 | assert!(KeyRef::from_str("aND").is_err()); 25 | assert!(KeyRef::from_str("_key").is_err()); 26 | assert!(KeyRef::from_str("7key").is_err()); 27 | } 28 | -------------------------------------------------------------------------------- /src/test_ref_serializer.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | 3 | use crate::{ 4 | key_ref, string_ref, token_ref, Decimal, DictSerializer, ItemSerializer, ListSerializer, 5 | }; 6 | 7 | #[test] 8 | fn test_fast_serialize_item() { 9 | fn check(ser: ItemSerializer>) { 10 | let output = ser 11 | .bare_item(token_ref("hello")) 12 | .parameter(key_ref("abc"), true) 13 | .finish(); 14 | assert_eq!("hello;abc", output.borrow()); 15 | } 16 | 17 | check(ItemSerializer::new()); 18 | check(ItemSerializer::with_buffer(&mut String::new())); 19 | } 20 | 21 | #[test] 22 | fn test_fast_serialize_list() { 23 | fn check(mut ser: ListSerializer>) { 24 | _ = ser 25 | .bare_item(token_ref("hello")) 26 | .parameter(key_ref("key1"), true) 27 | .parameter(key_ref("key2"), false); 28 | 29 | { 30 | let mut ser = ser.inner_list(); 31 | _ = ser.bare_item(string_ref("some_string")); 32 | _ = ser 33 | .bare_item(12) 34 | .parameter(key_ref("inner-member-key"), true); 35 | _ = ser 36 | .finish() 37 | .parameter(key_ref("inner-list-param"), token_ref("*")); 38 | } 39 | 40 | assert_eq!( 41 | Some(r#"hello;key1;key2=?0, ("some_string" 12;inner-member-key);inner-list-param=*"#), 42 | ser.finish().as_ref().map(|output| output.borrow().as_str()), 43 | ); 44 | } 45 | 46 | check(ListSerializer::new()); 47 | check(ListSerializer::with_buffer(&mut String::new())); 48 | } 49 | 50 | #[test] 51 | fn test_fast_serialize_dict() { 52 | fn check(mut ser: DictSerializer>) { 53 | _ = ser 54 | .bare_item(key_ref("member1"), token_ref("hello")) 55 | .parameter(key_ref("key1"), true) 56 | .parameter(key_ref("key2"), false); 57 | 58 | _ = ser 59 | .bare_item(key_ref("member2"), true) 60 | .parameter(key_ref("key3"), Decimal::try_from(45.4586).unwrap()) 61 | .parameter(key_ref("key4"), string_ref("str")); 62 | 63 | { 64 | let mut ser = ser.inner_list(key_ref("key5")); 65 | _ = ser.bare_item(45); 66 | _ = ser.bare_item(0); 67 | } 68 | 69 | _ = ser.bare_item(key_ref("key6"), string_ref("foo")); 70 | 71 | { 72 | let mut ser = ser.inner_list(key_ref("key7")); 73 | _ = ser.bare_item("some_string".as_bytes()); 74 | _ = ser.bare_item("other_string".as_bytes()); 75 | _ = ser.finish().parameter(key_ref("lparam"), 10); 76 | } 77 | 78 | _ = ser.bare_item(key_ref("key8"), true); 79 | 80 | assert_eq!( 81 | Some( 82 | r#"member1=hello;key1;key2=?0, member2;key3=45.459;key4="str", key5=(45 0), key6="foo", key7=(:c29tZV9zdHJpbmc=: :b3RoZXJfc3RyaW5n:);lparam=10, key8"# 83 | ), 84 | ser.finish().as_ref().map(|output| output.borrow().as_str()), 85 | ); 86 | } 87 | 88 | check(DictSerializer::new()); 89 | check(DictSerializer::with_buffer(&mut String::new())); 90 | } 91 | 92 | #[test] 93 | fn test_serialize_empty() { 94 | assert_eq!(None, ListSerializer::new().finish()); 95 | assert_eq!(None, DictSerializer::new().finish()); 96 | 97 | let mut output = String::from(" "); 98 | assert_eq!(None, ListSerializer::with_buffer(&mut output).finish()); 99 | 100 | let mut output = String::from(" "); 101 | assert_eq!(None, DictSerializer::with_buffer(&mut output).finish()); 102 | } 103 | 104 | // Regression test for https://github.com/undef1nd/sfv/issues/131. 105 | #[test] 106 | fn test_with_buffer_separator() { 107 | let mut output = String::from(" "); 108 | _ = ListSerializer::with_buffer(&mut output).bare_item(1); 109 | assert_eq!(output, " 1"); 110 | 111 | let mut output = String::from(" "); 112 | _ = DictSerializer::with_buffer(&mut output).bare_item(key_ref("key1"), 1); 113 | assert_eq!(output, " key1=1"); 114 | } 115 | -------------------------------------------------------------------------------- /src/test_serializer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | integer, key_ref, serializer::Serializer, string_ref, token_ref, Date, Decimal, Error, 3 | }; 4 | #[cfg(feature = "parsed-types")] 5 | use crate::{BareItem, Dictionary, FieldType, InnerList, Item, List, Parameters}; 6 | 7 | #[test] 8 | #[cfg(feature = "parsed-types")] 9 | fn serialize_value_empty_dict() { 10 | let dict_field_value = Dictionary::new(); 11 | assert_eq!(None, dict_field_value.serialize()); 12 | } 13 | 14 | #[test] 15 | #[cfg(feature = "parsed-types")] 16 | fn serialize_value_empty_list() { 17 | let list_field_value = List::new(); 18 | assert_eq!(None, list_field_value.serialize()); 19 | } 20 | 21 | #[test] 22 | #[cfg(feature = "parsed-types")] 23 | fn serialize_value_list_mixed_members_with_params() -> Result<(), Error> { 24 | let item1 = Item::new(Decimal::try_from(42.4568)?); 25 | let item2_param = Parameters::from_iter(vec![( 26 | key_ref("itm2_p").to_owned(), 27 | BareItem::Boolean(true), 28 | )]); 29 | let item2 = Item::with_params(17, item2_param); 30 | 31 | let inner_list_item1_param = Parameters::from_iter(vec![( 32 | key_ref("in1_p").to_owned(), 33 | BareItem::Boolean(false), 34 | )]); 35 | let inner_list_item1 = Item::with_params(string_ref("str1"), inner_list_item1_param); 36 | let inner_list_item2_param = Parameters::from_iter(vec![( 37 | key_ref("in2_p").to_owned(), 38 | BareItem::String(string_ref("valu\\e").to_owned()), 39 | )]); 40 | let inner_list_item2 = Item::with_params(token_ref("str2"), inner_list_item2_param); 41 | let inner_list_param = Parameters::from_iter(vec![( 42 | key_ref("inner_list_param").to_owned(), 43 | BareItem::ByteSequence(b"weather".to_vec()), 44 | )]); 45 | let inner_list = 46 | InnerList::with_params(vec![inner_list_item1, inner_list_item2], inner_list_param); 47 | 48 | let list_field_value: List = vec![item1.into(), item2.into(), inner_list.into()]; 49 | assert_eq!( 50 | Some( 51 | r#"42.457, 17;itm2_p, ("str1";in1_p=?0 str2;in2_p="valu\\e");inner_list_param=:d2VhdGhlcg==:"# 52 | ), 53 | list_field_value.serialize().as_deref(), 54 | ); 55 | Ok(()) 56 | } 57 | 58 | #[test] 59 | #[cfg(feature = "parsed-types")] 60 | fn serialize_item_byteseq_with_param() { 61 | let item_param = ( 62 | key_ref("a").to_owned(), 63 | BareItem::Token(token_ref("*ab_1").to_owned()), 64 | ); 65 | let item_param = Parameters::from_iter(vec![item_param]); 66 | let item = Item::with_params(b"parser".to_vec(), item_param); 67 | assert_eq!(":cGFyc2Vy:;a=*ab_1", item.serialize()); 68 | } 69 | 70 | #[test] 71 | #[cfg(feature = "parsed-types")] 72 | fn serialize_item_without_params() { 73 | let item = Item::new(1); 74 | assert_eq!("1", item.serialize()); 75 | } 76 | 77 | #[test] 78 | #[cfg(feature = "parsed-types")] 79 | fn serialize_item_with_bool_true_param() -> Result<(), Error> { 80 | let param = Parameters::from_iter(vec![(key_ref("a").to_owned(), BareItem::Boolean(true))]); 81 | let item = Item::with_params(Decimal::try_from(12.35)?, param); 82 | assert_eq!("12.35;a", item.serialize()); 83 | Ok(()) 84 | } 85 | 86 | #[test] 87 | #[cfg(feature = "parsed-types")] 88 | fn serialize_item_with_token_param() { 89 | let param = Parameters::from_iter(vec![( 90 | key_ref("a1").to_owned(), 91 | BareItem::Token(token_ref("*tok").to_owned()), 92 | )]); 93 | let item = Item::with_params(string_ref("12.35"), param); 94 | assert_eq!(r#""12.35";a1=*tok"#, item.serialize()); 95 | } 96 | 97 | #[test] 98 | fn serialize_integer() { 99 | let mut buf = String::new(); 100 | Serializer::serialize_integer(integer(-12), &mut buf); 101 | assert_eq!("-12", &buf); 102 | 103 | buf.clear(); 104 | Serializer::serialize_integer(integer(0), &mut buf); 105 | assert_eq!("0", &buf); 106 | 107 | buf.clear(); 108 | Serializer::serialize_integer(integer(999_999_999_999_999), &mut buf); 109 | assert_eq!("999999999999999", &buf); 110 | 111 | buf.clear(); 112 | Serializer::serialize_integer(integer(-999_999_999_999_999), &mut buf); 113 | assert_eq!("-999999999999999", &buf); 114 | } 115 | 116 | #[test] 117 | fn serialize_decimal() -> Result<(), Error> { 118 | let mut buf = String::new(); 119 | Serializer::serialize_decimal(Decimal::try_from(-99.134_689_7)?, &mut buf); 120 | assert_eq!("-99.135", &buf); 121 | 122 | buf.clear(); 123 | Serializer::serialize_decimal(Decimal::try_from(-1.00)?, &mut buf); 124 | assert_eq!("-1.0", &buf); 125 | 126 | buf.clear(); 127 | Serializer::serialize_decimal(Decimal::try_from(-99.134_689_7)?, &mut buf); 128 | assert_eq!("-99.135", &buf); 129 | 130 | buf.clear(); 131 | Serializer::serialize_decimal(Decimal::try_from(100.13)?, &mut buf); 132 | assert_eq!("100.13", &buf); 133 | 134 | buf.clear(); 135 | Serializer::serialize_decimal(Decimal::try_from(-100.130)?, &mut buf); 136 | assert_eq!("-100.13", &buf); 137 | 138 | buf.clear(); 139 | Serializer::serialize_decimal(Decimal::try_from(-100.100)?, &mut buf); 140 | assert_eq!("-100.1", &buf); 141 | 142 | buf.clear(); 143 | Serializer::serialize_decimal(Decimal::try_from(-137.0)?, &mut buf); 144 | assert_eq!("-137.0", &buf); 145 | 146 | buf.clear(); 147 | Serializer::serialize_decimal(Decimal::try_from(137_121_212_112.123)?, &mut buf); 148 | assert_eq!("137121212112.123", &buf); 149 | 150 | buf.clear(); 151 | Serializer::serialize_decimal(Decimal::try_from(137_121_212_112.123_8)?, &mut buf); 152 | assert_eq!("137121212112.124", &buf); 153 | Ok(()) 154 | } 155 | 156 | #[test] 157 | fn serialize_string() { 158 | let mut buf = String::new(); 159 | Serializer::serialize_string(string_ref("1.1 text"), &mut buf); 160 | assert_eq!(r#""1.1 text""#, &buf); 161 | 162 | buf.clear(); 163 | Serializer::serialize_string(string_ref(r#"hello "name""#), &mut buf); 164 | assert_eq!(r#""hello \"name\"""#, &buf); 165 | 166 | buf.clear(); 167 | Serializer::serialize_string(string_ref("something\\nothing"), &mut buf); 168 | assert_eq!(r#""something\\nothing""#, &buf); 169 | 170 | buf.clear(); 171 | Serializer::serialize_string(string_ref(""), &mut buf); 172 | assert_eq!(r#""""#, &buf); 173 | 174 | buf.clear(); 175 | Serializer::serialize_string(string_ref(" "), &mut buf); 176 | assert_eq!(r#"" ""#, &buf); 177 | 178 | buf.clear(); 179 | Serializer::serialize_string(string_ref(" "), &mut buf); 180 | assert_eq!(r#"" ""#, &buf); 181 | } 182 | 183 | #[test] 184 | fn serialize_token() { 185 | let mut buf = String::new(); 186 | Serializer::serialize_token(token_ref("*"), &mut buf); 187 | assert_eq!("*", &buf); 188 | 189 | buf.clear(); 190 | Serializer::serialize_token(token_ref("abc"), &mut buf); 191 | assert_eq!("abc", &buf); 192 | 193 | buf.clear(); 194 | Serializer::serialize_token(token_ref("abc:de"), &mut buf); 195 | assert_eq!("abc:de", &buf); 196 | 197 | buf.clear(); 198 | Serializer::serialize_token(token_ref("smth/#!else"), &mut buf); 199 | assert_eq!("smth/#!else", &buf); 200 | } 201 | 202 | #[test] 203 | fn serialize_byte_sequence() { 204 | let mut buf = String::new(); 205 | Serializer::serialize_byte_sequence("hello".as_bytes(), &mut buf); 206 | assert_eq!(":aGVsbG8=:", &buf); 207 | 208 | buf.clear(); 209 | Serializer::serialize_byte_sequence("test_encode".as_bytes(), &mut buf); 210 | assert_eq!(":dGVzdF9lbmNvZGU=:", &buf); 211 | 212 | buf.clear(); 213 | Serializer::serialize_byte_sequence("".as_bytes(), &mut buf); 214 | assert_eq!("::", &buf); 215 | 216 | buf.clear(); 217 | Serializer::serialize_byte_sequence("pleasure.".as_bytes(), &mut buf); 218 | assert_eq!(":cGxlYXN1cmUu:", &buf); 219 | 220 | buf.clear(); 221 | Serializer::serialize_byte_sequence("leasure.".as_bytes(), &mut buf); 222 | assert_eq!(":bGVhc3VyZS4=:", &buf); 223 | 224 | buf.clear(); 225 | Serializer::serialize_byte_sequence("easure.".as_bytes(), &mut buf); 226 | assert_eq!(":ZWFzdXJlLg==:", &buf); 227 | 228 | buf.clear(); 229 | Serializer::serialize_byte_sequence("asure.".as_bytes(), &mut buf); 230 | assert_eq!(":YXN1cmUu:", &buf); 231 | 232 | buf.clear(); 233 | Serializer::serialize_byte_sequence("sure.".as_bytes(), &mut buf); 234 | assert_eq!(":c3VyZS4=:", &buf); 235 | } 236 | 237 | #[test] 238 | fn serialize_bool() { 239 | let mut buf = String::new(); 240 | Serializer::serialize_bool(true, &mut buf); 241 | assert_eq!("?1", &buf); 242 | 243 | buf.clear(); 244 | Serializer::serialize_bool(false, &mut buf); 245 | assert_eq!("?0", &buf); 246 | } 247 | 248 | #[test] 249 | #[cfg(feature = "parsed-types")] 250 | fn serialize_params_bool() { 251 | let mut buf = String::new(); 252 | Serializer::serialize_parameter(key_ref("*b"), true, &mut buf); 253 | assert_eq!(";*b", buf); 254 | 255 | buf.clear(); 256 | Serializer::serialize_parameter(key_ref("a.a"), false, &mut buf); 257 | assert_eq!(";a.a=?0", buf); 258 | } 259 | 260 | #[test] 261 | #[cfg(feature = "parsed-types")] 262 | fn serialize_params_string() { 263 | let mut buf = String::new(); 264 | 265 | Serializer::serialize_parameter(key_ref("b"), string_ref("param_val"), &mut buf); 266 | assert_eq!(r#";b="param_val""#, buf); 267 | } 268 | 269 | #[test] 270 | #[cfg(feature = "parsed-types")] 271 | fn serialize_params_numbers() -> Result<(), Error> { 272 | let mut buf = String::new(); 273 | 274 | Serializer::serialize_parameter(key_ref("key1"), Decimal::try_from(746.15)?, &mut buf); 275 | assert_eq!(";key1=746.15", buf); 276 | 277 | buf.clear(); 278 | Serializer::serialize_parameter(key_ref("key2"), 11111, &mut buf); 279 | assert_eq!(";key2=11111", buf); 280 | Ok(()) 281 | } 282 | 283 | #[test] 284 | fn serialize_key() { 285 | let mut buf = String::new(); 286 | Serializer::serialize_key(key_ref("*a_fg"), &mut buf); 287 | assert_eq!("*a_fg", &buf); 288 | 289 | buf.clear(); 290 | Serializer::serialize_key(key_ref("*a_fg*"), &mut buf); 291 | assert_eq!("*a_fg*", &buf); 292 | 293 | buf.clear(); 294 | Serializer::serialize_key(key_ref("key1"), &mut buf); 295 | assert_eq!("key1", &buf); 296 | 297 | buf.clear(); 298 | Serializer::serialize_key(key_ref("ke-y.1"), &mut buf); 299 | assert_eq!("ke-y.1", &buf); 300 | } 301 | 302 | #[test] 303 | #[cfg(feature = "parsed-types")] 304 | fn serialize_list_of_items_and_inner_list() { 305 | let item1 = Item::new(12); 306 | let item2 = Item::new(14); 307 | let item3 = Item::new(token_ref("a")); 308 | let item4 = Item::new(token_ref("b")); 309 | let inner_list_param = Parameters::from_iter(vec![( 310 | key_ref("param").to_owned(), 311 | BareItem::String(string_ref("param_value_1").to_owned()), 312 | )]); 313 | let inner_list = InnerList::with_params(vec![item3, item4], inner_list_param); 314 | let input: List = vec![item1.into(), item2.into(), inner_list.into()]; 315 | 316 | assert_eq!( 317 | Some(r#"12, 14, (a b);param="param_value_1""#), 318 | input.serialize().as_deref(), 319 | ); 320 | } 321 | 322 | #[test] 323 | #[cfg(feature = "parsed-types")] 324 | fn serialize_list_of_lists() { 325 | let item1 = Item::new(1); 326 | let item2 = Item::new(2); 327 | let item3 = Item::new(42); 328 | let item4 = Item::new(43); 329 | let inner_list_1 = InnerList::new(vec![item1, item2]); 330 | let inner_list_2 = InnerList::new(vec![item3, item4]); 331 | let input: List = vec![inner_list_1.into(), inner_list_2.into()]; 332 | 333 | assert_eq!(Some("(1 2), (42 43)"), input.serialize().as_deref()); 334 | } 335 | 336 | #[test] 337 | #[cfg(feature = "parsed-types")] 338 | fn serialize_list_with_bool_item_and_bool_params() { 339 | let item1_params = Parameters::from_iter(vec![ 340 | (key_ref("a").to_owned(), BareItem::Boolean(true)), 341 | (key_ref("b").to_owned(), BareItem::Boolean(false)), 342 | ]); 343 | let item1 = Item::with_params(false, item1_params); 344 | let item2 = Item::new(token_ref("cde_456")); 345 | 346 | let input: List = vec![item1.into(), item2.into()]; 347 | assert_eq!(Some("?0;a;b=?0, cde_456"), input.serialize().as_deref()); 348 | } 349 | 350 | #[test] 351 | #[cfg(feature = "parsed-types")] 352 | fn serialize_dictionary_with_params() { 353 | let item1_params = Parameters::from_iter(vec![ 354 | (key_ref("a").to_owned(), 1.into()), 355 | (key_ref("b").to_owned(), BareItem::Boolean(true)), 356 | ]); 357 | let item2_params = Parameters::new(); 358 | let item3_params = Parameters::from_iter(vec![ 359 | (key_ref("q").to_owned(), BareItem::Boolean(false)), 360 | ( 361 | key_ref("r").to_owned(), 362 | BareItem::String(string_ref("+w").to_owned()), 363 | ), 364 | ]); 365 | 366 | let item1 = Item::with_params(123, item1_params); 367 | let item2 = Item::with_params(456, item2_params); 368 | let item3 = Item::with_params(789, item3_params); 369 | 370 | let input = Dictionary::from_iter(vec![ 371 | (key_ref("abc").to_owned(), item1.into()), 372 | (key_ref("def").to_owned(), item2.into()), 373 | (key_ref("ghi").to_owned(), item3.into()), 374 | ]); 375 | 376 | assert_eq!( 377 | Some(r#"abc=123;a=1;b, def=456, ghi=789;q=?0;r="+w""#), 378 | input.serialize().as_deref(), 379 | ); 380 | } 381 | 382 | #[test] 383 | #[cfg(feature = "parsed-types")] 384 | fn serialize_dict_empty_member_value() { 385 | let inner_list = InnerList::new(vec![]); 386 | let input = Dictionary::from_iter(vec![(key_ref("a").to_owned(), inner_list.into())]); 387 | assert_eq!(Some("a=()"), input.serialize().as_deref()); 388 | } 389 | 390 | #[test] 391 | fn serialize_date_with() { 392 | let mut buf = String::new(); 393 | Serializer::serialize_date(Date::UNIX_EPOCH, &mut buf); 394 | assert_eq!(buf, "@0"); 395 | } 396 | 397 | #[test] 398 | fn serialize_display_string() { 399 | let mut buf = String::new(); 400 | Serializer::serialize_display_string("üsers", &mut buf); 401 | assert_eq!(buf, r#"%"%c3%bcsers""#); 402 | } 403 | -------------------------------------------------------------------------------- /src/test_string.rs: -------------------------------------------------------------------------------- 1 | use crate::StringRef; 2 | 3 | #[test] 4 | #[should_panic = "invalid character"] 5 | fn test_constant_invalid_char() { 6 | let _ = StringRef::constant("text \x00"); 7 | } 8 | 9 | #[test] 10 | fn test_conversions() { 11 | assert!(StringRef::from_str("text \x00").is_err()); 12 | assert!(StringRef::from_str("text \x1f").is_err()); 13 | assert!(StringRef::from_str("text \x7f").is_err()); 14 | assert!(StringRef::from_str("рядок").is_err()); 15 | assert!(StringRef::from_str("non-ascii text 🐹").is_err()); 16 | } 17 | -------------------------------------------------------------------------------- /src/test_token.rs: -------------------------------------------------------------------------------- 1 | use crate::TokenRef; 2 | 3 | #[test] 4 | #[should_panic = "cannot be empty"] 5 | fn test_constant_empty() { 6 | let _ = TokenRef::constant(""); 7 | } 8 | 9 | #[test] 10 | #[should_panic = "invalid character"] 11 | fn test_constant_invalid_start_char() { 12 | let _ = TokenRef::constant("#some"); 13 | } 14 | 15 | #[test] 16 | #[should_panic = "invalid character"] 17 | fn test_constant_invalid_inner_char() { 18 | let _ = TokenRef::constant("s "); 19 | } 20 | 21 | #[test] 22 | fn test_conversions() { 23 | assert!(TokenRef::from_str("").is_err()); 24 | assert!(TokenRef::from_str("#some").is_err()); 25 | assert!(TokenRef::from_str("s ").is_err()); 26 | assert!(TokenRef::from_str("abc:de\t").is_err()); 27 | } 28 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Borrow, fmt}; 2 | 3 | use crate::{ 4 | error::{Error, NonEmptyStringError}, 5 | utils, 6 | }; 7 | 8 | /// An owned structured field value [token]. 9 | /// 10 | /// Tokens must match the following regular expression: 11 | /// 12 | /// ```re 13 | /// ^[A-Za-z*][A-Za-z*0-9!#$%&'+\-.^_`|~]*$ 14 | /// ``` 15 | /// 16 | /// [token]: 17 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub struct Token(String); 19 | 20 | /// A borrowed structured field value [token]. 21 | /// 22 | /// Tokens must match the following regular expression: 23 | /// 24 | /// ```re 25 | /// ^[A-Za-z*][A-Za-z*0-9!#$%&'+\-.^_`|~]*$ 26 | /// ``` 27 | /// 28 | /// This type is to [`Token`] as [`str`] is to [`String`]. 29 | /// 30 | /// [token]: 31 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, ref_cast::RefCastCustom)] 32 | #[repr(transparent)] 33 | pub struct TokenRef(str); 34 | 35 | const fn validate(v: &[u8]) -> Result<(), NonEmptyStringError> { 36 | if v.is_empty() { 37 | return Err(NonEmptyStringError::empty()); 38 | } 39 | 40 | if !utils::is_allowed_start_token_char(v[0]) { 41 | return Err(NonEmptyStringError::invalid_character(0)); 42 | } 43 | 44 | let mut index = 1; 45 | 46 | while index < v.len() { 47 | if !utils::is_allowed_inner_token_char(v[index]) { 48 | return Err(NonEmptyStringError::invalid_character(index)); 49 | } 50 | index += 1; 51 | } 52 | 53 | Ok(()) 54 | } 55 | 56 | impl TokenRef { 57 | #[ref_cast::ref_cast_custom] 58 | const fn cast(v: &str) -> &Self; 59 | 60 | /// Creates a `&TokenRef` from a `&str`. 61 | /// 62 | /// # Errors 63 | /// The error result reports the reason for any failed validation. 64 | #[allow(clippy::should_implement_trait)] 65 | pub fn from_str(v: &str) -> Result<&Self, Error> { 66 | validate(v.as_bytes())?; 67 | Ok(Self::cast(v)) 68 | } 69 | 70 | // Like `from_str`, but assumes that the contents of the string have already 71 | // been validated as a token. 72 | pub(crate) fn from_validated_str(v: &str) -> &Self { 73 | debug_assert!(validate(v.as_bytes()).is_ok()); 74 | Self::cast(v) 75 | } 76 | 77 | /// Creates a `&TokenRef`, panicking if the value is invalid. 78 | /// 79 | /// This method is intended to be called from `const` contexts in which the 80 | /// value is known to be valid. Use [`TokenRef::from_str`] for non-panicking 81 | /// conversions. 82 | #[must_use] 83 | pub const fn constant(v: &str) -> &Self { 84 | match validate(v.as_bytes()) { 85 | Ok(()) => Self::cast(v), 86 | Err(err) => panic!("{}", err.msg()), 87 | } 88 | } 89 | 90 | /// Returns the token as a `&str`. 91 | #[must_use] 92 | pub fn as_str(&self) -> &str { 93 | &self.0 94 | } 95 | } 96 | 97 | impl ToOwned for TokenRef { 98 | type Owned = Token; 99 | 100 | fn to_owned(&self) -> Token { 101 | Token(self.0.to_owned()) 102 | } 103 | 104 | fn clone_into(&self, target: &mut Token) { 105 | self.0.clone_into(&mut target.0); 106 | } 107 | } 108 | 109 | impl Borrow for Token { 110 | fn borrow(&self) -> &TokenRef { 111 | self 112 | } 113 | } 114 | 115 | impl std::ops::Deref for Token { 116 | type Target = TokenRef; 117 | 118 | fn deref(&self) -> &TokenRef { 119 | TokenRef::cast(&self.0) 120 | } 121 | } 122 | 123 | impl From for String { 124 | fn from(v: Token) -> String { 125 | v.0 126 | } 127 | } 128 | 129 | impl TryFrom for Token { 130 | type Error = Error; 131 | 132 | fn try_from(v: String) -> Result { 133 | validate(v.as_bytes())?; 134 | Ok(Token(v)) 135 | } 136 | } 137 | 138 | impl Token { 139 | /// Creates a `Token` from a `String`. 140 | /// 141 | /// Returns the original value if the conversion failed. 142 | /// 143 | /// # Errors 144 | /// The error result reports the reason for any failed validation. 145 | pub fn from_string(v: String) -> Result { 146 | match validate(v.as_bytes()) { 147 | Ok(()) => Ok(Self(v)), 148 | Err(err) => Err((err.into(), v)), 149 | } 150 | } 151 | } 152 | 153 | /// Creates a `&TokenRef`, panicking if the value is invalid. 154 | /// 155 | /// This is a convenience free function for [`TokenRef::constant`]. 156 | /// 157 | /// This method is intended to be called from `const` contexts in which the 158 | /// value is known to be valid. Use [`TokenRef::from_str`] for non-panicking 159 | /// conversions. 160 | #[must_use] 161 | pub const fn token_ref(v: &str) -> &TokenRef { 162 | TokenRef::constant(v) 163 | } 164 | 165 | impl fmt::Display for TokenRef { 166 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 167 | f.write_str(self.as_str()) 168 | } 169 | } 170 | 171 | impl fmt::Display for Token { 172 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 173 | ::fmt(self, f) 174 | } 175 | } 176 | 177 | macro_rules! impl_eq { 178 | ($a: ty, $b: ty) => { 179 | impl PartialEq<$a> for $b { 180 | fn eq(&self, other: &$a) -> bool { 181 | ::eq(self, other) 182 | } 183 | } 184 | impl PartialEq<$b> for $a { 185 | fn eq(&self, other: &$b) -> bool { 186 | ::eq(self, other) 187 | } 188 | } 189 | }; 190 | } 191 | 192 | impl_eq!(Token, TokenRef); 193 | impl_eq!(Token, &TokenRef); 194 | 195 | impl<'a> TryFrom<&'a str> for &'a TokenRef { 196 | type Error = Error; 197 | 198 | fn try_from(v: &'a str) -> Result<&'a TokenRef, Error> { 199 | TokenRef::from_str(v) 200 | } 201 | } 202 | 203 | impl Borrow for Token { 204 | fn borrow(&self) -> &str { 205 | self.as_str() 206 | } 207 | } 208 | 209 | impl Borrow for TokenRef { 210 | fn borrow(&self) -> &str { 211 | self.as_str() 212 | } 213 | } 214 | 215 | #[cfg(feature = "arbitrary")] 216 | impl<'a> arbitrary::Arbitrary<'a> for &'a TokenRef { 217 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 218 | TokenRef::from_str(<&str>::arbitrary(u)?).map_err(|_| arbitrary::Error::IncorrectFormat) 219 | } 220 | } 221 | 222 | #[cfg(feature = "arbitrary")] 223 | impl<'a> arbitrary::Arbitrary<'a> for Token { 224 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 225 | <&TokenRef>::arbitrary(u).map(ToOwned::to_owned) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use base64::engine; 2 | 3 | pub(crate) const BASE64: engine::GeneralPurpose = engine::GeneralPurpose::new( 4 | &base64::alphabet::STANDARD, 5 | engine::GeneralPurposeConfig::new() 6 | .with_decode_allow_trailing_bits(true) 7 | .with_decode_padding_mode(engine::DecodePaddingMode::Indifferent) 8 | .with_encode_padding(true), 9 | ); 10 | 11 | const fn is_tchar(c: u8) -> bool { 12 | // See tchar values list in https://tools.ietf.org/html/rfc7230#section-3.2.6 13 | matches!( 14 | c, 15 | b'!' | b'#' 16 | | b'$' 17 | | b'%' 18 | | b'&' 19 | | b'\'' 20 | | b'*' 21 | | b'+' 22 | | b'-' 23 | | b'.' 24 | | b'^' 25 | | b'_' 26 | | b'`' 27 | | b'|' 28 | | b'~' 29 | ) || c.is_ascii_alphanumeric() 30 | } 31 | 32 | pub(crate) const fn is_allowed_start_token_char(c: u8) -> bool { 33 | c.is_ascii_alphabetic() || c == b'*' 34 | } 35 | 36 | pub(crate) const fn is_allowed_inner_token_char(c: u8) -> bool { 37 | is_tchar(c) || c == b':' || c == b'/' 38 | } 39 | 40 | pub(crate) const fn is_allowed_start_key_char(c: u8) -> bool { 41 | c.is_ascii_lowercase() || c == b'*' 42 | } 43 | 44 | pub(crate) const fn is_allowed_inner_key_char(c: u8) -> bool { 45 | c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, b'_' | b'-' | b'*' | b'.') 46 | } 47 | -------------------------------------------------------------------------------- /src/visitor.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Contains traits for parsing structured-field values incrementally. 3 | 4 | These can be used to borrow data from the input without copies in some cases. 5 | 6 | The various visitor methods are invoked *during* parsing, i.e. before validation 7 | of the entire input is complete. Therefore, users of these traits should 8 | carefully consider whether they want to induce side effects or perform expensive 9 | operations *before* knowing whether the entire input is valid. 10 | 11 | For example, it may make sense to defer storage of these values in a database 12 | until after validation is complete, in order to avoid the need for rollbacks in 13 | the event that a later error occurs. In this case, the visitor could retain the 14 | relevant state in its fields, before using that state to perform the operation 15 | *after* parsing is complete: 16 | 17 | ``` 18 | # use sfv::visitor::{Ignored, ItemVisitor, ParameterVisitor}; 19 | # use sfv::{BareItemFromInput, TokenRef}; 20 | # fn main() -> Result<(), sfv::Error> { 21 | struct Visitor<'de> { 22 | token: Option<&'de TokenRef>, 23 | } 24 | 25 | impl<'de> ItemVisitor<'de> for &mut Visitor<'de> { 26 | type Error = std::convert::Infallible; 27 | 28 | fn bare_item(self, bare_item: BareItemFromInput<'de>) -> Result, Self::Error> { 29 | self.token = 30 | if let BareItemFromInput::Token(token) = bare_item { 31 | Some(token) 32 | } else { 33 | None 34 | }; 35 | 36 | Ok(Ignored) 37 | } 38 | } 39 | 40 | let input = "abc"; 41 | 42 | let mut visitor = Visitor { token: None }; 43 | 44 | sfv::Parser::new(input).parse_item_with_visitor(&mut visitor)?; 45 | 46 | // Use `visitor.token` to do something expensive or with side effects now that 47 | // we know the entire input is valid. 48 | 49 | # Ok(()) 50 | # } 51 | ``` 52 | 53 | # Discarding irrelevant parts 54 | 55 | Two kinds of helpers are provided for silently discarding structured-field 56 | parts: 57 | 58 | - [`Ignored`]: This type implements all of the visitor traits as no-ops, and can 59 | be used when a visitor implementation would unconditionally do nothing. An 60 | example of this is when an item's bare item needs to be validated, but its 61 | parameters do not (e.g. because the relevant field definition prescribes 62 | none and permits unknown ones). 63 | 64 | - Blanket implementations of [`ParameterVisitor`], [`ItemVisitor`], 65 | [`EntryVisitor`], and [`InnerListVisitor`] for [`Option`] where `V` 66 | implements that trait: These implementations act like `Ignored` when `self` is 67 | [`None`], and forward to `V`'s implementation when `self` is [`Some`]. These 68 | can be used when the visitor dynamically handles or ignores field parts. An 69 | example of this is when a field definition prescribes the format of certain 70 | dictionary keys, but ignores unknown ones. 71 | 72 | Note that the discarded parts are still validated during parsing: syntactic 73 | errors in the input still cause parsing to fail even when these helpers are 74 | used, [as required by RFC 9651](https://httpwg.org/specs/rfc9651.html#strict). 75 | 76 | The following example demonstrates usage of both kinds of helpers: 77 | 78 | ``` 79 | # use sfv::{BareItemFromInput, KeyRef, Parser, visitor::*}; 80 | #[derive(Debug, Default, PartialEq)] 81 | struct Point { 82 | x: i64, 83 | y: i64, 84 | } 85 | 86 | struct CoordVisitor<'a> { 87 | coord: &'a mut i64, 88 | } 89 | 90 | impl<'de> DictionaryVisitor<'de> for Point { 91 | type Error = std::convert::Infallible; 92 | 93 | fn entry( 94 | &mut self, 95 | key: &'de KeyRef, 96 | ) -> Result, Self::Error> 97 | { 98 | let coord = match key.as_str() { 99 | "x" => &mut self.x, 100 | "y" => &mut self.y, 101 | // Ignore this key by returning `None`. Its value will still be 102 | // validated syntactically during parsing, but we don't need to 103 | // visit it. 104 | _ => return Ok(None), 105 | }; 106 | // Visit this key's value by returning `Some`. 107 | Ok(Some(CoordVisitor { coord })) 108 | } 109 | } 110 | 111 | #[derive(Debug)] 112 | struct NotAnInteger; 113 | 114 | impl std::fmt::Display for NotAnInteger { 115 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 116 | f.write_str("must be an integer") 117 | } 118 | } 119 | 120 | impl std::error::Error for NotAnInteger {} 121 | 122 | impl<'de> ItemVisitor<'de> for CoordVisitor<'_> { 123 | type Error = NotAnInteger; 124 | 125 | fn bare_item( 126 | self, 127 | bare_item: BareItemFromInput<'de>, 128 | ) -> Result, Self::Error> { 129 | if let BareItemFromInput::Integer(v) = bare_item { 130 | *self.coord = i64::from(v); 131 | // Ignore the item's parameters by returning `Ignored`. The 132 | // parameters will still be validated syntactically during parsing, 133 | // but we don't need to visit them. 134 | // 135 | // We could return `None` instead to ignore the parameters only 136 | // some of the time, returning `Some(visitor)` otherwise. 137 | Ok(Ignored) 138 | } else { 139 | Err(NotAnInteger) 140 | } 141 | } 142 | } 143 | 144 | impl<'de> EntryVisitor<'de> for CoordVisitor<'_> { 145 | fn inner_list(self) -> Result, Self::Error> { 146 | // Use `Never` to enforce at the type level that this method will only 147 | // return `Err`, as our coordinate must be a single integer, not an 148 | // inner list. 149 | Err::(NotAnInteger) 150 | } 151 | } 152 | 153 | # fn main() -> Result<(), sfv::Error> { 154 | let mut point = Point::default(); 155 | Parser::new("x=10, z=abc, y=3").parse_dictionary_with_visitor(&mut point)?; 156 | assert_eq!(point, Point { x: 10, y: 3 }); 157 | # Ok(()) 158 | # } 159 | ``` 160 | */ 161 | 162 | use std::{convert::Infallible, error::Error}; 163 | 164 | use crate::{BareItemFromInput, KeyRef}; 165 | 166 | /// A visitor whose methods are called during parameter parsing. 167 | /// 168 | /// The lifetime `'de` is the lifetime of the input. 169 | pub trait ParameterVisitor<'de> { 170 | /// The error type that can be returned if some error occurs during parsing. 171 | type Error: Error; 172 | 173 | /// Called after a parameter has been parsed. 174 | /// 175 | /// Parsing will be terminated early if an error is returned. 176 | /// 177 | /// Note: Per [RFC 9651], when duplicate parameter keys are encountered in 178 | /// the same scope, all but the last instance are ignored. Implementations 179 | /// of this trait must respect that requirement in order to comply with the 180 | /// specification. For example, if parameters are stored in a map, earlier 181 | /// values for a given parameter key must be overwritten by later ones. 182 | /// 183 | /// [RFC 9651]: 184 | /// 185 | /// # Errors 186 | /// The error result should report the reason for any failed validation. 187 | fn parameter( 188 | &mut self, 189 | key: &'de KeyRef, 190 | value: BareItemFromInput<'de>, 191 | ) -> Result<(), Self::Error>; 192 | 193 | /// Called after all parameters have been parsed. 194 | /// 195 | /// Parsing will be terminated early if an error is returned. 196 | /// 197 | /// # Errors 198 | /// The error result should report the reason for any failed validation. 199 | fn finish(self) -> Result<(), Self::Error> 200 | where 201 | Self: Sized, 202 | { 203 | Ok(()) 204 | } 205 | } 206 | 207 | /// A visitor whose methods are called during item parsing. 208 | /// 209 | /// The lifetime `'de` is the lifetime of the input. 210 | /// 211 | /// Use this trait with 212 | /// [`Parser::parse_item_with_visitor`][crate::Parser::parse_item_with_visitor]. 213 | pub trait ItemVisitor<'de> { 214 | /// The error type that can be returned if some error occurs during parsing. 215 | type Error: Error; 216 | 217 | /// Called after a bare item has been parsed. 218 | /// 219 | /// The returned visitor is used to handle the bare item's parameters. 220 | /// See [the module documentation](crate::visitor#discarding-irrelevant-parts) 221 | /// for guidance on discarding parameters. 222 | /// 223 | /// Parsing will be terminated early if an error is returned. 224 | /// 225 | /// # Errors 226 | /// The error result should report the reason for any failed validation. 227 | fn bare_item( 228 | self, 229 | bare_item: BareItemFromInput<'de>, 230 | ) -> Result, Self::Error>; 231 | } 232 | 233 | /// A visitor whose methods are called during inner-list parsing. 234 | /// 235 | /// The lifetime `'de` is the lifetime of the input. 236 | pub trait InnerListVisitor<'de> { 237 | /// The error type that can be returned if some error occurs during parsing. 238 | type Error: Error; 239 | 240 | /// Called before an item has been parsed. 241 | /// 242 | /// The returned visitor is used to handle the bare item. 243 | /// 244 | /// Parsing will be terminated early if an error is returned. 245 | /// 246 | /// # Errors 247 | /// The error result should report the reason for any failed validation. 248 | fn item(&mut self) -> Result, Self::Error>; 249 | 250 | /// Called after all inner-list items have been parsed. 251 | /// 252 | /// The returned visitor is used to handle the inner list's parameters. 253 | /// See [the module documentation](crate::visitor#discarding-irrelevant-parts) 254 | /// for guidance on discarding parameters. 255 | /// 256 | /// Parsing will be terminated early if an error is returned. 257 | /// 258 | /// # Errors 259 | /// The error result should report the reason for any failed validation. 260 | fn finish(self) -> Result, Self::Error>; 261 | } 262 | 263 | /// A visitor whose methods are called during entry parsing. 264 | /// 265 | /// The lifetime `'de` is the lifetime of the input. 266 | pub trait EntryVisitor<'de>: ItemVisitor<'de> { 267 | /// Called before an inner list has been parsed. 268 | /// 269 | /// The returned visitor is used to handle the inner list. 270 | /// 271 | /// Parsing will be terminated early if an error is returned. 272 | /// 273 | /// # Errors 274 | /// The error result should report the reason for any failed validation. 275 | fn inner_list(self) -> Result, Self::Error>; 276 | } 277 | 278 | /// A visitor whose methods are called during dictionary parsing. 279 | /// 280 | /// The lifetime `'de` is the lifetime of the input. 281 | /// 282 | /// Use this trait with 283 | /// [`Parser::parse_dictionary_with_visitor`][crate::Parser::parse_dictionary_with_visitor]. 284 | pub trait DictionaryVisitor<'de> { 285 | /// The error type that can be returned if some error occurs during parsing. 286 | type Error: Error; 287 | 288 | /// Called after a dictionary key has been parsed. 289 | /// 290 | /// The returned visitor is used to handle the associated value. 291 | /// See [the module documentation](crate::visitor#discarding-irrelevant-parts) 292 | /// for guidance on discarding entries. 293 | /// 294 | /// Parsing will be terminated early if an error is returned. 295 | /// 296 | /// Note: Per [RFC 9651], when duplicate dictionary keys are encountered in 297 | /// the same scope, all but the last instance are ignored. Implementations 298 | /// of this trait must respect that requirement in order to comply with the 299 | /// specification. For example, if dictionary entries are stored in a map, 300 | /// earlier values for a given dictionary key must be overwritten by later 301 | /// ones. 302 | /// 303 | /// [RFC 9651]: 304 | /// 305 | /// # Errors 306 | /// The error result should report the reason for any failed validation. 307 | fn entry(&mut self, key: &'de KeyRef) -> Result, Self::Error>; 308 | } 309 | 310 | /// A visitor whose methods are called during list parsing. 311 | /// 312 | /// The lifetime `'de` is the lifetime of the input. 313 | /// 314 | /// Use this trait with 315 | /// [`Parser::parse_list_with_visitor`][crate::Parser::parse_list_with_visitor]. 316 | pub trait ListVisitor<'de> { 317 | /// The error type that can be returned if some error occurs during parsing. 318 | type Error: Error; 319 | 320 | /// Called before a list entry has been parsed. 321 | /// 322 | /// The returned visitor is used to handle the entry. 323 | /// 324 | /// Parsing will be terminated early if an error is returned. 325 | /// 326 | /// # Errors 327 | /// The error result should report the reason for any failed validation. 328 | fn entry(&mut self) -> Result, Self::Error>; 329 | } 330 | 331 | /// A visitor that can be used to silently discard structured-field parts. 332 | /// 333 | /// Note that the discarded parts are still validated during parsing: syntactic 334 | /// errors in the input still cause parsing to fail even when this type is used, 335 | /// [as required by RFC 9651](https://httpwg.org/specs/rfc9651.html#strict). 336 | /// 337 | /// See [the module documentation](crate::visitor#discarding-irrelevant-parts) 338 | /// for example usage. 339 | #[derive(Clone, Copy, Debug, Default)] 340 | pub struct Ignored; 341 | 342 | impl<'de> ParameterVisitor<'de> for Ignored { 343 | type Error = Infallible; 344 | 345 | fn parameter( 346 | &mut self, 347 | _key: &'de KeyRef, 348 | _value: BareItemFromInput<'de>, 349 | ) -> Result<(), Self::Error> { 350 | Ok(()) 351 | } 352 | } 353 | 354 | impl<'de> ItemVisitor<'de> for Ignored { 355 | type Error = Infallible; 356 | 357 | fn bare_item( 358 | self, 359 | _bare_item: BareItemFromInput<'de>, 360 | ) -> Result, Self::Error> { 361 | Ok(Ignored) 362 | } 363 | } 364 | 365 | impl<'de> EntryVisitor<'de> for Ignored { 366 | fn inner_list(self) -> Result, Self::Error> { 367 | Ok(Ignored) 368 | } 369 | } 370 | 371 | impl<'de> InnerListVisitor<'de> for Ignored { 372 | type Error = Infallible; 373 | 374 | fn item(&mut self) -> Result, Self::Error> { 375 | Ok(Ignored) 376 | } 377 | 378 | fn finish(self) -> Result, Self::Error> { 379 | Ok(Ignored) 380 | } 381 | } 382 | 383 | impl<'de> DictionaryVisitor<'de> for Ignored { 384 | type Error = Infallible; 385 | 386 | fn entry(&mut self, _key: &'de KeyRef) -> Result, Self::Error> { 387 | Ok(Ignored) 388 | } 389 | } 390 | 391 | impl<'de> ListVisitor<'de> for Ignored { 392 | type Error = Infallible; 393 | 394 | fn entry(&mut self) -> Result, Self::Error> { 395 | Ok(Ignored) 396 | } 397 | } 398 | 399 | impl<'de, V: ParameterVisitor<'de>> ParameterVisitor<'de> for Option { 400 | type Error = V::Error; 401 | 402 | fn parameter( 403 | &mut self, 404 | key: &'de KeyRef, 405 | value: BareItemFromInput<'de>, 406 | ) -> Result<(), Self::Error> { 407 | match self { 408 | None => Ok(()), 409 | Some(visitor) => visitor.parameter(key, value), 410 | } 411 | } 412 | } 413 | 414 | impl<'de, V: ItemVisitor<'de>> ItemVisitor<'de> for Option { 415 | type Error = V::Error; 416 | 417 | fn bare_item( 418 | self, 419 | bare_item: BareItemFromInput<'de>, 420 | ) -> Result, Self::Error> { 421 | match self { 422 | None => Ok(None), 423 | Some(visitor) => visitor.bare_item(bare_item).map(Some), 424 | } 425 | } 426 | } 427 | 428 | impl<'de, V: EntryVisitor<'de>> EntryVisitor<'de> for Option { 429 | fn inner_list(self) -> Result, Self::Error> { 430 | match self { 431 | None => Ok(None), 432 | Some(visitor) => visitor.inner_list().map(Some), 433 | } 434 | } 435 | } 436 | 437 | impl<'de, V: InnerListVisitor<'de>> InnerListVisitor<'de> for Option { 438 | type Error = V::Error; 439 | 440 | fn item(&mut self) -> Result, Self::Error> { 441 | match self { 442 | None => Ok(None), 443 | Some(visitor) => visitor.item().map(Some), 444 | } 445 | } 446 | 447 | fn finish(self) -> Result, Self::Error> { 448 | match self { 449 | None => Ok(None), 450 | Some(visitor) => visitor.finish().map(Some), 451 | } 452 | } 453 | } 454 | 455 | /// A visitor that cannot be instantiated, but can be used as a type in 456 | /// situations guaranteed to return an error `Result`, analogous to 457 | /// [`std::convert::Infallible`]. 458 | /// 459 | /// When [`!`] is stabilized, this type will be replaced with an alias for it. 460 | #[derive(Clone, Copy, Debug)] 461 | pub enum Never {} 462 | 463 | impl<'de> ParameterVisitor<'de> for Never { 464 | type Error = Infallible; 465 | 466 | fn parameter( 467 | &mut self, 468 | _key: &'de KeyRef, 469 | _value: BareItemFromInput<'de>, 470 | ) -> Result<(), Self::Error> { 471 | match *self {} 472 | } 473 | } 474 | 475 | impl<'de> ItemVisitor<'de> for Never { 476 | type Error = Infallible; 477 | 478 | fn bare_item( 479 | self, 480 | _bare_item: BareItemFromInput<'de>, 481 | ) -> Result, Self::Error> { 482 | Ok(self) 483 | } 484 | } 485 | 486 | impl<'de> EntryVisitor<'de> for Never { 487 | fn inner_list(self) -> Result, Self::Error> { 488 | Ok(self) 489 | } 490 | } 491 | 492 | impl<'de> InnerListVisitor<'de> for Never { 493 | type Error = Infallible; 494 | 495 | fn item(&mut self) -> Result, Self::Error> { 496 | Ok(*self) 497 | } 498 | 499 | fn finish(self) -> Result, Self::Error> { 500 | Ok(self) 501 | } 502 | } 503 | 504 | impl<'de> DictionaryVisitor<'de> for Never { 505 | type Error = Infallible; 506 | 507 | fn entry(&mut self, _key: &'de KeyRef) -> Result, Self::Error> { 508 | Ok(*self) 509 | } 510 | } 511 | 512 | impl<'de> ListVisitor<'de> for Never { 513 | type Error = Infallible; 514 | 515 | fn entry(&mut self) -> Result, Self::Error> { 516 | Ok(*self) 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /tests/specification_tests.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fmt, fs, io, path::Path}; 2 | 3 | use serde::Deserialize; 4 | use sfv::{ 5 | BareItem, Date, Dictionary, FieldType, InnerList, Item, Key, List, ListEntry, Parameters, 6 | Parser, 7 | }; 8 | 9 | #[derive(Debug, Deserialize)] 10 | struct TestData { 11 | name: String, 12 | #[serde(flatten)] 13 | header_type: ExpectedHeaderType, 14 | #[serde(default)] 15 | must_fail: bool, 16 | canonical: Option>, 17 | } 18 | 19 | #[derive(Debug, Deserialize)] 20 | struct ParseTestData { 21 | #[serde(flatten)] 22 | data: TestData, 23 | raw: Vec, 24 | } 25 | 26 | #[derive(Debug, Deserialize)] 27 | #[serde(rename_all = "lowercase", tag = "header_type", content = "expected")] 28 | // https://github.com/httpwg/structured-field-tests/blob/main/README.md#test-format 29 | enum ExpectedHeaderType { 30 | Item(Option), 31 | List(Option), 32 | Dictionary(Option), 33 | } 34 | 35 | #[derive(Debug, Deserialize)] 36 | #[serde(tag = "__type", content = "value")] 37 | enum ExpectedBareItem { 38 | #[serde(rename = "binary")] 39 | ByteSequence(String), 40 | #[serde(rename = "token")] 41 | Token(String), 42 | #[serde(rename = "date")] 43 | Date(i64), 44 | #[serde(rename = "displaystring")] 45 | DisplayString(String), 46 | #[serde(untagged)] 47 | Boolean(bool), 48 | #[serde(untagged)] 49 | Integer(i64), 50 | #[serde(untagged)] 51 | Decimal(f64), 52 | #[serde(untagged)] 53 | String(String), 54 | } 55 | 56 | type ExpectedParameters = Vec<(String, ExpectedBareItem)>; 57 | 58 | type ExpectedItem = (ExpectedBareItem, ExpectedParameters); 59 | 60 | type ExpectedInnerList = (Vec, ExpectedParameters); 61 | 62 | #[derive(Debug, Deserialize)] 63 | #[serde(untagged)] 64 | enum ExpectedListEntry { 65 | Item(ExpectedItem), 66 | InnerList(ExpectedInnerList), 67 | } 68 | 69 | type ExpectedList = Vec; 70 | 71 | type ExpectedDict = Vec<(String, ExpectedListEntry)>; 72 | 73 | trait TestCase: for<'de> Deserialize<'de> { 74 | fn run(self); 75 | } 76 | 77 | impl TestCase for ParseTestData { 78 | fn run(mut self) { 79 | fn check( 80 | test_case: &ParseTestData, 81 | expected: Option>, 82 | ) { 83 | println!("- {}", test_case.data.name); 84 | let input = test_case.raw.join(", "); 85 | 86 | match Parser::new(&input).parse::() { 87 | Ok(actual) => { 88 | assert!(!test_case.data.must_fail); 89 | assert_eq!( 90 | actual, 91 | expected 92 | .expect("expected value should be present") 93 | .build() 94 | .expect("build should succeed") 95 | ); 96 | 97 | let serialized: Option = actual.serialize().into(); 98 | 99 | match test_case.data.canonical { 100 | // If the canonical field is omitted, the canonical form is the input. 101 | None => { 102 | assert_eq!(serialized.expect("serialization should succeed"), input); 103 | } 104 | Some(ref canonical) => { 105 | // If the canonical field is an empty list, the serialization 106 | // should be omitted, which corresponds to `None` from `serialize`. 107 | if canonical.is_empty() { 108 | assert!(serialized.is_none()); 109 | } else { 110 | assert_eq!( 111 | serialized.expect("serialization should succeed"), 112 | canonical[0] 113 | ); 114 | } 115 | } 116 | } 117 | } 118 | Err(_) => assert!(test_case.data.must_fail), 119 | } 120 | } 121 | 122 | #[allow(clippy::redundant_closure_for_method_calls)] // HRTB issue 123 | match self.data.header_type { 124 | ExpectedHeaderType::Item(ref mut expected) => { 125 | let expected = expected.take(); 126 | check(&self, expected); 127 | } 128 | ExpectedHeaderType::List(ref mut expected) => { 129 | let expected = expected.take(); 130 | check(&self, expected); 131 | } 132 | ExpectedHeaderType::Dictionary(ref mut expected) => { 133 | let expected = expected.take(); 134 | check(&self, expected); 135 | } 136 | } 137 | } 138 | } 139 | 140 | impl TestCase for TestData { 141 | fn run(mut self) { 142 | fn check(test_case: &TestData, value: Option>) { 143 | println!("- {}", test_case.name); 144 | match value.expect("expected value should be present").build() { 145 | Ok(value) => match value.serialize().into() { 146 | Some(serialized) => { 147 | assert!(!test_case.must_fail); 148 | assert_eq!( 149 | serialized, 150 | test_case 151 | .canonical 152 | .as_ref() 153 | .expect("canonical serialization should be present")[0] 154 | ); 155 | } 156 | None => assert!(test_case.must_fail), 157 | }, 158 | Err(_) => assert!(test_case.must_fail), 159 | } 160 | } 161 | 162 | match self.header_type { 163 | ExpectedHeaderType::Item(ref mut expected) => { 164 | let expected = expected.take(); 165 | check(&self, expected); 166 | } 167 | ExpectedHeaderType::List(ref mut expected) => { 168 | let expected = expected.take(); 169 | check(&self, expected); 170 | } 171 | ExpectedHeaderType::Dictionary(ref mut expected) => { 172 | let expected = expected.take(); 173 | check(&self, expected); 174 | } 175 | } 176 | } 177 | } 178 | 179 | trait Build { 180 | fn build(self) -> Result>; 181 | } 182 | 183 | impl Build for ExpectedListEntry { 184 | fn build(self) -> Result> { 185 | match self { 186 | Self::Item(value) => value.build().map(ListEntry::from), 187 | Self::InnerList(value) => value.build().map(ListEntry::from), 188 | } 189 | } 190 | } 191 | 192 | impl Build for ExpectedDict { 193 | fn build(self) -> Result> { 194 | let mut dict = Dictionary::new(); 195 | for (key, value) in self { 196 | let key = Key::try_from(key)?; 197 | let value = value.build()?; 198 | dict.insert(key, value); 199 | } 200 | Ok(dict) 201 | } 202 | } 203 | 204 | impl Build for ExpectedList { 205 | fn build(self) -> Result> { 206 | self.into_iter().map(Build::build).collect() 207 | } 208 | } 209 | 210 | impl Build for ExpectedInnerList { 211 | fn build(self) -> Result> { 212 | Ok(InnerList { 213 | items: self 214 | .0 215 | .into_iter() 216 | .map(Build::build) 217 | .collect::, Box>>()?, 218 | params: self.1.build()?, 219 | }) 220 | } 221 | } 222 | 223 | impl Build for ExpectedItem { 224 | fn build(self) -> Result> { 225 | Ok(Item { 226 | bare_item: self.0.build()?, 227 | params: self.1.build()?, 228 | }) 229 | } 230 | } 231 | 232 | impl Build for ExpectedBareItem { 233 | fn build(self) -> Result> { 234 | Ok(match self { 235 | Self::Integer(value) => value.try_into()?, 236 | Self::Decimal(value) => value.try_into()?, 237 | Self::Boolean(value) => value.into(), 238 | Self::String(value) => BareItem::String(value.try_into()?), 239 | Self::Token(value) => BareItem::Token(value.try_into()?), 240 | Self::ByteSequence(ref value) => { 241 | base32::decode(base32::Alphabet::Rfc4648 { padding: true }, value) 242 | .ok_or("invalid base32")? 243 | .into() 244 | } 245 | Self::Date(value) => Date::from_unix_seconds(value.try_into()?).into(), 246 | Self::DisplayString(value) => BareItem::DisplayString(value), 247 | }) 248 | } 249 | } 250 | 251 | impl Build for ExpectedParameters { 252 | fn build(self) -> Result> { 253 | let mut parameters = Parameters::new(); 254 | for (key, value) in self { 255 | let key = Key::try_from(key)?; 256 | let value = value.build()?; 257 | parameters.insert(key, value); 258 | } 259 | Ok(parameters) 260 | } 261 | } 262 | 263 | fn run_tests(dir_path: impl AsRef) -> Result<(), Box> { 264 | for entry in fs::read_dir(env::current_dir()?.join(dir_path))? { 265 | let entry = entry?; 266 | 267 | if entry.path().extension().unwrap_or_default() != "json" { 268 | continue; 269 | } 270 | 271 | println!("\n## Test suite file: {:?}\n", entry.file_name()); 272 | 273 | let test_cases: Vec = 274 | serde_json::from_reader(io::BufReader::new(fs::File::open(entry.path())?))?; 275 | 276 | for test_case in test_cases { 277 | test_case.run(); 278 | } 279 | } 280 | Ok(()) 281 | } 282 | 283 | #[test] 284 | fn run_spec_parse_serialize_tests() -> Result<(), Box> { 285 | run_tests::("tests/spec_tests") 286 | } 287 | 288 | #[test] 289 | fn run_spec_serialize_only_tests() -> Result<(), Box> { 290 | run_tests::("tests/spec_tests/serialisation-tests") 291 | } 292 | --------------------------------------------------------------------------------