├── .github ├── FUNDING.yml └── workflows │ ├── release-plz.yml │ └── test.yml ├── .gitignore ├── .vscode └── ltex.dictionary.en-US.txt ├── .zed └── settings.json ├── Cargo.lock ├── Cargo.toml ├── Justfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── merde ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── examples │ ├── ahash.rs │ ├── enums.rs │ ├── infinite-stack.rs │ ├── into-static.rs │ ├── mixed.rs │ ├── mousedown.rs │ ├── opinions.rs │ ├── return-deserialize.rs │ ├── simple.rs │ └── yaml.rs └── src │ └── lib.rs ├── merde_core ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── src │ ├── array.rs │ ├── covariance_proofs.rs │ ├── cowbytes.rs │ ├── cowstr.rs │ ├── deserialize.rs │ ├── deserialize │ │ └── tests.rs │ ├── error.rs │ ├── event.rs │ ├── into_static.rs │ ├── lib.rs │ ├── map.rs │ ├── metastack.rs │ ├── serialize.rs │ ├── serialize │ │ ├── snapshots │ │ │ └── merde_core__serialize__tests__serialize.snap │ │ └── tests.rs │ ├── time.rs │ ├── value.rs │ └── with_lifetime.rs └── tests │ └── ui │ ├── static-borrow-lifetime.rs │ ├── static-borrow-lifetime.stderr │ ├── static-s-lifetime.rs │ ├── static-s-lifetime.stderr │ ├── subtyping.rs │ └── subtyping.stderr ├── merde_json ├── CHANGELOG.md ├── Cargo.toml ├── README.md └── src │ ├── deserialize.rs │ ├── error.rs │ ├── jiter_lite │ ├── errors.rs │ ├── jiter.rs │ ├── mod.rs │ ├── number_decoder.rs │ ├── parse.rs │ ├── simd_aarch64.rs │ └── string_decoder.rs │ ├── lib.rs │ └── serialize.rs ├── merde_loggingserializer ├── Cargo.toml └── src │ └── lib.rs ├── merde_msgpack ├── CHANGELOG.md ├── Cargo.toml ├── Justfile ├── README.md ├── src │ └── lib.rs ├── testdata-maker │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs └── testdata │ └── test.msgpack ├── merde_yaml ├── CHANGELOG.md ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── rust-toolchain.toml └── zerodeps-example ├── .gitignore ├── Cargo.toml └── src └── lib.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [fasterthanlime] 2 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | token: ${{ secrets.PAT }} 22 | - name: Install Rust toolchain 23 | uses: dtolnay/rust-toolchain@stable 24 | - name: Run release-plz 25 | uses: MarcoIeni/release-plz-action@v0.5 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.PAT }} 28 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | merge_group: 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest] 15 | runs-on: ${{ matrix.os }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install tools 20 | uses: taiki-e/install-action@v2 21 | with: 22 | tool: cargo-hack,just 23 | - name: Run tests 24 | shell: bash 25 | run: | 26 | rustup toolchain install nightly --component miri 27 | just 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.vscode/ltex.dictionary.en-US.txt: -------------------------------------------------------------------------------- 1 | merde 2 | -------------------------------------------------------------------------------- /.zed/settings.json: -------------------------------------------------------------------------------- 1 | // Folder-specific settings 2 | // 3 | // For a full list of overridable settings, and general information on folder-specific settings, 4 | // see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings 5 | { 6 | "lsp": { 7 | "rust-analyzer": { 8 | "initialization_options": { 9 | "check": { 10 | "command": "clippy" 11 | }, 12 | "cargo": { 13 | "features": ["merde/full", "merde_core/full", "merde_json/full"] 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "merde", 5 | "merde_json", 6 | "merde_core", 7 | "merde_yaml", 8 | "merde_msgpack", 9 | "merde_loggingserializer", 10 | ] 11 | exclude = ["zerodeps-example", "merde_msgpack/testdata-maker"] 12 | 13 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | check: 2 | #!/bin/bash -eux 3 | cargo check --all-targets 4 | cargo check --all-features --all-targets 5 | cargo hack --each-feature --exclude-features=default,full check 6 | 7 | # can't use cargo-nextest because we want to run doctests 8 | cargo test -F full 9 | 10 | pushd zerodeps-example 11 | cargo check 12 | cargo check --features=merde 13 | cargo tree --prefix none --no-dedupe | grep -v merde-core 14 | cargo tree --prefix none --no-dedupe --features=merde | grep merde_core 15 | popd 16 | 17 | pushd merde 18 | EXAMPLES=() 19 | for file in examples/*.rs; do 20 | EXAMPLES+=("$(basename "${file%.rs}")") 21 | done 22 | echo "EXAMPLES: ${EXAMPLES[@]}" 23 | 24 | for example in "${EXAMPLES[@]}"; do 25 | cargo run --features full,ahash --example "$example" 26 | done 27 | popd 28 | 29 | just miri 30 | 31 | miri: 32 | cargo +nightly miri run --example opinions -F deserialize,json 33 | cargo +nightly miri test -p merde_core fieldslot 34 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/LICENSE-2.0 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 | https://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. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) 2 | [![crates.io](https://img.shields.io/crates/v/merde.svg)](https://crates.io/crates/merde) 3 | [![docs.rs](https://docs.rs/merde/badge.svg)](https://docs.rs/merde) 4 | 5 | # merde 6 | 7 | ![The merde logo: a glorious poop floating above a pair of hands](https://github.com/user-attachments/assets/763d60e0-5101-48af-bc72-f96f516a5d0f) 8 | 9 | _Logo by [MisiasArt](https://misiasart.com)_ 10 | 11 | A simpler (and slightly shittier) take on [serde](https://crates.io/crates/serde) 12 | 13 | Do you want to deal with JSON data? Are you not _that_ worried about the 14 | performance overhead? (ie. you're writing a backend in Rust, but if it was 15 | written in Node.js nobody would bat an eye?). 16 | 17 | Do you value short build times at the expense of some comfort? 18 | 19 | Then head over to the crate documentations: 20 | 21 | * [merde](./merde/README.md) 22 | * [merde_json](./merde_json/README.md) 23 | * [merde_yaml](./merde_yaml/README.md) 24 | * [merde_msgpack](./merde_msgpack/README.md) 25 | 26 | ## FAQ 27 | 28 | ### What's with the name? 29 | 30 | It's pronounced "murr-day", because we're merializing and demerializing things. 31 | 32 | It's also something you may hear a French person yell when they're sick of waiting 33 | for things to build, just before "j'en ai marre!!" 34 | 35 | ### Why? 36 | 37 | I value iteration times (re: builds, etc.) more than I value microseconds saved deserializing 38 | JSON payloads — I got tired of proc macros getting compiled, parsing all my code, generating 39 | a ton of generic code of their own, etc. 40 | 41 | I also wanted a nice, ergonomic `Value` type that isn't _quite_ tied to JSON, for when you 42 | just can't be arsed deriving your own structs. 43 | 44 | The declarative macro approach is less flexible and not so DRY but so much lighter. Some more 45 | subtlety might be added later, who knows. 46 | 47 | ## License 48 | 49 | This project is primarily distributed under the terms of both the MIT license 50 | and the Apache License (Version 2.0). 51 | 52 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 53 | -------------------------------------------------------------------------------- /merde/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [10.0.7](https://github.com/bearcove/merde/compare/merde-v10.0.6...merde-v10.0.7) - 2025-04-25 11 | 12 | ### Other 13 | 14 | - updated the following local packages: merde_core, merde_json, merde_msgpack, merde_yaml 15 | 16 | ## [10.0.6](https://github.com/bearcove/merde/compare/merde-v10.0.5...merde-v10.0.6) - 2025-04-16 17 | 18 | ### Other 19 | 20 | - updated the following local packages: merde_core, merde_json, merde_msgpack, merde_yaml 21 | 22 | ## [10.0.5](https://github.com/bearcove/merde/compare/merde-v10.0.4...merde-v10.0.5) - 2025-04-16 23 | 24 | ### Other 25 | 26 | - updated the following local packages: merde_core, merde_json, merde_msgpack, merde_yaml 27 | 28 | ## [10.0.4](https://github.com/bearcove/merde/compare/merde-v10.0.3...merde-v10.0.4) - 2025-04-16 29 | 30 | ### Other 31 | 32 | - Add support for camino 33 | 34 | ## [10.0.3](https://github.com/bearcove/merde/compare/merde-v10.0.2...merde-v10.0.3) - 2025-03-06 35 | 36 | ### Fixed 37 | 38 | - make more calls to trait functions, or trait methods, fully qualified to avoid clenches with serde 39 | 40 | ### Other 41 | 42 | - Well, good thing I gave that a check before releasing Like a madman. 43 | 44 | ## [10.0.2](https://github.com/bearcove/merde/compare/merde-v10.0.1...merde-v10.0.2) - 2025-01-29 45 | 46 | ### Other 47 | 48 | - updated the following local packages: merde_core 49 | 50 | ## [10.0.1](https://github.com/bearcove/merde/compare/merde-v10.0.0...merde-v10.0.1) - 2025-01-28 51 | 52 | ### Other 53 | 54 | - Allow empty structs 55 | 56 | ## [10.0.0](https://github.com/bearcove/merde/compare/merde-v9.0.1...merde-v10.0.0) - 2024-12-04 57 | 58 | ### Other 59 | 60 | - [**breaking**] Force a major version bump 61 | 62 | ## [9.0.1](https://github.com/bearcove/merde/compare/merde-v9.0.0...merde-v9.0.1) - 2024-12-02 63 | 64 | ### Other 65 | 66 | - updated the following local packages: merde_core 67 | 68 | ## [9.0.0](https://github.com/bearcove/merde/compare/merde-v8.1.3...merde-v9.0.0) - 2024-11-30 69 | 70 | ### Other 71 | 72 | - remove async versions of things 73 | - wip dyn serialize 74 | - Tests pass 75 | - no errors left? 76 | - Fix more warnings and errors 77 | - More! 78 | - yay other errors 79 | - Dwip 80 | - Remove JsonSerialize trait 81 | - Expose an async Deserializer interface 82 | - Make Deserializer::next async 83 | - Move things around re: absorbing merde_time in merde_core 84 | - Yus 85 | 86 | ## [8.1.3](https://github.com/bearcove/merde/compare/merde-v8.1.2...merde-v8.1.3) - 2024-11-24 87 | 88 | ### Other 89 | 90 | - updated the following local packages: merde_core 91 | 92 | ## [8.1.2](https://github.com/bearcove/merde/compare/merde-v8.1.1...merde-v8.1.2) - 2024-11-20 93 | 94 | ### Other 95 | 96 | - Fix deser/ser impls in merde_time after phasing out JsonSerialize trait 97 | 98 | ## [8.1.1](https://github.com/bearcove/merde/compare/merde-v8.1.0...merde-v8.1.1) - 2024-11-20 99 | 100 | ### Other 101 | 102 | - Enable 'serialize' feature of merde_time by default, when merde's time feature is enabled 103 | 104 | ## [8.1.0](https://github.com/bearcove/merde/compare/merde-v8.0.0...merde-v8.1.0) - 2024-11-20 105 | 106 | ### Added 107 | 108 | - Implement Deserialize and IntoStatic for `Box` ([#107](https://github.com/bearcove/merde/pull/107)) 109 | 110 | ## [8.0.0](https://github.com/bearcove/merde/compare/merde-v6.2.1...merde-v8.0.0) - 2024-11-04 111 | 112 | ### Other 113 | 114 | - Make compact_str / compact_bytes non-optional 115 | - Introduce Serialize trait 116 | - Don't allow trivial UB via FieldSlot in safe code 117 | - I made miri sad 118 | - Add deserializer opinions, cf. [#89](https://github.com/bearcove/merde/pull/89) 119 | - woops wrong examples 120 | - Actually query the stack size, don't hardcode anything 121 | - The trick actually works 122 | - Add surprise example 123 | 124 | ## [6.2.1](https://github.com/bearcove/merde/compare/merde-v6.2.0...merde-v6.2.1) - 2024-10-07 125 | 126 | ### Fixed 127 | 128 | - Proper starter handling in merde_msgpack 129 | 130 | ## [6.2.0](https://github.com/bearcove/merde/compare/merde-v6.1.0...merde-v6.2.0) - 2024-10-06 131 | 132 | ### Added 133 | 134 | - Implement Eq for values 135 | 136 | ### Other 137 | 138 | - Fix tests 139 | - Add support for msgpack deserialization 140 | 141 | ## [6.1.0](https://github.com/bearcove/merde/compare/merde-v6.0.4...merde-v6.1.0) - 2024-10-06 142 | 143 | ### Added 144 | 145 | - Add support for HashMap (for other S) 146 | 147 | ## [6.0.4](https://github.com/bearcove/merde/compare/merde-v6.0.3...merde-v6.0.4) - 2024-10-04 148 | 149 | ### Other 150 | 151 | - Fix empty objects / empty arrays 152 | 153 | ## [6.0.3](https://github.com/bearcove/merde/compare/merde-v6.0.2...merde-v6.0.3) - 2024-10-04 154 | 155 | ### Other 156 | 157 | - updated the following local packages: merde_core, merde_json 158 | 159 | ## [6.0.2](https://github.com/bearcove/merde/compare/merde-v6.0.1...merde-v6.0.2) - 2024-10-04 160 | 161 | ### Other 162 | 163 | - Introduce DeserializeOwned trait 164 | 165 | ## [6.0.1](https://github.com/bearcove/merde/compare/merde-v6.0.0...merde-v6.0.1) - 2024-10-01 166 | 167 | ### Other 168 | 169 | - respect StreamEnd 170 | - merde_yaml is v6 171 | 172 | ## [6.0.0](https://github.com/bearcove/merde/compare/merde-v5.1.1...merde-v6.0.0) - 2024-09-22 173 | 174 | ### Added 175 | 176 | - [**breaking**] Include key name in error ([#73](https://github.com/bearcove/merde/pull/73)) 177 | 178 | ### Other 179 | 180 | - Initial merde_yaml addition ([#77](https://github.com/bearcove/merde/pull/77)) 181 | - Remove ValueDeserialize macros 182 | - Make option optional 183 | - Port more things to deserialize 184 | - Steal @compiler-errors's suggestion (thanks Michael!) 185 | - port one more example 186 | - impl_deserialize is a noop unless the feature is enabled 187 | - Convert example 188 | - Move mixed example to deserialize 189 | - Move more examples over to Deserialize 190 | - Move away from ValueDeserialize 191 | - Fix all tests 192 | - add lifetimes to errors aw yiss 193 | 194 | ## [5.1.1](https://github.com/bearcove/merde/compare/merde-v5.1.0...merde-v5.1.1) - 2024-09-20 195 | 196 | ### Other 197 | 198 | - updated the following local packages: merde_core, merde_json 199 | 200 | ## [5.1.0](https://github.com/bearcove/merde/compare/merde-v5.0.5...merde-v5.1.0) - 2024-09-20 201 | 202 | ### Added 203 | 204 | - Add support for string-like enums 205 | 206 | ## [5.0.5](https://github.com/bearcove/merde/compare/merde-v5.0.4...merde-v5.0.5) - 2024-09-17 207 | 208 | ### Other 209 | 210 | - update Cargo.lock dependencies 211 | 212 | ## [5.0.4](https://github.com/bearcove/merde/compare/merde-v5.0.3...merde-v5.0.4) - 2024-09-17 213 | 214 | ### Other 215 | 216 | - update Cargo.lock dependencies 217 | 218 | ## [5.0.3](https://github.com/bearcove/merde/compare/merde-v5.0.2...merde-v5.0.3) - 2024-09-17 219 | 220 | ### Other 221 | 222 | - update Cargo.lock dependencies 223 | 224 | ## [5.0.2](https://github.com/bearcove/merde/compare/merde-v5.0.1...merde-v5.0.2) - 2024-09-17 225 | 226 | ### Other 227 | 228 | - updated the following local packages: merde_core, merde_json, merde_time 229 | 230 | ## [5.0.1](https://github.com/bearcove/merde/compare/merde-v5.0.0...merde-v5.0.1) - 2024-09-16 231 | 232 | ### Other 233 | 234 | - Don't depend on merde_time by default 235 | - Add rusqlite ToSql/FromSql implementations for CowStr if the corresponding feature is enabled 236 | 237 | ## [5.0.0](https://github.com/bearcove/merde/compare/merde-v4.0.5...merde-v5.0.0) - 2024-09-15 238 | 239 | ### Added 240 | 241 | - Introduce OwnedValueDeserialize 242 | - [**breaking**] Introduce WithLifetime trait 243 | 244 | ### Other 245 | 246 | - Doc for externally-tagged enums 247 | - Add doc in derive for tuple structs 248 | - Add doc for enums & tuple structs 249 | - done with tuple structs 250 | - rejiggle order 251 | - wip tuple structs 252 | - Allow deriving for externally-tagged enums 253 | - WIP enum support 254 | - Require 'struct' prefix when deriving valuedeserialize etc. 255 | - Introduce WithLifetime trait (thanks @JaSpa) 256 | - Showcase 'impl is not general enough' problem 257 | 258 | ## [4.0.5](https://github.com/bearcove/merde/compare/merde-v4.0.4...merde-v4.0.5) - 2024-09-14 259 | 260 | ### Other 261 | 262 | - one more cfg-gate lacking 263 | 264 | ## [4.0.4](https://github.com/bearcove/merde/compare/merde-v4.0.3...merde-v4.0.4) - 2024-09-14 265 | 266 | ### Other 267 | 268 | - Make merde_time flags make sense 269 | 270 | ## [4.0.3](https://github.com/bearcove/merde/compare/merde-v4.0.2...merde-v4.0.3) - 2024-09-14 271 | 272 | ### Other 273 | 274 | - Pull feature gates outside macros 275 | 276 | ## [4.0.2](https://github.com/bearcove/merde/compare/merde-v4.0.1...merde-v4.0.2) - 2024-09-14 277 | 278 | ### Other 279 | 280 | - updated the following local packages: merde_core 281 | 282 | ## [4.0.1](https://github.com/bearcove/merde/compare/merde-v4.0.0...merde-v4.0.1) - 2024-09-14 283 | 284 | ### Other 285 | 286 | - Add serde feature for merde/merde_core for CowStr 287 | 288 | ## [3.1.1](https://github.com/bearcove/merde/compare/merde-v3.1.0...merde-v3.1.1) - 2024-09-12 289 | 290 | ### Other 291 | 292 | - Fix logo 293 | -------------------------------------------------------------------------------- /merde/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merde" 3 | version = "10.0.7" 4 | edition = "2021" 5 | authors = ["Amos Wenger "] 6 | description = "Serialize and deserialize with declarative macros" 7 | license = "Apache-2.0 OR MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/bearcove/merde" 10 | keywords = ["serialization", "deserialization"] 11 | categories = ["encoding", "parser-implementations"] 12 | 13 | [[example]] 14 | name = "simple" 15 | path = "examples/simple.rs" 16 | required-features = ["json"] 17 | 18 | [[example]] 19 | name = "mixed" 20 | path = "examples/mixed.rs" 21 | required-features = ["json"] 22 | 23 | [[example]] 24 | name = "into-static" 25 | path = "examples/into-static.rs" 26 | required-features = ["json"] 27 | 28 | [[example]] 29 | name = "return-deserialize" 30 | path = "examples/return-deserialize.rs" 31 | required-features = ["json"] 32 | 33 | [[example]] 34 | name = "enums" 35 | path = "examples/enums.rs" 36 | required-features = ["json"] 37 | 38 | [[example]] 39 | name = "yaml" 40 | path = "examples/yaml.rs" 41 | required-features = ["yaml"] 42 | 43 | [[example]] 44 | name = "ahash" 45 | path = "examples/ahash.rs" 46 | required-features = ["json", "ahash"] 47 | 48 | [[example]] 49 | name = "infinite-stack" 50 | path = "examples/infinite-stack.rs" 51 | required-features = ["json"] 52 | 53 | [[example]] 54 | name = "opinions" 55 | path = "examples/opinions.rs" 56 | required-features = ["json"] 57 | 58 | [[example]] 59 | name = "mousedown" 60 | path = "examples/mousedown.rs" 61 | required-features = ["json"] 62 | 63 | [dependencies] 64 | merde_core = { version = "10.0.6", path = "../merde_core", optional = true } 65 | merde_json = { version = "10.0.6", path = "../merde_json", optional = true } 66 | merde_yaml = { version = "10.0.6", path = "../merde_yaml", optional = true } 67 | merde_msgpack = { version = "10.0.6", path = "../merde_msgpack", optional = true } 68 | ahash = { version = "0.8.11", optional = true } 69 | 70 | [features] 71 | default = ["core"] 72 | full = [ 73 | "core", 74 | "serialize", 75 | "deserialize", 76 | "json", 77 | "yaml", 78 | "msgpack", 79 | "time", 80 | "rusqlite", 81 | "camino", 82 | ] 83 | core = ["dep:merde_core"] 84 | serialize = ["core"] 85 | deserialize = ["core"] 86 | 87 | # merde_core re-exports 88 | serde = ["merde_core/serde"] 89 | rusqlite = ["merde_core/rusqlite"] 90 | time = ["merde_core/time"] 91 | camino = ["merde_core/camino"] 92 | 93 | # non-core crates 94 | json = ["dep:merde_json"] 95 | yaml = ["dep:merde_yaml"] 96 | msgpack = ["dep:merde_msgpack"] 97 | 98 | # others 99 | ahash = ["dep:ahash"] 100 | -------------------------------------------------------------------------------- /merde/examples/ahash.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ahash")] 2 | fn main() { 3 | use ahash::HashMap; 4 | use merde::json; 5 | 6 | let mut h = HashMap::default(); 7 | h.insert("street".to_string(), "123 Main St".to_string()); 8 | h.insert("city".to_string(), "Anytown".to_string()); 9 | h.insert("state".to_string(), "CA".to_string()); 10 | h.insert("zip".to_string(), "12345".to_string()); 11 | 12 | println!("h: {:#?}", h); 13 | 14 | let serialized = json::to_string(&h).unwrap(); 15 | println!("serialized: {}", serialized); 16 | 17 | let deserialized: HashMap = json::from_str(&serialized).unwrap(); 18 | println!("deserialized: {:#?}", deserialized); 19 | 20 | assert_eq!(h, deserialized); 21 | 22 | assert_eq!(deserialized.get("street"), Some(&"123 Main St".to_string())); 23 | assert_eq!(deserialized.get("city"), Some(&"Anytown".to_string())); 24 | assert_eq!(deserialized.get("state"), Some(&"CA".to_string())); 25 | assert_eq!(deserialized.get("zip"), Some(&"12345".to_string())); 26 | } 27 | -------------------------------------------------------------------------------- /merde/examples/enums.rs: -------------------------------------------------------------------------------- 1 | use merde::CowStr; 2 | 3 | fn main() { 4 | let events = vec![ 5 | ExampleEvent::MouseUp(MouseUp { x: 10, y: 20 }), 6 | ExampleEvent::MouseDown(MouseDown { x: 30, y: 40 }), 7 | ExampleEvent::TextInput(TextInput { 8 | text: "Hello".into(), 9 | }), 10 | ExampleEvent::StringStuff(StringStuff("Some string".into())), 11 | ExampleEvent::Emergency(Box::new(Emergency::NoPizzaLeft)), 12 | ]; 13 | 14 | for event in events { 15 | let json = merde::json::to_string(&event).unwrap(); 16 | println!("JSON: {}", json); 17 | let deserialized: ExampleEvent = merde::json::from_str(&json).unwrap(); 18 | println!("Deserialized: {:?}", deserialized); 19 | assert_eq!(event, deserialized); 20 | } 21 | 22 | println!("All events successfully round-tripped through JSON!"); 23 | } 24 | 25 | #[derive(Debug, PartialEq, Eq)] 26 | enum ExampleEvent<'s> { 27 | MouseUp(MouseUp), 28 | MouseDown(MouseDown), 29 | TextInput(TextInput<'s>), 30 | StringStuff(StringStuff<'s>), 31 | Emergency(Box), 32 | } 33 | 34 | merde::derive! { 35 | impl (Serialize, Deserialize) for enum ExampleEvent<'s> 36 | externally_tagged { 37 | "mouseup" => MouseUp, 38 | "mousedown" => MouseDown, 39 | "textinput" => TextInput, 40 | "stringstuff" => StringStuff, 41 | "emergency" => Emergency, 42 | } 43 | } 44 | 45 | #[derive(Debug, PartialEq, Eq)] 46 | struct MouseUp { 47 | x: i32, 48 | y: i32, 49 | } 50 | 51 | merde::derive! { 52 | impl (Serialize, Deserialize) for struct MouseUp { 53 | x, 54 | y 55 | } 56 | } 57 | 58 | #[derive(Debug, PartialEq, Eq)] 59 | struct MouseDown { 60 | x: i32, 61 | y: i32, 62 | } 63 | 64 | merde::derive! { 65 | impl (Serialize, Deserialize) for struct MouseDown { 66 | x, 67 | y 68 | } 69 | } 70 | 71 | #[derive(Debug, PartialEq, Eq)] 72 | struct TextInput<'s> { 73 | text: CowStr<'s>, 74 | } 75 | 76 | merde::derive! { impl (Serialize, Deserialize) for struct TextInput<'s> { text } } 77 | 78 | #[derive(Debug, PartialEq, Eq)] 79 | struct StringStuff<'s>(CowStr<'s>); 80 | 81 | merde::derive! { 82 | impl (Serialize, Deserialize) for struct StringStuff<'s> transparent 83 | } 84 | 85 | #[derive(Debug, PartialEq, Eq)] 86 | enum Emergency { 87 | NoPizzaLeft, 88 | CuddlesRequired, 89 | SmoothieReady, 90 | } 91 | 92 | merde::derive! { 93 | impl (Serialize, Deserialize) for enum Emergency string_like { 94 | "nopizza" => NoPizzaLeft, 95 | "cuddles" => CuddlesRequired, 96 | "smoothie" => SmoothieReady, 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /merde/examples/infinite-stack.rs: -------------------------------------------------------------------------------- 1 | use merde::Value; 2 | 3 | struct Person { 4 | first_name: String, 5 | last_name: String, 6 | } 7 | 8 | merde::derive! { 9 | impl (Deserialize) for struct Person { first_name, last_name } 10 | } 11 | 12 | fn main() { 13 | let jh = std::thread::Builder::new() 14 | .stack_size(128 * 1024) 15 | .spawn(|| { 16 | let cool_factor = 100_000; 17 | 18 | let first_half = "[".repeat(cool_factor); 19 | let second_half = "]".repeat(cool_factor); 20 | let input = format!("{first_half}{second_half}"); 21 | 22 | let value: Value<'_> = merde::json::from_str(&input[..]).unwrap(); 23 | 24 | let mut current_value = &value; 25 | let mut count = 0; 26 | loop { 27 | if let Value::Array(arr) = ¤t_value { 28 | if arr.len() == 0 { 29 | break; 30 | } else { 31 | current_value = &arr[0]; 32 | count += 1; 33 | } 34 | } 35 | } 36 | println!("final count {count}"); 37 | 38 | // at this point `value` is a bomb — if we try to drop it, it _will_ 39 | // overflow the stack. the only way out of this is to mem::forget it 40 | std::mem::forget(value); 41 | }) 42 | .unwrap(); 43 | 44 | jh.join().unwrap(); 45 | } 46 | -------------------------------------------------------------------------------- /merde/examples/into-static.rs: -------------------------------------------------------------------------------- 1 | use merde_core::IntoStatic; 2 | use std::borrow::Cow; 3 | 4 | #[allow(dead_code)] 5 | #[derive(Debug, PartialEq, Eq)] 6 | struct Address<'s> { 7 | street: Cow<'s, str>, 8 | city: Cow<'s, str>, 9 | state: Cow<'s, str>, 10 | zip: u16, 11 | } 12 | 13 | merde::derive! { 14 | impl (Deserialize) for struct Address<'s> { 15 | street, 16 | city, 17 | state, 18 | zip 19 | } 20 | } 21 | 22 | #[allow(dead_code)] 23 | #[derive(Debug, PartialEq, Eq)] 24 | struct Person<'s> { 25 | name: Cow<'s, str>, 26 | age: u8, 27 | address: Address<'s>, 28 | } 29 | 30 | merde::derive! { 31 | impl (Deserialize) for struct Person<'s> { name, age, address } 32 | } 33 | 34 | fn get_person() -> Person<'static> { 35 | let input = r#" 36 | { 37 | "name": "John Doe", 38 | "age": 42, 39 | "address": { 40 | "street": "123 Main St", 41 | "city": "Anytown", 42 | "state": "CA", 43 | "zip": 12345 44 | } 45 | } 46 | "#; 47 | 48 | merde_json::from_str::(input).unwrap().into_static() 49 | } 50 | 51 | fn main() { 52 | let person = get_person(); 53 | println!("{:?}", person); 54 | } 55 | -------------------------------------------------------------------------------- /merde/examples/mixed.rs: -------------------------------------------------------------------------------- 1 | use merde::CowStr; 2 | use merde::Value; 3 | 4 | #[derive(Debug, PartialEq)] 5 | struct MixedArray<'s> { 6 | items: Vec>, 7 | } 8 | merde::derive! { 9 | 10 | impl (Serialize, Deserialize) for struct MixedArray<'s> { items } 11 | } 12 | 13 | #[derive(Debug, PartialEq)] 14 | struct MixedArray2<'s> { 15 | items: (u64, CowStr<'s>, bool), 16 | } 17 | merde::derive! { 18 | impl (Serialize, Deserialize) for struct MixedArray2<'s> { items } 19 | } 20 | 21 | fn main() { 22 | let input = r#" 23 | { 24 | "items": [ 25 | 42, "foo", true 26 | ] 27 | } 28 | "#; 29 | 30 | let ma: MixedArray = merde_json::from_str(input).unwrap(); 31 | println!("{:?}", ma); 32 | 33 | let ma: MixedArray2 = merde_json::from_str(input).unwrap(); 34 | println!("{:?}", ma); 35 | } 36 | -------------------------------------------------------------------------------- /merde/examples/mousedown.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | enum TestEvent { 3 | MouseUp(MouseUp), 4 | MouseDown(MouseDown), 5 | } 6 | 7 | merde::derive! { 8 | impl (Serialize, Deserialize) for enum TestEvent 9 | externally_tagged { 10 | "mouseup" => MouseUp, 11 | "mousedown" => MouseDown, 12 | } 13 | } 14 | 15 | #[derive(Debug, PartialEq, Eq)] 16 | struct MouseUp { 17 | x: i32, 18 | y: i32, 19 | } 20 | 21 | merde::derive! { 22 | impl (Serialize, Deserialize) for struct MouseUp { 23 | x, 24 | y 25 | } 26 | } 27 | 28 | #[derive(Debug, PartialEq, Eq)] 29 | struct MouseDown { 30 | x: i32, 31 | y: i32, 32 | } 33 | 34 | merde::derive! { 35 | impl (Serialize, Deserialize) for struct MouseDown { 36 | x, 37 | y 38 | } 39 | } 40 | 41 | fn main() { 42 | let input = r#"{"mouseup": {"x": 100, "y": 200}}"#; 43 | let event: TestEvent = merde::json::from_str(input).unwrap(); 44 | println!("TestEvent: {:?}", event); 45 | } 46 | -------------------------------------------------------------------------------- /merde/examples/opinions.rs: -------------------------------------------------------------------------------- 1 | use merde::{CowStr, DeserOpinions, FieldSlot}; 2 | 3 | fn main() { 4 | let input_precise = r#" 5 | { "foo_bar": "hello" } 6 | "#; 7 | let o: Owned = merde_json::from_str(input_precise).unwrap(); 8 | assert_eq!(o.foo_bar, "hello"); 9 | eprintln!("{o:#?}"); 10 | 11 | let input_camel_case = r#" 12 | { "fooBar": "hello" } 13 | "#; 14 | let o: Owned = merde_json::from_str(input_camel_case).unwrap(); 15 | assert_eq!(o.foo_bar, "hello"); 16 | eprintln!("{o:#?}"); 17 | 18 | let input_too_many_fields = r#" 19 | { "foo_bar": "hello", "foo_bar2": "world" } 20 | "#; 21 | assert!(merde_json::from_str::(input_too_many_fields).is_err()); 22 | let o: OwnedRelaxed = merde_json::from_str(input_too_many_fields).unwrap(); 23 | assert_eq!(o.foo_bar, "hello"); 24 | eprintln!("{o:#?}"); 25 | 26 | let input_missing_field = r#" 27 | {} 28 | "#; 29 | let o: Owned = merde_json::from_str(input_missing_field).unwrap(); 30 | assert_eq!(o.foo_bar, "(default)"); 31 | eprintln!("{o:#?}"); 32 | } 33 | 34 | #[derive(Debug)] 35 | struct Owned { 36 | foo_bar: String, 37 | } 38 | 39 | struct OwnedOpinions; 40 | 41 | impl DeserOpinions for OwnedOpinions { 42 | fn deny_unknown_fields(&self) -> bool { 43 | true 44 | } 45 | 46 | #[allow(clippy::needless_lifetimes)] 47 | fn default_field_value<'s, 'borrow>(&self, key: &'borrow str, slot: FieldSlot<'s, 'borrow>) { 48 | if key == "foo_bar" { 49 | slot.fill::("(default)".into()); 50 | } 51 | } 52 | 53 | fn map_key_name<'s>(&self, key: CowStr<'s>) -> CowStr<'s> { 54 | if key == "fooBar" { 55 | CowStr::Owned("foo_bar".into()) 56 | } else { 57 | key 58 | } 59 | } 60 | } 61 | 62 | merde::derive! { 63 | impl (Deserialize) for struct Owned { 64 | foo_bar 65 | } via OwnedOpinions 66 | } 67 | 68 | #[derive(Debug)] 69 | struct OwnedRelaxed { 70 | foo_bar: String, 71 | } 72 | 73 | merde::derive! { 74 | impl (Deserialize) for struct OwnedRelaxed { 75 | foo_bar 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /merde/examples/return-deserialize.rs: -------------------------------------------------------------------------------- 1 | use merde::{CowStr, DeserializeOwned, IntoStatic, MerdeError, MetastackExt}; 2 | 3 | fn deser_and_return(s: String) -> Result> 4 | where 5 | T: DeserializeOwned, 6 | { 7 | // here `s` is a `String`, but pretend we're making 8 | // a network request instead — the point is is that we 9 | // need to borrow from a local from the function body. 10 | let mut deser = merde_json::JsonDeserializer::new(&s); 11 | T::deserialize_owned(&mut deser) 12 | .run_sync_with_metastack() 13 | .map_err(|e| e.into_static()) 14 | } 15 | 16 | fn main() { 17 | let input = r#" 18 | { 19 | "name": "John Doe", 20 | "age": 42, 21 | "address": { 22 | "street": "123 Main St", 23 | "city": "Anytown", 24 | "state": "CA", 25 | "zip": 12345 26 | } 27 | } 28 | "#; 29 | 30 | let person: Person = merde_json::from_str(input).unwrap(); 31 | println!("{:?}", person); 32 | 33 | let serialized = merde_json::to_string(&person).unwrap(); 34 | let person2: Person = merde_json::from_str(&serialized).unwrap(); 35 | println!("{:?}", person2); 36 | 37 | assert_eq!(person, person2); 38 | 39 | let person3 = deser_and_return::(serialized).unwrap(); 40 | println!("{:?}", person3); 41 | 42 | assert_eq!(person, person3); 43 | } 44 | 45 | #[derive(Debug, PartialEq)] 46 | struct Address<'s> { 47 | street: CowStr<'s>, 48 | city: CowStr<'s>, 49 | state: CowStr<'s>, 50 | zip: u16, 51 | } 52 | 53 | merde::derive! { 54 | impl (Serialize, Deserialize) for struct Address<'s> { 55 | street, 56 | city, 57 | state, 58 | zip 59 | } 60 | } 61 | 62 | #[derive(Debug, PartialEq)] 63 | struct Person<'s> { 64 | name: CowStr<'s>, 65 | age: u8, 66 | address: Address<'s>, 67 | } 68 | 69 | merde::derive! { 70 | impl (Serialize, Deserialize) for struct Person<'s> { name, age, address } 71 | } 72 | -------------------------------------------------------------------------------- /merde/examples/simple.rs: -------------------------------------------------------------------------------- 1 | use merde::CowStr; 2 | 3 | fn main() { 4 | let input = r#" 5 | { 6 | "name": "John Doe", 7 | "age": 42, 8 | "address": { 9 | "street": "123 Main St", 10 | "city": "Anytown", 11 | "state": "CA", 12 | "zip": 12345 13 | } 14 | } 15 | "#; 16 | 17 | // Note: those two bindings are necessary — `Person` borrows from `JsonValue` 18 | // If we wanted a `Person` we can move, we could do `.to_static()` 19 | let person: Person = merde_json::from_str(input).unwrap(); 20 | println!("{:#?}", person); 21 | 22 | // Round-trip! Again, every binding borrows from the previous one, and 23 | // everything can be converted from `F<'a>` to `F<'static>` via the 24 | // `IntoStatic` trait. 25 | let serialized = merde_json::to_string(&person).unwrap(); 26 | let person2: Person = merde_json::from_str(&serialized).unwrap(); 27 | println!("{:#?}", person2); 28 | 29 | assert_eq!(person, person2); 30 | } 31 | 32 | #[derive(Debug, PartialEq, Eq)] 33 | #[allow(dead_code)] 34 | struct Address<'s> { 35 | street: CowStr<'s>, 36 | city: CowStr<'s>, 37 | state: CowStr<'s>, 38 | zip: u16, 39 | extra: Option>, 40 | } 41 | 42 | merde::derive! { 43 | impl (Serialize, Deserialize) for struct Address<'s> { 44 | street, 45 | city, 46 | state, 47 | zip, 48 | extra 49 | } 50 | } 51 | 52 | #[derive(Debug, PartialEq, Eq)] 53 | #[allow(dead_code)] 54 | struct Person<'s> { 55 | name: CowStr<'s>, 56 | age: u8, 57 | address: Address<'s>, 58 | } 59 | 60 | merde::derive! { 61 | impl (Serialize, Deserialize) for struct Person<'s> { name, age, address } 62 | } 63 | 64 | struct Empty {} 65 | 66 | merde::derive! { 67 | impl (Serialize, Deserialize) for struct Empty {} 68 | } 69 | -------------------------------------------------------------------------------- /merde/examples/yaml.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use merde::{DynDeserializerExt, Value}; 4 | use merde_yaml::YamlDeserializer; 5 | 6 | #[derive(Debug, PartialEq)] 7 | struct ComplexStruct { 8 | name: String, 9 | age: u32, 10 | hobbies: Vec, 11 | address: Address, 12 | scores: Vec, 13 | } 14 | 15 | merde::derive! { 16 | impl (Deserialize) for struct ComplexStruct { 17 | name, 18 | age, 19 | hobbies, 20 | address, 21 | scores 22 | } 23 | } 24 | 25 | #[derive(Debug, PartialEq)] 26 | struct Address { 27 | street: String, 28 | city: String, 29 | country: String, 30 | } 31 | 32 | merde::derive! { 33 | impl (Deserialize) for struct Address { 34 | street, 35 | city, 36 | country 37 | } 38 | } 39 | 40 | #[derive(Debug, PartialEq)] 41 | struct Score { 42 | subject: String, 43 | value: f32, 44 | } 45 | 46 | merde::derive! { 47 | impl (Deserialize) for struct Score { 48 | subject, 49 | value 50 | } 51 | } 52 | 53 | fn main() { 54 | let yaml = r#" 55 | name: John Doe 56 | age: 30 57 | hobbies: 58 | - reading 59 | - swimming 60 | - coding 61 | address: 62 | street: 123 Main St 63 | city: Anytown 64 | country: Wonderland 65 | scores: 66 | - subject: Math 67 | value: 95.5 68 | - subject: Science 69 | value: 88.0 70 | - subject: Literature 71 | value: 92.5 72 | "#; 73 | 74 | let mut de = YamlDeserializer::new(yaml); 75 | let result: ComplexStruct = de.deserialize().unwrap(); 76 | 77 | println!("Deserialized ComplexStruct: {result:#?}"); 78 | 79 | let yaml_map = r#" 80 | 1: 100 81 | "two": 200.5 82 | true: "three hundred" 83 | [1, 2, 3]: { "nested": "value" } 84 | "#; 85 | 86 | let mut de = YamlDeserializer::new(yaml_map); 87 | let result: HashMap = de.deserialize().unwrap(); 88 | 89 | println!("Deserialized HashMap: {result:#?}"); 90 | } 91 | -------------------------------------------------------------------------------- /merde_core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [10.0.6](https://github.com/bearcove/merde/compare/merde_core-v10.0.5...merde_core-v10.0.6) - 2025-04-25 10 | 11 | ### Other 12 | 13 | - Various non-major dependency upgrades 14 | - Upgrade rusqlite 15 | - Update ordered-float to v5.0.0 16 | 17 | ## [10.0.5](https://github.com/bearcove/merde/compare/merde_core-v10.0.4...merde_core-v10.0.5) - 2025-04-16 18 | 19 | ### Other 20 | 21 | - mh 22 | 23 | ## [10.0.4](https://github.com/bearcove/merde/compare/merde_core-v10.0.3...merde_core-v10.0.4) - 2025-04-16 24 | 25 | ### Other 26 | 27 | - IntoStatic for Utf8PathBuf 28 | 29 | ## [10.0.3](https://github.com/bearcove/merde/compare/merde_core-v10.0.2...merde_core-v10.0.3) - 2025-04-16 30 | 31 | ### Other 32 | 33 | - Add support for camino 34 | 35 | ## [10.0.2](https://github.com/bearcove/merde/compare/merde_core-v10.0.1...merde_core-v10.0.2) - 2025-03-06 36 | 37 | ### Fixed 38 | 39 | - make more calls to trait functions, or trait methods, fully qualified to avoid clenches with serde 40 | 41 | ## [10.0.1](https://github.com/bearcove/merde/compare/merde_core-v10.0.0...merde_core-v10.0.1) - 2025-01-29 42 | 43 | ### Other 44 | 45 | - Add impls for Arc 46 | 47 | ## [10.0.0](https://github.com/bearcove/merde/compare/merde_core-v9.0.1...merde_core-v10.0.0) - 2024-12-04 48 | 49 | ### Other 50 | 51 | - [**breaking**] Force a major version bump 52 | - remove rubicon (merde has a single ABI now anyway) 53 | 54 | ## [9.0.1](https://github.com/bearcove/merde/compare/merde_core-v9.0.0...merde_core-v9.0.1) - 2024-12-02 55 | 56 | ### Other 57 | 58 | - Remove backtrace 59 | 60 | ## [9.0.0](https://github.com/bearcove/merde/compare/merde_core-v8.1.1...merde_core-v9.0.0) - 2024-11-30 61 | 62 | ### Other 63 | 64 | - Introduce DynDeserialize 65 | - Remove workaround for https://github.com/rust-lang/rust/issues/133676 66 | - Mhh 67 | - Use DynSerialize in merde_json 68 | - Introduce DynSerialize 69 | - Rename serialize_sync to serialize 70 | - remove async versions of things 71 | - wip dyn serialize 72 | - More! 73 | - yay other errors 74 | - Dwip 75 | - lift static requirement 76 | - mhh lifetimes yes 77 | - mh 78 | - well that's hard 79 | - Expose to_tokio_writer 80 | - Remove JsonSerialize trait 81 | - Expose an async Deserializer interface 82 | - Make Deserializer::next async 83 | - Move things around re: absorbing merde_time in merde_core 84 | - Yus 85 | 86 | ## [8.1.1](https://github.com/bearcove/merde/compare/merde_core-v8.1.0...merde_core-v8.1.1) - 2024-11-24 87 | 88 | ### Other 89 | 90 | - impl WIthLifetime for Option 91 | 92 | ## [8.1.0](https://github.com/bearcove/merde/compare/merde_core-v8.0.0...merde_core-v8.1.0) - 2024-11-20 93 | 94 | ### Added 95 | 96 | - Implement Deserialize and IntoStatic for `Box` ([#107](https://github.com/bearcove/merde/pull/107)) 97 | 98 | ## [8.0.0](https://github.com/bearcove/merde/compare/merde_core-v7.0.0...merde_core-v8.0.0) - 2024-11-04 99 | 100 | ### Added 101 | 102 | - Impl WithLifetime for Value (woops) 103 | 104 | ### Other 105 | 106 | - Make compact_str / compact_bytes non-optional 107 | - Introduce Serialize trait 108 | - As pointed out, FieldSlot must be invariant 109 | - We did ask miri 110 | - More tests around FieldSlot ([#101](https://github.com/bearcove/merde/pull/101)) 111 | - Don't allow trivial UB via FieldSlot in safe code 112 | - I made miri unsad 113 | - I made miri sad 114 | - Add deserializer opinions, cf. [#89](https://github.com/bearcove/merde/pull/89) 115 | - Introduce deserialization opinions 116 | - macOS fixes 117 | - Fix infinite stack linux support 118 | - Oh yeah our MSRV is 1.75 because AFIT 119 | - fine let's not make msrv rust 1.82 120 | - Actually query the stack size, don't hardcode anything 121 | - Comments-- 122 | - The trick actually works 123 | - Committing before something bad happens 124 | - Start the trick 125 | - Deserialize borrowed variants of cowstr 126 | 127 | ## [7.0.0](https://github.com/bearcove/merde/compare/merde_core-v6.1.0...merde_core-v7.0.0) - 2024-10-06 128 | 129 | ### Added 130 | 131 | - Implement Eq for values 132 | 133 | ### Other 134 | 135 | - Fix tests 136 | - Add support for msgpack deserialization 137 | 138 | ## [6.1.0](https://github.com/bearcove/merde/compare/merde_core-v6.0.2...merde_core-v6.1.0) - 2024-10-06 139 | 140 | ### Added 141 | 142 | - Add support for HashMap (for other S) 143 | 144 | ## [6.0.2](https://github.com/bearcove/merde/compare/merde_core-v6.0.1...merde_core-v6.0.2) - 2024-10-04 145 | 146 | ### Other 147 | 148 | - Make MerdeJsonError implement IntoStatic + impl for Result 149 | 150 | ## [6.0.1](https://github.com/bearcove/merde/compare/merde_core-v6.0.0...merde_core-v6.0.1) - 2024-10-04 151 | 152 | ### Other 153 | 154 | - Introduce from_str_owned in the json module 155 | - Introduce DeserializeOwned trait 156 | 157 | ## [6.0.0](https://github.com/bearcove/merde/compare/merde_core-v5.1.0...merde_core-v6.0.0) - 2024-09-22 158 | 159 | ### Added 160 | 161 | - [**breaking**] Include key name in error ([#73](https://github.com/bearcove/merde/pull/73)) 162 | 163 | ### Other 164 | 165 | - Add bytes type ([#76](https://github.com/bearcove/merde/pull/76)) 166 | - Remove ValueDeserialize macros 167 | - Remove definition of ValueDeserialize 168 | - Make option optional 169 | - Convert example 170 | - Move mixed example to deserialize 171 | - Move away from ValueDeserialize 172 | - Use UnexpectedEvent 173 | - Deserializable => Deserialize, a-la serde 174 | - Fix all tests 175 | - Well that works 176 | - okay hang on 177 | - Play around with API 178 | - mhmh 179 | - poll failed you say 180 | - add lifetimes to errors aw yiss 181 | - des2 ideas 182 | 183 | ## [5.1.0](https://github.com/bearcove/merde/compare/merde_core-v5.0.5...merde_core-v5.1.0) - 2024-09-20 184 | 185 | ### Added 186 | 187 | - Add JsonSerialize and ValueDeserialize impls for f32, f64 188 | 189 | ## [5.0.5](https://github.com/bearcove/merde/compare/merde_core-v5.0.4...merde_core-v5.0.5) - 2024-09-17 190 | 191 | ### Fixed 192 | 193 | - require rubicon 3.4.9 194 | 195 | ## [5.0.4](https://github.com/bearcove/merde/compare/merde_core-v5.0.3...merde_core-v5.0.4) - 2024-09-17 196 | 197 | ### Fixed 198 | 199 | - Require rubicon 3.4.8 200 | 201 | ## [5.0.3](https://github.com/bearcove/merde/compare/merde_core-v5.0.2...merde_core-v5.0.3) - 2024-09-17 202 | 203 | ### Other 204 | 205 | - Run rubicon compatibility checks in various places around CowStr (deref, etc.) 206 | 207 | ## [5.0.2](https://github.com/bearcove/merde/compare/merde_core-v5.0.1...merde_core-v5.0.2) - 2024-09-17 208 | 209 | ### Other 210 | 211 | - Add/fix logo attribution 212 | 213 | ## [5.0.1](https://github.com/bearcove/merde/compare/merde_core-v5.0.0...merde_core-v5.0.1) - 2024-09-16 214 | 215 | ### Other 216 | 217 | - Add rusqlite ToSql/FromSql implementations for CowStr if the corresponding feature is enabled 218 | 219 | ## [5.0.0](https://github.com/bearcove/merde/compare/merde_core-v4.0.2...merde_core-v5.0.0) - 2024-09-15 220 | 221 | ### Added 222 | 223 | - Introduce OwnedValueDeserialize 224 | - [**breaking**] Introduce WithLifetime trait 225 | 226 | ### Other 227 | 228 | - Implement ValueDeserialize for Box, Rc, Arc 229 | - Add rubicon compat check to merde_core, closes [#58](https://github.com/bearcove/merde/pull/58) 230 | - Provide from_utf8 family of functions + AsRef for CowStr 231 | 232 | ## [4.0.2](https://github.com/bearcove/merde/compare/merde_core-v4.0.1...merde_core-v4.0.2) - 2024-09-14 233 | 234 | ### Other 235 | 236 | - Add more PartialEq implementations for CowStr to allow 'cow_str == blah' 237 | 238 | ## [4.0.1](https://github.com/bearcove/merde/compare/merde_core-v4.0.0...merde_core-v4.0.1) - 2024-09-14 239 | 240 | ### Other 241 | 242 | - Add serde feature for merde/merde_core for CowStr 243 | 244 | ## [3.0.1](https://github.com/bearcove/merde/compare/merde_core-v3.0.0...merde_core-v3.0.1) - 2024-09-12 245 | 246 | ### Other 247 | 248 | - Remove unused dependencies 249 | 250 | ## [2.2.3](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.2.2...merde_json_types-v2.2.3) - 2024-09-05 251 | 252 | ### Other 253 | - Update logo attribution 254 | 255 | ## [2.2.2](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.2.1...merde_json_types-v2.2.2) - 2024-08-16 256 | 257 | ### Other 258 | - updated the following local packages: merde_json, merde_json 259 | 260 | ## [2.2.1](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.2.0...merde_json_types-v2.2.1) - 2024-08-16 261 | 262 | ### Other 263 | - updated the following local packages: merde_json, merde_json 264 | 265 | ## [2.2.0](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.1.2...merde_json_types-v2.2.0) - 2024-08-16 266 | 267 | ### Added 268 | - Provide Fantome from both merde-json and merde-json-types 269 | 270 | ## [2.1.2](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.1.1...merde_json_types-v2.1.2) - 2024-08-16 271 | 272 | ### Other 273 | - updated the following local packages: merde_json, merde_json 274 | 275 | ## [2.1.1](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.1.0...merde_json_types-v2.1.1) - 2024-07-31 276 | 277 | ### Other 278 | - Use public URL, hopefully works on crates too? 279 | - Add @2x asset 280 | - Add logo 281 | 282 | ## [2.1.0](https://github.com/bearcove/merde_json/compare/merde_json_types-v2.0.0...merde_json_types-v2.1.0) - 2024-07-31 283 | 284 | ### Added 285 | - Add From for Rfc3339 wrapper 286 | 287 | ## [2.0.0](https://github.com/bearcove/merde_json/releases/tag/merde_json_types-v2.0.0) - 2024-07-31 288 | 289 | ### Added 290 | - Introduce merde_json_types 291 | -------------------------------------------------------------------------------- /merde_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "merde_core" 4 | version = "10.0.6" 5 | authors = ["Amos Wenger "] 6 | description = "Base types for merde" 7 | license = "Apache-2.0 OR MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/bearcove/merde" 10 | keywords = ["merde", "serialization", "deserialization"] 11 | categories = ["encoding", "parser-implementations"] 12 | rust-version = "1.83" 13 | 14 | [dependencies] 15 | compact_str = { version = "0.8.1" } 16 | compact_bytes = { version = "0.1.3" } 17 | ordered-float = "5.0.0" 18 | rusqlite = { version = "0.35.0", optional = true } 19 | serde = { version = "1", optional = true } 20 | time = { version = "0.3.41", optional = true, features = [ 21 | "parsing", 22 | "formatting", 23 | ] } 24 | camino = { version = "1", optional = true } 25 | 26 | [features] 27 | default = [] 28 | full = [ 29 | # (1 per line) 30 | "serde", 31 | "rusqlite", 32 | "time", 33 | ] 34 | # Add `serde` implementations for merde_core types 35 | serde = ["dep:serde", "compact_str/serde"] 36 | # Add `merde` implementations for types of the `time` crate 37 | time = ["dep:time"] 38 | # Add `merde` implementations for types of the `rusqlite` crate 39 | rusqlite = ["dep:rusqlite"] 40 | camino = ["dep:camino"] 41 | 42 | [dev-dependencies] 43 | insta = "1.42.2" 44 | trybuild = "1.0.104" 45 | time = { version = "0.3.41", features = ["macros"] } 46 | -------------------------------------------------------------------------------- /merde_core/README.md: -------------------------------------------------------------------------------- 1 | [![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) 2 | [![crates.io](https://img.shields.io/crates/v/merde_core.svg)](https://crates.io/crates/merde_core) 3 | [![docs.rs](https://docs.rs/merde_core/badge.svg)](https://docs.rs/merde_core) 4 | 5 | # merde_core 6 | 7 | ![The merde logo: a glorious poop floating above a pair of hands](https://github.com/user-attachments/assets/763d60e0-5101-48af-bc72-f96f516a5d0f) 8 | 9 | _Logo by [MisiasArt](https://misiasart.com)_ 10 | 11 | The `merde` family of crates aims to provide a lighter, simpler, and 12 | build-time-friendly alternative to `serde`. 13 | 14 | This "core" crate provides core types like `Value`, `Array`, `Map`, 15 | and `CowStr<'s>` (a copy-on-write string type that also leverages 16 | [compact_str](https://crates.io/crates/compact_str)'s small string 17 | optimization), and traits like `Deserialize` and `IntoStatic`. 18 | 19 | Crates that provide support for formats (like [merde_json](https://crates.io/crates/merde_json)), depend only on the "core" crate. 20 | 21 | The umbrella crate [merde](https://crates.io/crates/merde) re-exports core's types, along 22 | with a `derive!` macro which lets you implement `Serialize`, `Deserialize`, `IntoStatic`, 23 | on structs, enums, etc. 24 | -------------------------------------------------------------------------------- /merde_core/src/array.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::{value::Value, IntoStatic}; 4 | 5 | /// An array of [`Value`] items 6 | #[derive(PartialEq, Eq, Hash, Clone)] 7 | #[repr(transparent)] 8 | pub struct Array<'s>(pub Vec>); 9 | 10 | impl<'s> Array<'s> { 11 | pub fn new() -> Self { 12 | Array(Vec::new()) 13 | } 14 | 15 | pub fn with_capacity(capacity: usize) -> Self { 16 | Array(Vec::with_capacity(capacity)) 17 | } 18 | 19 | pub fn into_inner(self) -> Vec> { 20 | self.0 21 | } 22 | } 23 | 24 | impl std::fmt::Debug for Array<'_> { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | self.0.fmt(f) 27 | } 28 | } 29 | 30 | impl IntoStatic for Array<'_> { 31 | type Output = Array<'static>; 32 | 33 | #[inline(always)] 34 | fn into_static(self) -> ::Output { 35 | Array(self.0.into_iter().map(|v| v.into_static()).collect()) 36 | } 37 | } 38 | 39 | impl<'s> IntoIterator for Array<'s> { 40 | type Item = Value<'s>; 41 | type IntoIter = std::vec::IntoIter; 42 | 43 | fn into_iter(self) -> Self::IntoIter { 44 | self.0.into_iter() 45 | } 46 | } 47 | 48 | impl Default for Array<'_> { 49 | fn default() -> Self { 50 | Self::new() 51 | } 52 | } 53 | 54 | impl<'s> From>> for Array<'s> { 55 | fn from(v: Vec>) -> Self { 56 | Array(v) 57 | } 58 | } 59 | 60 | impl<'s> Deref for Array<'s> { 61 | type Target = Vec>; 62 | 63 | fn deref(&self) -> &Self::Target { 64 | &self.0 65 | } 66 | } 67 | 68 | impl DerefMut for Array<'_> { 69 | fn deref_mut(&mut self) -> &mut Self::Target { 70 | &mut self.0 71 | } 72 | } 73 | 74 | impl<'s> Array<'s> { 75 | /// Pushes a value onto the back of the array. 76 | pub fn with(mut self, value: impl Into>) -> Self { 77 | self.push(value.into()); 78 | self 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /merde_core/src/covariance_proofs.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{CowStr, Event, MerdeError}; 4 | 5 | fn _assert_cow_str_covariant<'s>(cs: CowStr<'static>) -> CowStr<'s> { 6 | cs 7 | } 8 | 9 | fn _assert_event_covariant<'s>(e: Event<'static>) -> Event<'s> { 10 | e 11 | } 12 | 13 | fn _assert_merde_error_covariant<'s>(me: MerdeError<'static>) -> MerdeError<'s> { 14 | me 15 | } 16 | 17 | fn _assert_event_result_covariant<'s>( 18 | er: Result, MerdeError<'static>>, 19 | ) -> Result, MerdeError<'s>> { 20 | er 21 | } 22 | 23 | #[allow(clippy::manual_async_fn)] 24 | fn _assert_future_event_covariant<'s>( 25 | f: impl Future, MerdeError<'static>>> + 'static, 26 | ) -> impl Future, MerdeError<'s>>> { 27 | // see 28 | #[allow(clippy::redundant_async_block)] 29 | async move { 30 | f.await 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /merde_core/src/cowbytes.rs: -------------------------------------------------------------------------------- 1 | use compact_bytes::CompactBytes; 2 | use std::{ 3 | borrow::Cow, 4 | fmt, 5 | hash::{Hash, Hasher}, 6 | ops::Deref, 7 | }; 8 | 9 | use crate::IntoStatic; 10 | 11 | /// A copy-on-write bytes type that uses [`CompactBytes`] for 12 | /// the "owned" variant. 13 | #[derive(Clone)] 14 | pub enum CowBytes<'a> { 15 | Borrowed(&'a [u8]), 16 | Owned(CompactBytes), 17 | } 18 | 19 | impl<'a> CowBytes<'a> { 20 | pub fn new(bytes: &'a [u8]) -> Self { 21 | CowBytes::Borrowed(bytes) 22 | } 23 | 24 | pub fn into_owned(self) -> Vec { 25 | match self { 26 | CowBytes::Borrowed(b) => b.to_vec(), 27 | CowBytes::Owned(b) => b.to_vec(), 28 | } 29 | } 30 | } 31 | 32 | impl AsRef<[u8]> for CowBytes<'_> { 33 | fn as_ref(&self) -> &[u8] { 34 | match self { 35 | CowBytes::Borrowed(b) => b, 36 | CowBytes::Owned(b) => b.as_ref(), 37 | } 38 | } 39 | } 40 | 41 | impl Deref for CowBytes<'_> { 42 | type Target = [u8]; 43 | 44 | fn deref(&self) -> &Self::Target { 45 | self.as_ref() 46 | } 47 | } 48 | 49 | impl<'a> From<&'a [u8]> for CowBytes<'a> { 50 | fn from(b: &'a [u8]) -> Self { 51 | CowBytes::Borrowed(b) 52 | } 53 | } 54 | 55 | impl From> for CowBytes<'_> { 56 | fn from(v: Vec) -> Self { 57 | CowBytes::Owned(CompactBytes::from(v)) 58 | } 59 | } 60 | 61 | impl<'a> From> for CowBytes<'a> { 62 | fn from(cow: Cow<'a, [u8]>) -> Self { 63 | match cow { 64 | Cow::Borrowed(b) => CowBytes::Borrowed(b), 65 | Cow::Owned(v) => v.into(), 66 | } 67 | } 68 | } 69 | 70 | impl<'a> PartialEq> for CowBytes<'_> { 71 | fn eq(&self, other: &CowBytes<'a>) -> bool { 72 | self.deref() == other.deref() 73 | } 74 | } 75 | 76 | impl PartialEq<[u8]> for CowBytes<'_> { 77 | fn eq(&self, other: &[u8]) -> bool { 78 | self.deref() == other 79 | } 80 | } 81 | 82 | impl PartialEq> for [u8] { 83 | fn eq(&self, other: &CowBytes<'_>) -> bool { 84 | self == other.deref() 85 | } 86 | } 87 | 88 | impl Eq for CowBytes<'_> {} 89 | 90 | impl Hash for CowBytes<'_> { 91 | fn hash(&self, state: &mut H) { 92 | self.deref().hash(state) 93 | } 94 | } 95 | 96 | impl fmt::Debug for CowBytes<'_> { 97 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 98 | fmt::Debug::fmt(self.deref(), f) 99 | } 100 | } 101 | 102 | impl IntoStatic for CowBytes<'_> { 103 | type Output = CowBytes<'static>; 104 | 105 | fn into_static(self) -> Self::Output { 106 | match self { 107 | CowBytes::Borrowed(b) => CowBytes::Owned(CompactBytes::new(b)), 108 | CowBytes::Owned(b) => CowBytes::Owned(b), 109 | } 110 | } 111 | } 112 | 113 | #[cfg(feature = "serde")] 114 | mod serde_impls { 115 | use super::*; 116 | use serde::{Deserialize, Serialize}; 117 | 118 | impl Serialize for CowBytes<'_> { 119 | fn serialize(&self, serializer: S) -> Result 120 | where 121 | S: serde::Serializer, 122 | { 123 | serializer.serialize_bytes(self) 124 | } 125 | } 126 | 127 | impl<'de> Deserialize<'de> for CowBytes<'_> { 128 | fn deserialize(deserializer: D) -> Result 129 | where 130 | D: serde::Deserializer<'de>, 131 | { 132 | let bytes = Vec::::deserialize(deserializer)?; 133 | Ok(CowBytes::from(bytes)) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /merde_core/src/cowstr.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | fmt, 4 | hash::{Hash, Hasher}, 5 | ops::Deref, 6 | }; 7 | 8 | use compact_str::CompactString; 9 | 10 | use crate::IntoStatic; 11 | 12 | /// A copy-on-write string type that uses [`CompactString`] for 13 | /// the "owned" variant. 14 | /// 15 | /// The standard [`Cow`] type cannot be used, since 16 | /// `::Owned` is `String`, and not `CompactString`. 17 | #[derive(Clone)] 18 | pub enum CowStr<'s> { 19 | Borrowed(&'s str), 20 | Owned(CompactString), 21 | } 22 | 23 | impl CowStr<'static> { 24 | /// Create a new `CowStr` by copying from a `&str` — this might allocate 25 | /// if the `compact_str` feature is disabled, or if the string is longer 26 | /// than `MAX_INLINE_SIZE`. 27 | pub fn copy_from_str(s: &str) -> Self { 28 | Self::Owned(CompactString::from(s)) 29 | } 30 | } 31 | 32 | impl<'s> CowStr<'s> { 33 | #[inline] 34 | pub fn from_utf8(s: &'s [u8]) -> Result { 35 | Ok(Self::Borrowed(std::str::from_utf8(s)?)) 36 | } 37 | 38 | #[inline] 39 | pub fn from_utf8_owned(s: Vec) -> Result { 40 | Ok(Self::Owned(CompactString::from_utf8(s)?)) 41 | } 42 | 43 | #[inline] 44 | pub fn from_utf8_lossy(s: &'s [u8]) -> Self { 45 | Self::Owned(CompactString::from_utf8_lossy(s)) 46 | } 47 | 48 | /// # Safety 49 | /// 50 | /// This function is unsafe because it does not check that the bytes are valid UTF-8. 51 | #[inline] 52 | pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self { 53 | Self::Owned(CompactString::from_utf8_unchecked(s)) 54 | } 55 | } 56 | 57 | impl AsRef for CowStr<'_> { 58 | #[inline] 59 | fn as_ref(&self) -> &str { 60 | match self { 61 | CowStr::Borrowed(s) => s, 62 | CowStr::Owned(s) => s.as_str(), 63 | } 64 | } 65 | } 66 | 67 | impl Deref for CowStr<'_> { 68 | type Target = str; 69 | 70 | #[inline] 71 | fn deref(&self) -> &Self::Target { 72 | match self { 73 | CowStr::Borrowed(s) => s, 74 | CowStr::Owned(s) => s.as_str(), 75 | } 76 | } 77 | } 78 | 79 | impl<'a> From> for CowStr<'a> { 80 | #[inline] 81 | fn from(s: Cow<'a, str>) -> Self { 82 | match s { 83 | Cow::Borrowed(s) => CowStr::Borrowed(s), 84 | #[allow(clippy::useless_conversion)] 85 | Cow::Owned(s) => CowStr::Owned(s.into()), 86 | } 87 | } 88 | } 89 | 90 | impl<'s> From<&'s str> for CowStr<'s> { 91 | #[inline] 92 | fn from(s: &'s str) -> Self { 93 | CowStr::Borrowed(s) 94 | } 95 | } 96 | 97 | impl From for CowStr<'_> { 98 | #[inline] 99 | fn from(s: String) -> Self { 100 | #[allow(clippy::useless_conversion)] 101 | CowStr::Owned(s.into()) 102 | } 103 | } 104 | 105 | impl From> for CowStr<'_> { 106 | #[inline] 107 | fn from(s: Box) -> Self { 108 | CowStr::Owned(s.into()) 109 | } 110 | } 111 | 112 | impl<'s> From<&'s String> for CowStr<'s> { 113 | #[inline] 114 | fn from(s: &'s String) -> Self { 115 | CowStr::Borrowed(s.as_str()) 116 | } 117 | } 118 | 119 | impl From> for String { 120 | #[inline] 121 | fn from(s: CowStr<'_>) -> Self { 122 | match s { 123 | CowStr::Borrowed(s) => s.into(), 124 | #[allow(clippy::useless_conversion)] 125 | CowStr::Owned(s) => s.into(), 126 | } 127 | } 128 | } 129 | 130 | impl From> for Box { 131 | #[inline] 132 | fn from(s: CowStr<'_>) -> Self { 133 | match s { 134 | CowStr::Borrowed(s) => s.into(), 135 | CowStr::Owned(s) => s.into(), 136 | } 137 | } 138 | } 139 | 140 | impl<'a> PartialEq> for CowStr<'_> { 141 | #[inline] 142 | fn eq(&self, other: &CowStr<'a>) -> bool { 143 | self.deref() == other.deref() 144 | } 145 | } 146 | 147 | impl PartialEq<&str> for CowStr<'_> { 148 | #[inline] 149 | fn eq(&self, other: &&str) -> bool { 150 | self.deref() == *other 151 | } 152 | } 153 | 154 | impl PartialEq> for &str { 155 | #[inline] 156 | fn eq(&self, other: &CowStr<'_>) -> bool { 157 | *self == other.deref() 158 | } 159 | } 160 | 161 | impl PartialEq for CowStr<'_> { 162 | #[inline] 163 | fn eq(&self, other: &String) -> bool { 164 | self.deref() == other.as_str() 165 | } 166 | } 167 | 168 | impl PartialEq> for String { 169 | #[inline] 170 | fn eq(&self, other: &CowStr<'_>) -> bool { 171 | self.as_str() == other.deref() 172 | } 173 | } 174 | 175 | impl Eq for CowStr<'_> {} 176 | 177 | impl Hash for CowStr<'_> { 178 | #[inline] 179 | fn hash(&self, state: &mut H) { 180 | self.deref().hash(state) 181 | } 182 | } 183 | 184 | impl fmt::Debug for CowStr<'_> { 185 | #[inline] 186 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 187 | self.deref().fmt(f) 188 | } 189 | } 190 | 191 | impl fmt::Display for CowStr<'_> { 192 | #[inline] 193 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 194 | self.deref().fmt(f) 195 | } 196 | } 197 | 198 | impl IntoStatic for CowStr<'_> { 199 | type Output = CowStr<'static>; 200 | 201 | #[inline] 202 | fn into_static(self) -> Self::Output { 203 | match self { 204 | CowStr::Borrowed(s) => CowStr::Owned((*s).into()), 205 | CowStr::Owned(s) => CowStr::Owned(s), 206 | } 207 | } 208 | } 209 | 210 | #[cfg(feature = "serde")] 211 | mod serde_impls { 212 | use super::*; 213 | 214 | use serde::{Deserialize, Serialize}; 215 | 216 | impl Serialize for CowStr<'_> { 217 | #[inline] 218 | fn serialize(&self, serializer: S) -> Result 219 | where 220 | S: serde::Serializer, 221 | { 222 | serializer.serialize_str(self) 223 | } 224 | } 225 | 226 | impl<'de: 'a, 'a> Deserialize<'de> for CowStr<'a> { 227 | #[inline] 228 | fn deserialize(deserializer: D) -> Result, D::Error> 229 | where 230 | D: serde::Deserializer<'de>, 231 | { 232 | struct CowStrVisitor; 233 | 234 | impl<'de> serde::de::Visitor<'de> for CowStrVisitor { 235 | type Value = CowStr<'de>; 236 | 237 | #[inline] 238 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 239 | write!(formatter, "a string") 240 | } 241 | 242 | #[inline] 243 | fn visit_str(self, v: &str) -> Result 244 | where 245 | E: serde::de::Error, 246 | { 247 | Ok(CowStr::copy_from_str(v)) 248 | } 249 | 250 | #[inline] 251 | fn visit_borrowed_str(self, v: &'de str) -> Result 252 | where 253 | E: serde::de::Error, 254 | { 255 | Ok(CowStr::Borrowed(v)) 256 | } 257 | 258 | #[inline] 259 | fn visit_string(self, v: String) -> Result 260 | where 261 | E: serde::de::Error, 262 | { 263 | Ok(v.into()) 264 | } 265 | } 266 | 267 | deserializer.deserialize_str(CowStrVisitor) 268 | } 269 | } 270 | } 271 | 272 | #[cfg(feature = "rusqlite")] 273 | mod rusqlite_impls { 274 | use super::*; 275 | use rusqlite::{types::FromSql, types::FromSqlError, types::ToSql, Result as RusqliteResult}; 276 | 277 | impl ToSql for CowStr<'_> { 278 | #[inline] 279 | fn to_sql(&self) -> RusqliteResult> { 280 | Ok(rusqlite::types::ToSqlOutput::Borrowed(self.as_ref().into())) 281 | } 282 | } 283 | 284 | impl FromSql for CowStr<'_> { 285 | #[inline] 286 | fn column_result(value: rusqlite::types::ValueRef<'_>) -> Result { 287 | match value { 288 | rusqlite::types::ValueRef::Text(s) => Ok(CowStr::from_utf8(s) 289 | .map_err(|e| FromSqlError::Other(Box::new(e)))? 290 | .into_static()), 291 | _ => Err(FromSqlError::InvalidType), 292 | } 293 | } 294 | } 295 | } 296 | 297 | #[cfg(test)] 298 | mod tests { 299 | use super::*; 300 | 301 | #[test] 302 | fn test_partialeq_with_str() { 303 | let cow_str1 = CowStr::Borrowed("hello"); 304 | let cow_str2 = CowStr::Borrowed("hello"); 305 | let cow_str3 = CowStr::Borrowed("world"); 306 | 307 | assert_eq!(cow_str1, "hello"); 308 | assert_eq!("hello", cow_str1); 309 | assert_eq!(cow_str1, cow_str2); 310 | assert_ne!(cow_str1, "world"); 311 | assert_ne!("world", cow_str1); 312 | assert_ne!(cow_str1, cow_str3); 313 | } 314 | 315 | #[cfg(feature = "rusqlite")] 316 | #[test] 317 | fn test_rusqlite_integration() -> Result<(), Box> { 318 | use rusqlite::Connection; 319 | 320 | // Create an in-memory database 321 | let conn = Connection::open_in_memory()?; 322 | 323 | // Create a table 324 | conn.execute( 325 | "CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)", 326 | [], 327 | )?; 328 | 329 | // Insert a CowStr value 330 | let cow_str = CowStr::from("Hello, Rusqlite!"); 331 | conn.execute("INSERT INTO test_table (value) VALUES (?1)", [&cow_str])?; 332 | 333 | // Retrieve the value 334 | let mut stmt = conn.prepare("SELECT value FROM test_table")?; 335 | let mut rows = stmt.query([])?; 336 | 337 | if let Some(row) = rows.next()? { 338 | let retrieved: CowStr = row.get(0)?; 339 | assert_eq!(retrieved, "Hello, Rusqlite!"); 340 | } else { 341 | panic!("No rows returned"); 342 | } 343 | 344 | Ok(()) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /merde_core/src/deserialize/tests.rs: -------------------------------------------------------------------------------- 1 | use super::FieldSlot; 2 | 3 | #[test] 4 | fn test_fieldslot_no_assign() { 5 | let mut option: Option = None; 6 | 7 | { 8 | let slot = FieldSlot::new(&mut option); 9 | // let it drop 10 | let _ = slot; 11 | } 12 | 13 | assert!(option.is_none()); 14 | } 15 | 16 | #[test] 17 | fn test_fieldslot_with_assign() { 18 | let mut option: Option = None; 19 | 20 | { 21 | let slot = FieldSlot::new(&mut option); 22 | slot.fill::(42); 23 | } 24 | 25 | assert_eq!(option, Some(42)); 26 | } 27 | 28 | #[test] 29 | #[should_panic(expected = "tried to assign")] 30 | fn test_fieldslot_with_assign_mismatched_type() { 31 | let mut option: Option = None; 32 | 33 | let slot = FieldSlot::new(&mut option); 34 | slot.fill::(42); 35 | } 36 | 37 | #[test] 38 | fn ui() { 39 | let t = trybuild::TestCases::new(); 40 | t.compile_fail("tests/ui/*.rs"); 41 | } 42 | -------------------------------------------------------------------------------- /merde_core/src/error.rs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------- 2 | // Error Handling and Field Type 3 | // ------------------------------------------------------------------------- 4 | 5 | use crate::{CowStr, EventType, IntoStatic, Value}; 6 | 7 | /// A content-less variant of the [`Value`] enum, used for reporting errors, see [`MerdeError::MismatchedType`]. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 9 | #[non_exhaustive] 10 | pub enum ValueType { 11 | /// The value is `null`. 12 | Null, 13 | 14 | /// The value is `true` or `false`. 15 | Bool, 16 | 17 | /// The value fits in an `i64`. 18 | I64, 19 | 20 | /// The value fits in a `u64`. 21 | U64, 22 | 23 | /// The value has decimal places. 24 | Float, 25 | 26 | /// The value is a string. 27 | String, 28 | 29 | /// The value is a byte array. 30 | Bytes, 31 | 32 | /// The value is an array. 33 | Array, 34 | 35 | /// The value is a map (associating keys and values) 36 | Map, 37 | } 38 | 39 | /// A grab-bag of errors that can occur when deserializing. 40 | /// This isn't super clean, not my proudest moment. 41 | #[derive(Debug)] 42 | #[non_exhaustive] 43 | pub enum MerdeError<'s> { 44 | /// We expected a certain type but got a different one. 45 | MismatchedType { 46 | /// The expected type. 47 | expected: ValueType, 48 | 49 | /// The type we got. 50 | found: ValueType, 51 | }, 52 | 53 | /// We expected an object to have a certain property, but it was missing. 54 | MissingProperty(CowStr<'s>), 55 | 56 | /// We tried to access an array index that was out of bounds. 57 | IndexOutOfBounds { 58 | /// The index we tried to access. 59 | index: usize, 60 | /// The length of the array. 61 | len: usize, 62 | }, 63 | 64 | /// We encountered a property that we didn't expect. 65 | UnknownProperty(CowStr<'s>), 66 | 67 | /// For example, we had a `u8` field but the JSON value was bigger than `u8::MAX`. 68 | OutOfRange, 69 | 70 | /// A field was missing (but we don't know its name) 71 | MissingValue, 72 | 73 | /// While calling out to [`FromStr::from_str`](std::str::FromStr::from_str) to build a [`HashMap`](std::collections::HashMap), we got an error. 74 | InvalidKey { 75 | key: CowStr<'s>, 76 | type_name: &'static str, 77 | }, 78 | 79 | /// While parsing a datetime, we got an error 80 | InvalidDateTimeValue, 81 | 82 | UnexpectedEvent { 83 | got: EventType, 84 | expected: &'static [EventType], 85 | help: Option, 86 | }, 87 | 88 | /// An I/O error occurred. 89 | Io(std::io::Error), 90 | 91 | /// An Utf8 error 92 | Utf8Error(std::str::Utf8Error), 93 | 94 | /// Error occured while parsing a string, we can format 95 | /// a nice error message with the source string, highlighted etc. 96 | StringParsingError { 97 | format: &'static str, 98 | source: CowStr<'s>, 99 | index: usize, 100 | message: String, 101 | }, 102 | 103 | /// Error occured while parsing binary input, let's not show source 104 | /// for now. 105 | BinaryParsingError { 106 | format: &'static str, 107 | message: String, 108 | }, 109 | 110 | /// `.put_back()` was called more than once 111 | PutBackCalledTwice, 112 | } 113 | 114 | impl MerdeError<'_> { 115 | pub fn eof() -> Self { 116 | MerdeError::Io(std::io::Error::new( 117 | std::io::ErrorKind::UnexpectedEof, 118 | "eof", 119 | )) 120 | } 121 | } 122 | 123 | impl IntoStatic for MerdeError<'_> { 124 | type Output = MerdeError<'static>; 125 | 126 | fn into_static(self) -> MerdeError<'static> { 127 | match self { 128 | MerdeError::MismatchedType { expected, found } => { 129 | MerdeError::MismatchedType { expected, found } 130 | } 131 | MerdeError::MissingProperty(prop) => MerdeError::MissingProperty(prop.into_static()), 132 | MerdeError::IndexOutOfBounds { index, len } => { 133 | MerdeError::IndexOutOfBounds { index, len } 134 | } 135 | MerdeError::UnknownProperty(prop) => MerdeError::UnknownProperty(prop.into_static()), 136 | MerdeError::OutOfRange => MerdeError::OutOfRange, 137 | MerdeError::MissingValue => MerdeError::MissingValue, 138 | MerdeError::InvalidKey { key, type_name } => MerdeError::InvalidKey { 139 | key: key.into_static(), 140 | type_name, 141 | }, 142 | MerdeError::InvalidDateTimeValue => MerdeError::InvalidDateTimeValue, 143 | MerdeError::Io(e) => MerdeError::Io(e), 144 | MerdeError::UnexpectedEvent { 145 | got, 146 | expected, 147 | help: additional, 148 | } => MerdeError::UnexpectedEvent { 149 | got, 150 | expected, 151 | help: additional, 152 | }, 153 | MerdeError::Utf8Error(e) => MerdeError::Utf8Error(e), 154 | MerdeError::StringParsingError { 155 | format, 156 | source, 157 | index, 158 | message, 159 | } => MerdeError::StringParsingError { 160 | format, 161 | source: source.into_static(), 162 | index, 163 | message, 164 | }, 165 | MerdeError::PutBackCalledTwice => MerdeError::PutBackCalledTwice, 166 | MerdeError::BinaryParsingError { format, message } => { 167 | MerdeError::BinaryParsingError { format, message } 168 | } 169 | } 170 | } 171 | } 172 | 173 | impl From for MerdeError<'_> { 174 | fn from(e: std::io::Error) -> Self { 175 | MerdeError::Io(e) 176 | } 177 | } 178 | 179 | impl From for MerdeError<'_> { 180 | fn from(e: std::str::Utf8Error) -> Self { 181 | MerdeError::Utf8Error(e) 182 | } 183 | } 184 | 185 | impl std::fmt::Display for MerdeError<'_> { 186 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 187 | match self { 188 | MerdeError::MismatchedType { expected, found } => { 189 | write!(f, "Expected {:?}, found {:?}", expected, found) 190 | } 191 | MerdeError::MissingProperty(prop) => { 192 | write!(f, "Missing property: {}", prop) 193 | } 194 | MerdeError::IndexOutOfBounds { index, len: length } => { 195 | write!( 196 | f, 197 | "Index out of bounds: index {} is not valid for length {}", 198 | index, length 199 | ) 200 | } 201 | MerdeError::UnknownProperty(prop) => { 202 | write!(f, "Unknown property: {}", prop) 203 | } 204 | MerdeError::OutOfRange => { 205 | write!(f, "Value is out of range") 206 | } 207 | MerdeError::MissingValue => { 208 | write!(f, "Missing value") 209 | } 210 | MerdeError::InvalidKey { key, type_name } => { 211 | write!( 212 | f, 213 | "Invalid key: couldn't convert {:?} to type {}", 214 | key, type_name 215 | ) 216 | } 217 | MerdeError::InvalidDateTimeValue => { 218 | write!(f, "Invalid date/time value") 219 | } 220 | MerdeError::Io(e) => { 221 | write!(f, "I/O error: {}", e) 222 | } 223 | MerdeError::UnexpectedEvent { 224 | got, 225 | expected, 226 | help, 227 | } => { 228 | write!( 229 | f, 230 | "Unexpected event: got {got:?}, expected one of {expected:?}" 231 | )?; 232 | if let Some(help) = help.as_ref() { 233 | write!(f, " {help}")?; 234 | } 235 | Ok(()) 236 | } 237 | MerdeError::Utf8Error(e) => { 238 | write!(f, "UTF-8 Error: {}", e) 239 | } 240 | MerdeError::StringParsingError { 241 | format, 242 | source, 243 | index, 244 | message, 245 | } => { 246 | let (format, source, index) = (*format, source as &str, *index); 247 | 248 | writeln!(f, "{format} parsing error: \x1b[31m{message}\x1b[0m",)?; 249 | let context_start = index.saturating_sub(20); 250 | let context_end = (index + 20).min(source.len()); 251 | let context = &source[context_start..context_end]; 252 | 253 | write!(f, "Source: ")?; 254 | for (i, c) in context.char_indices() { 255 | if i + context_start == index { 256 | write!(f, "\x1b[48;2;255;200;200m\x1b[97m{}\x1b[0m", c)?; 257 | } else { 258 | write!(f, "\x1b[48;2;200;200;255m\x1b[97m{}\x1b[0m", c)?; 259 | } 260 | } 261 | writeln!(f)?; 262 | Ok(()) 263 | } 264 | MerdeError::PutBackCalledTwice => { 265 | write!(f, "put_back() was called twice") 266 | } 267 | MerdeError::BinaryParsingError { format, message } => { 268 | write!(f, "{format} parsing error: {message}") 269 | } 270 | } 271 | } 272 | } 273 | 274 | impl std::error::Error for MerdeError<'_> {} 275 | 276 | impl Value<'_> { 277 | /// Returns the [ValueType] for a given [Value]. 278 | pub fn value_type(&self) -> ValueType { 279 | match self { 280 | Value::Null => ValueType::Null, 281 | Value::Bool(_) => ValueType::Bool, 282 | Value::I64(_) => ValueType::I64, 283 | Value::U64(_) => ValueType::U64, 284 | Value::Float(_) => ValueType::Float, 285 | Value::Str(_) => ValueType::String, 286 | Value::Bytes(_) => ValueType::Bytes, 287 | Value::Array(_) => ValueType::Array, 288 | Value::Map(_) => ValueType::Map, 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /merde_core/src/event.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use crate::{CowBytes, CowStr, MerdeError}; 4 | 5 | #[derive(Debug)] 6 | pub enum Event<'s> { 7 | I64(i64), 8 | U64(u64), 9 | F64(f64), 10 | Str(CowStr<'s>), 11 | Bytes(CowBytes<'s>), 12 | Bool(bool), 13 | Null, 14 | MapStart(MapStart), 15 | MapEnd, 16 | ArrayStart(ArrayStart), 17 | ArrayEnd, 18 | } 19 | 20 | macro_rules! impl_from_for_event { 21 | ($ty:ty => $variant:ident, $($rest:tt)*) => { 22 | impl_from_for_event!($ty => $variant); 23 | impl_from_for_event!($($rest)*); 24 | }; 25 | 26 | ($ty:ty => $variant:ident) => { 27 | impl From<$ty> for Event<'_> { 28 | fn from(v: $ty) -> Self { 29 | Event::$variant(v.into()) 30 | } 31 | } 32 | }; 33 | 34 | (,) => {}; 35 | () => {}; 36 | } 37 | 38 | impl_from_for_event! { 39 | // signed 40 | i8 => I64, 41 | i16 => I64, 42 | i32 => I64, 43 | i64 => I64, 44 | // unsigned 45 | u8 => U64, 46 | u16 => U64, 47 | u32 => U64, 48 | u64 => U64, 49 | // floats 50 | f32 => F64, 51 | f64 => F64, 52 | // misc. 53 | bool => Bool, 54 | } 55 | 56 | impl From for Event<'_> { 57 | fn from(v: isize) -> Self { 58 | Event::I64(i64::try_from(v).unwrap()) 59 | } 60 | } 61 | 62 | impl From for Event<'_> { 63 | fn from(v: usize) -> Self { 64 | Event::U64(u64::try_from(v).unwrap()) 65 | } 66 | } 67 | 68 | impl<'s> From<&'s str> for Event<'s> { 69 | fn from(v: &'s str) -> Self { 70 | Event::Str(v.into()) 71 | } 72 | } 73 | 74 | impl From for Event<'_> { 75 | fn from(v: String) -> Self { 76 | Event::Str(v.into()) 77 | } 78 | } 79 | 80 | impl<'s> From> for Event<'s> { 81 | fn from(v: Cow<'s, str>) -> Self { 82 | Event::Str(v.into()) 83 | } 84 | } 85 | 86 | impl<'s> From<&'s [u8]> for Event<'s> { 87 | fn from(b: &'s [u8]) -> Self { 88 | Event::Bytes(b.into()) 89 | } 90 | } 91 | 92 | impl From> for Event<'_> { 93 | fn from(v: Vec) -> Self { 94 | Event::Bytes(v.into()) 95 | } 96 | } 97 | 98 | impl<'s> From> for Event<'s> { 99 | fn from(b: CowBytes<'s>) -> Self { 100 | Event::Bytes(b) 101 | } 102 | } 103 | 104 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 105 | pub enum EventType { 106 | I64, 107 | U64, 108 | Float, 109 | Str, 110 | Bytes, 111 | Bool, 112 | Null, 113 | MapStart, 114 | MapEnd, 115 | ArrayStart, 116 | ArrayEnd, 117 | } 118 | 119 | impl From<&Event<'_>> for EventType { 120 | fn from(event: &Event<'_>) -> Self { 121 | match event { 122 | Event::I64(_) => EventType::I64, 123 | Event::U64(_) => EventType::U64, 124 | Event::F64(_) => EventType::Float, 125 | Event::Str(_) => EventType::Str, 126 | Event::Bytes(_) => EventType::Bytes, 127 | Event::Bool(_) => EventType::Bool, 128 | Event::Null => EventType::Null, 129 | Event::MapStart(_) => EventType::MapStart, 130 | Event::MapEnd => EventType::MapEnd, 131 | Event::ArrayStart(_) => EventType::ArrayStart, 132 | Event::ArrayEnd => EventType::ArrayEnd, 133 | } 134 | } 135 | } 136 | 137 | #[derive(Debug)] 138 | pub struct ArrayStart { 139 | pub size_hint: Option, 140 | } 141 | 142 | #[derive(Debug)] 143 | pub struct MapStart { 144 | pub size_hint: Option, 145 | } 146 | 147 | impl<'s> Event<'s> { 148 | pub fn into_i64(self) -> Result> { 149 | match self { 150 | Event::I64(i) => Ok(i), 151 | _ => Err(MerdeError::UnexpectedEvent { 152 | got: EventType::from(&self), 153 | expected: &[EventType::I64], 154 | help: None, 155 | }), 156 | } 157 | } 158 | 159 | pub fn into_u64(self) -> Result> { 160 | match self { 161 | Event::U64(u) => Ok(u), 162 | _ => Err(MerdeError::UnexpectedEvent { 163 | got: EventType::from(&self), 164 | expected: &[EventType::U64], 165 | help: None, 166 | }), 167 | } 168 | } 169 | 170 | pub fn into_f64(self) -> Result> { 171 | match self { 172 | Event::F64(f) => Ok(f), 173 | _ => Err(MerdeError::UnexpectedEvent { 174 | got: EventType::from(&self), 175 | expected: &[EventType::Float], 176 | help: None, 177 | }), 178 | } 179 | } 180 | 181 | pub fn into_str(self) -> Result, MerdeError<'s>> { 182 | match self { 183 | Event::Str(s) => Ok(s), 184 | _ => Err(MerdeError::UnexpectedEvent { 185 | got: EventType::from(&self), 186 | expected: &[EventType::Str], 187 | help: None, 188 | }), 189 | } 190 | } 191 | 192 | pub fn into_bytes(self) -> Result, MerdeError<'s>> { 193 | match self { 194 | Event::Bytes(b) => Ok(b), 195 | _ => Err(MerdeError::UnexpectedEvent { 196 | got: EventType::from(&self), 197 | expected: &[EventType::Bytes], 198 | help: None, 199 | }), 200 | } 201 | } 202 | 203 | pub fn into_bool(self) -> Result> { 204 | match self { 205 | Event::Bool(b) => Ok(b), 206 | _ => Err(MerdeError::UnexpectedEvent { 207 | got: EventType::from(&self), 208 | expected: &[EventType::Bool], 209 | help: None, 210 | }), 211 | } 212 | } 213 | 214 | pub fn into_null(self) -> Result<(), MerdeError<'s>> { 215 | match self { 216 | Event::Null => Ok(()), 217 | _ => Err(MerdeError::UnexpectedEvent { 218 | got: EventType::from(&self), 219 | expected: &[EventType::Null], 220 | help: None, 221 | }), 222 | } 223 | } 224 | 225 | pub fn into_map_start(self) -> Result> { 226 | match self { 227 | Event::MapStart(ms) => Ok(ms), 228 | _ => Err(MerdeError::UnexpectedEvent { 229 | got: EventType::from(&self), 230 | expected: &[EventType::MapStart], 231 | help: None, 232 | }), 233 | } 234 | } 235 | 236 | pub fn into_map_end(self) -> Result<(), MerdeError<'s>> { 237 | match self { 238 | Event::MapEnd => Ok(()), 239 | _ => Err(MerdeError::UnexpectedEvent { 240 | got: EventType::from(&self), 241 | expected: &[EventType::MapEnd], 242 | help: None, 243 | }), 244 | } 245 | } 246 | 247 | pub fn into_array_start(self) -> Result> { 248 | match self { 249 | Event::ArrayStart(array_start) => Ok(array_start), 250 | _ => Err(MerdeError::UnexpectedEvent { 251 | got: EventType::from(&self), 252 | expected: &[EventType::ArrayStart], 253 | help: None, 254 | }), 255 | } 256 | } 257 | 258 | pub fn into_array_end(self) -> Result<(), MerdeError<'s>> { 259 | match self { 260 | Event::ArrayEnd => Ok(()), 261 | _ => Err(MerdeError::UnexpectedEvent { 262 | got: EventType::from(&self), 263 | expected: &[EventType::ArrayEnd], 264 | help: None, 265 | }), 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /merde_core/src/into_static.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashMap; 3 | use std::collections::HashSet; 4 | use std::collections::VecDeque; 5 | use std::hash::BuildHasher; 6 | use std::hash::Hash; 7 | use std::sync::Arc; 8 | 9 | use crate::Event; 10 | 11 | /// Allow turning a value into an "owned" variant, which can then be 12 | /// returned, moved, etc. 13 | /// 14 | /// This usually involves allocating buffers for `Cow<'a, str>`, etc. 15 | pub trait IntoStatic: Sized { 16 | /// The "owned" variant of the type. For `Cow<'a, str>`, this is `Cow<'static, str>`, for example. 17 | type Output: 'static; 18 | 19 | /// Turns the value into an "owned" variant, which can then be returned, moved, etc. 20 | /// 21 | /// This allocates, for all but the most trivial types. 22 | fn into_static(self) -> Self::Output; 23 | } 24 | 25 | impl IntoStatic for Result 26 | where 27 | T: IntoStatic, 28 | E: IntoStatic, 29 | { 30 | type Output = Result; 31 | 32 | fn into_static(self) -> Self::Output { 33 | match self { 34 | Ok(v) => Ok(v.into_static()), 35 | Err(e) => Err(e.into_static()), 36 | } 37 | } 38 | } 39 | 40 | impl IntoStatic for Cow<'_, T> 41 | where 42 | T: ToOwned + ?Sized + 'static, 43 | { 44 | type Output = Cow<'static, T>; 45 | 46 | #[inline(always)] 47 | fn into_static(self) -> Self::Output { 48 | match self { 49 | Cow::Borrowed(b) => Cow::Owned(b.to_owned()), 50 | Cow::Owned(o) => Cow::Owned(o), 51 | } 52 | } 53 | } 54 | 55 | impl IntoStatic for Event<'_> { 56 | type Output = Event<'static>; 57 | 58 | fn into_static(self) -> Self::Output { 59 | match self { 60 | Event::I64(v) => Event::I64(v), 61 | Event::U64(v) => Event::U64(v), 62 | Event::F64(v) => Event::F64(v), 63 | Event::Str(v) => Event::Str(v.into_static()), 64 | Event::Bytes(v) => Event::Bytes(v.into_static()), 65 | Event::Bool(v) => Event::Bool(v), 66 | Event::Null => Event::Null, 67 | Event::MapStart(v) => Event::MapStart(v), 68 | Event::MapEnd => Event::MapEnd, 69 | Event::ArrayStart(v) => Event::ArrayStart(v), 70 | Event::ArrayEnd => Event::ArrayEnd, 71 | } 72 | } 73 | } 74 | 75 | macro_rules! impl_into_static_passthru { 76 | ($($ty:ty),+) => { 77 | $( 78 | impl IntoStatic for $ty { 79 | type Output = $ty; 80 | 81 | #[inline(always)] 82 | fn into_static(self) -> Self::Output { 83 | self 84 | } 85 | } 86 | )+ 87 | }; 88 | } 89 | 90 | impl_into_static_passthru!( 91 | String, u128, u64, u32, u16, u8, i128, i64, i32, i16, i8, bool, char, usize, isize, f32, f64 92 | ); 93 | 94 | #[cfg(feature = "camino")] 95 | impl IntoStatic for camino::Utf8PathBuf { 96 | type Output = camino::Utf8PathBuf; 97 | 98 | #[inline(always)] 99 | fn into_static(self) -> Self::Output { 100 | self 101 | } 102 | } 103 | 104 | impl IntoStatic for Box { 105 | type Output = Box; 106 | 107 | fn into_static(self) -> Self::Output { 108 | Box::new((*self).into_static()) 109 | } 110 | } 111 | 112 | impl IntoStatic for Option { 113 | type Output = Option; 114 | 115 | fn into_static(self) -> Self::Output { 116 | self.map(|v| v.into_static()) 117 | } 118 | } 119 | 120 | impl IntoStatic for Vec { 121 | type Output = Vec; 122 | 123 | fn into_static(self) -> Self::Output { 124 | self.into_iter().map(|v| v.into_static()).collect() 125 | } 126 | } 127 | 128 | impl IntoStatic for Arc { 129 | type Output = Arc; 130 | 131 | fn into_static(self) -> Self::Output { 132 | let t: T = (*self).clone(); 133 | Arc::new(t.into_static()) 134 | } 135 | } 136 | 137 | impl IntoStatic for HashMap 138 | where 139 | S: BuildHasher + Default + 'static, 140 | K: IntoStatic + Eq + Hash, 141 | V: IntoStatic, 142 | K::Output: Eq + Hash, 143 | { 144 | type Output = HashMap; 145 | 146 | fn into_static(self) -> Self::Output { 147 | self.into_iter() 148 | .map(|(k, v)| (k.into_static(), v.into_static())) 149 | .collect() 150 | } 151 | } 152 | 153 | impl IntoStatic for HashSet 154 | where 155 | T::Output: Eq + Hash, 156 | { 157 | type Output = HashSet; 158 | 159 | fn into_static(self) -> Self::Output { 160 | self.into_iter().map(|v| v.into_static()).collect() 161 | } 162 | } 163 | 164 | impl IntoStatic for VecDeque { 165 | type Output = VecDeque; 166 | 167 | fn into_static(self) -> Self::Output { 168 | self.into_iter().map(|v| v.into_static()).collect() 169 | } 170 | } 171 | 172 | impl IntoStatic for (T1,) { 173 | type Output = (T1::Output,); 174 | 175 | fn into_static(self) -> Self::Output { 176 | (self.0.into_static(),) 177 | } 178 | } 179 | 180 | impl IntoStatic for (T1, T2) { 181 | type Output = (T1::Output, T2::Output); 182 | 183 | fn into_static(self) -> Self::Output { 184 | (self.0.into_static(), self.1.into_static()) 185 | } 186 | } 187 | 188 | impl IntoStatic for (T1, T2, T3) { 189 | type Output = (T1::Output, T2::Output, T3::Output); 190 | 191 | fn into_static(self) -> Self::Output { 192 | ( 193 | self.0.into_static(), 194 | self.1.into_static(), 195 | self.2.into_static(), 196 | ) 197 | } 198 | } 199 | 200 | impl IntoStatic 201 | for (T1, T2, T3, T4) 202 | { 203 | type Output = (T1::Output, T2::Output, T3::Output, T4::Output); 204 | 205 | fn into_static(self) -> Self::Output { 206 | ( 207 | self.0.into_static(), 208 | self.1.into_static(), 209 | self.2.into_static(), 210 | self.3.into_static(), 211 | ) 212 | } 213 | } 214 | 215 | impl IntoStatic 216 | for (T1, T2, T3, T4, T5) 217 | { 218 | type Output = (T1::Output, T2::Output, T3::Output, T4::Output, T5::Output); 219 | 220 | fn into_static(self) -> Self::Output { 221 | ( 222 | self.0.into_static(), 223 | self.1.into_static(), 224 | self.2.into_static(), 225 | self.3.into_static(), 226 | self.4.into_static(), 227 | ) 228 | } 229 | } 230 | 231 | impl< 232 | T1: IntoStatic, 233 | T2: IntoStatic, 234 | T3: IntoStatic, 235 | T4: IntoStatic, 236 | T5: IntoStatic, 237 | T6: IntoStatic, 238 | > IntoStatic for (T1, T2, T3, T4, T5, T6) 239 | { 240 | type Output = ( 241 | T1::Output, 242 | T2::Output, 243 | T3::Output, 244 | T4::Output, 245 | T5::Output, 246 | T6::Output, 247 | ); 248 | 249 | fn into_static(self) -> Self::Output { 250 | ( 251 | self.0.into_static(), 252 | self.1.into_static(), 253 | self.2.into_static(), 254 | self.3.into_static(), 255 | self.4.into_static(), 256 | self.5.into_static(), 257 | ) 258 | } 259 | } 260 | 261 | impl< 262 | T1: IntoStatic, 263 | T2: IntoStatic, 264 | T3: IntoStatic, 265 | T4: IntoStatic, 266 | T5: IntoStatic, 267 | T6: IntoStatic, 268 | T7: IntoStatic, 269 | > IntoStatic for (T1, T2, T3, T4, T5, T6, T7) 270 | { 271 | type Output = ( 272 | T1::Output, 273 | T2::Output, 274 | T3::Output, 275 | T4::Output, 276 | T5::Output, 277 | T6::Output, 278 | T7::Output, 279 | ); 280 | 281 | fn into_static(self) -> Self::Output { 282 | ( 283 | self.0.into_static(), 284 | self.1.into_static(), 285 | self.2.into_static(), 286 | self.3.into_static(), 287 | self.4.into_static(), 288 | self.5.into_static(), 289 | self.6.into_static(), 290 | ) 291 | } 292 | } 293 | 294 | impl< 295 | T1: IntoStatic, 296 | T2: IntoStatic, 297 | T3: IntoStatic, 298 | T4: IntoStatic, 299 | T5: IntoStatic, 300 | T6: IntoStatic, 301 | T7: IntoStatic, 302 | T8: IntoStatic, 303 | > IntoStatic for (T1, T2, T3, T4, T5, T6, T7, T8) 304 | { 305 | type Output = ( 306 | T1::Output, 307 | T2::Output, 308 | T3::Output, 309 | T4::Output, 310 | T5::Output, 311 | T6::Output, 312 | T7::Output, 313 | T8::Output, 314 | ); 315 | 316 | fn into_static(self) -> Self::Output { 317 | ( 318 | self.0.into_static(), 319 | self.1.into_static(), 320 | self.2.into_static(), 321 | self.3.into_static(), 322 | self.4.into_static(), 323 | self.5.into_static(), 324 | self.6.into_static(), 325 | self.7.into_static(), 326 | ) 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /merde_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod cowstr; 2 | 3 | pub use cowstr::CowStr; 4 | 5 | mod covariance_proofs; 6 | 7 | mod cowbytes; 8 | pub use cowbytes::CowBytes; 9 | 10 | mod array; 11 | pub use array::Array; 12 | 13 | mod map; 14 | pub use map::Map; 15 | 16 | mod error; 17 | pub use error::MerdeError; 18 | pub use error::ValueType; 19 | 20 | mod into_static; 21 | pub use into_static::IntoStatic; 22 | 23 | mod with_lifetime; 24 | pub use with_lifetime::WithLifetime; 25 | 26 | mod value; 27 | pub use value::Value; 28 | 29 | mod metastack; 30 | pub use metastack::{with_metastack_resume_point, MetastackExt}; 31 | 32 | mod event; 33 | pub use event::ArrayStart; 34 | pub use event::Event; 35 | pub use event::EventType; 36 | pub use event::MapStart; 37 | 38 | mod serialize; 39 | pub use serialize::DynSerialize; 40 | pub use serialize::DynSerializer; 41 | pub use serialize::DynSerializerExt; 42 | pub use serialize::Serialize; 43 | pub use serialize::Serializer; 44 | 45 | mod deserialize; 46 | pub use deserialize::DefaultDeserOpinions; 47 | pub use deserialize::DeserOpinions; 48 | pub use deserialize::Deserialize; 49 | pub use deserialize::DeserializeOwned; 50 | pub use deserialize::Deserializer; 51 | pub use deserialize::DynDeserialize; 52 | pub use deserialize::DynDeserializer; 53 | pub use deserialize::DynDeserializerExt; 54 | pub use deserialize::FieldSlot; 55 | 56 | pub mod time; 57 | -------------------------------------------------------------------------------- /merde_core/src/map.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | hash::{Hash, Hasher}, 4 | ops::{Deref, DerefMut}, 5 | }; 6 | 7 | use crate::{value::Value, CowStr, IntoStatic}; 8 | 9 | /// A map, dictionary, object, whatever — with string keys. 10 | #[derive(PartialEq, Eq, Clone)] 11 | #[repr(transparent)] 12 | pub struct Map<'s>(pub HashMap, Value<'s>>); 13 | 14 | impl Hash for Map<'_> { 15 | fn hash(&self, state: &mut H) { 16 | for (k, v) in self.iter() { 17 | k.hash(state); 18 | v.hash(state); 19 | } 20 | } 21 | } 22 | 23 | impl std::fmt::Debug for Map<'_> { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | self.0.fmt(f) 26 | } 27 | } 28 | 29 | impl<'s> Map<'s> { 30 | pub fn new() -> Self { 31 | Map(HashMap::new()) 32 | } 33 | 34 | pub fn with_capacity(capacity: usize) -> Self { 35 | Map(HashMap::with_capacity(capacity)) 36 | } 37 | 38 | pub fn with(mut self, key: impl Into>, value: impl Into>) -> Self { 39 | self.insert(key.into(), value.into()); 40 | self 41 | } 42 | 43 | pub fn into_inner(self) -> HashMap, Value<'s>> { 44 | self.0 45 | } 46 | } 47 | 48 | impl IntoStatic for Map<'_> { 49 | type Output = Map<'static>; 50 | 51 | #[inline(always)] 52 | fn into_static(self) -> ::Output { 53 | Map(self 54 | .into_iter() 55 | .map(|(k, v)| (k.into_static(), v.into_static())) 56 | .collect()) 57 | } 58 | } 59 | 60 | impl<'s> IntoIterator for Map<'s> { 61 | type Item = (CowStr<'s>, Value<'s>); 62 | type IntoIter = std::collections::hash_map::IntoIter, Value<'s>>; 63 | 64 | fn into_iter(self) -> Self::IntoIter { 65 | self.0.into_iter() 66 | } 67 | } 68 | 69 | impl Default for Map<'_> { 70 | fn default() -> Self { 71 | Self::new() 72 | } 73 | } 74 | 75 | impl<'s> From, Value<'s>>> for Map<'s> { 76 | fn from(v: HashMap, Value<'s>>) -> Self { 77 | Map(v) 78 | } 79 | } 80 | 81 | impl<'s> Deref for Map<'s> { 82 | type Target = HashMap, Value<'s>>; 83 | 84 | fn deref(&self) -> &Self::Target { 85 | &self.0 86 | } 87 | } 88 | 89 | impl DerefMut for Map<'_> { 90 | fn deref_mut(&mut self) -> &mut Self::Target { 91 | &mut self.0 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /merde_core/src/serialize/snapshots/merde_core__serialize__tests__serialize.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: merde_core/src/serialize/tests.rs 3 | expression: s.events 4 | --- 5 | [ 6 | MapStart( 7 | MapStart { 8 | size_hint: Some( 9 | 1, 10 | ), 11 | }, 12 | ), 13 | Str( 14 | "foo", 15 | ), 16 | I64( 17 | 42, 18 | ), 19 | MapEnd, 20 | ] 21 | -------------------------------------------------------------------------------- /merde_core/src/serialize/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{DynSerializerExt, Event, IntoStatic, Map, MerdeError, Serializer, Value}; 2 | use insta::assert_debug_snapshot; 3 | 4 | #[test] 5 | fn test_serialize() { 6 | #[derive(Default, Debug)] 7 | struct ToySerializer { 8 | events: Vec>, 9 | } 10 | 11 | impl Serializer for ToySerializer { 12 | fn write<'fut>( 13 | &'fut mut self, 14 | ev: Event<'fut>, 15 | ) -> impl std::future::Future>> + 'fut { 16 | self.events.push(ev.into_static()); 17 | async { Ok(()) } 18 | } 19 | } 20 | 21 | let mut s = ToySerializer::default(); 22 | let value: Value = Map::new().with("foo", Value::from(42)).into(); 23 | s.serialize(&value).unwrap(); 24 | 25 | assert_debug_snapshot!(s.events); 26 | } 27 | -------------------------------------------------------------------------------- /merde_core/src/time.rs: -------------------------------------------------------------------------------- 1 | //! Provides [Rfc3339], a wrapper around [time::OffsetDateTime] that implements 2 | //! [Serialize] and [Deserialize] when the right 3 | //! cargo features are enabled. 4 | 5 | use std::{ 6 | fmt, 7 | ops::{Deref, DerefMut}, 8 | }; 9 | 10 | use crate::WithLifetime; 11 | 12 | /// A wrapper around date-time types that implements `Serialize` and `Deserialize` 13 | /// when the right cargo features are enabled. 14 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | #[repr(transparent)] 16 | pub struct Rfc3339(pub T); 17 | 18 | impl WithLifetime<'_> for Rfc3339 19 | where 20 | T: 'static, 21 | { 22 | type Lifetimed = Self; 23 | } 24 | 25 | impl From for Rfc3339 { 26 | fn from(t: T) -> Self { 27 | Rfc3339(t) 28 | } 29 | } 30 | 31 | impl Deref for Rfc3339 { 32 | type Target = T; 33 | 34 | fn deref(&self) -> &T { 35 | &self.0 36 | } 37 | } 38 | 39 | impl DerefMut for Rfc3339 { 40 | fn deref_mut(&mut self) -> &mut T { 41 | &mut self.0 42 | } 43 | } 44 | 45 | impl fmt::Debug for Rfc3339 46 | where 47 | T: fmt::Debug, 48 | { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | self.0.fmt(f) 51 | } 52 | } 53 | 54 | impl fmt::Display for Rfc3339 55 | where 56 | T: fmt::Display, 57 | { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | self.0.fmt(f) 60 | } 61 | } 62 | 63 | #[cfg(feature = "time")] 64 | pub use time::OffsetDateTime; 65 | 66 | #[cfg(feature = "time")] 67 | mod time_impls { 68 | use std::future::Future; 69 | 70 | use super::*; 71 | 72 | use time::OffsetDateTime; 73 | impl crate::IntoStatic for Rfc3339 { 74 | type Output = Rfc3339; 75 | 76 | fn into_static(self) -> Self::Output { 77 | self 78 | } 79 | } 80 | 81 | impl<'s> crate::Deserialize<'s> for Rfc3339 { 82 | async fn deserialize( 83 | de: &mut dyn crate::DynDeserializer<'s>, 84 | ) -> Result> { 85 | let s = crate::CowStr::deserialize(de).await?; 86 | Ok(Rfc3339( 87 | time::OffsetDateTime::parse( 88 | s.as_ref(), 89 | &time::format_description::well_known::Rfc3339, 90 | ) 91 | .map_err(|_| crate::MerdeError::InvalidDateTimeValue)?, 92 | )) 93 | } 94 | } 95 | 96 | impl crate::Serialize for Rfc3339 { 97 | #[allow(clippy::manual_async_fn)] 98 | fn serialize<'fut>( 99 | &'fut self, 100 | serializer: &'fut mut dyn crate::DynSerializer, 101 | ) -> impl Future>> + 'fut { 102 | async move { 103 | let s = self 104 | .0 105 | .format(&time::format_description::well_known::Rfc3339) 106 | .unwrap(); 107 | serializer 108 | .write(crate::Event::Str(crate::CowStr::Borrowed(&s))) 109 | .await 110 | } 111 | } 112 | } 113 | } 114 | 115 | #[cfg(all(test, feature = "full"))] 116 | mod tests { 117 | use super::*; 118 | use crate::{Deserializer, DynSerializerExt, Event, IntoStatic, MerdeError, Serializer}; 119 | use std::{collections::VecDeque, future::Future}; 120 | use time::macros::datetime; 121 | 122 | #[derive(Debug, Default)] 123 | struct Journal { 124 | events: VecDeque>, 125 | } 126 | 127 | impl Serializer for Journal { 128 | async fn write<'fut>( 129 | &'fut mut self, 130 | event: Event<'fut>, 131 | ) -> Result<(), MerdeError<'static>> { 132 | self.events.push_back(event.into_static()); 133 | Ok(()) 134 | } 135 | } 136 | 137 | impl<'s> Deserializer<'s> for Journal { 138 | // FIXME: that's a workaround for 139 | #[allow(clippy::manual_async_fn)] 140 | fn next(&mut self) -> impl Future, MerdeError<'s>>> + '_ { 141 | async { self.events.pop_front().ok_or_else(MerdeError::eof) } 142 | } 143 | 144 | fn put_back(&mut self, ev: Event<'s>) -> Result<(), MerdeError<'s>> { 145 | self.events.push_front(ev.into_static()); 146 | Ok(()) 147 | } 148 | } 149 | 150 | #[test] 151 | fn test_rfc3339_offset_date_time_roundtrip() { 152 | let original = Rfc3339(datetime!(2023-05-15 14:30:00 UTC)); 153 | let mut journal: Journal = Default::default(); 154 | 155 | use crate::DynDeserializerExt; 156 | 157 | journal.serialize(&original).unwrap(); 158 | let deserialized = journal 159 | .deserialize_owned::>() 160 | .unwrap(); 161 | 162 | assert_eq!(original, deserialized); 163 | } 164 | 165 | // #[test] 166 | // fn test_rfc3339_offset_date_time_serialization() { 167 | // let dt = Rfc3339(datetime!(2023-05-15 14:30:00 UTC)); 168 | // let serialized = dt.to_json_string().unwrap(); 169 | // assert_eq!(serialized, r#""2023-05-15T14:30:00Z""#); 170 | // } 171 | 172 | // #[test] 173 | // fn test_rfc3339_offset_date_time_deserialization() { 174 | // let json = r#""2023-05-15T14:30:00Z""#; 175 | // let deserialized: Rfc3339 = from_str(json).unwrap(); 176 | // assert_eq!(deserialized, Rfc3339(datetime!(2023-05-15 14:30:00 UTC))); 177 | // } 178 | } 179 | -------------------------------------------------------------------------------- /merde_core/src/value.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use ordered_float::OrderedFloat; 4 | 5 | use crate::{array::Array, map::Map, CowBytes, CowStr, IntoStatic, MerdeError, ValueType}; 6 | 7 | /// Think [`serde_json::Value`](https://docs.rs/serde_json/1.0.128/serde_json/enum.Value.html), but with a small string optimization, 8 | /// copy-on-write strings, etc. Might include other value types later. 9 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 10 | pub enum Value<'s> { 11 | I64(i64), 12 | U64(u64), 13 | Float(OrderedFloat), 14 | Str(CowStr<'s>), 15 | Bytes(CowBytes<'s>), 16 | Null, 17 | Bool(bool), 18 | Array(Array<'s>), 19 | Map(Map<'s>), 20 | } 21 | 22 | impl IntoStatic for Value<'_> { 23 | type Output = Value<'static>; 24 | 25 | #[inline(always)] 26 | fn into_static(self) -> ::Output { 27 | match self { 28 | Value::I64(i) => Value::I64(i), 29 | Value::U64(u) => Value::U64(u), 30 | Value::Float(f) => Value::Float(f), 31 | Value::Str(s) => Value::Str(s.into_static()), 32 | Value::Bytes(b) => Value::Bytes(b.into_static()), 33 | Value::Null => Value::Null, 34 | Value::Bool(b) => Value::Bool(b), 35 | Value::Array(arr) => Value::Array(arr.into_static()), 36 | Value::Map(map) => Value::Map(map.into_static()), 37 | } 38 | } 39 | } 40 | 41 | macro_rules! impl_from_for_value { 42 | ($ty:ty => $variant:ident, $($rest:tt)*) => { 43 | impl_from_for_value!($ty => $variant); 44 | impl_from_for_value!($($rest)*); 45 | }; 46 | 47 | ($ty:ty => $variant:ident) => { 48 | impl<'s> From<$ty> for Value<'s> { 49 | fn from(v: $ty) -> Self { 50 | Value::$variant(v.into()) 51 | } 52 | } 53 | }; 54 | 55 | (,) => {}; 56 | () => {}; 57 | } 58 | 59 | impl_from_for_value! { 60 | // signed 61 | i8 => I64, 62 | i16 => I64, 63 | i32 => I64, 64 | i64 => I64, 65 | // unsigned 66 | u8 => U64, 67 | u16 => U64, 68 | u32 => U64, 69 | u64 => U64, 70 | // misc. 71 | CowStr<'s> => Str, 72 | CowBytes<'s> => Bytes, 73 | } 74 | 75 | impl From for Value<'_> { 76 | fn from(v: f32) -> Self { 77 | Value::Float((v as f64).into()) 78 | } 79 | } 80 | 81 | impl From for Value<'_> { 82 | fn from(v: f64) -> Self { 83 | Value::Float(v.into()) 84 | } 85 | } 86 | 87 | impl<'s> From<&'s str> for Value<'s> { 88 | fn from(v: &'s str) -> Self { 89 | Value::Str(v.into()) 90 | } 91 | } 92 | 93 | impl From for Value<'_> { 94 | fn from(v: String) -> Self { 95 | Value::Str(v.into()) 96 | } 97 | } 98 | 99 | impl<'s> From<&'s String> for Value<'s> { 100 | fn from(v: &'s String) -> Self { 101 | Value::Str(v.as_str().into()) 102 | } 103 | } 104 | 105 | impl From<()> for Value<'_> { 106 | fn from(_: ()) -> Self { 107 | Value::Null 108 | } 109 | } 110 | 111 | impl From for Value<'_> { 112 | fn from(v: bool) -> Self { 113 | Value::Bool(v) 114 | } 115 | } 116 | 117 | impl<'s> From> for Value<'s> { 118 | fn from(v: Array<'s>) -> Self { 119 | Value::Array(v) 120 | } 121 | } 122 | 123 | impl<'s> From> for Value<'s> { 124 | fn from(v: Map<'s>) -> Self { 125 | Value::Map(v) 126 | } 127 | } 128 | 129 | impl<'s> From>> for Value<'s> { 130 | fn from(v: Vec>) -> Self { 131 | Value::Array(Array(v)) 132 | } 133 | } 134 | 135 | impl<'s> From, Value<'s>>> for Value<'s> { 136 | fn from(v: HashMap, Value<'s>>) -> Self { 137 | Value::Map(Map(v)) 138 | } 139 | } 140 | 141 | impl<'s> Value<'s> { 142 | #[inline(always)] 143 | pub fn as_map(&self) -> Result<&Map<'s>, MerdeError<'static>> { 144 | match self { 145 | Value::Map(obj) => Ok(obj), 146 | _ => Err(MerdeError::MismatchedType { 147 | expected: ValueType::Map, 148 | found: self.value_type(), 149 | }), 150 | } 151 | } 152 | 153 | #[inline(always)] 154 | pub fn into_map(self) -> Result, MerdeError<'static>> { 155 | match self { 156 | Value::Map(obj) => Ok(obj), 157 | _ => Err(MerdeError::MismatchedType { 158 | expected: ValueType::Map, 159 | found: self.value_type(), 160 | }), 161 | } 162 | } 163 | 164 | #[inline(always)] 165 | pub fn as_array(&self) -> Result<&Array<'s>, MerdeError<'static>> { 166 | match self { 167 | Value::Array(arr) => Ok(arr), 168 | _ => Err(MerdeError::MismatchedType { 169 | expected: ValueType::Array, 170 | found: self.value_type(), 171 | }), 172 | } 173 | } 174 | 175 | #[inline(always)] 176 | pub fn into_array(self) -> Result, MerdeError<'static>> { 177 | match self { 178 | Value::Array(arr) => Ok(arr), 179 | _ => Err(MerdeError::MismatchedType { 180 | expected: ValueType::Array, 181 | found: self.value_type(), 182 | }), 183 | } 184 | } 185 | 186 | #[inline(always)] 187 | pub fn as_str(&self) -> Result<&CowStr<'s>, MerdeError<'static>> { 188 | match self { 189 | Value::Str(s) => Ok(s), 190 | _ => Err(MerdeError::MismatchedType { 191 | expected: ValueType::String, 192 | found: self.value_type(), 193 | }), 194 | } 195 | } 196 | 197 | #[inline(always)] 198 | pub fn into_str(self) -> Result, MerdeError<'static>> { 199 | match self { 200 | Value::Str(s) => Ok(s), 201 | _ => Err(MerdeError::MismatchedType { 202 | expected: ValueType::String, 203 | found: self.value_type(), 204 | }), 205 | } 206 | } 207 | 208 | #[inline(always)] 209 | pub fn as_bytes(&self) -> Result<&CowBytes<'s>, MerdeError<'static>> { 210 | match self { 211 | Value::Bytes(b) => Ok(b), 212 | _ => Err(MerdeError::MismatchedType { 213 | expected: ValueType::Bytes, 214 | found: self.value_type(), 215 | }), 216 | } 217 | } 218 | 219 | #[inline(always)] 220 | pub fn into_bytes(self) -> Result, MerdeError<'static>> { 221 | match self { 222 | Value::Bytes(b) => Ok(b), 223 | _ => Err(MerdeError::MismatchedType { 224 | expected: ValueType::Bytes, 225 | found: self.value_type(), 226 | }), 227 | } 228 | } 229 | 230 | #[inline(always)] 231 | pub fn as_i64(&self) -> Result> { 232 | match self { 233 | Value::I64(n) => Ok(*n), 234 | Value::U64(n) if *n <= i64::MAX as u64 => Ok(*n as i64), 235 | _ => Err(MerdeError::MismatchedType { 236 | expected: ValueType::I64, 237 | found: self.value_type(), 238 | }), 239 | } 240 | } 241 | 242 | #[inline(always)] 243 | pub fn as_u64(&self) -> Result> { 244 | match self { 245 | Value::U64(n) => Ok(*n), 246 | Value::I64(n) => Ok((*n).try_into().map_err(|_| MerdeError::OutOfRange)?), 247 | _ => Err(MerdeError::MismatchedType { 248 | expected: ValueType::U64, 249 | found: self.value_type(), 250 | }), 251 | } 252 | } 253 | 254 | #[inline(always)] 255 | pub fn as_f64(&self) -> Result> { 256 | match self { 257 | Value::Float(n) => Ok(n.into_inner()), 258 | _ => Err(MerdeError::MismatchedType { 259 | expected: ValueType::Float, 260 | found: self.value_type(), 261 | }), 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /merde_core/src/with_lifetime.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | collections::{HashMap, HashSet, VecDeque}, 4 | sync::Arc, 5 | }; 6 | 7 | use crate::{CowStr, Value}; 8 | 9 | /// Allow instantiating a type with a lifetime parameter, which in 10 | /// turn lets us require `Deserialize<'s>` for `CowStr<'s>` for 11 | /// example, even when `CowStr<'s>` is erased behind a `T`. 12 | /// 13 | /// See for details 14 | pub trait WithLifetime<'s> { 15 | type Lifetimed: 's; 16 | } 17 | 18 | macro_rules! impl_with_lifetime { 19 | ($($struct_name:ident $(<$lifetime:lifetime>)?),* $(,)?) => { 20 | $( 21 | impl_with_lifetime!(@inner $struct_name $(<$lifetime>)?); 22 | )* 23 | }; 24 | 25 | (@inner $struct_name:ident <$lifetime:lifetime>) => { 26 | impl<$lifetime, 'instantiated_lifetime> WithLifetime<'instantiated_lifetime> 27 | for $struct_name<$lifetime> 28 | { 29 | type Lifetimed = $struct_name<'instantiated_lifetime>; 30 | } 31 | }; 32 | 33 | (@inner $struct_name:ident) => { 34 | impl<'s> WithLifetime<'s> for $struct_name { 35 | type Lifetimed = $struct_name; 36 | } 37 | }; 38 | } 39 | 40 | impl<'s, B: ToOwned + ?Sized + 's> WithLifetime<'s> for Cow<'_, B> { 41 | type Lifetimed = Cow<'s, B>; 42 | } 43 | 44 | impl<'s> WithLifetime<'s> for &str { 45 | type Lifetimed = &'s str; 46 | } 47 | 48 | impl_with_lifetime!( 49 | Value<'s>, 50 | CowStr<'s>, 51 | String, 52 | u128, 53 | u64, 54 | u32, 55 | u16, 56 | u8, 57 | i128, 58 | i64, 59 | i32, 60 | i16, 61 | i8, 62 | bool, 63 | char, 64 | usize, 65 | isize, 66 | f32, 67 | f64, 68 | ); 69 | 70 | impl WithLifetime<'_> for () { 71 | type Lifetimed = (); 72 | } 73 | 74 | impl<'s, T> WithLifetime<'s> for Option 75 | where 76 | T: WithLifetime<'s>, 77 | { 78 | type Lifetimed = Option; 79 | } 80 | 81 | impl<'s, T> WithLifetime<'s> for Vec 82 | where 83 | T: WithLifetime<'s>, 84 | { 85 | type Lifetimed = Vec; 86 | } 87 | 88 | impl<'s, T> WithLifetime<'s> for Arc 89 | where 90 | T: WithLifetime<'s>, 91 | { 92 | type Lifetimed = Arc; 93 | } 94 | 95 | impl<'s, T> WithLifetime<'s> for VecDeque 96 | where 97 | T: WithLifetime<'s>, 98 | { 99 | type Lifetimed = VecDeque; 100 | } 101 | 102 | impl<'s, T> WithLifetime<'s> for HashSet 103 | where 104 | T: WithLifetime<'s>, 105 | { 106 | type Lifetimed = HashSet; 107 | } 108 | 109 | impl<'s, K, V, S> WithLifetime<'s> for HashMap 110 | where 111 | S: 's, 112 | K: WithLifetime<'s>, 113 | V: WithLifetime<'s>, 114 | { 115 | type Lifetimed = HashMap; 116 | } 117 | 118 | impl<'s, T1: WithLifetime<'s>> WithLifetime<'s> for (T1,) { 119 | type Lifetimed = (T1::Lifetimed,); 120 | } 121 | 122 | impl<'s, T1: WithLifetime<'s>, T2: WithLifetime<'s>> WithLifetime<'s> for (T1, T2) { 123 | type Lifetimed = (T1::Lifetimed, T2::Lifetimed); 124 | } 125 | 126 | impl<'s, T1: WithLifetime<'s>, T2: WithLifetime<'s>, T3: WithLifetime<'s>> WithLifetime<'s> 127 | for (T1, T2, T3) 128 | { 129 | type Lifetimed = (T1::Lifetimed, T2::Lifetimed, T3::Lifetimed); 130 | } 131 | 132 | impl< 133 | 's, 134 | T1: WithLifetime<'s>, 135 | T2: WithLifetime<'s>, 136 | T3: WithLifetime<'s>, 137 | T4: WithLifetime<'s>, 138 | > WithLifetime<'s> for (T1, T2, T3, T4) 139 | { 140 | type Lifetimed = (T1::Lifetimed, T2::Lifetimed, T3::Lifetimed, T4::Lifetimed); 141 | } 142 | 143 | impl< 144 | 's, 145 | T1: WithLifetime<'s>, 146 | T2: WithLifetime<'s>, 147 | T3: WithLifetime<'s>, 148 | T4: WithLifetime<'s>, 149 | T5: WithLifetime<'s>, 150 | > WithLifetime<'s> for (T1, T2, T3, T4, T5) 151 | { 152 | type Lifetimed = ( 153 | T1::Lifetimed, 154 | T2::Lifetimed, 155 | T3::Lifetimed, 156 | T4::Lifetimed, 157 | T5::Lifetimed, 158 | ); 159 | } 160 | 161 | impl< 162 | 's, 163 | T1: WithLifetime<'s>, 164 | T2: WithLifetime<'s>, 165 | T3: WithLifetime<'s>, 166 | T4: WithLifetime<'s>, 167 | T5: WithLifetime<'s>, 168 | T6: WithLifetime<'s>, 169 | > WithLifetime<'s> for (T1, T2, T3, T4, T5, T6) 170 | { 171 | type Lifetimed = ( 172 | T1::Lifetimed, 173 | T2::Lifetimed, 174 | T3::Lifetimed, 175 | T4::Lifetimed, 176 | T5::Lifetimed, 177 | T6::Lifetimed, 178 | ); 179 | } 180 | 181 | impl< 182 | 's, 183 | T1: WithLifetime<'s>, 184 | T2: WithLifetime<'s>, 185 | T3: WithLifetime<'s>, 186 | T4: WithLifetime<'s>, 187 | T5: WithLifetime<'s>, 188 | T6: WithLifetime<'s>, 189 | T7: WithLifetime<'s>, 190 | > WithLifetime<'s> for (T1, T2, T3, T4, T5, T6, T7) 191 | { 192 | type Lifetimed = ( 193 | T1::Lifetimed, 194 | T2::Lifetimed, 195 | T3::Lifetimed, 196 | T4::Lifetimed, 197 | T5::Lifetimed, 198 | T6::Lifetimed, 199 | T7::Lifetimed, 200 | ); 201 | } 202 | 203 | impl< 204 | 's, 205 | T1: WithLifetime<'s>, 206 | T2: WithLifetime<'s>, 207 | T3: WithLifetime<'s>, 208 | T4: WithLifetime<'s>, 209 | T5: WithLifetime<'s>, 210 | T6: WithLifetime<'s>, 211 | T7: WithLifetime<'s>, 212 | T8: WithLifetime<'s>, 213 | > WithLifetime<'s> for (T1, T2, T3, T4, T5, T6, T7, T8) 214 | { 215 | type Lifetimed = ( 216 | T1::Lifetimed, 217 | T2::Lifetimed, 218 | T3::Lifetimed, 219 | T4::Lifetimed, 220 | T5::Lifetimed, 221 | T6::Lifetimed, 222 | T7::Lifetimed, 223 | T8::Lifetimed, 224 | ); 225 | } 226 | -------------------------------------------------------------------------------- /merde_core/tests/ui/static-borrow-lifetime.rs: -------------------------------------------------------------------------------- 1 | use merde_core::FieldSlot; 2 | 3 | fn main() { 4 | let mut option: Option = None; 5 | let slot = FieldSlot::new(&mut option); 6 | 7 | #[allow(clippy::needless_lifetimes)] 8 | fn take_static_fieldslot<'s>(_f: FieldSlot<'s, 'static>) {} 9 | 10 | take_static_fieldslot(slot); 11 | } 12 | -------------------------------------------------------------------------------- /merde_core/tests/ui/static-borrow-lifetime.stderr: -------------------------------------------------------------------------------- 1 | error[E0597]: `option` does not live long enough 2 | --> tests/ui/static-borrow-lifetime.rs:5:31 3 | | 4 | 4 | let mut option: Option = None; 5 | | ---------- binding `option` declared here 6 | 5 | let slot = FieldSlot::new(&mut option); 7 | | ---------------^^^^^^^^^^^- 8 | | | | 9 | | | borrowed value does not live long enough 10 | | argument requires that `option` is borrowed for `'static` 11 | ... 12 | 11 | } 13 | | - `option` dropped here while still borrowed 14 | -------------------------------------------------------------------------------- /merde_core/tests/ui/static-s-lifetime.rs: -------------------------------------------------------------------------------- 1 | use merde_core::FieldSlot; 2 | 3 | fn main() { 4 | let mut option: Option = None; 5 | let slot = FieldSlot::new(&mut option); 6 | 7 | #[allow(clippy::needless_lifetimes)] 8 | fn take_static_fieldslot<'borrow>(_f: FieldSlot<'static, 'borrow>) {} 9 | 10 | take_static_fieldslot(slot); 11 | } 12 | -------------------------------------------------------------------------------- /merde_core/tests/ui/static-s-lifetime.stderr: -------------------------------------------------------------------------------- 1 | error[E0597]: `option` does not live long enough 2 | --> tests/ui/static-s-lifetime.rs:5:31 3 | | 4 | 4 | let mut option: Option = None; 5 | | ---------- binding `option` declared here 6 | 5 | let slot = FieldSlot::new(&mut option); 7 | | ---------------^^^^^^^^^^^- 8 | | | | 9 | | | borrowed value does not live long enough 10 | | argument requires that `option` is borrowed for `'static` 11 | ... 12 | 11 | } 13 | | - `option` dropped here while still borrowed 14 | -------------------------------------------------------------------------------- /merde_core/tests/ui/subtyping.rs: -------------------------------------------------------------------------------- 1 | use merde_core::FieldSlot; 2 | 3 | fn main() { 4 | let mut option: Option = None; 5 | let slot = FieldSlot::new(&mut option); 6 | 7 | fn prove_invariance<'long, 'short: 'long>( 8 | long: FieldSlot<'long, 'long>, 9 | ) -> FieldSlot<'short, 'short> { 10 | long // Error: mismatched types 11 | } 12 | 13 | assert!(option.is_none()); 14 | } 15 | -------------------------------------------------------------------------------- /merde_core/tests/ui/subtyping.stderr: -------------------------------------------------------------------------------- 1 | warning: unused variable: `slot` 2 | --> tests/ui/subtyping.rs:5:9 3 | | 4 | 5 | let slot = FieldSlot::new(&mut option); 5 | | ^^^^ help: if this is intentional, prefix it with an underscore: `_slot` 6 | | 7 | = note: `#[warn(unused_variables)]` on by default 8 | 9 | error: lifetime may not live long enough 10 | --> tests/ui/subtyping.rs:10:9 11 | | 12 | 7 | fn prove_invariance<'long, 'short: 'long>( 13 | | ----- ------ lifetime `'short` defined here 14 | | | 15 | | lifetime `'long` defined here 16 | ... 17 | 10 | long // Error: mismatched types 18 | | ^^^^ function was supposed to return data with lifetime `'short` but it is returning data with lifetime `'long` 19 | | 20 | = help: consider adding the following bound: `'long: 'short` 21 | = note: requirement occurs because of the type `FieldSlot<'_, '_>`, which makes the generic argument `'_` invariant 22 | = note: the struct `FieldSlot<'s, 'borrow>` is invariant over the parameter `'s` 23 | = help: see for more information about variance 24 | -------------------------------------------------------------------------------- /merde_json/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [10.0.6](https://github.com/bearcove/merde/compare/merde_json-v10.0.5...merde_json-v10.0.6) - 2025-04-25 10 | 11 | ### Other 12 | 13 | - Various non-major dependency upgrades 14 | 15 | ## [10.0.5](https://github.com/bearcove/merde/compare/merde_json-v10.0.4...merde_json-v10.0.5) - 2025-04-16 16 | 17 | ### Other 18 | 19 | - updated the following local packages: merde_core 20 | 21 | ## [10.0.4](https://github.com/bearcove/merde/compare/merde_json-v10.0.3...merde_json-v10.0.4) - 2025-04-16 22 | 23 | ### Other 24 | 25 | - updated the following local packages: merde_core 26 | 27 | ## [10.0.3](https://github.com/bearcove/merde/compare/merde_json-v10.0.2...merde_json-v10.0.3) - 2025-04-16 28 | 29 | ### Other 30 | 31 | - updated the following local packages: merde_core 32 | 33 | ## [10.0.2](https://github.com/bearcove/merde/compare/merde_json-v10.0.1...merde_json-v10.0.2) - 2025-03-06 34 | 35 | ### Other 36 | 37 | - updated the following local packages: merde_core 38 | 39 | ## [10.0.1](https://github.com/bearcove/merde/compare/merde_json-v10.0.0...merde_json-v10.0.1) - 2025-01-29 40 | 41 | ### Other 42 | 43 | - updated the following local packages: merde_core 44 | 45 | ## [10.0.0](https://github.com/bearcove/merde/compare/merde_json-v9.0.1...merde_json-v10.0.0) - 2024-12-04 46 | 47 | ### Other 48 | 49 | - [**breaking**] Force a major version bump 50 | 51 | ## [9.0.1](https://github.com/bearcove/merde/compare/merde_json-v9.0.0...merde_json-v9.0.1) - 2024-12-02 52 | 53 | ### Other 54 | 55 | - updated the following local packages: merde_core 56 | 57 | ## [9.0.0](https://github.com/bearcove/merde/compare/merde_json-v8.0.2...merde_json-v9.0.0) - 2024-11-30 58 | 59 | ### Other 60 | 61 | - Mhh 62 | - Use DynSerialize in merde_json 63 | - Rename serialize_sync to serialize 64 | - async stuff Does Not Work for now 65 | - remove async versions of things 66 | - wip dyn serialize 67 | - bye bigint 68 | - Remove unused jiter_lite methods 69 | - Fix more warnings and errors 70 | - More! 71 | - yay other errors 72 | - Dwip 73 | - Expose to_tokio_writer 74 | - Remove JsonSerialize trait 75 | - Expose an async Deserializer interface 76 | - Make Deserializer::next async 77 | - Move things around re: absorbing merde_time in merde_core 78 | 79 | ## [8.0.2](https://github.com/bearcove/merde/compare/merde_json-v8.0.1...merde_json-v8.0.2) - 2024-11-24 80 | 81 | ### Other 82 | 83 | - updated the following local packages: merde_core 84 | 85 | ## [8.0.1](https://github.com/bearcove/merde/compare/merde_json-v8.0.0...merde_json-v8.0.1) - 2024-11-20 86 | 87 | ### Other 88 | 89 | - updated the following local packages: merde_core 90 | 91 | ## [8.0.0](https://github.com/bearcove/merde/compare/merde_json-v6.2.1...merde_json-v8.0.0) - 2024-11-04 92 | 93 | ### Other 94 | 95 | - Introduce Serialize trait 96 | 97 | ## [6.2.1](https://github.com/bearcove/merde/compare/merde_json-v6.2.0...merde_json-v6.2.1) - 2024-10-07 98 | 99 | ### Fixed 100 | 101 | - Proper starter handling in merde_msgpack 102 | 103 | ## [6.2.0](https://github.com/bearcove/merde/compare/merde_json-v6.1.0...merde_json-v6.2.0) - 2024-10-06 104 | 105 | ### Added 106 | 107 | - Implement Eq for values 108 | 109 | ### Other 110 | 111 | - Fix tests 112 | - Add support for msgpack deserialization 113 | 114 | ## [6.1.0](https://github.com/bearcove/merde/compare/merde_json-v6.0.3...merde_json-v6.1.0) - 2024-10-06 115 | 116 | ### Added 117 | 118 | - Add support for HashMap (for other S) 119 | - Remove debug prints, provide yaml::from_str/owned 120 | 121 | ## [6.0.3](https://github.com/bearcove/merde/compare/merde_json-v6.0.2...merde_json-v6.0.3) - 2024-10-04 122 | 123 | ### Other 124 | 125 | - Fix empty objects / empty arrays 126 | 127 | ## [6.0.2](https://github.com/bearcove/merde/compare/merde_json-v6.0.1...merde_json-v6.0.2) - 2024-10-04 128 | 129 | ### Other 130 | 131 | - Make MerdeJsonError implement IntoStatic + impl for Result 132 | 133 | ## [6.0.1](https://github.com/bearcove/merde/compare/merde_json-v6.0.0...merde_json-v6.0.1) - 2024-10-04 134 | 135 | ### Other 136 | 137 | - Introduce from_str_owned in the json module 138 | 139 | ## [6.0.0](https://github.com/bearcove/merde/compare/merde_json-v5.1.0...merde_json-v6.0.0) - 2024-09-22 140 | 141 | ### Other 142 | 143 | - Add bytes type ([#76](https://github.com/bearcove/merde/pull/76)) 144 | - Remove ValueDeserialize macros 145 | - Remove definition of ValueDeserialize 146 | - Convert example 147 | - Move away from ValueDeserialize 148 | - Use UnexpectedEvent 149 | - Deserializable => Deserialize, a-la serde 150 | - Fix all tests 151 | - Well that works 152 | - okay hang on 153 | - get rid of queue in JsonSerializer 154 | - Play around with API 155 | - mhmh 156 | - Well the new deserializer seems to be working 157 | - poll failed you say 158 | - add lifetimes to errors aw yiss 159 | 160 | ## [5.1.0](https://github.com/bearcove/merde/compare/merde_json-v5.0.5...merde_json-v5.1.0) - 2024-09-20 161 | 162 | ### Added 163 | 164 | - Add JsonSerialize and ValueDeserialize impls for f32, f64 165 | 166 | ## [5.0.5](https://github.com/bearcove/merde/compare/merde_json-v5.0.4...merde_json-v5.0.5) - 2024-09-17 167 | 168 | ### Other 169 | 170 | - updated the following local packages: merde_core 171 | 172 | ## [5.0.4](https://github.com/bearcove/merde/compare/merde_json-v5.0.3...merde_json-v5.0.4) - 2024-09-17 173 | 174 | ### Other 175 | 176 | - updated the following local packages: merde_core 177 | 178 | ## [5.0.3](https://github.com/bearcove/merde/compare/merde_json-v5.0.2...merde_json-v5.0.3) - 2024-09-17 179 | 180 | ### Other 181 | 182 | - updated the following local packages: merde_core 183 | 184 | ## [5.0.2](https://github.com/bearcove/merde/compare/merde_json-v5.0.1...merde_json-v5.0.2) - 2024-09-17 185 | 186 | ### Other 187 | 188 | - Add/fix logo attribution 189 | 190 | ## [5.0.1](https://github.com/bearcove/merde/compare/merde_json-v5.0.0...merde_json-v5.0.1) - 2024-09-16 191 | 192 | ### Other 193 | 194 | - updated the following local packages: merde_core 195 | 196 | ## [5.0.0](https://github.com/bearcove/merde/compare/merde_json-v4.0.2...merde_json-v5.0.0) - 2024-09-15 197 | 198 | ### Added 199 | 200 | - Introduce OwnedValueDeserialize 201 | - [**breaking**] Introduce WithLifetime trait 202 | 203 | ## [4.0.2](https://github.com/bearcove/merde/compare/merde_json-v4.0.1...merde_json-v4.0.2) - 2024-09-14 204 | 205 | ### Other 206 | 207 | - updated the following local packages: merde_core 208 | 209 | ## [4.0.1](https://github.com/bearcove/merde/compare/merde_json-v4.0.0...merde_json-v4.0.1) - 2024-09-14 210 | 211 | ### Other 212 | 213 | - updated the following local packages: merde_core 214 | 215 | ## [3.0.1](https://github.com/bearcove/merde/compare/merde_json-v3.0.0...merde_json-v3.0.1) - 2024-09-12 216 | 217 | ### Other 218 | 219 | - updated the following local packages: merde_core 220 | 221 | ## [2.4.1](https://github.com/bearcove/merde_json/compare/merde_json-v2.4.0...merde_json-v2.4.1) - 2024-09-05 222 | 223 | ### Other 224 | - Update logo attribution 225 | 226 | ## [2.4.0](https://github.com/bearcove/merde_json/compare/merde_json-v2.3.1...merde_json-v2.4.0) - 2024-08-16 227 | 228 | ### Added 229 | - Implement ToStatic for String 230 | 231 | ## [2.3.1](https://github.com/bearcove/merde_json/compare/merde_json-v2.3.0...merde_json-v2.3.1) - 2024-08-16 232 | 233 | ### Fixed 234 | - Remove (dev) dep on serde_json 235 | 236 | ## [2.3.0](https://github.com/bearcove/merde_json/compare/merde_json-v2.2.0...merde_json-v2.3.0) - 2024-08-16 237 | 238 | ### Added 239 | - Provide Fantome from both merde-json and merde-json-types 240 | 241 | ## [2.2.0](https://github.com/bearcove/merde_json/compare/merde_json-v2.1.2...merde_json-v2.2.0) - 2024-08-16 242 | 243 | ### Added 244 | - Impl ToStatic for more standard collection types 245 | 246 | ### Other 247 | - Run examples in CI 248 | 249 | ## [2.1.2](https://github.com/bearcove/merde_json/compare/merde_json-v2.1.1...merde_json-v2.1.2) - 2024-07-31 250 | 251 | ### Other 252 | - Use public URL, hopefully works on crates too? 253 | - Add @2x asset 254 | - Add logo 255 | 256 | ## [2.1.1](https://github.com/bearcove/merde_json/compare/merde_json-v2.1.0...merde_json-v2.1.1) - 2024-07-31 257 | 258 | ### Other 259 | - Move CHANGELOG in the right place 260 | 261 | ## [2.0.0](https://github.com/bearcove/merde_json/compare/v1.0.1...v2.0.0) - 2024-07-31 262 | 263 | ### Added 264 | - Introduce `to_string` and other top-level functions for serde_json compat 265 | - Implement ToStatic for Option 266 | 267 | ### Other 268 | - I guess that bound wasn't necessary 269 | - Elide lifetimes 270 | - Tests pass! Let's only do OffsetDateTime 271 | - Some unit tests for datetime (failing so far) 272 | - Make both enums non-exhaustive 273 | - WIP time implementation 274 | - Also run on merge_group 275 | 276 | ## [1.0.1](https://github.com/bearcove/merde_json/compare/v1.0.0...v1.0.1) - 2024-07-29 277 | 278 | ### Fixed 279 | - Declare lifetime parameters in a consistent order, always ([#4](https://github.com/bearcove/merde_json/pull/4)) 280 | 281 | ### Other 282 | - release 283 | 284 | ## [1.0.0](https://github.com/bearcove/merde_json/releases/tag/v1.0.0) - 2024-07-29 285 | 286 | ### Other 287 | - Add release-plz flow 288 | - Alright then 289 | - Flesh out README, add funding 290 | - Don't need the rust action? 291 | - All tests pass I believe 292 | - Mhmh 293 | - More tests pass 294 | - Show off ToStatic 295 | - mh 296 | - Add mixed example 297 | - Getting somewhere 298 | - Fix CI workflow 299 | - Lift 'inner at the trait level for JsonDeserialize 300 | - More docs 301 | - Initial import 302 | -------------------------------------------------------------------------------- /merde_json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merde_json" 3 | version = "10.0.6" 4 | edition = "2021" 5 | authors = ["Amos Wenger "] 6 | description = "JSON serialization and deserialization for merde, via jiter" 7 | license = "Apache-2.0 OR MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/bearcove/merde" 10 | keywords = ["json", "serialization", "deserialization", "jiter"] 11 | categories = ["encoding", "parser-implementations"] 12 | 13 | [dependencies] 14 | itoa = "1.0.15" 15 | lexical-parse-float = { version = "0.8.5", features = ["format"] } 16 | merde_core = { version = "10.0.6", path = "../merde_core" } 17 | ryu = "1.0.20" 18 | tokio = { version = "1", optional = true, features = ["io-util"] } 19 | 20 | [features] 21 | default = [] 22 | full = [] 23 | 24 | [dev-dependencies] 25 | merde_loggingserializer = { path = "../merde_loggingserializer" } 26 | 27 | -------------------------------------------------------------------------------- /merde_json/README.md: -------------------------------------------------------------------------------- 1 | [![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) 2 | [![crates.io](https://img.shields.io/crates/v/merde_json.svg)](https://crates.io/crates/merde_json) 3 | [![docs.rs](https://docs.rs/merde_json/badge.svg)](https://docs.rs/merde_json) 4 | 5 | # merde_json 6 | 7 | ![The merde logo: a glorious poop floating above a pair of hands](https://github.com/user-attachments/assets/763d60e0-5101-48af-bc72-f96f516a5d0f) 8 | 9 | _Logo by [MisiasArt](https://misiasart.com)_ 10 | 11 | Adds JSON serialization/deserialization support for 12 | [merde](https://crates.io/crates/merde). 13 | 14 | You would normally add a dependency on [merde](https://crates.io/crates/merde) 15 | directly, enabling its `json` feature. 16 | 17 | Don't forget to enable the `serialize` and `deserialize` features as needed — those are opt-in. 18 | 19 | ## Implementation 20 | 21 | The underlying parser (including aarch64 SIMD support, bignum support, etc.) has been 22 | taken wholesale from the [jiter crate](https://crates.io/crates/jiter) for now. 23 | 24 | [An issue has been opened](https://github.com/pydantic/jiter/issues/139) to discuss sharing a core. 25 | -------------------------------------------------------------------------------- /merde_json/src/error.rs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------- 2 | // Error Handling and Field Type 3 | // ------------------------------------------------------------------------- 4 | 5 | use std::borrow::Cow; 6 | 7 | use jiter::JsonValue; 8 | 9 | /// A content-less variant of the [JsonValue] enum, used for reporting errors, see [MerdeJsonError::MismatchedType]. 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 11 | #[non_exhaustive] 12 | pub enum JsonFieldType { 13 | /// The JSON value is `null`. 14 | Null, 15 | 16 | /// The JSON value is `true` or `false`. 17 | Bool, 18 | 19 | /// The JSON value fits in an `i64`. 20 | Int, 21 | 22 | /// The JSON value has decimal places. 23 | Float, 24 | 25 | /// The JSON value is a string. 26 | String, 27 | 28 | /// The JSON value is an array. 29 | Array, 30 | 31 | /// The JSON value is an object. Keys must be strings. 32 | Object, 33 | } 34 | 35 | /// A grab-bag of errors that can occur when deserializing JSON. 36 | /// This isn't super clean, not my proudest moment. 37 | #[derive(Debug)] 38 | #[non_exhaustive] 39 | pub enum MerdeJsonError { 40 | /// We expected a certain type but got a different one. 41 | MismatchedType { 42 | /// The expected type. 43 | expected: JsonFieldType, 44 | 45 | /// The type we got. 46 | found: JsonFieldType, 47 | }, 48 | 49 | /// We expected an object to have a certain property, but it was missing. 50 | MissingProperty(Cow<'static, str>), 51 | 52 | /// We tried to access an array index that was out of bounds. 53 | IndexOutOfBounds { 54 | /// The index we tried to access. 55 | index: usize, 56 | /// The length of the array. 57 | len: usize, 58 | }, 59 | 60 | /// We encountered a property that we didn't expect. 61 | UnknownProperty(String), 62 | 63 | /// We encountered an error in the underlying JSON parser. 64 | JsonError(jiter::JsonError), 65 | 66 | /// For example, we had a `u8` field but the JSON value was bigger than `u8::MAX`. 67 | OutOfRange, 68 | 69 | /// A field was missing (but we don't know its name) 70 | MissingValue, 71 | 72 | /// While calling out to [FromStr::from_str](std::str::FromStr::from_str) to build a [HashMap](std::collections::HashMap), we got an error. 73 | InvalidKey, 74 | 75 | /// While parsing a datetime, we got an error 76 | InvalidDateTimeValue, 77 | 78 | /// An I/O error occurred. 79 | Io(std::io::Error), 80 | } 81 | 82 | impl From for MerdeJsonError { 83 | fn from(e: jiter::JsonError) -> Self { 84 | MerdeJsonError::JsonError(e) 85 | } 86 | } 87 | 88 | impl From for MerdeJsonError { 89 | fn from(e: std::io::Error) -> Self { 90 | MerdeJsonError::Io(e) 91 | } 92 | } 93 | 94 | impl std::fmt::Display for MerdeJsonError { 95 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 96 | match self { 97 | MerdeJsonError::MismatchedType { expected, found } => { 98 | write!(f, "Expected {:?}, found {:?}", expected, found) 99 | } 100 | MerdeJsonError::MissingProperty(prop) => { 101 | write!(f, "Missing property: {}", prop) 102 | } 103 | MerdeJsonError::IndexOutOfBounds { index, len: length } => { 104 | write!( 105 | f, 106 | "Index out of bounds: index {} is not valid for length {}", 107 | index, length 108 | ) 109 | } 110 | MerdeJsonError::UnknownProperty(prop) => { 111 | write!(f, "Unknown property: {}", prop) 112 | } 113 | MerdeJsonError::JsonError(e) => { 114 | write!(f, "JsonError: {}", e) 115 | } 116 | MerdeJsonError::OutOfRange => { 117 | write!(f, "Value is out of range") 118 | } 119 | MerdeJsonError::MissingValue => { 120 | write!(f, "Missing value") 121 | } 122 | MerdeJsonError::InvalidKey => { 123 | write!(f, "Invalid key") 124 | } 125 | MerdeJsonError::InvalidDateTimeValue => { 126 | write!(f, "Invalid date/time value") 127 | } 128 | MerdeJsonError::Io(e) => { 129 | write!(f, "I/O error: {}", e) 130 | } 131 | } 132 | } 133 | } 134 | 135 | impl std::error::Error for MerdeJsonError {} 136 | 137 | impl JsonFieldType { 138 | /// Returns the [JsonFieldType] for a given [JsonValue]. 139 | pub fn for_json_value(value: &JsonValue<'_>) -> Self { 140 | match value { 141 | JsonValue::Null => JsonFieldType::Null, 142 | JsonValue::Bool(_) => JsonFieldType::Bool, 143 | JsonValue::Int(_) => JsonFieldType::Int, 144 | JsonValue::Float(_) => JsonFieldType::Float, 145 | JsonValue::Str(_) => JsonFieldType::String, 146 | JsonValue::Array(_) => JsonFieldType::Array, 147 | JsonValue::Object(_) => JsonFieldType::Object, 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /merde_json/src/jiter_lite/errors.rs: -------------------------------------------------------------------------------- 1 | /// Enum representing all possible errors in JSON syntax. 2 | /// 3 | /// Almost all of `JsonErrorType` is copied from [serde_json](https://github.com/serde-rs) so errors match 4 | /// those expected from `serde_json`. 5 | #[derive(Debug, PartialEq, Eq, Clone)] 6 | pub enum JsonErrorType { 7 | /// float value was found where an int was expected 8 | FloatExpectingInt, 9 | 10 | /// duplicate keys in an object 11 | DuplicateKey(String), 12 | 13 | /// happens when getting the `Decimal` type or constructing a decimal fails 14 | InternalError(String), 15 | 16 | /// NOTE: all errors from here on are copied from serde_json 17 | /// [src/error.rs](https://github.com/serde-rs/json/blob/v1.0.107/src/error.rs#L236) 18 | /// with `Io` and `Message` removed 19 | /// 20 | /// EOF while parsing a list. 21 | EofWhileParsingList, 22 | 23 | /// EOF while parsing an object. 24 | EofWhileParsingObject, 25 | 26 | /// EOF while parsing a string. 27 | EofWhileParsingString, 28 | 29 | /// EOF while parsing a JSON value. 30 | EofWhileParsingValue, 31 | 32 | /// Expected this character to be a `':'`. 33 | ExpectedColon, 34 | 35 | /// Expected this character to be either a `','` or a `']'`. 36 | ExpectedListCommaOrEnd, 37 | 38 | /// Expected this character to be either a `','` or a `'}'`. 39 | ExpectedObjectCommaOrEnd, 40 | 41 | /// Expected to parse either a `true`, `false`, or a `null`. 42 | ExpectedSomeIdent, 43 | 44 | /// Expected this character to start a JSON value. 45 | ExpectedSomeValue, 46 | 47 | /// Invalid hex escape code. 48 | InvalidEscape, 49 | 50 | /// Invalid number. 51 | InvalidNumber, 52 | 53 | /// Number is bigger than the maximum value of its type. 54 | NumberOutOfRange, 55 | 56 | /// Invalid unicode code point. 57 | InvalidUnicodeCodePoint, 58 | 59 | /// Control character found while parsing a string. 60 | ControlCharacterWhileParsingString, 61 | 62 | /// Object key is not a string. 63 | KeyMustBeAString, 64 | 65 | /// Lone leading surrogate in hex escape. 66 | LoneLeadingSurrogateInHexEscape, 67 | 68 | /// JSON has a comma after the last value in an array or map. 69 | TrailingComma, 70 | 71 | /// JSON has non-whitespace trailing characters after the value. 72 | TrailingCharacters, 73 | 74 | /// Unexpected end of hex escape. 75 | UnexpectedEndOfHexEscape, 76 | 77 | /// Encountered nesting of JSON maps and arrays more than 128 layers deep. 78 | RecursionLimitExceeded, 79 | } 80 | 81 | impl std::fmt::Display for JsonErrorType { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 | // Messages for enum members copied from serde_json are unchanged 84 | match self { 85 | Self::FloatExpectingInt => { 86 | f.write_str("float value was found where an int was expected") 87 | } 88 | Self::DuplicateKey(s) => write!(f, "Detected duplicate key {s:?}"), 89 | Self::InternalError(s) => write!(f, "Internal error: {s:?}"), 90 | Self::EofWhileParsingList => f.write_str("EOF while parsing a list"), 91 | Self::EofWhileParsingObject => f.write_str("EOF while parsing an object"), 92 | Self::EofWhileParsingString => f.write_str("EOF while parsing a string"), 93 | Self::EofWhileParsingValue => f.write_str("EOF while parsing a value"), 94 | Self::ExpectedColon => f.write_str("expected `:`"), 95 | Self::ExpectedListCommaOrEnd => f.write_str("expected `,` or `]`"), 96 | Self::ExpectedObjectCommaOrEnd => f.write_str("expected `,` or `}`"), 97 | Self::ExpectedSomeIdent => f.write_str("expected ident"), 98 | Self::ExpectedSomeValue => f.write_str("expected value"), 99 | Self::InvalidEscape => f.write_str("invalid escape"), 100 | Self::InvalidNumber => f.write_str("invalid number"), 101 | Self::NumberOutOfRange => f.write_str("number out of range"), 102 | Self::InvalidUnicodeCodePoint => f.write_str("invalid unicode code point"), 103 | Self::ControlCharacterWhileParsingString => { 104 | f.write_str("control character (\\u0000-\\u001F) found while parsing a string") 105 | } 106 | Self::KeyMustBeAString => f.write_str("key must be a string"), 107 | Self::LoneLeadingSurrogateInHexEscape => { 108 | f.write_str("lone leading surrogate in hex escape") 109 | } 110 | Self::TrailingComma => f.write_str("trailing comma"), 111 | Self::TrailingCharacters => f.write_str("trailing characters"), 112 | Self::UnexpectedEndOfHexEscape => f.write_str("unexpected end of hex escape"), 113 | Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded"), 114 | } 115 | } 116 | } 117 | 118 | pub type JsonResult = Result; 119 | 120 | /// Represents an error from parsing JSON 121 | #[derive(Debug, Clone, Eq, PartialEq)] 122 | pub struct JsonError { 123 | /// The type of error. 124 | pub error_type: JsonErrorType, 125 | /// The index in the data where the error occurred. 126 | pub index: usize, 127 | } 128 | 129 | impl JsonError { 130 | pub(crate) fn new(error_type: JsonErrorType, index: usize) -> Self { 131 | Self { error_type, index } 132 | } 133 | 134 | pub fn get_position(&self, json_data: &[u8]) -> LinePosition { 135 | LinePosition::find(json_data, self.index) 136 | } 137 | 138 | pub fn description(&self, json_data: &[u8]) -> String { 139 | let position = self.get_position(json_data); 140 | format!("{} at {}", self.error_type, position) 141 | } 142 | } 143 | 144 | impl std::fmt::Display for JsonError { 145 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 146 | write!(f, "{} at index {}", self.error_type, self.index) 147 | } 148 | } 149 | 150 | macro_rules! json_error { 151 | ($error_type:ident, $index:expr) => { 152 | crate::jiter_lite::errors::JsonError::new( 153 | crate::jiter_lite::errors::JsonErrorType::$error_type, 154 | $index, 155 | ) 156 | }; 157 | } 158 | 159 | pub(crate) use json_error; 160 | 161 | macro_rules! json_err { 162 | ($error_type:ident, $index:expr) => { 163 | Err(crate::jiter_lite::errors::json_error!($error_type, $index)) 164 | }; 165 | } 166 | 167 | pub(crate) use json_err; 168 | 169 | /// Enum representing all JSON types. 170 | #[derive(Debug, Clone, Eq, PartialEq)] 171 | pub enum JsonType { 172 | Null, 173 | Bool, 174 | Int, 175 | Float, 176 | String, 177 | Array, 178 | Object, 179 | } 180 | 181 | impl std::fmt::Display for JsonType { 182 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 183 | match self { 184 | Self::Null => f.write_str("null"), 185 | Self::Bool => f.write_str("bool"), 186 | Self::Int => f.write_str("int"), 187 | Self::Float => f.write_str("float"), 188 | Self::String => f.write_str("string"), 189 | Self::Array => f.write_str("array"), 190 | Self::Object => f.write_str("object"), 191 | } 192 | } 193 | } 194 | 195 | /// Enum representing either a [JsonErrorType] or a WrongType error. 196 | #[derive(Debug, Clone, Eq, PartialEq)] 197 | pub enum JiterErrorType { 198 | JsonError(JsonErrorType), 199 | WrongType { 200 | expected: JsonType, 201 | actual: JsonType, 202 | }, 203 | } 204 | 205 | impl std::fmt::Display for JiterErrorType { 206 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 207 | match self { 208 | Self::JsonError(error_type) => write!(f, "{error_type}"), 209 | Self::WrongType { expected, actual } => { 210 | write!(f, "expected {expected} but found {actual}") 211 | } 212 | } 213 | } 214 | } 215 | 216 | /// An error from the Jiter iterator. 217 | #[derive(Debug, Clone, Eq, PartialEq)] 218 | pub struct JiterError { 219 | pub error_type: JiterErrorType, 220 | pub index: usize, 221 | } 222 | 223 | impl std::fmt::Display for JiterError { 224 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 225 | write!(f, "{} at index {}", self.error_type, self.index) 226 | } 227 | } 228 | 229 | impl JiterError { 230 | pub(crate) fn new(error_type: JiterErrorType, index: usize) -> Self { 231 | Self { error_type, index } 232 | } 233 | 234 | pub(crate) fn wrong_type(expected: JsonType, actual: JsonType, index: usize) -> Self { 235 | Self::new(JiterErrorType::WrongType { expected, actual }, index) 236 | } 237 | } 238 | 239 | impl From for JiterError { 240 | fn from(error: JsonError) -> Self { 241 | Self { 242 | error_type: JiterErrorType::JsonError(error.error_type), 243 | index: error.index, 244 | } 245 | } 246 | } 247 | 248 | /// Represents a line and column in a file or input string, used for both errors and value positions. 249 | #[derive(Debug, Clone, PartialEq, Eq)] 250 | pub struct LinePosition { 251 | /// Line number, starting at 1. 252 | pub line: usize, 253 | /// Column number, starting at 1. 254 | pub column: usize, 255 | } 256 | 257 | impl std::fmt::Display for LinePosition { 258 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 259 | write!(f, "line {} column {}", self.line, self.column) 260 | } 261 | } 262 | 263 | impl LinePosition { 264 | pub fn new(line: usize, column: usize) -> Self { 265 | Self { line, column } 266 | } 267 | 268 | /// Find the line and column of a byte index in a string. 269 | pub fn find(json_data: &[u8], find: usize) -> Self { 270 | let mut line = 1; 271 | let mut last_line_start = 0; 272 | let mut index = 0; 273 | while let Some(next) = json_data.get(index) { 274 | if *next == b'\n' { 275 | line += 1; 276 | last_line_start = index + 1; 277 | } 278 | if index == find { 279 | return Self { 280 | line, 281 | column: index + 1 - last_line_start, 282 | }; 283 | } 284 | index += 1; 285 | } 286 | Self { 287 | line, 288 | column: index.saturating_sub(last_line_start), 289 | } 290 | } 291 | 292 | pub fn short(&self) -> String { 293 | format!("{}:{}", self.line, self.column) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /merde_json/src/jiter_lite/jiter.rs: -------------------------------------------------------------------------------- 1 | use crate::jiter_lite as jiter; 2 | 3 | use jiter::errors::{json_error, JiterError, JsonError, JsonType}; 4 | use jiter::number_decoder::{NumberAny, NumberFloat}; 5 | use jiter::parse::{Parser, Peek}; 6 | use jiter::string_decoder::{StringDecoder, Tape}; 7 | 8 | pub type JiterResult = Result; 9 | 10 | /// A JSON iterator. 11 | #[derive(Debug)] 12 | pub struct Jiter<'j> { 13 | data: &'j [u8], 14 | parser: Parser<'j>, 15 | tape: Tape, 16 | allow_inf_nan: bool, 17 | allow_partial_strings: bool, 18 | } 19 | 20 | impl Clone for Jiter<'_> { 21 | /// Clone a `Jiter`. Like the default implementation, but a new empty `tape` is used. 22 | fn clone(&self) -> Self { 23 | Self { 24 | data: self.data, 25 | parser: self.parser.clone(), 26 | tape: Tape::default(), 27 | allow_inf_nan: self.allow_inf_nan, 28 | allow_partial_strings: self.allow_partial_strings, 29 | } 30 | } 31 | } 32 | 33 | impl<'j> Jiter<'j> { 34 | /// Constructs a new `Jiter`. 35 | /// 36 | /// # Arguments 37 | /// - `data`: The JSON data to be parsed. 38 | /// - `allow_inf_nan`: Whether to allow `NaN`, `Infinity` and `-Infinity` as numbers. 39 | pub fn new(data: &'j [u8]) -> Self { 40 | Self { 41 | data, 42 | parser: Parser::new(data), 43 | tape: Tape::default(), 44 | allow_inf_nan: false, 45 | allow_partial_strings: false, 46 | } 47 | } 48 | 49 | /// Peek at the next JSON value without consuming it. 50 | pub fn peek(&mut self) -> JiterResult { 51 | self.parser.peek().map_err(Into::into) 52 | } 53 | 54 | /// Knowing the next value is `null`, consume it. 55 | pub fn known_null(&mut self) -> JiterResult<()> { 56 | self.parser.consume_null()?; 57 | Ok(()) 58 | } 59 | 60 | /// Knowing the next value is `true` or `false`, parse it. 61 | pub fn known_bool(&mut self, peek: Peek) -> JiterResult { 62 | match peek { 63 | Peek::True => { 64 | self.parser.consume_true()?; 65 | Ok(true) 66 | } 67 | Peek::False => { 68 | self.parser.consume_false()?; 69 | Ok(false) 70 | } 71 | _ => Err(self.wrong_type(JsonType::Bool, peek)), 72 | } 73 | } 74 | 75 | /// Knowing the next value is a float, parse it. 76 | pub fn known_float(&mut self, peek: Peek) -> JiterResult { 77 | self.parser 78 | .consume_number::(peek.into_inner(), self.allow_inf_nan) 79 | .map_err(|e| self.maybe_number_error(e, JsonType::Float, peek)) 80 | } 81 | 82 | /// Knowing the next value is a string, parse it. 83 | pub fn known_str(&mut self) -> JiterResult<&str> { 84 | match self 85 | .parser 86 | .consume_string::(&mut self.tape, self.allow_partial_strings) 87 | { 88 | Ok(output) => Ok(output.as_str()), 89 | Err(e) => Err(e.into()), 90 | } 91 | } 92 | 93 | /// Assuming the next value is an array, peek at the first value. 94 | pub fn known_array(&mut self) -> JiterResult> { 95 | self.parser.array_first().map_err(Into::into) 96 | } 97 | 98 | /// Peek at the next value in an array. 99 | pub fn array_step(&mut self) -> JiterResult> { 100 | self.parser.array_step().map_err(Into::into) 101 | } 102 | 103 | /// Assuming the next value is an object, conssume the first key and return bytes from the original JSON data. 104 | pub fn known_object(&mut self) -> JiterResult> { 105 | let op_str = self.parser.object_first::(&mut self.tape)?; 106 | Ok(op_str.map(|s| s.as_str())) 107 | } 108 | 109 | /// Get the next key in an object, or `None` if there are no more keys. 110 | pub fn next_key(&mut self) -> JiterResult> { 111 | let strs = self.parser.object_step::(&mut self.tape)?; 112 | Ok(strs.map(|s| s.as_str())) 113 | } 114 | 115 | fn wrong_type(&self, expected: JsonType, peek: Peek) -> JiterError { 116 | match peek { 117 | Peek::True | Peek::False => { 118 | JiterError::wrong_type(expected, JsonType::Bool, self.parser.index) 119 | } 120 | Peek::Null => JiterError::wrong_type(expected, JsonType::Null, self.parser.index), 121 | Peek::String => JiterError::wrong_type(expected, JsonType::String, self.parser.index), 122 | Peek::Array => JiterError::wrong_type(expected, JsonType::Array, self.parser.index), 123 | Peek::Object => JiterError::wrong_type(expected, JsonType::Object, self.parser.index), 124 | _ if peek.is_num() => self.wrong_num(peek.into_inner(), expected), 125 | _ => json_error!(ExpectedSomeValue, self.parser.index).into(), 126 | } 127 | } 128 | 129 | fn wrong_num(&self, first: u8, expected: JsonType) -> JiterError { 130 | let mut parser2 = self.parser.clone(); 131 | let actual = match parser2.consume_number::(first, self.allow_inf_nan) { 132 | Ok(NumberAny::Int { .. }) => JsonType::Int, 133 | Ok(NumberAny::Float { .. }) => JsonType::Float, 134 | Err(e) => return e.into(), 135 | }; 136 | JiterError::wrong_type(expected, actual, self.parser.index) 137 | } 138 | 139 | fn maybe_number_error(&self, e: JsonError, expected: JsonType, peek: Peek) -> JiterError { 140 | if peek.is_num() { 141 | e.into() 142 | } else { 143 | self.wrong_type(expected, peek) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /merde_json/src/jiter_lite/mod.rs: -------------------------------------------------------------------------------- 1 | //! This contains a stripped-down version of [jiter](https://crates.io/crates/jiter), 2 | //! containing only their parsers/decoders and not their value types. 3 | 4 | pub(crate) mod errors; 5 | #[allow(clippy::module_inception)] 6 | pub(crate) mod jiter; 7 | pub(crate) mod number_decoder; 8 | pub(crate) mod parse; 9 | #[cfg(target_arch = "aarch64")] 10 | pub(crate) mod simd_aarch64; 11 | pub(crate) mod string_decoder; 12 | -------------------------------------------------------------------------------- /merde_json/src/jiter_lite/parse.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::Range; 3 | 4 | use crate::jiter_lite as jiter; 5 | 6 | use jiter::errors::{json_err, JsonResult}; 7 | use jiter::number_decoder::AbstractNumberDecoder; 8 | use jiter::string_decoder::{AbstractStringDecoder, Tape}; 9 | 10 | #[derive(Copy, Clone, PartialEq, Eq)] 11 | pub struct Peek(u8); 12 | 13 | #[allow(non_upper_case_globals)] // while testing 14 | impl Peek { 15 | pub const Null: Self = Self(b'n'); 16 | pub const True: Self = Self(b't'); 17 | pub const False: Self = Self(b'f'); 18 | pub const Minus: Self = Self(b'-'); 19 | pub const Infinity: Self = Self(b'I'); 20 | pub const NaN: Self = Self(b'N'); 21 | pub const String: Self = Self(b'"'); 22 | pub const Array: Self = Self(b'['); 23 | pub const Object: Self = Self(b'{'); 24 | } 25 | 26 | impl fmt::Debug for Peek { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | match self.0 { 29 | b'n' => write!(f, "Null"), 30 | b't' => write!(f, "True"), 31 | b'f' => write!(f, "False"), 32 | b'-' => write!(f, "Minus"), 33 | b'I' => write!(f, "Infinity"), 34 | b'N' => write!(f, "NaN"), 35 | b'"' => write!(f, "String"), 36 | b'[' => write!(f, "Array"), 37 | b'{' => write!(f, "Object"), 38 | _ => write!(f, "Peek({:?})", self.0 as char), 39 | } 40 | } 41 | } 42 | 43 | impl Peek { 44 | pub const fn new(next: u8) -> Self { 45 | Self(next) 46 | } 47 | 48 | pub const fn is_num(self) -> bool { 49 | self.0.is_ascii_digit() || matches!(self, Self::Minus | Self::Infinity | Self::NaN) 50 | } 51 | 52 | pub const fn into_inner(self) -> u8 { 53 | self.0 54 | } 55 | } 56 | 57 | static TRUE_REST: [u8; 3] = [b'r', b'u', b'e']; 58 | static FALSE_REST: [u8; 4] = [b'a', b'l', b's', b'e']; 59 | static NULL_REST: [u8; 3] = [b'u', b'l', b'l']; 60 | static NAN_REST: [u8; 2] = [b'a', b'N']; 61 | static INFINITY_REST: [u8; 7] = [b'n', b'f', b'i', b'n', b'i', b't', b'y']; 62 | 63 | #[derive(Debug, Clone)] 64 | pub(crate) struct Parser<'j> { 65 | data: &'j [u8], 66 | pub index: usize, 67 | } 68 | 69 | impl<'j> Parser<'j> { 70 | pub fn new(data: &'j [u8]) -> Self { 71 | Self { data, index: 0 } 72 | } 73 | 74 | #[allow(dead_code)] 75 | pub fn slice(&self, range: Range) -> Option<&[u8]> { 76 | self.data.get(range) 77 | } 78 | 79 | pub fn peek(&mut self) -> JsonResult { 80 | if let Some(next) = self.eat_whitespace() { 81 | Ok(Peek::new(next)) 82 | } else { 83 | json_err!(EofWhileParsingValue, self.index) 84 | } 85 | } 86 | 87 | pub fn array_first(&mut self) -> JsonResult> { 88 | self.index += 1; 89 | if let Some(next) = self.eat_whitespace() { 90 | if next == b']' { 91 | self.index += 1; 92 | Ok(None) 93 | } else { 94 | Ok(Some(Peek::new(next))) 95 | } 96 | } else { 97 | json_err!(EofWhileParsingList, self.index) 98 | } 99 | } 100 | 101 | pub fn array_step(&mut self) -> JsonResult> { 102 | if let Some(next) = self.eat_whitespace() { 103 | match next { 104 | b',' => { 105 | self.index += 1; 106 | let next = self.array_peek()?; 107 | if next.is_none() { 108 | json_err!(TrailingComma, self.index) 109 | } else { 110 | Ok(next) 111 | } 112 | } 113 | b']' => { 114 | self.index += 1; 115 | Ok(None) 116 | } 117 | _ => { 118 | json_err!(ExpectedListCommaOrEnd, self.index) 119 | } 120 | } 121 | } else { 122 | json_err!(EofWhileParsingList, self.index) 123 | } 124 | } 125 | 126 | pub fn object_first<'t, D: AbstractStringDecoder<'t, 'j>>( 127 | &mut self, 128 | tape: &'t mut Tape, 129 | ) -> JsonResult> 130 | where 131 | 'j: 't, 132 | { 133 | self.index += 1; 134 | if let Some(next) = self.eat_whitespace() { 135 | match next { 136 | b'"' => self.object_key::(tape).map(Some), 137 | b'}' => { 138 | self.index += 1; 139 | Ok(None) 140 | } 141 | _ => json_err!(KeyMustBeAString, self.index), 142 | } 143 | } else { 144 | json_err!(EofWhileParsingObject, self.index) 145 | } 146 | } 147 | 148 | pub fn object_step<'t, D: AbstractStringDecoder<'t, 'j>>( 149 | &mut self, 150 | tape: &'t mut Tape, 151 | ) -> JsonResult> 152 | where 153 | 'j: 't, 154 | { 155 | if let Some(next) = self.eat_whitespace() { 156 | match next { 157 | b',' => { 158 | self.index += 1; 159 | match self.eat_whitespace() { 160 | Some(b'"') => self.object_key::(tape).map(Some), 161 | Some(b'}') => json_err!(TrailingComma, self.index), 162 | Some(_) => json_err!(KeyMustBeAString, self.index), 163 | None => json_err!(EofWhileParsingValue, self.index), 164 | } 165 | } 166 | b'}' => { 167 | self.index += 1; 168 | Ok(None) 169 | } 170 | _ => json_err!(ExpectedObjectCommaOrEnd, self.index), 171 | } 172 | } else { 173 | json_err!(EofWhileParsingObject, self.index) 174 | } 175 | } 176 | 177 | pub fn consume_true(&mut self) -> JsonResult<()> { 178 | self.consume_ident(TRUE_REST) 179 | } 180 | 181 | pub fn consume_false(&mut self) -> JsonResult<()> { 182 | self.consume_ident(FALSE_REST) 183 | } 184 | 185 | pub fn consume_null(&mut self) -> JsonResult<()> { 186 | self.consume_ident(NULL_REST) 187 | } 188 | 189 | pub fn consume_string<'t, D: AbstractStringDecoder<'t, 'j>>( 190 | &mut self, 191 | tape: &'t mut Tape, 192 | allow_partial: bool, 193 | ) -> JsonResult 194 | where 195 | 'j: 't, 196 | { 197 | let (output, index) = D::decode(self.data, self.index, tape, allow_partial)?; 198 | self.index = index; 199 | Ok(output) 200 | } 201 | 202 | pub fn consume_number( 203 | &mut self, 204 | first: u8, 205 | allow_inf_nan: bool, 206 | ) -> JsonResult { 207 | let (output, index) = D::decode(self.data, self.index, first, allow_inf_nan)?; 208 | self.index = index; 209 | Ok(output) 210 | } 211 | 212 | /// private method to get an object key, then consume the colon which should follow 213 | fn object_key<'t, D: AbstractStringDecoder<'t, 'j>>( 214 | &mut self, 215 | tape: &'t mut Tape, 216 | ) -> JsonResult 217 | where 218 | 'j: 't, 219 | { 220 | let (output, index) = D::decode(self.data, self.index, tape, false)?; 221 | self.index = index; 222 | if let Some(next) = self.eat_whitespace() { 223 | if next == b':' { 224 | self.index += 1; 225 | Ok(output) 226 | } else { 227 | json_err!(ExpectedColon, self.index) 228 | } 229 | } else { 230 | json_err!(EofWhileParsingObject, self.index) 231 | } 232 | } 233 | 234 | fn consume_ident(&mut self, expected: [u8; SIZE]) -> JsonResult<()> { 235 | self.index = consume_ident(self.data, self.index, expected)?; 236 | Ok(()) 237 | } 238 | 239 | fn array_peek(&mut self) -> JsonResult> { 240 | if let Some(next) = self.eat_whitespace() { 241 | match next { 242 | b']' => Ok(None), 243 | _ => Ok(Some(Peek::new(next))), 244 | } 245 | } else { 246 | json_err!(EofWhileParsingValue, self.index) 247 | } 248 | } 249 | 250 | fn eat_whitespace(&mut self) -> Option { 251 | while let Some(next) = self.data.get(self.index) { 252 | match next { 253 | b' ' | b'\r' | b'\t' | b'\n' => self.index += 1, 254 | _ => return Some(*next), 255 | } 256 | } 257 | None 258 | } 259 | } 260 | 261 | pub(crate) fn consume_infinity(data: &[u8], index: usize) -> JsonResult { 262 | consume_ident(data, index, INFINITY_REST) 263 | } 264 | 265 | pub(crate) fn consume_nan(data: &[u8], index: usize) -> JsonResult { 266 | consume_ident(data, index, NAN_REST) 267 | } 268 | 269 | fn consume_ident( 270 | data: &[u8], 271 | mut index: usize, 272 | expected: [u8; SIZE], 273 | ) -> JsonResult { 274 | match data.get(index + 1..=index + SIZE) { 275 | Some(s) if s == expected => Ok(index + SIZE + 1), 276 | // TODO very sadly iterating over expected cause extra branches in the generated assembly 277 | // and is significantly slower than just returning an error 278 | _ => { 279 | index += 1; 280 | for c in &expected { 281 | match data.get(index) { 282 | Some(v) if v == c => index += 1, 283 | Some(_) => return json_err!(ExpectedSomeIdent, index), 284 | _ => break, 285 | } 286 | } 287 | json_err!(EofWhileParsingValue, index) 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /merde_json/src/jiter_lite/simd_aarch64.rs: -------------------------------------------------------------------------------- 1 | use crate::jiter_lite as jiter; 2 | 3 | use std::mem::transmute; 4 | #[rustfmt::skip] 5 | use std::arch::aarch64::{ 6 | uint8x16_t, 7 | uint16x8_t, 8 | uint32x4_t, 9 | uint64x2_t, 10 | uint8x8_t, 11 | uint16x4_t, 12 | uint32x2_t, 13 | uint64x1_t, 14 | // 16 byte methods 15 | vld1q_u8 as simd_load_16, 16 | vcgtq_u8 as simd_gt_16, 17 | vcltq_u8 as simd_lt_16, 18 | vorrq_u8 as simd_or_16, 19 | vceqq_u8 as simd_eq_16, 20 | vextq_u8 as combine_vecs_16, 21 | vsubq_u8 as simd_sub_16, 22 | vmulq_u8 as simd_mul_16, 23 | vpaddlq_u8 as simd_add_16, 24 | vmulq_u16 as simd_mul_u16_8, 25 | vpaddlq_u16 as simd_add_u16_8, 26 | vmulq_u32 as simd_mul_u32_4, 27 | vpaddlq_u32 as simd_add_u32_4, 28 | // 8 byte methods 29 | vget_low_u8 as simd_get_low, 30 | vext_u8 as combine_vecs_8, 31 | vsub_u8 as simd_sub_8, 32 | vmul_u8 as simd_mul_8, 33 | vpaddl_u8 as simd_add_8, 34 | vmul_u16 as simd_mul_u16_4, 35 | vpaddl_u16 as simd_add_u16_4, 36 | vmul_u32 as simd_mul_u32_2, 37 | vpaddl_u32 as simd_add_u32_2, 38 | }; 39 | use jiter::errors::JsonResult; 40 | 41 | use jiter::number_decoder::{decode_int_chunk_fallback, IntChunk}; 42 | use jiter::string_decoder::StringChunk; 43 | 44 | type SimdVecu8_16 = uint8x16_t; 45 | type SimdVecu16_8 = uint16x8_t; 46 | type SimdVecu32_4 = uint32x4_t; 47 | type SimdVecu64_2 = uint64x2_t; 48 | 49 | type SimdVecu8_8 = uint8x8_t; 50 | type SimdVecu16_4 = uint16x4_t; 51 | type SimdVecu32_2 = uint32x2_t; 52 | type SimdVecu64_1 = uint64x1_t; 53 | const SIMD_STEP: usize = 16; 54 | 55 | macro_rules! simd_const { 56 | ($array:expr) => { 57 | unsafe { transmute($array) } 58 | }; 59 | } 60 | 61 | const ZERO_DIGIT_U8_8: SimdVecu8_8 = simd_const!([b'0'; 8]); 62 | const ZERO_VAL_U8_8: SimdVecu8_8 = simd_const!([0u8; 8]); 63 | const ALT_MUL_U8_8: SimdVecu8_8 = simd_const!([10u8, 1u8, 10u8, 1u8, 10u8, 1u8, 10u8, 1u8]); 64 | const ALT_MUL_U16_4: SimdVecu16_4 = simd_const!([100u16, 1u16, 100u16, 1u16]); 65 | const ALT_MUL_U32_2: SimdVecu32_2 = simd_const!([10000u32, 1u32]); 66 | const ZERO_DIGIT_16: SimdVecu8_16 = simd_const!([b'0'; 16]); 67 | const NINE_DIGIT_16: SimdVecu8_16 = simd_const!([b'9'; 16]); 68 | 69 | const ZERO_VAL_U8_16: SimdVecu8_16 = simd_const!([0u8; 16]); 70 | const ALT_MUL_U8_16: SimdVecu8_16 = simd_const!([ 71 | 10u8, 1u8, 10u8, 1u8, 10u8, 1u8, 10u8, 1u8, 10u8, 1u8, 10u8, 1u8, 10u8, 1u8, 10u8, 1u8 72 | ]); 73 | const ALT_MUL_U16_8: SimdVecu16_8 = 74 | simd_const!([100u16, 1u16, 100u16, 1u16, 100u16, 1u16, 100u16, 1u16]); 75 | const ALT_MUL_U32_4: SimdVecu32_4 = simd_const!([10000u32, 1u32, 10000u32, 1u32]); 76 | 77 | #[inline(always)] 78 | pub(crate) fn decode_int_chunk(data: &[u8], index: usize) -> (IntChunk, usize) { 79 | if let Some(byte_chunk) = data.get(index..index + SIMD_STEP) { 80 | let byte_vec = load_slice(byte_chunk); 81 | 82 | let digit_mask = get_digit_mask(byte_vec); 83 | if is_zero(digit_mask) { 84 | // all lanes are digits, parse the full vector 85 | let value = unsafe { full_calc(byte_vec, 16) }; 86 | (IntChunk::Ongoing(value), index + SIMD_STEP) 87 | } else { 88 | // some lanes are not digits, transmute to a pair of u64 and find the first non-digit 89 | let last_digit = find_end(digit_mask); 90 | let index = index + last_digit as usize; 91 | if next_is_float(data, index) { 92 | (IntChunk::Float, index) 93 | } else if last_digit <= 8 { 94 | // none-digit in the first 8 bytes 95 | let value = unsafe { first_half_calc(byte_vec, last_digit) }; 96 | (IntChunk::Done(value), index) 97 | } else { 98 | // none-digit in the last 8 bytes 99 | let value = unsafe { full_calc(byte_vec, last_digit) }; 100 | (IntChunk::Done(value), index) 101 | } 102 | } 103 | } else { 104 | // we got near the end of the string, fall back to the slow path 105 | decode_int_chunk_fallback(data, index, 0) 106 | } 107 | } 108 | 109 | #[rustfmt::skip] 110 | fn get_digit_mask(byte_vec: SimdVecu8_16) -> SimdVecu8_16 { 111 | unsafe { 112 | simd_or_16( 113 | simd_lt_16(byte_vec, ZERO_DIGIT_16), 114 | simd_gt_16(byte_vec, NINE_DIGIT_16), 115 | ) 116 | } 117 | } 118 | 119 | unsafe fn first_half_calc(byte_vec: SimdVecu8_16, last_digit: u32) -> u64 { 120 | let small_byte_vec = simd_get_low(byte_vec); 121 | // subtract ascii '0' from every byte to get the digit values 122 | let digits: SimdVecu8_8 = simd_sub_8(small_byte_vec, ZERO_DIGIT_U8_8); 123 | let digits = match last_digit { 124 | 0 => return 0, 125 | 1 => { 126 | let t: [u8; 8] = transmute(digits); 127 | return t[0] as u64; 128 | } 129 | 2 => combine_vecs_8::<2>(ZERO_VAL_U8_8, digits), 130 | 3 => combine_vecs_8::<3>(ZERO_VAL_U8_8, digits), 131 | 4 => combine_vecs_8::<4>(ZERO_VAL_U8_8, digits), 132 | 5 => combine_vecs_8::<5>(ZERO_VAL_U8_8, digits), 133 | 6 => combine_vecs_8::<6>(ZERO_VAL_U8_8, digits), 134 | 7 => combine_vecs_8::<7>(ZERO_VAL_U8_8, digits), 135 | 8 => digits, 136 | _ => unreachable!("last_digit should be less than 8"), 137 | }; 138 | // multiple every other digit by 10 139 | let x: SimdVecu8_8 = simd_mul_8(digits, ALT_MUL_U8_8); 140 | // add the value together and combine the 8x8-bit lanes into 4x16-bit lanes 141 | let x: SimdVecu16_4 = simd_add_8(x); 142 | // multiple every other digit by 100 143 | let x: SimdVecu16_4 = simd_mul_u16_4(x, ALT_MUL_U16_4); 144 | // add the value together and combine the 4x16-bit lanes into 2x32-bit lanes 145 | let x: SimdVecu32_2 = simd_add_u16_4(x); 146 | // multiple the first value 10000 147 | let x: SimdVecu32_2 = simd_mul_u32_2(x, ALT_MUL_U32_2); 148 | // add the value together and combine the 2x32-bit lanes into 1x64-bit lane 149 | let x: SimdVecu64_1 = simd_add_u32_2(x); 150 | // transmute the 64-bit lane into a u64 151 | transmute(x) 152 | } 153 | 154 | unsafe fn full_calc(byte_vec: SimdVecu8_16, last_digit: u32) -> u64 { 155 | // subtract ascii '0' from every byte to get the digit values 156 | let digits: SimdVecu8_16 = simd_sub_16(byte_vec, ZERO_DIGIT_16); 157 | let digits = match last_digit { 158 | 9 => combine_vecs_16::<9>(ZERO_VAL_U8_16, digits), 159 | 10 => combine_vecs_16::<10>(ZERO_VAL_U8_16, digits), 160 | 11 => combine_vecs_16::<11>(ZERO_VAL_U8_16, digits), 161 | 12 => combine_vecs_16::<12>(ZERO_VAL_U8_16, digits), 162 | 13 => combine_vecs_16::<13>(ZERO_VAL_U8_16, digits), 163 | 14 => combine_vecs_16::<14>(ZERO_VAL_U8_16, digits), 164 | 15 => combine_vecs_16::<15>(ZERO_VAL_U8_16, digits), 165 | 16 => digits, 166 | _ => unreachable!("last_digit should be between 9 and 16"), 167 | }; 168 | // multiple every other digit by 10 169 | let x: SimdVecu8_16 = simd_mul_16(digits, ALT_MUL_U8_16); 170 | // add the value together and combine the 16x8-bit lanes into 8x16-bit lanes 171 | let x: SimdVecu16_8 = simd_add_16(x); 172 | // multiple every other digit by 100 173 | let x: SimdVecu16_8 = simd_mul_u16_8(x, ALT_MUL_U16_8); 174 | // add the value together and combine the 8x16-bit lanes into 4x32-bit lanes 175 | let x: SimdVecu32_4 = simd_add_u16_8(x); 176 | // multiple every other digit by 10000 177 | let x: SimdVecu32_4 = simd_mul_u32_4(x, ALT_MUL_U32_4); 178 | // add the value together and combine the 4x32-bit lanes into 2x64-bit lane 179 | let x: SimdVecu64_2 = simd_add_u32_4(x); 180 | 181 | // transmute the 2x64-bit lane into an array; 182 | let t: [u64; 2] = transmute(x); 183 | // since the data started out as digits, it's safe to assume the result fits in a u64 184 | t[0].wrapping_mul(100_000_000).wrapping_add(t[1]) 185 | } 186 | 187 | fn next_is_float(data: &[u8], index: usize) -> bool { 188 | let next = unsafe { data.get_unchecked(index) }; 189 | matches!(next, b'.' | b'e' | b'E') 190 | } 191 | 192 | const QUOTE_16: SimdVecu8_16 = simd_const!([b'"'; 16]); 193 | const BACKSLASH_16: SimdVecu8_16 = simd_const!([b'\\'; 16]); 194 | // values below 32 are control characters 195 | const CONTROL_16: SimdVecu8_16 = simd_const!([32u8; 16]); 196 | const ASCII_MAX_16: SimdVecu8_16 = simd_const!([127u8; 16]); 197 | 198 | #[inline(always)] 199 | pub(crate) fn decode_string_chunk( 200 | data: &[u8], 201 | mut index: usize, 202 | mut ascii_only: bool, 203 | allow_partial: bool, 204 | ) -> JsonResult<(StringChunk, bool, usize)> { 205 | while let Some(byte_chunk) = data.get(index..index + SIMD_STEP) { 206 | let byte_vec = load_slice(byte_chunk); 207 | 208 | let ascii_mask = string_ascii_mask(byte_vec); 209 | if is_zero(ascii_mask) { 210 | // this chunk is just ascii, continue to the next chunk 211 | index += SIMD_STEP; 212 | } else { 213 | // this chunk contains either a stop character or a non-ascii character 214 | let a: [u8; 16] = unsafe { transmute(byte_vec) }; 215 | #[allow(clippy::redundant_else)] 216 | if let Some(r) = StringChunk::decode_array(a, &mut index, ascii_only) { 217 | return r; 218 | } else { 219 | ascii_only = false; 220 | } 221 | } 222 | } 223 | // we got near the end of the string, fall back to the slow path 224 | StringChunk::decode_fallback(data, index, ascii_only, allow_partial) 225 | } 226 | 227 | #[rustfmt::skip] 228 | /// returns a mask where any non-zero byte means we don't have a simple ascii character, either 229 | /// quote, backslash, control character, or non-ascii (above 127) 230 | fn string_ascii_mask(byte_vec: SimdVecu8_16) -> SimdVecu8_16 { 231 | unsafe { 232 | simd_or_16( 233 | simd_eq_16(byte_vec, QUOTE_16), 234 | simd_or_16( 235 | simd_eq_16(byte_vec, BACKSLASH_16), 236 | simd_or_16( 237 | simd_gt_16(byte_vec, ASCII_MAX_16), 238 | simd_lt_16(byte_vec, CONTROL_16), 239 | ) 240 | ) 241 | ) 242 | } 243 | } 244 | 245 | fn find_end(digit_mask: SimdVecu8_16) -> u32 { 246 | let t: [u64; 2] = unsafe { transmute(digit_mask) }; 247 | if t[0] != 0 { 248 | // non-digit in the first 8 bytes 249 | t[0].trailing_zeros() / 8 250 | } else { 251 | t[1].trailing_zeros() / 8 + 8 252 | } 253 | } 254 | 255 | /// return true if all bytes are zero 256 | fn is_zero(vec: SimdVecu8_16) -> bool { 257 | let t: [u64; 2] = unsafe { transmute(vec) }; 258 | t[0] == 0 && t[1] == 0 259 | } 260 | 261 | fn load_slice(bytes: &[u8]) -> SimdVecu8_16 { 262 | debug_assert_eq!(bytes.len(), 16); 263 | unsafe { simd_load_16(bytes.as_ptr()) } 264 | } 265 | -------------------------------------------------------------------------------- /merde_json/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![doc = include_str!("../README.md")] 3 | 4 | mod deserialize; 5 | pub use deserialize::JsonDeserializer; 6 | 7 | mod serialize; 8 | pub use serialize::{JsonSerializer, JsonSerializerWriter}; 9 | 10 | mod jiter_lite; 11 | 12 | use merde_core::{ 13 | Deserialize, DeserializeOwned, DynDeserializerExt, DynSerialize, DynSerializerExt, MerdeError, 14 | MetastackExt, 15 | }; 16 | 17 | /// Deserialize an instance of type `T` from a string of JSON text. 18 | pub fn from_str<'s, T>(s: &'s str) -> Result> 19 | where 20 | T: Deserialize<'s>, 21 | { 22 | let mut deser = JsonDeserializer::new(s); 23 | deser.deserialize::() 24 | } 25 | 26 | /// Deserialize an instance of type `T` from a string of JSON text, 27 | /// and return its static variant e.g. (CowStr<'static>, etc.) 28 | pub fn from_str_owned(s: &str) -> Result> 29 | where 30 | T: DeserializeOwned, 31 | { 32 | let mut deser = JsonDeserializer::new(s); 33 | T::deserialize_owned(&mut deser).run_sync_with_metastack() 34 | } 35 | 36 | /// Deserialize an instance of type `T` from a byte slice of JSON text. 37 | pub fn from_bytes<'s, T>(b: &'s [u8]) -> Result> 38 | where 39 | T: Deserialize<'s>, 40 | { 41 | let s = std::str::from_utf8(b)?; 42 | from_str(s) 43 | } 44 | 45 | /// Deserialize an instance of type `T` from a byte slice of JSON text, 46 | /// and return its static variant e.g. (CowStr<'static>, etc.) 47 | pub fn from_bytes_owned(b: &[u8]) -> Result> 48 | where 49 | T: DeserializeOwned, 50 | { 51 | let s = std::str::from_utf8(b)?; 52 | from_str_owned::(s) 53 | } 54 | 55 | /// Serialize the given data structure as a String of JSON. 56 | pub fn to_string(value: &dyn DynSerialize) -> Result> { 57 | // SAFETY: This is safe because we know that the JSON serialization 58 | // produced by `to_json_bytes` will always be valid UTF-8. 59 | let res = unsafe { String::from_utf8_unchecked(to_vec(value)?) }; 60 | Ok(res) 61 | } 62 | 63 | /// Serialize as JSON to a `Vec` 64 | pub fn to_vec(value: &dyn DynSerialize) -> Result, MerdeError<'static>> { 65 | let mut v: Vec = vec![]; 66 | { 67 | let mut s = JsonSerializer::new(&mut v); 68 | s.dyn_serialize(value)?; 69 | } 70 | Ok(v) 71 | } 72 | 73 | /// Serialize the given data structure as JSON into the I/O stream. 74 | pub fn to_writer( 75 | writer: &mut dyn std::io::Write, 76 | value: &dyn DynSerialize, 77 | ) -> Result<(), MerdeError<'static>> { 78 | let mut s = JsonSerializer::from_writer(writer); 79 | s.dyn_serialize(value)?; 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /merde_json/src/serialize.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::VecDeque, future::Future, io::Write}; 2 | 3 | use merde_core::{Event, MerdeError, Serializer}; 4 | 5 | /// Something the JSON serializer can write to 6 | pub trait JsonSerializerWriter { 7 | /// Extend the buffer with the given slice 8 | fn extend_from_slice( 9 | &mut self, 10 | slice: &[u8], 11 | ) -> impl Future>; 12 | } 13 | 14 | impl JsonSerializerWriter for &mut Vec { 15 | async fn extend_from_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { 16 | Vec::extend_from_slice(self, slice); 17 | Ok(()) 18 | } 19 | } 20 | 21 | /// A wrapper around a `std::io::Write` that implements `JsonSerializerWriter` 22 | pub struct SyncWriteWrapper<'s>(&'s mut dyn std::io::Write); 23 | 24 | impl<'s> JsonSerializerWriter for SyncWriteWrapper<'s> { 25 | async fn extend_from_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { 26 | self.0.write_all(slice) 27 | } 28 | } 29 | 30 | #[cfg(feature = "tokio")] 31 | pub mod tokio_io { 32 | //! Adapter types from `tokio::io::AsyncWrite` to `JsonSerializerWriter` 33 | 34 | use std::pin::Pin; 35 | 36 | use tokio::io::AsyncWriteExt; 37 | 38 | /// Implements `JsonSerializerWriter` for `tokio::io::AsyncWrite` 39 | pub struct AsyncWriteWrapper<'s>(pub Pin<&'s mut dyn tokio::io::AsyncWrite>); 40 | 41 | impl super::JsonSerializerWriter for AsyncWriteWrapper<'_> { 42 | async fn extend_from_slice(&mut self, slice: &[u8]) -> Result<(), std::io::Error> { 43 | self.0.write_all(slice).await 44 | } 45 | } 46 | } 47 | 48 | /// Writes JSON to a `Vec`. None of its methods can fail, since it doesn't target 49 | /// an `io::Write`. You can provide your own buffer via `JsonSerializer::from_vec`. 50 | /// 51 | /// When you're done with the serializer, you can call `JsonSerializer::into_inner` to 52 | /// get the buffer back. 53 | #[derive(Default)] 54 | pub struct JsonSerializer 55 | where 56 | W: JsonSerializerWriter, 57 | { 58 | w: W, 59 | stack: VecDeque, 60 | } 61 | 62 | enum StackFrame { 63 | // the next item to be written is an array element 64 | Array { first: bool }, 65 | // the next item to be written is a map key 66 | MapKey { first: bool }, 67 | // the next item to be written is a map value 68 | // (and needs a ":" before it) 69 | MapValue, 70 | } 71 | 72 | impl Serializer for JsonSerializer 73 | where 74 | W: JsonSerializerWriter, 75 | { 76 | #[allow(clippy::manual_async_fn)] 77 | fn write<'fut>( 78 | &'fut mut self, 79 | ev: Event<'fut>, 80 | ) -> impl Future>> + 'fut { 81 | async move { 82 | let stack_top = self.stack.back_mut(); 83 | if let Some(stack_top) = stack_top { 84 | match stack_top { 85 | StackFrame::Array { first } => { 86 | if matches!(ev, merde_core::Event::ArrayEnd) { 87 | self.w.extend_from_slice(b"]").await?; 88 | self.stack.pop_back(); 89 | return Ok(()); 90 | } else if *first { 91 | *first = false 92 | } else { 93 | self.w.extend_from_slice(b",").await?; 94 | } 95 | } 96 | StackFrame::MapKey { first } => { 97 | if matches!(ev, merde_core::Event::MapEnd) { 98 | self.w.extend_from_slice(b"}").await?; 99 | self.stack.pop_back(); 100 | return Ok(()); 101 | } else { 102 | if !*first { 103 | self.w.extend_from_slice(b",").await?; 104 | } 105 | *stack_top = StackFrame::MapValue; 106 | // and then let the value write itself 107 | } 108 | } 109 | StackFrame::MapValue => { 110 | self.w.extend_from_slice(b":").await?; 111 | *stack_top = StackFrame::MapKey { first: false }; 112 | } 113 | } 114 | } 115 | 116 | match ev { 117 | merde_core::Event::Null => { 118 | self.w.extend_from_slice(b"null").await?; 119 | } 120 | merde_core::Event::Bool(b) => { 121 | self.w 122 | .extend_from_slice(if b { b"true" } else { b"false" }) 123 | .await?; 124 | } 125 | merde_core::Event::I64(i) => { 126 | let mut buf = itoa::Buffer::new(); 127 | self.w.extend_from_slice(buf.format(i).as_bytes()).await?; 128 | } 129 | merde_core::Event::U64(u) => { 130 | let mut buf = itoa::Buffer::new(); 131 | self.w.extend_from_slice(buf.format(u).as_bytes()).await?; 132 | } 133 | merde_core::Event::F64(f) => { 134 | let mut buf = ryu::Buffer::new(); 135 | self.w.extend_from_slice(buf.format(f).as_bytes()).await?; 136 | } 137 | merde_core::Event::Str(s) => { 138 | // slow path 139 | self.w.extend_from_slice(b"\"").await?; 140 | for c in s.chars() { 141 | match c { 142 | '"' => self.w.extend_from_slice(b"\\\"").await?, 143 | '\\' => self.w.extend_from_slice(b"\\\\").await?, 144 | '\n' => self.w.extend_from_slice(b"\\n").await?, 145 | '\r' => self.w.extend_from_slice(b"\\r").await?, 146 | '\t' => self.w.extend_from_slice(b"\\t").await?, 147 | c if c.is_control() => { 148 | let mut buf = [0u8; 6]; 149 | write!(&mut buf[..], "\\u{:04x}", c as u32).unwrap(); 150 | self.w.extend_from_slice(&buf[..6]).await?; 151 | } 152 | c => self.w.extend_from_slice(c.to_string().as_bytes()).await?, 153 | } 154 | } 155 | self.w.extend_from_slice(b"\"").await?; 156 | } 157 | merde_core::Event::MapStart(_) => { 158 | self.w.extend_from_slice(b"{").await?; 159 | self.stack.push_back(StackFrame::MapKey { first: true }); 160 | } 161 | merde_core::Event::MapEnd => { 162 | self.w.extend_from_slice(b"}").await?; 163 | } 164 | merde_core::Event::ArrayStart(_) => { 165 | self.w.extend_from_slice(b"[").await?; 166 | self.stack.push_back(StackFrame::Array { first: true }); 167 | } 168 | merde_core::Event::ArrayEnd => { 169 | panic!("array end without array start"); 170 | } 171 | merde_core::Event::Bytes(_) => { 172 | // figure out what to do with those? maybe base64, maybe an array of 173 | // integers? unclear. maybe it should be a serializer setting. 174 | } 175 | } 176 | Ok(()) 177 | } 178 | } 179 | } 180 | 181 | impl JsonSerializer 182 | where 183 | W: JsonSerializerWriter, 184 | { 185 | /// Uses the provided buffer as the target for serialization. 186 | pub fn new(w: W) -> Self { 187 | JsonSerializer { 188 | w, 189 | stack: Default::default(), 190 | } 191 | } 192 | } 193 | 194 | impl<'w> JsonSerializer> { 195 | /// Makes a json serializer that writes to a std::io::Write 196 | pub fn from_writer(w: &'w mut dyn std::io::Write) -> JsonSerializer> { 197 | JsonSerializer::new(SyncWriteWrapper(w)) 198 | } 199 | } 200 | 201 | #[cfg(feature = "tokio")] 202 | impl<'w> JsonSerializer> { 203 | /// Makes a json serializer that writes to a tokio::io::AsyncWrite 204 | pub fn from_tokio_writer( 205 | w: std::pin::Pin<&'w mut SW>, 206 | ) -> JsonSerializer> { 207 | JsonSerializer::new(tokio_io::AsyncWriteWrapper(w)) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /merde_loggingserializer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merde_loggingserializer" 3 | version = "8.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | merde_core = { version = "10.0.6", path = "../merde_core" } 9 | 10 | -------------------------------------------------------------------------------- /merde_loggingserializer/src/lib.rs: -------------------------------------------------------------------------------- 1 | use merde_core::{Deserializer, Event, MerdeError}; 2 | 3 | pub struct LoggingDeserializer<'s, I> 4 | where 5 | I: Deserializer<'s>, 6 | { 7 | inner: I, 8 | starter: Option>, 9 | } 10 | 11 | impl<'s, I> std::fmt::Debug for LoggingDeserializer<'s, I> 12 | where 13 | I: Deserializer<'s>, 14 | { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | f.debug_struct("LoggingDeserializer") 17 | .field("inner", &self.inner) 18 | .finish() 19 | } 20 | } 21 | 22 | impl<'s, I> LoggingDeserializer<'s, I> 23 | where 24 | I: Deserializer<'s>, 25 | { 26 | pub fn new(inner: I) -> Self { 27 | Self { 28 | inner, 29 | starter: None, 30 | } 31 | } 32 | } 33 | 34 | impl<'s, I> Deserializer<'s> for LoggingDeserializer<'s, I> 35 | where 36 | I: Deserializer<'s>, 37 | { 38 | async fn next(&mut self) -> Result, MerdeError<'s>> { 39 | if let Some(ev) = self.starter.take() { 40 | eprintln!("> (from starter) {:?}", ev); 41 | return Ok(ev); 42 | } 43 | 44 | let ev = self.inner.next().await?; 45 | eprintln!("> (from inner.next) {:?}", ev); 46 | Ok(ev) 47 | } 48 | 49 | fn put_back(&mut self, ev: Event<'s>) -> Result<(), MerdeError<'s>> { 50 | if self.starter.is_some() { 51 | return Err(MerdeError::PutBackCalledTwice); 52 | } 53 | self.starter = Some(ev); 54 | Ok(()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /merde_msgpack/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [10.0.6](https://github.com/bearcove/merde/compare/merde_msgpack-v10.0.5...merde_msgpack-v10.0.6) - 2025-04-25 11 | 12 | ### Other 13 | 14 | - updated the following local packages: merde_core 15 | 16 | ## [10.0.5](https://github.com/bearcove/merde/compare/merde_msgpack-v10.0.4...merde_msgpack-v10.0.5) - 2025-04-16 17 | 18 | ### Other 19 | 20 | - updated the following local packages: merde_core 21 | 22 | ## [10.0.4](https://github.com/bearcove/merde/compare/merde_msgpack-v10.0.3...merde_msgpack-v10.0.4) - 2025-04-16 23 | 24 | ### Other 25 | 26 | - updated the following local packages: merde_core 27 | 28 | ## [10.0.3](https://github.com/bearcove/merde/compare/merde_msgpack-v10.0.2...merde_msgpack-v10.0.3) - 2025-04-16 29 | 30 | ### Other 31 | 32 | - updated the following local packages: merde_core 33 | 34 | ## [10.0.2](https://github.com/bearcove/merde/compare/merde_msgpack-v10.0.1...merde_msgpack-v10.0.2) - 2025-03-06 35 | 36 | ### Other 37 | 38 | - updated the following local packages: merde_core 39 | 40 | ## [10.0.1](https://github.com/bearcove/merde/compare/merde_msgpack-v10.0.0...merde_msgpack-v10.0.1) - 2025-01-29 41 | 42 | ### Other 43 | 44 | - updated the following local packages: merde_core 45 | 46 | ## [10.0.0](https://github.com/bearcove/merde/compare/merde_msgpack-v9.0.1...merde_msgpack-v10.0.0) - 2024-12-04 47 | 48 | ### Other 49 | 50 | - [**breaking**] Force a major version bump 51 | 52 | ## [9.0.1](https://github.com/bearcove/merde/compare/merde_msgpack-v9.0.0...merde_msgpack-v9.0.1) - 2024-12-02 53 | 54 | ### Other 55 | 56 | - updated the following local packages: merde_core 57 | 58 | ## [9.0.0](https://github.com/bearcove/merde/compare/merde_msgpack-v8.0.2...merde_msgpack-v9.0.0) - 2024-11-30 59 | 60 | ### Other 61 | 62 | - remove async versions of things 63 | - More! 64 | - yay other errors 65 | - Expose an async Deserializer interface 66 | - Make Deserializer::next async 67 | 68 | ## [8.0.2](https://github.com/bearcove/merde/compare/merde_msgpack-v8.0.1...merde_msgpack-v8.0.2) - 2024-11-24 69 | 70 | ### Other 71 | 72 | - updated the following local packages: merde_core 73 | 74 | ## [8.0.1](https://github.com/bearcove/merde/compare/merde_msgpack-v8.0.0...merde_msgpack-v8.0.1) - 2024-11-20 75 | 76 | ### Other 77 | 78 | - updated the following local packages: merde_core 79 | 80 | ## [8.0.0](https://github.com/bearcove/merde/compare/merde_msgpack-v7.1.1...merde_msgpack-v8.0.0) - 2024-11-04 81 | 82 | ### Other 83 | 84 | - Introduce Serialize trait 85 | 86 | ## [7.1.1](https://github.com/bearcove/merde/compare/merde_msgpack-v7.1.0...merde_msgpack-v7.1.1) - 2024-10-07 87 | 88 | ### Fixed 89 | 90 | - Proper starter handling in merde_msgpack 91 | 92 | ## [7.1.0](https://github.com/bearcove/merde/releases/tag/merde_msgpack-v7.1.0) - 2024-10-06 93 | 94 | ### Other 95 | 96 | - Add support for msgpack deserialization 97 | -------------------------------------------------------------------------------- /merde_msgpack/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merde_msgpack" 3 | version = "10.0.6" 4 | edition = "2021" 5 | authors = ["Amos Wenger "] 6 | description = "msgpack serizliation/deserialization for merde" 7 | license = "Apache-2.0 OR MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/bearcove/merde" 10 | keywords = ["msgpack", "messagepack", "serialization", "deserialization"] 11 | categories = ["encoding", "parser-implementations"] 12 | 13 | [dependencies] 14 | merde_core = { version = "10.0.6", path = "../merde_core" } 15 | rmp = "0.8.14" 16 | 17 | [dev-dependencies] 18 | merde_loggingserializer = { path = "../merde_loggingserializer" } 19 | 20 | -------------------------------------------------------------------------------- /merde_msgpack/Justfile: -------------------------------------------------------------------------------- 1 | 2 | regen: 3 | cargo run --manifest-path testdata-maker/Cargo.toml 4 | -------------------------------------------------------------------------------- /merde_msgpack/README.md: -------------------------------------------------------------------------------- 1 | [![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) 2 | [![crates.io](https://img.shields.io/crates/v/merde_rmp.svg)](https://crates.io/crates/merde_rmp) 3 | [![docs.rs](https://docs.rs/merde_rmp/badge.svg)](https://docs.rs/merde_rmp) 4 | 5 | # merde_rmp 6 | 7 | ![The merde logo: a glorious poop floating above a pair of hands](https://github.com/user-attachments/assets/763d60e0-5101-48af-bc72-f96f516a5d0f) 8 | 9 | _Logo by [MisiasArt](https://misiasart.com)_ 10 | 11 | Adds RMP serialization/deserialization support for 12 | [merde](https://crates.io/crates/merde). 13 | 14 | You would normally add a dependency on [merde](https://crates.io/crates/merde) 15 | directly, enabling its `rmp` feature. 16 | -------------------------------------------------------------------------------- /merde_msgpack/testdata-maker/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /merde_msgpack/testdata-maker/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.4.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 10 | 11 | [[package]] 12 | name = "byteorder" 13 | version = "1.5.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 16 | 17 | [[package]] 18 | name = "num-traits" 19 | version = "0.2.19" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 22 | dependencies = [ 23 | "autocfg", 24 | ] 25 | 26 | [[package]] 27 | name = "paste" 28 | version = "1.0.15" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 31 | 32 | [[package]] 33 | name = "rmp" 34 | version = "0.8.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" 37 | dependencies = [ 38 | "byteorder", 39 | "num-traits", 40 | "paste", 41 | ] 42 | 43 | [[package]] 44 | name = "rmpv" 45 | version = "1.3.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "58450723cd9ee93273ce44a20b6ec4efe17f8ed2e3631474387bfdecf18bb2a9" 48 | dependencies = [ 49 | "num-traits", 50 | "rmp", 51 | ] 52 | 53 | [[package]] 54 | name = "testdata-maker" 55 | version = "0.1.0" 56 | dependencies = [ 57 | "rmpv", 58 | ] 59 | -------------------------------------------------------------------------------- /merde_msgpack/testdata-maker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testdata-maker" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [dependencies] 9 | rmpv = "1.3.0" 10 | 11 | -------------------------------------------------------------------------------- /merde_msgpack/testdata-maker/src/main.rs: -------------------------------------------------------------------------------- 1 | use rmpv::Value; 2 | use std::fs::File; 3 | use std::io::Write; 4 | 5 | fn generate_test_messagepack() -> Vec { 6 | let value = Value::Array(vec![ 7 | Value::Nil, 8 | Value::Boolean(false), 9 | Value::Boolean(true), 10 | Value::Integer(42.into()), 11 | Value::Integer((-123).into()), 12 | Value::Integer(1000000.into()), 13 | Value::Integer((-9876543210i64).into()), 14 | Value::Integer(18446744073709551615u64.into()), 15 | Value::F32(1.23456), 16 | Value::F32(0.0), 17 | Value::F32(f32::INFINITY), 18 | Value::F32(f32::NEG_INFINITY), 19 | Value::F32(f32::MIN), 20 | Value::F32(f32::MAX), 21 | Value::F64(1.23456789), 22 | Value::F64(0.0), 23 | Value::F64(f64::INFINITY), 24 | Value::F64(f64::NEG_INFINITY), 25 | Value::F64(f64::MIN), 26 | Value::F64(f64::MAX), 27 | Value::F64(1e-100), 28 | Value::F64(1e100), 29 | Value::String("Hello, MessagePack!".into()), 30 | Value::Binary(vec![]), 31 | Value::Binary(vec![0xDE, 0xAD, 0xBE, 0xEF]), 32 | Value::Binary(vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]), 33 | Value::Binary(vec![0xFF; 256]), 34 | Value::Array(vec![]), 35 | Value::Array(vec![Value::Nil, Value::Boolean(true)]), 36 | Value::Map(vec![ 37 | (Value::String("key1".into()), Value::Integer(1.into())), 38 | (Value::String("key2".into()), Value::F64(2.7118)), 39 | ]), 40 | Value::Map(vec![]), 41 | ]); 42 | 43 | let mut buf = Vec::new(); 44 | rmpv::encode::write_value(&mut buf, &value).unwrap(); 45 | buf 46 | } 47 | 48 | fn main() { 49 | let encoded = generate_test_messagepack(); 50 | let mut file = File::create("testdata/test.msgpack").unwrap(); 51 | file.write_all(&encoded).unwrap(); 52 | } 53 | -------------------------------------------------------------------------------- /merde_msgpack/testdata/test.msgpack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bearcove/merde/f469a036e6f08f69f0a70361e2b579b064d6963a/merde_msgpack/testdata/test.msgpack -------------------------------------------------------------------------------- /merde_yaml/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [10.0.6](https://github.com/bearcove/merde/compare/merde_yaml-v10.0.5...merde_yaml-v10.0.6) - 2025-04-25 11 | 12 | ### Other 13 | 14 | - updated the following local packages: merde_core 15 | 16 | ## [10.0.5](https://github.com/bearcove/merde/compare/merde_yaml-v10.0.4...merde_yaml-v10.0.5) - 2025-04-16 17 | 18 | ### Other 19 | 20 | - updated the following local packages: merde_core 21 | 22 | ## [10.0.4](https://github.com/bearcove/merde/compare/merde_yaml-v10.0.3...merde_yaml-v10.0.4) - 2025-04-16 23 | 24 | ### Other 25 | 26 | - updated the following local packages: merde_core 27 | 28 | ## [10.0.3](https://github.com/bearcove/merde/compare/merde_yaml-v10.0.2...merde_yaml-v10.0.3) - 2025-04-16 29 | 30 | ### Other 31 | 32 | - updated the following local packages: merde_core 33 | 34 | ## [10.0.2](https://github.com/bearcove/merde/compare/merde_yaml-v10.0.1...merde_yaml-v10.0.2) - 2025-03-06 35 | 36 | ### Other 37 | 38 | - updated the following local packages: merde_core 39 | 40 | ## [10.0.1](https://github.com/bearcove/merde/compare/merde_yaml-v10.0.0...merde_yaml-v10.0.1) - 2025-01-29 41 | 42 | ### Other 43 | 44 | - updated the following local packages: merde_core 45 | 46 | ## [10.0.0](https://github.com/bearcove/merde/compare/merde_yaml-v9.0.1...merde_yaml-v10.0.0) - 2024-12-04 47 | 48 | ### Other 49 | 50 | - [**breaking**] Force a major version bump 51 | 52 | ## [9.0.1](https://github.com/bearcove/merde/compare/merde_yaml-v9.0.0...merde_yaml-v9.0.1) - 2024-12-02 53 | 54 | ### Other 55 | 56 | - updated the following local packages: merde_core 57 | 58 | ## [9.0.0](https://github.com/bearcove/merde/compare/merde_yaml-v8.0.2...merde_yaml-v9.0.0) - 2024-11-30 59 | 60 | ### Other 61 | 62 | - remove async versions of things 63 | - More! 64 | - yay other errors 65 | - Expose an async Deserializer interface 66 | - Make Deserializer::next async 67 | 68 | ## [8.0.2](https://github.com/bearcove/merde/compare/merde_yaml-v8.0.1...merde_yaml-v8.0.2) - 2024-11-24 69 | 70 | ### Other 71 | 72 | - updated the following local packages: merde_core 73 | 74 | ## [8.0.1](https://github.com/bearcove/merde/compare/merde_yaml-v8.0.0...merde_yaml-v8.0.1) - 2024-11-20 75 | 76 | ### Other 77 | 78 | - updated the following local packages: merde_core 79 | 80 | ## [8.0.0](https://github.com/bearcove/merde/compare/merde_yaml-v7.1.1...merde_yaml-v8.0.0) - 2024-11-04 81 | 82 | ### Other 83 | 84 | - Introduce Serialize trait 85 | 86 | ## [7.1.1](https://github.com/bearcove/merde/compare/merde_yaml-v7.1.0...merde_yaml-v7.1.1) - 2024-10-06 87 | 88 | ### Other 89 | 90 | - Add support for msgpack deserialization 91 | 92 | ## [7.1.0](https://github.com/bearcove/merde/compare/merde_yaml-v7.0.2...merde_yaml-v7.1.0) - 2024-10-06 93 | 94 | ### Added 95 | 96 | - Remove debug prints, provide yaml::from_str/owned 97 | 98 | ## [7.0.2](https://github.com/bearcove/merde/compare/merde_yaml-v7.0.1...merde_yaml-v7.0.2) - 2024-10-04 99 | 100 | ### Other 101 | 102 | - updated the following local packages: merde_core 103 | 104 | ## [7.0.1](https://github.com/bearcove/merde/compare/merde_yaml-v7.0.0...merde_yaml-v7.0.1) - 2024-10-04 105 | 106 | ### Other 107 | 108 | - updated the following local packages: merde_core 109 | 110 | ## [7.0.0](https://github.com/bearcove/merde/compare/merde_yaml-v6.0.0...merde_yaml-v7.0.0) - 2024-10-01 111 | 112 | ### Other 113 | 114 | - respect StreamEnd 115 | -------------------------------------------------------------------------------- /merde_yaml/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merde_yaml" 3 | version = "10.0.6" 4 | edition = "2021" 5 | authors = ["Amos Wenger "] 6 | description = "YAML deserialization for merde" 7 | license = "Apache-2.0 OR MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/bearcove/merde" 10 | keywords = ["yaml", "serialization", "deserialization"] 11 | categories = ["encoding", "parser-implementations"] 12 | 13 | [dependencies] 14 | merde_core = { version = "10.0.6", path = "../merde_core" } 15 | yaml-rust2 = { version = "0.8.1", default-features = false } 16 | 17 | -------------------------------------------------------------------------------- /merde_yaml/README.md: -------------------------------------------------------------------------------- 1 | [![license: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) 2 | [![crates.io](https://img.shields.io/crates/v/merde_yaml.svg)](https://crates.io/crates/merde_yaml) 3 | [![docs.rs](https://docs.rs/merde_yaml/badge.svg)](https://docs.rs/merde_yaml) 4 | 5 | # merde_yaml 6 | 7 | ![The merde logo: a glorious poop floating above a pair of hands](https://github.com/user-attachments/assets/763d60e0-5101-48af-bc72-f96f516a5d0f) 8 | 9 | _Logo by [MisiasArt](https://misiasart.com)_ 10 | 11 | Adds YAML serialization/deserialization support for 12 | [merde](https://crates.io/crates/merde). 13 | 14 | You would normally add a dependency on [merde](https://crates.io/crates/merde) 15 | directly, enabling its `yaml` feature. 16 | -------------------------------------------------------------------------------- /merde_yaml/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![doc = include_str!("../README.md")] 3 | 4 | use std::str::Chars; 5 | 6 | use merde_core::{ 7 | ArrayStart, Deserialize, DeserializeOwned, Deserializer, DynDeserializerExt, Event, MapStart, 8 | MerdeError, 9 | }; 10 | use yaml_rust2::{parser::Parser, scanner::TScalarStyle}; 11 | 12 | /// A YAML deserializer, that implements [`merde_core::Deserializer`]. 13 | pub struct YamlDeserializer<'s> { 14 | source: &'s str, 15 | parser: Parser>, 16 | starter: Option>, 17 | } 18 | 19 | impl std::fmt::Debug for YamlDeserializer<'_> { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | f.debug_struct("YamlDeserializer") 22 | .field("source_len", &self.source) 23 | .finish() 24 | } 25 | } 26 | 27 | impl<'s> YamlDeserializer<'s> { 28 | /// Construct a new YAML deserializer 29 | pub fn new(source: &'s str) -> Self { 30 | Self { 31 | source, 32 | parser: Parser::new_from_str(source), 33 | starter: None, 34 | } 35 | } 36 | } 37 | 38 | impl<'s> Deserializer<'s> for YamlDeserializer<'s> { 39 | async fn next(&mut self) -> Result, MerdeError<'s>> { 40 | loop { 41 | if let Some(starter) = self.starter.take() { 42 | return Ok(starter); 43 | } 44 | 45 | let (ev, _marker) = match self.parser.next_token() { 46 | Ok(ev) => ev, 47 | Err(e) => { 48 | return Err(MerdeError::StringParsingError { 49 | format: "yaml", 50 | source: self.source.into(), 51 | index: 0, 52 | message: e.to_string(), 53 | }); 54 | } 55 | }; 56 | 57 | use yaml_rust2::Event as YEvent; 58 | 59 | let res = match ev { 60 | YEvent::StreamEnd => Err(MerdeError::eof()), 61 | YEvent::Nothing 62 | | YEvent::StreamStart 63 | | YEvent::DocumentStart 64 | | YEvent::DocumentEnd => { 65 | // ignore those 66 | continue; 67 | } 68 | YEvent::Alias(_) => { 69 | todo!("aliases?") 70 | } 71 | YEvent::Scalar(s, style, _anchor_id, tag) => { 72 | if style != TScalarStyle::Plain { 73 | Ok(Event::Str(s.into())) 74 | } else if let Some(tag) = tag { 75 | if tag.handle == "tag:yaml.org,2002:" { 76 | // TODO: use faster int/float parsers 77 | match tag.suffix.as_ref() { 78 | "bool" => match s.parse::() { 79 | Ok(v) => Ok(Event::Bool(v)), 80 | Err(_) => Err(MerdeError::StringParsingError { 81 | format: "yaml", 82 | source: self.source.into(), 83 | index: 0, 84 | message: "failed to parse bool".to_string(), 85 | }), 86 | }, 87 | "int" => match s.parse::() { 88 | Ok(v) => Ok(Event::I64(v)), 89 | Err(_) => Err(MerdeError::StringParsingError { 90 | format: "yaml", 91 | source: self.source.into(), 92 | index: 0, 93 | message: "failed to parse int".to_string(), 94 | }), 95 | }, 96 | "float" => match s.parse::() { 97 | Ok(v) => Ok(Event::F64(v)), 98 | Err(_) => Err(MerdeError::StringParsingError { 99 | format: "yaml", 100 | source: self.source.into(), 101 | index: 0, 102 | message: "failed to parse float".to_string(), 103 | }), 104 | }, 105 | "null" => match s.as_ref() { 106 | "~" | "null" => Ok(Event::Null), 107 | _ => Err(MerdeError::StringParsingError { 108 | format: "yaml", 109 | source: self.source.into(), 110 | index: 0, 111 | message: "failed to parse null".to_string(), 112 | }), 113 | }, 114 | _ => Ok(Event::Str(s.into())), 115 | } 116 | } else { 117 | Ok(Event::Str(s.into())) 118 | } 119 | } else { 120 | // Datatype is not specified, try to infer 121 | if let Ok(v) = s.parse::() { 122 | Ok(Event::Bool(v)) 123 | } else if let Ok(v) = s.parse::() { 124 | Ok(Event::I64(v)) 125 | } else if let Ok(v) = s.parse::() { 126 | Ok(Event::F64(v)) 127 | } else if s == "~" || s == "null" { 128 | Ok(Event::Null) 129 | } else { 130 | Ok(Event::Str(s.into())) 131 | } 132 | } 133 | } 134 | YEvent::SequenceStart(_, _tag) => { 135 | Ok(Event::ArrayStart(ArrayStart { size_hint: None })) 136 | } 137 | YEvent::SequenceEnd => Ok(Event::ArrayEnd), 138 | YEvent::MappingStart(_, _tag) => Ok(Event::MapStart(MapStart { size_hint: None })), 139 | YEvent::MappingEnd => Ok(Event::MapEnd), 140 | }; 141 | return res; 142 | } 143 | } 144 | 145 | fn put_back(&mut self, event: Event<'s>) -> Result<(), MerdeError<'s>> { 146 | if self.starter.is_some() { 147 | return Err(MerdeError::PutBackCalledTwice); 148 | } 149 | self.starter = Some(event); 150 | Ok(()) 151 | } 152 | } 153 | 154 | /// Deserialize an instance of type `T` from a string of YAML text. 155 | pub fn from_str<'s, T>(s: &'s str) -> Result> 156 | where 157 | T: Deserialize<'s>, 158 | { 159 | let mut deser = YamlDeserializer::new(s); 160 | deser.deserialize::() 161 | } 162 | 163 | /// Deserialize an instance of type `T` from a string of YAML text, 164 | /// and return its static variant e.g. (CowStr<'static>, etc.) 165 | pub fn from_str_owned(s: &str) -> Result> 166 | where 167 | T: DeserializeOwned, 168 | { 169 | use merde_core::MetastackExt; 170 | let mut deser = YamlDeserializer::new(s); 171 | T::deserialize_owned(&mut deser).run_sync_with_metastack() 172 | } 173 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.83.0" 3 | -------------------------------------------------------------------------------- /zerodeps-example/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | -------------------------------------------------------------------------------- /zerodeps-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zerodeps-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | merde = { path = "../merde", default-features = false } 9 | 10 | [features] 11 | default = [] 12 | merde = ["merde/core"] 13 | 14 | -------------------------------------------------------------------------------- /zerodeps-example/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "merde")] 2 | use merde::CowStr; 3 | 4 | #[cfg(not(feature = "merde"))] 5 | pub type CowStr<'s> = std::borrow::Cow<'s, str>; 6 | 7 | #[derive(Debug)] 8 | pub struct Person<'s> { 9 | pub name: CowStr<'s>, 10 | pub age: u8, // sorry 256-year-olds 11 | } 12 | 13 | merde::derive! { 14 | impl (Deserialize, Serialize) for struct Person<'s> { name, age } 15 | } 16 | --------------------------------------------------------------------------------