├── .github └── workflows │ ├── audit.yaml │ └── build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── config.rs ├── full.rs ├── migration.rs.incomplete ├── server │ ├── Cargo.toml │ ├── server.ron │ ├── src │ │ └── main.rs │ └── templates │ │ └── index.hbs └── switching.rs ├── src ├── backend │ ├── mmap.rs │ ├── mod.rs │ └── path.rs ├── deser.rs ├── error.rs └── lib.rs └── tests └── integration_tests.rs /.github/workflows/audit.yaml: -------------------------------------------------------------------------------- 1 | # Run cargo audit once every week and create issues for vulnerabilities found 2 | name: Security Audit 3 | on: 4 | schedule: 5 | # Run once every week (Sunday at midnight) 6 | - cron: '0 0 * * 0' 7 | jobs: 8 | audit: 9 | name: Cargo Audit 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout sources 13 | uses: actions/checkout@v1 14 | - name: Run cargo audit 15 | uses: actions-rs/audit-check@v1 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout sources 15 | uses: actions/checkout@v2 16 | 17 | - name: Install stable toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | 24 | - name: Run cargo check 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: check 28 | args: --all-features 29 | 30 | test: 31 | name: Test Suite 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout sources 35 | uses: actions/checkout@v2 36 | 37 | - name: Install stable toolchain 38 | uses: actions-rs/toolchain@v1 39 | with: 40 | profile: minimal 41 | toolchain: stable 42 | override: true 43 | 44 | - name: Run cargo test 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: test 48 | args: --all-features 49 | 50 | lints: 51 | name: Rustfmt 52 | runs-on: ubuntu-latest 53 | steps: 54 | - name: Checkout sources 55 | uses: actions/checkout@v2 56 | 57 | - name: Install stable toolchain 58 | uses: actions-rs/toolchain@v1 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | override: true 63 | components: rustfmt 64 | 65 | - name: Run cargo fmt 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: fmt 69 | args: --all -- --check 70 | 71 | clippy: 72 | name: Clippy 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: Checkout sources 76 | uses: actions/checkout@v2 77 | - name: Install nightly toolchain 78 | uses: actions-rs/toolchain@v1 79 | with: 80 | toolchain: nightly 81 | components: clippy 82 | override: true 83 | - uses: actions-rs/clippy-check@v1 84 | with: 85 | token: ${{ secrets.GITHUB_TOKEN }} 86 | args: --all-features 87 | 88 | tsan: 89 | name: Thread Sanitiser 90 | continue-on-error: true 91 | runs-on: ubuntu-latest 92 | steps: 93 | - name: Checkout sources 94 | uses: actions/checkout@v2 95 | 96 | - name: Install nightly toolchain 97 | uses: actions-rs/toolchain@v1 98 | with: 99 | profile: minimal 100 | toolchain: nightly 101 | override: true 102 | components: rust-src 103 | 104 | - name: Run cargo test 105 | uses: actions-rs/cargo@v1 106 | with: 107 | command: test 108 | args: -Z build-std --target x86_64-unknown-linux-gnu --all-features 109 | env: 110 | CC: clang 111 | CXX: clang++ 112 | CFLAGS: -fsanitize=thread 113 | CXXFLAGS: -fsanitize=thread 114 | RUSTFLAGS: -Zsanitizer=thread 115 | RUSTDOCFLAGS: -Zsanitizer=thread 116 | TSAN_OPTIONS: second_deadlock_stack=1 117 | 118 | miri: 119 | name: Miri 120 | continue-on-error: true 121 | runs-on: ubuntu-latest 122 | steps: 123 | - name: Checkout sources 124 | uses: actions/checkout@v2 125 | 126 | - name: Get latest toolchain version with miri 127 | run: echo "::set-env name=TOOLCHAIN::$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" 128 | 129 | - name: Install latest nightly toolchain with miri 130 | uses: actions-rs/toolchain@v1 131 | with: 132 | profile: minimal 133 | toolchain: nightly-${{ env.TOOLCHAIN }} 134 | override: true 135 | components: rust-src, miri 136 | 137 | - name: Run cargo test 138 | run: cargo miri test --all-features 139 | env: 140 | MIRI_LOG: 1 141 | MIRI_BACKTRACE: 1 142 | 143 | code_cov: 144 | name: Code Coverage (Tarpaulin) 145 | continue-on-error: true 146 | runs-on: ubuntu-latest 147 | steps: 148 | - name: Checkout repository 149 | uses: actions/checkout@v2 150 | 151 | - name: Install stable toolchain 152 | uses: actions-rs/toolchain@v1 153 | with: 154 | toolchain: stable 155 | override: true 156 | 157 | - name: Run cargo-tarpaulin 158 | uses: actions-rs/tarpaulin@v0.1 159 | with: 160 | args: --ignore-tests --all-features --force-clean 161 | out-type: Xml 162 | 163 | - name: Upload to codecov.io 164 | uses: codecov/codecov-action@v1.0.10 165 | with: 166 | file: cobertura.xml 167 | name: cargo-tarpaulin-xml-codecov 168 | 169 | - name: Archive code coverage results 170 | uses: actions/upload-artifact@v1 171 | with: 172 | name: code-coverage-report 173 | path: cobertura.xml 174 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Marcel Müller "] 3 | categories = ["database-implementations"] 4 | description = "A modular and configurable database" 5 | documentation = "https://docs.rs/rustbreak" 6 | edition = "2018" 7 | homepage = "https://github.com/TheNeikos/rustbreak" 8 | keywords = ["database", "simple", "fast", "rustbreak"] 9 | license = "MPL-2.0" 10 | name = "rustbreak" 11 | readme = "README.md" 12 | repository = "https://github.com/TheNeikos/rustbreak" 13 | version = "2.0.0" 14 | 15 | [package.metadata.docs.rs] 16 | all-features = true 17 | 18 | [dependencies] 19 | serde = "1" 20 | tempfile = "3" 21 | thiserror = "1.0.20" 22 | 23 | [dependencies.ron] 24 | optional = true 25 | version = "0.6" 26 | 27 | [dependencies.base64] 28 | optional = true 29 | version = "0.12" 30 | 31 | [dependencies.bincode] 32 | optional = true 33 | version = "1" 34 | 35 | [dependencies.serde_yaml] 36 | optional = true 37 | version = "0.8.5" 38 | 39 | [dependencies.memmap] 40 | optional = true 41 | version = "0.7" 42 | 43 | [dependencies.anyhow] 44 | optional = true 45 | version = "1.0.32" 46 | 47 | [dev-dependencies] 48 | lazy_static = "1" 49 | serde_derive = "1" 50 | 51 | [features] 52 | default = [] 53 | ron_enc = ["ron"] 54 | bin_enc = ["bincode", "base64"] 55 | yaml_enc = ["serde_yaml"] 56 | other_errors = ["anyhow"] 57 | mmap = ["memmap"] 58 | 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0.Mozilla Public License Version 2.0 374 | ================================== 375 | 376 | 1. Definitions 377 | -------------- 378 | 379 | 1.1. "Contributor" 380 | means each individual or legal entity that creates, contributes to 381 | the creation of, or owns Covered Software. 382 | 383 | 1.2. "Contributor Version" 384 | means the combination of the Contributions of others (if any) used 385 | by a Contributor and that particular Contributor's Contribution. 386 | 387 | 1.3. "Contribution" 388 | means Covered Software of a particular Contributor. 389 | 390 | 1.4. "Covered Software" 391 | means Source Code Form to which the initial Contributor has attached 392 | the notice in Exhibit A, the Executable Form of such Source Code 393 | Form, and Modifications of such Source Code Form, in each case 394 | including portions thereof. 395 | 396 | 1.5. "Incompatible With Secondary Licenses" 397 | means 398 | 399 | (a) that the initial Contributor has attached the notice described 400 | in Exhibit B to the Covered Software; or 401 | 402 | (b) that the Covered Software was made available under the terms of 403 | version 1.1 or earlier of the License, but not also under the 404 | terms of a Secondary License. 405 | 406 | 1.6. "Executable Form" 407 | means any form of the work other than Source Code Form. 408 | 409 | 1.7. "Larger Work" 410 | means a work that combines Covered Software with other material, in 411 | a separate file or files, that is not Covered Software. 412 | 413 | 1.8. "License" 414 | means this document. 415 | 416 | 1.9. "Licensable" 417 | means having the right to grant, to the maximum extent possible, 418 | whether at the time of the initial grant or subsequently, any and 419 | all of the rights conveyed by this License. 420 | 421 | 1.10. "Modifications" 422 | means any of the following: 423 | 424 | (a) any file in Source Code Form that results from an addition to, 425 | deletion from, or modification of the contents of Covered 426 | Software; or 427 | 428 | (b) any new file in Source Code Form that contains any Covered 429 | Software. 430 | 431 | 1.11. "Patent Claims" of a Contributor 432 | means any patent claim(s), including without limitation, method, 433 | process, and apparatus claims, in any patent Licensable by such 434 | Contributor that would be infringed, but for the grant of the 435 | License, by the making, using, selling, offering for sale, having 436 | made, import, or transfer of either its Contributions or its 437 | Contributor Version. 438 | 439 | 1.12. "Secondary License" 440 | means either the GNU General Public License, Version 2.0, the GNU 441 | Lesser General Public License, Version 2.1, the GNU Affero General 442 | Public License, Version 3.0, or any later versions of those 443 | licenses. 444 | 445 | 1.13. "Source Code Form" 446 | means the form of the work preferred for making modifications. 447 | 448 | 1.14. "You" (or "Your") 449 | means an individual or a legal entity exercising rights under this 450 | License. For legal entities, "You" includes any entity that 451 | controls, is controlled by, or is under common control with You. For 452 | purposes of this definition, "control" means (a) the power, direct 453 | or indirect, to cause the direction or management of such entity, 454 | whether by contract or otherwise, or (b) ownership of more than 455 | fifty percent (50%) of the outstanding shares or beneficial 456 | ownership of such entity. 457 | 458 | 2. License Grants and Conditions 459 | -------------------------------- 460 | 461 | 2.1. Grants 462 | 463 | Each Contributor hereby grants You a world-wide, royalty-free, 464 | non-exclusive license: 465 | 466 | (a) under intellectual property rights (other than patent or trademark) 467 | Licensable by such Contributor to use, reproduce, make available, 468 | modify, display, perform, distribute, and otherwise exploit its 469 | Contributions, either on an unmodified basis, with Modifications, or 470 | as part of a Larger Work; and 471 | 472 | (b) under Patent Claims of such Contributor to make, use, sell, offer 473 | for sale, have made, import, and otherwise transfer either its 474 | Contributions or its Contributor Version. 475 | 476 | 2.2. Effective Date 477 | 478 | The licenses granted in Section 2.1 with respect to any Contribution 479 | become effective for each Contribution on the date the Contributor first 480 | distributes such Contribution. 481 | 482 | 2.3. Limitations on Grant Scope 483 | 484 | The licenses granted in this Section 2 are the only rights granted under 485 | this License. No additional rights or licenses will be implied from the 486 | distribution or licensing of Covered Software under this License. 487 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 488 | Contributor: 489 | 490 | (a) for any code that a Contributor has removed from Covered Software; 491 | or 492 | 493 | (b) for infringements caused by: (i) Your and any other third party's 494 | modifications of Covered Software, or (ii) the combination of its 495 | Contributions with other software (except as part of its Contributor 496 | Version); or 497 | 498 | (c) under Patent Claims infringed by Covered Software in the absence of 499 | its Contributions. 500 | 501 | This License does not grant any rights in the trademarks, service marks, 502 | or logos of any Contributor (except as may be necessary to comply with 503 | the notice requirements in Section 3.4). 504 | 505 | 2.4. Subsequent Licenses 506 | 507 | No Contributor makes additional grants as a result of Your choice to 508 | distribute the Covered Software under a subsequent version of this 509 | License (see Section 10.2) or under the terms of a Secondary License (if 510 | permitted under the terms of Section 3.3). 511 | 512 | 2.5. Representation 513 | 514 | Each Contributor represents that the Contributor believes its 515 | Contributions are its original creation(s) or it has sufficient rights 516 | to grant the rights to its Contributions conveyed by this License. 517 | 518 | 2.6. Fair Use 519 | 520 | This License is not intended to limit any rights You have under 521 | applicable copyright doctrines of fair use, fair dealing, or other 522 | equivalents. 523 | 524 | 2.7. Conditions 525 | 526 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 527 | in Section 2.1. 528 | 529 | 3. Responsibilities 530 | ------------------- 531 | 532 | 3.1. Distribution of Source Form 533 | 534 | All distribution of Covered Software in Source Code Form, including any 535 | Modifications that You create or to which You contribute, must be under 536 | the terms of this License. You must inform recipients that the Source 537 | Code Form of the Covered Software is governed by the terms of this 538 | License, and how they can obtain a copy of this License. You may not 539 | attempt to alter or restrict the recipients' rights in the Source Code 540 | Form. 541 | 542 | 3.2. Distribution of Executable Form 543 | 544 | If You distribute Covered Software in Executable Form then: 545 | 546 | (a) such Covered Software must also be made available in Source Code 547 | Form, as described in Section 3.1, and You must inform recipients of 548 | the Executable Form how they can obtain a copy of such Source Code 549 | Form by reasonable means in a timely manner, at a charge no more 550 | than the cost of distribution to the recipient; and 551 | 552 | (b) You may distribute such Executable Form under the terms of this 553 | License, or sublicense it under different terms, provided that the 554 | license for the Executable Form does not attempt to limit or alter 555 | the recipients' rights in the Source Code Form under this License. 556 | 557 | 3.3. Distribution of a Larger Work 558 | 559 | You may create and distribute a Larger Work under terms of Your choice, 560 | provided that You also comply with the requirements of this License for 561 | the Covered Software. If the Larger Work is a combination of Covered 562 | Software with a work governed by one or more Secondary Licenses, and the 563 | Covered Software is not Incompatible With Secondary Licenses, this 564 | License permits You to additionally distribute such Covered Software 565 | under the terms of such Secondary License(s), so that the recipient of 566 | the Larger Work may, at their option, further distribute the Covered 567 | Software under the terms of either this License or such Secondary 568 | License(s). 569 | 570 | 3.4. Notices 571 | 572 | You may not remove or alter the substance of any license notices 573 | (including copyright notices, patent notices, disclaimers of warranty, 574 | or limitations of liability) contained within the Source Code Form of 575 | the Covered Software, except that You may alter any license notices to 576 | the extent required to remedy known factual inaccuracies. 577 | 578 | 3.5. Application of Additional Terms 579 | 580 | You may choose to offer, and to charge a fee for, warranty, support, 581 | indemnity or liability obligations to one or more recipients of Covered 582 | Software. However, You may do so only on Your own behalf, and not on 583 | behalf of any Contributor. You must make it absolutely clear that any 584 | such warranty, support, indemnity, or liability obligation is offered by 585 | You alone, and You hereby agree to indemnify every Contributor for any 586 | liability incurred by such Contributor as a result of warranty, support, 587 | indemnity or liability terms You offer. You may include additional 588 | disclaimers of warranty and limitations of liability specific to any 589 | jurisdiction. 590 | 591 | 4. Inability to Comply Due to Statute or Regulation 592 | --------------------------------------------------- 593 | 594 | If it is impossible for You to comply with any of the terms of this 595 | License with respect to some or all of the Covered Software due to 596 | statute, judicial order, or regulation then You must: (a) comply with 597 | the terms of this License to the maximum extent possible; and (b) 598 | describe the limitations and the code they affect. Such description must 599 | be placed in a text file included with all distributions of the Covered 600 | Software under this License. Except to the extent prohibited by statute 601 | or regulation, such description must be sufficiently detailed for a 602 | recipient of ordinary skill to be able to understand it. 603 | 604 | 5. Termination 605 | -------------- 606 | 607 | 5.1. The rights granted under this License will terminate automatically 608 | if You fail to comply with any of its terms. However, if You become 609 | compliant, then the rights granted under this License from a particular 610 | Contributor are reinstated (a) provisionally, unless and until such 611 | Contributor explicitly and finally terminates Your grants, and (b) on an 612 | ongoing basis, if such Contributor fails to notify You of the 613 | non-compliance by some reasonable means prior to 60 days after You have 614 | come back into compliance. Moreover, Your grants from a particular 615 | Contributor are reinstated on an ongoing basis if such Contributor 616 | notifies You of the non-compliance by some reasonable means, this is the 617 | first time You have received notice of non-compliance with this License 618 | from such Contributor, and You become compliant prior to 30 days after 619 | Your receipt of the notice. 620 | 621 | 5.2. If You initiate litigation against any entity by asserting a patent 622 | infringement claim (excluding declaratory judgment actions, 623 | counter-claims, and cross-claims) alleging that a Contributor Version 624 | directly or indirectly infringes any patent, then the rights granted to 625 | You by any and all Contributors for the Covered Software under Section 626 | 2.1 of this License shall terminate. 627 | 628 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 629 | end user license agreements (excluding distributors and resellers) which 630 | have been validly granted by You or Your distributors under this License 631 | prior to termination shall survive termination. 632 | 633 | ************************************************************************ 634 | * * 635 | * 6. Disclaimer of Warranty * 636 | * ------------------------- * 637 | * * 638 | * Covered Software is provided under this License on an "as is" * 639 | * basis, without warranty of any kind, either expressed, implied, or * 640 | * statutory, including, without limitation, warranties that the * 641 | * Covered Software is free of defects, merchantable, fit for a * 642 | * particular purpose or non-infringing. The entire risk as to the * 643 | * quality and performance of the Covered Software is with You. * 644 | * Should any Covered Software prove defective in any respect, You * 645 | * (not any Contributor) assume the cost of any necessary servicing, * 646 | * repair, or correction. This disclaimer of warranty constitutes an * 647 | * essential part of this License. No use of any Covered Software is * 648 | * authorized under this License except under this disclaimer. * 649 | * * 650 | ************************************************************************ 651 | 652 | ************************************************************************ 653 | * * 654 | * 7. Limitation of Liability * 655 | * -------------------------- * 656 | * * 657 | * Under no circumstances and under no legal theory, whether tort * 658 | * (including negligence), contract, or otherwise, shall any * 659 | * Contributor, or anyone who distributes Covered Software as * 660 | * permitted above, be liable to You for any direct, indirect, * 661 | * special, incidental, or consequential damages of any character * 662 | * including, without limitation, damages for lost profits, loss of * 663 | * goodwill, work stoppage, computer failure or malfunction, or any * 664 | * and all other commercial damages or losses, even if such party * 665 | * shall have been informed of the possibility of such damages. This * 666 | * limitation of liability shall not apply to liability for death or * 667 | * personal injury resulting from such party's negligence to the * 668 | * extent applicable law prohibits such limitation. Some * 669 | * jurisdictions do not allow the exclusion or limitation of * 670 | * incidental or consequential damages, so this exclusion and * 671 | * limitation may not apply to You. * 672 | * * 673 | ************************************************************************ 674 | 675 | 8. Litigation 676 | ------------- 677 | 678 | Any litigation relating to this License may be brought only in the 679 | courts of a jurisdiction where the defendant maintains its principal 680 | place of business and such litigation shall be governed by laws of that 681 | jurisdiction, without reference to its conflict-of-law provisions. 682 | Nothing in this Section shall prevent a party's ability to bring 683 | cross-claims or counter-claims. 684 | 685 | 9. Miscellaneous 686 | ---------------- 687 | 688 | This License represents the complete agreement concerning the subject 689 | matter hereof. If any provision of this License is held to be 690 | unenforceable, such provision shall be reformed only to the extent 691 | necessary to make it enforceable. Any law or regulation which provides 692 | that the language of a contract shall be construed against the drafter 693 | shall not be used to construe this License against a Contributor. 694 | 695 | 10. Versions of the License 696 | --------------------------- 697 | 698 | 10.1. New Versions 699 | 700 | Mozilla Foundation is the license steward. Except as provided in Section 701 | 10.3, no one other than the license steward has the right to modify or 702 | publish new versions of this License. Each version will be given a 703 | distinguishing version number. 704 | 705 | 10.2. Effect of New Versions 706 | 707 | You may distribute the Covered Software under the terms of the version 708 | of the License under which You originally received the Covered Software, 709 | or under the terms of any subsequent version published by the license 710 | steward. 711 | 712 | 10.3. Modified Versions 713 | 714 | If you create software not governed by this License, and you want to 715 | create a new license for such software, you may create and use a 716 | modified version of this License if you rename the license and remove 717 | any references to the name of the license steward (except to note that 718 | such modified license differs from this License). 719 | 720 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 721 | Licenses 722 | 723 | If You choose to distribute Source Code Form that is Incompatible With 724 | Secondary Licenses under the terms of this version of the License, the 725 | notice described in Exhibit B of this License must be attached. 726 | 727 | Exhibit A - Source Code Form License Notice 728 | ------------------------------------------- 729 | 730 | This Source Code Form is subject to the terms of the Mozilla Public 731 | License, v. 2.0. If a copy of the MPL was not distributed with this 732 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 733 | 734 | If it is not possible or desirable to put the notice in a particular 735 | file, then You may include the notice in a location (such as a LICENSE 736 | file in a relevant directory) where a recipient would be likely to look 737 | for such a notice. 738 | 739 | You may add additional accurate notices of copyright ownership. 740 | 741 | Exhibit B - "Incompatible With Secondary Licenses" Notice 742 | --------------------------------------------------------- 743 | 744 | This Source Code Form is "Incompatible With Secondary Licenses", as 745 | defined by the Mozilla Public License, v. 2.0. 746 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rustbreak 2 | ========= 3 | 4 | [![Build Status](https://travis-ci.org/TheNeikos/rustbreak.svg?branch=master)](https://travis-ci.org/TheNeikos/rustbreak) 5 | [![Crates Link](https://img.shields.io/crates/v/rustbreak.svg)](https://crates.io/crates/rustbreak) 6 | 7 | **[Documentation][doc]** 8 | 9 | Rustbreak is an [Daybreak] inspired self-contained file 10 | database. It is meant to be fast and simple to use. You add it to your 11 | application and it should just work for you. The only thing you will have to 12 | take care of is saving. 13 | 14 | When to use it 15 | -------------- 16 | 17 | This library started out because of a need to be able to quickly write an 18 | application in rust that needed some persistence while still being able to write 19 | arbitrary data to it. 20 | 21 | In Ruby there is [Daybreak] however for Rust there was no similar crate, until 22 | now! 23 | 24 | When not to use it 25 | ------------------ 26 | 27 | Rustbreak makes several trade-offs to be easy to use and extend, so knowing of these drawbacks is important if 28 | you wish to use the library: 29 | 30 | - The Database needs to fit into memory (Rustbreak cannot do partial loads/saves, so if the Database exceeds your available memory you will run OOM) 31 | - Not all backends support atomic saves, so if your program crashes while it is saving you might save incomplete data (Notably only `PathBackend` supports atomic saves) 32 | 33 | Features 34 | -------- 35 | 36 | - Simple To Use, Fast, Secure 37 | - Threadsafe 38 | - Serde compatible storage (ron, bincode, or yaml included) 39 | 40 | Quickstart 41 | ---------- 42 | 43 | Add this to your `Cargo.toml`: 44 | 45 | ```toml 46 | [dependencies.rustbreak] 47 | version = "2" 48 | features = ["ron_enc"] # You can also use "yaml_enc" or "bin_enc" 49 | # Check the documentation to add your own! 50 | ``` 51 | 52 | ```rust 53 | extern crate rustbreak; 54 | use std::collections::HashMap; 55 | use rustbreak::{MemoryDatabase, deser::Ron}; 56 | 57 | fn main() -> rustbreak::Result<()> { 58 | let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; 59 | 60 | println!("Writing to Database"); 61 | db.write(|db| { 62 | db.insert(0, String::from("world")); 63 | db.insert(1, String::from("bar")); 64 | }); 65 | 66 | db.read(|db| { 67 | // db.insert("foo".into(), String::from("bar")); 68 | // The above line will not compile since we are only reading 69 | println!("Hello: {:?}", db.get(&0)); 70 | })?; 71 | 72 | Ok(()) 73 | } 74 | ``` 75 | 76 | Usage 77 | ----- 78 | 79 | Usage is quite simple: 80 | 81 | - Create/open a database using one of the Database constructors: 82 | - Create a `FileDatabase` with `FileDatabase::from_path`. 83 | - Create a `MemoryDatabase` with `MemoryDatabase::memory`. 84 | - Create a `MmapDatabase` with `MmapDatabase::mmap` or `MmapDatabase::mmap_with_size` with `mmap` feature. 85 | - Create a `Database` with `Database::from_parts`. 86 | - `Write`/`Read` data from the Database 87 | - Don't forget to run `save` periodically, or whenever it makes sense. 88 | - You can save in parallel to using the Database. However you will lock 89 | write acess while it is being written to storage. 90 | 91 | ```rust 92 | # use std::collections::HashMap; 93 | use rustbreak::{MemoryDatabase, deser::Ron}; 94 | 95 | let db = MemoryDatabase::, Ron>::memory(HashMap::new(), Ron); 96 | 97 | println!("Writing to Database"); 98 | db.write(|db| { 99 | db.insert("hello".into(), String::from("world")); 100 | db.insert("foo".into(), String::from("bar")); 101 | }); 102 | 103 | db.read(|db| { 104 | // db.insert("foo".into(), String::from("bar")); 105 | // The above line will not compile since we are only reading 106 | println!("Hello: {:?}", db.get("hello")); 107 | }); 108 | ``` 109 | 110 | ## Encodings 111 | 112 | The following parts explain how to enable the respective features. You can also 113 | enable several at the same time. 114 | 115 | ### Yaml 116 | 117 | If you would like to use yaml you need to specify `yaml_enc` as a feature: 118 | 119 | ```toml 120 | [dependencies.rustbreak] 121 | version = "2" 122 | features = ["yaml_enc"] 123 | ``` 124 | 125 | You can now use `rustbreak::deser::Yaml` as deserialization struct. 126 | 127 | ### Ron 128 | 129 | If you would like to use [`ron`](https://github.com/ron-rs/ron) you need to 130 | specify `ron_enc` as a feature: 131 | 132 | ```toml 133 | [dependencies.rustbreak] 134 | version = "2" 135 | features = ["ron_enc"] 136 | ``` 137 | 138 | You can now use `rustbreak::deser::Ron` as deserialization struct. 139 | 140 | ### Bincode 141 | 142 | If you would like to use bincode you need to specify `bin_enc` as a feature: 143 | 144 | ```toml 145 | [dependencies.rustbreak] 146 | version = "2" 147 | features = ["bin_enc"] 148 | ``` 149 | 150 | You can now use `rustbreak::deser::Bincode` as deserialization struct. 151 | 152 | 153 | [doc]:http://neikos.me/rustbreak/rustbreak/index.html 154 | [Daybreak]:https://propublica.github.io/daybreak/ 155 | -------------------------------------------------------------------------------- /examples/config.rs: -------------------------------------------------------------------------------- 1 | // This just reads an example configuration. 2 | // If it doesn't find one, it uses your default configuration 3 | // 4 | // You can create one by writing this file to `/tmp/config.ron`: 5 | // ``` 6 | // --- 7 | // user_path: /tmp/nope 8 | // allow_overwrite: true 9 | // ``` 10 | // 11 | 12 | #[macro_use] 13 | extern crate serde_derive; 14 | #[macro_use] 15 | extern crate lazy_static; 16 | 17 | use rustbreak::deser::Ron; 18 | use rustbreak::FileDatabase; 19 | use std::default::Default; 20 | use std::path::PathBuf; 21 | 22 | type DB = FileDatabase; 23 | 24 | lazy_static! { 25 | static ref CONFIG: DB = { 26 | let db = FileDatabase::load_from_path_or_default("/tmp/config.ron") 27 | .expect("Create database from path"); 28 | db.load().expect("Config to load"); 29 | db 30 | }; 31 | } 32 | 33 | #[derive(Debug, Serialize, Deserialize, Clone)] 34 | struct Config { 35 | user_path: PathBuf, 36 | allow_overwrite: bool, 37 | } 38 | 39 | impl Default for Config { 40 | fn default() -> Config { 41 | Config { 42 | user_path: PathBuf::from("/tmp"), 43 | allow_overwrite: false, 44 | } 45 | } 46 | } 47 | 48 | fn main() { 49 | let _conf: Config = CONFIG 50 | .read(|conf| conf.clone()) 51 | .expect("Reading configuration"); 52 | 53 | let (user_path, allow_overwrite) = CONFIG 54 | .read(|conf| (conf.user_path.clone(), conf.allow_overwrite.clone())) 55 | .expect("Read config"); 56 | 57 | println!( 58 | "The current configuration is: {:?} and {}", 59 | user_path, allow_overwrite 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /examples/full.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | use rustbreak::deser::Ron; 5 | use rustbreak::FileDatabase; 6 | 7 | #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] 8 | enum Country { 9 | Italy, 10 | UnitedKingdom, 11 | } 12 | 13 | #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] 14 | struct Person { 15 | name: String, 16 | country: Country, 17 | } 18 | 19 | fn do_main() -> Result<(), rustbreak::RustbreakError> { 20 | use std::collections::HashMap; 21 | 22 | let db = FileDatabase::, Ron>::load_from_path_or_default("test.ron")?; 23 | 24 | println!("Writing to Database"); 25 | db.write(|db| { 26 | db.insert( 27 | "john".into(), 28 | Person { 29 | name: String::from("John Andersson"), 30 | country: Country::Italy, 31 | }, 32 | ); 33 | db.insert( 34 | "fred".into(), 35 | Person { 36 | name: String::from("Fred Johnson"), 37 | country: Country::UnitedKingdom, 38 | }, 39 | ); 40 | println!("Entries: \n{:#?}", db); 41 | })?; 42 | 43 | println!("Syncing Database"); 44 | db.save()?; 45 | 46 | println!("Loading Database"); 47 | db.load()?; 48 | 49 | println!("Reading from Database"); 50 | db.read(|db| { 51 | println!("Results:"); 52 | println!("{:#?}", db); 53 | })?; 54 | 55 | Ok(()) 56 | } 57 | 58 | fn main() { 59 | if let Err(e) = do_main() { 60 | eprintln!("An error has occurred at: \n{}", e); 61 | std::process::exit(1); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/migration.rs.incomplete: -------------------------------------------------------------------------------- 1 | /* This file is a complete work in progress! And is meant to illustrate a possible way of 2 | * migrating databases with Rustbreak. 3 | * 4 | * Currently the major challenge is to statically define the upgrade process. 5 | * 6 | * This is currently done by using the `upgrading_macro`. 7 | * 8 | * The idea is to read the 'version' tag in the file, load it into a simple tag struct and 9 | * read the version from there. This version is then used to dynamically get the corresponding 10 | * struct type. This then gets converted to the latest version. 11 | */ 12 | 13 | 14 | extern crate rustbreak; 15 | #[macro_use] extern crate serde_derive; 16 | 17 | use rustbreak::FileDatabase; 18 | use rustbreak::deser::Ron; 19 | 20 | mod data { 21 | #[derive(Debug, Serialize, Deserialize, Clone, Copy)] 22 | pub enum Version { 23 | First, Second, Third, 24 | } 25 | 26 | #[derive(Debug, Serialize, Deserialize, Clone)] 27 | pub struct Versioning { 28 | pub version: Version 29 | } 30 | 31 | pub mod v1 { 32 | use data::Version; 33 | use std::collections::HashMap; 34 | 35 | #[derive(Debug, Serialize, Deserialize, Clone)] 36 | pub struct Players { 37 | version: Version, 38 | pub players: HashMap, 39 | } 40 | 41 | impl Players { 42 | pub fn new() -> Players { 43 | Players { 44 | version: Version::First, 45 | players: HashMap::new(), 46 | } 47 | } 48 | } 49 | } 50 | 51 | pub mod v2 { 52 | use data::Version; 53 | use std::collections::HashMap; 54 | 55 | #[derive(Debug, Serialize, Deserialize, Clone)] 56 | pub struct Player { 57 | pub name: String, 58 | pub level: i32, 59 | } 60 | 61 | #[derive(Debug, Serialize, Deserialize, Clone)] 62 | pub struct Players { 63 | version: Version, 64 | pub players: HashMap, 65 | } 66 | 67 | impl Players { 68 | pub fn new() -> Players { 69 | Players { 70 | version: Version::Second, 71 | players: HashMap::new(), 72 | } 73 | } 74 | } 75 | } 76 | 77 | pub mod v3 { 78 | use data::Version; 79 | use std::collections::HashMap; 80 | 81 | #[derive(Debug, Serialize, Deserialize, Clone)] 82 | pub struct Player { 83 | pub name: String, 84 | pub score: i64 85 | } 86 | 87 | #[derive(Debug, Serialize, Deserialize, Clone)] 88 | pub struct Players { 89 | version: Version, 90 | pub players: HashMap, 91 | } 92 | 93 | impl Players { 94 | pub fn new() -> Players { 95 | Players { 96 | version: Version::Third, 97 | players: HashMap::new(), 98 | } 99 | } 100 | } 101 | } 102 | 103 | pub fn convert_v1_v2(input: v1::Players) -> v2::Players { 104 | let mut new = v2::Players::new(); 105 | for (key, player) in input.players { 106 | new.players.insert(key, 107 | v2::Player { 108 | name: player, 109 | level: 1 110 | }); 111 | } 112 | 113 | new 114 | } 115 | 116 | pub fn convert_v2_v3(input: v2::Players) -> v3::Players { 117 | let mut new = v3::Players::new(); 118 | for (key, player) in input.players { 119 | new.players.insert(key, 120 | v3::Player { 121 | name: player.name, 122 | score: 0 123 | }); 124 | } 125 | 126 | new 127 | } 128 | } 129 | 130 | macro_rules! migration_rules { 131 | ( $name:ident -> $res:ty { $( $t:ty => $conv:path ),* $(,)* } ) => { 132 | fn $name(input: T) -> Option<$res> { 133 | fn inner(mut input: Box) -> Option<$res> { 134 | println!("We have: {:#?}", input); 135 | $( 136 | { 137 | let mut maybe_in = None; 138 | if let Some(out) = input.downcast_ref::<$t>() { 139 | maybe_in = Some(Box::new($conv(out.clone()))); 140 | } 141 | 142 | if let Some(inp) = maybe_in { 143 | input = inp; 144 | } 145 | } 146 | )* 147 | 148 | if let Ok(out) = input.downcast::<$res>() { 149 | return Some(*out); 150 | } 151 | 152 | None 153 | } 154 | inner(Box::new(input)) 155 | } 156 | } 157 | } 158 | 159 | migration_rules! { 160 | update_database -> data::v3::Players { 161 | data::v1::Players => data::convert_v1_v2, 162 | data::v2::Players => data::convert_v2_v3, 163 | } 164 | } 165 | 166 | fn get_version() -> data::Version { 167 | let db = FileDatabase::::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap(); 168 | db.load().unwrap(); 169 | db.read(|ver| ver.version).unwrap() 170 | } 171 | 172 | fn main() { 173 | use data::v3::Players; 174 | 175 | let version = get_version(); 176 | 177 | 178 | // Let's check if there are any updates 179 | let updated_data = update_database(database.get_data(false).unwrap()).unwrap(); 180 | let database = FileDatabase::::from_path("migration.ron", updated_data).unwrap(); 181 | 182 | println!("{:#?}", database); 183 | } 184 | -------------------------------------------------------------------------------- /examples/server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Marcel Müller "] 3 | name = "server" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | rocket = "0.3.3" 8 | rocket_codegen = "0.3.3" 9 | serde = "1.0.23" 10 | serde_derive = "1.0.23" 11 | 12 | [dependencies.rocket_contrib] 13 | default-features = false 14 | features = ["handlebars_templates"] 15 | version = "0.3.3" 16 | 17 | [dependencies.rustbreak] 18 | path = "../.." 19 | features = ["ron_enc"] 20 | -------------------------------------------------------------------------------- /examples/server/server.ron: -------------------------------------------------------------------------------- 1 | ( 2 | pastes: [ 3 | ( 4 | user: "neikos@neikos.email", 5 | body: "Hello :)", 6 | ), 7 | ( 8 | user: "neikos@neikos.email", 9 | body: "This works great!", 10 | ), 11 | ( 12 | user: "neikos@neikos.email", 13 | body: "Hehe", 14 | ), 15 | ( 16 | user: "neikos@neikos.email", 17 | body: "Hello World :)", 18 | ), 19 | ], 20 | users: { 21 | "neikos@neikos.email": ( 22 | username: "neikos@neikos.email", 23 | password: "asdf", 24 | ), 25 | }, 26 | ) -------------------------------------------------------------------------------- /examples/server/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin, decl_macro, custom_derive)] 2 | #![plugin(rocket_codegen)] 3 | 4 | extern crate rustbreak; 5 | extern crate rocket; 6 | extern crate rocket_contrib; 7 | #[macro_use] extern crate serde_derive; 8 | 9 | use std::collections::HashMap; 10 | 11 | use rocket::{State, Outcome}; 12 | use rocket::http::{Cookies, Cookie}; 13 | use rocket::request::{self, Request, FromRequest, Form}; 14 | use rocket::response::Redirect; 15 | use rocket_contrib::Template; 16 | use rustbreak::FileDatabase; 17 | use rustbreak::deser::Ron; 18 | 19 | // We create a type alias so that we always associate the same types to it 20 | type DB = FileDatabase; 21 | 22 | #[derive(Debug, Serialize, Deserialize, Clone)] 23 | struct Paste { 24 | user: String, 25 | body: String, 26 | } 27 | 28 | #[derive(Debug, Serialize, Deserialize, Clone, FromForm)] 29 | struct NewPaste { 30 | body: String 31 | } 32 | 33 | #[derive(Debug, Serialize, Deserialize, Clone, FromForm)] 34 | struct User { 35 | username: String, 36 | password: String, 37 | } 38 | 39 | impl <'a, 'r> FromRequest<'a, 'r> for User { 40 | type Error = (); 41 | 42 | fn from_request(request: &'a Request<'r>) -> request::Outcome { 43 | let mut cookies = request.cookies(); 44 | let db = request.guard::>()?; 45 | match cookies.get_private("user_id") { 46 | Some(cookie) => { 47 | let mut outcome = Outcome::Forward(()); 48 | let _ = db.read(|db| { 49 | if db.users.contains_key(cookie.value()) { 50 | outcome = Outcome::Success(db.users.get(cookie.value()).unwrap().clone()); 51 | } 52 | }); 53 | return outcome; 54 | } 55 | None => return Outcome::Forward(()) 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug, Serialize, Deserialize, Clone)] 61 | struct ServerData { 62 | pastes: Vec, 63 | users: HashMap 64 | } 65 | 66 | #[derive(Debug, Serialize)] 67 | struct TemplateData { 68 | pastes: Vec, 69 | logged_in: bool, 70 | user: String 71 | } 72 | 73 | 74 | // Routing 75 | 76 | #[get("/")] 77 | fn index(db: State, user: Option) -> Template { 78 | let mut data = TemplateData { 79 | logged_in: user.is_some(), 80 | user: user.map(|u| u.username).unwrap_or_else(|| String::new()), 81 | pastes: vec![], 82 | }; 83 | let _ = db.read(|db| { 84 | data.pastes = db.pastes.clone(); 85 | }); 86 | 87 | return Template::render("index", &data); 88 | } 89 | 90 | #[post("/register", data = "")] 91 | fn post_register(db: State, req_user: Form, mut cookies: Cookies) -> Redirect { 92 | let user = req_user.into_inner(); 93 | let _ = db.write(|db| { 94 | if db.users.contains_key(&user.username) { 95 | return; 96 | } 97 | db.users.insert(user.username.clone(), user.clone()); 98 | cookies.add_private( 99 | Cookie::build("user_id", user.username.clone()).http_only(true).finish() 100 | ); 101 | }); 102 | let _ = db.save(); 103 | 104 | Redirect::to("/") 105 | } 106 | 107 | #[post("/login", data = "")] 108 | fn post_login(db: State, req_user: Form, mut cookies: Cookies) -> Redirect { 109 | let user = req_user.into_inner(); 110 | let _ = db.read(|db| { 111 | match db.users.get(&user.username) { 112 | Some(u) => { 113 | if u.password == user.password { 114 | cookies.add_private( 115 | Cookie::build("user_id", user.username.clone()).http_only(true).finish() 116 | ); 117 | } 118 | } 119 | None => () 120 | } 121 | }); 122 | 123 | Redirect::to("/") 124 | } 125 | 126 | #[post("/paste", data = "")] 127 | fn post_paste(db: State, user: User, paste: Form) -> Redirect { 128 | let body : String = paste.into_inner().body.clone(); 129 | let _ = db.write(|db| { 130 | let paste = Paste { 131 | body: body, 132 | user: user.username.clone(), 133 | }; 134 | db.pastes.push(paste); 135 | }); 136 | let _ = db.save(); 137 | 138 | Redirect::to("/") 139 | } 140 | 141 | #[get("/dummy")] 142 | fn get_dummy() -> &'static str { 143 | "Hello World" 144 | } 145 | 146 | fn main() { 147 | let db : DB = FileDatabase::from_path("server.ron", ServerData { 148 | pastes: vec![], 149 | users: HashMap::new(), 150 | }).unwrap(); 151 | let _ = db.load(); 152 | 153 | 154 | rocket::ignite() 155 | .mount("/", routes![index, post_login, post_paste, post_register, get_dummy]) 156 | .attach(Template::fairing()) 157 | .manage(db) 158 | .launch(); 159 | } 160 | -------------------------------------------------------------------------------- /examples/server/templates/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rustbreak Pastes 5 | 7 | 8 | 9 | 10 | {{#if logged_in }} 11 |
12 |
13 | You are logged in as: {{user}} 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | {{else}} 30 |
31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 |
72 | {{/if}} 73 | 74 |
75 | 76 |
77 |

Pastes

78 | 79 | {{#each pastes as |p| ~}} 80 |
81 |
82 |
83 |

84 | {{p.user}}
85 | {{p.body}} 86 |

87 |

88 |
89 |
90 | {{/each~}} 91 |
92 |
93 |
94 |
95 |

96 | Made with love in Germany — Created using Rocket, Handlebars and Rustbreak 97 |

98 | 99 | Made with Bulma 100 | 101 |
102 |
103 |
104 | 105 | 106 | -------------------------------------------------------------------------------- /examples/switching.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | use rustbreak::deser::{Ron, Yaml}; 5 | use rustbreak::{backend::FileBackend, FileDatabase}; 6 | 7 | #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] 8 | enum Country { 9 | Italy, 10 | UnitedKingdom, 11 | } 12 | 13 | #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] 14 | struct Person { 15 | name: String, 16 | country: Country, 17 | } 18 | 19 | fn do_main() -> Result<(), rustbreak::RustbreakError> { 20 | use std::collections::HashMap; 21 | 22 | let db = FileDatabase::, Ron>::load_from_path_or_default("test.ron")?; 23 | 24 | println!("Writing to Database"); 25 | db.write(|db| { 26 | db.insert( 27 | "john".into(), 28 | Person { 29 | name: String::from("John Andersson"), 30 | country: Country::Italy, 31 | }, 32 | ); 33 | db.insert( 34 | "fred".into(), 35 | Person { 36 | name: String::from("Fred Johnson"), 37 | country: Country::UnitedKingdom, 38 | }, 39 | ); 40 | println!("Entries: \n{:#?}", db); 41 | })?; 42 | 43 | println!("Syncing Database"); 44 | db.save()?; 45 | 46 | // Now lets switch it 47 | 48 | let db = db 49 | .with_deser(Yaml) 50 | .with_backend(FileBackend::from_path_or_create("test.yml").map(|p| p.0)?); 51 | db.save()?; 52 | 53 | Ok(()) 54 | } 55 | 56 | fn main() { 57 | if let Err(e) = do_main() { 58 | eprintln!("An error has occurred at: \n{}", e); 59 | std::process::exit(1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/backend/mmap.rs: -------------------------------------------------------------------------------- 1 | use super::Backend; 2 | 3 | use crate::error; 4 | 5 | use std::cmp; 6 | use std::io; 7 | 8 | #[derive(Debug)] 9 | struct Mmap { 10 | inner: memmap::MmapMut, 11 | /// End of data 12 | pub end: usize, 13 | /// Mmap total len 14 | pub len: usize, 15 | } 16 | 17 | impl Mmap { 18 | fn new(len: usize) -> io::Result { 19 | let inner = memmap::MmapOptions::new().len(len).map_anon()?; 20 | 21 | Ok(Self { inner, end: 0, len }) 22 | } 23 | 24 | fn as_slice(&self) -> &[u8] { 25 | &self.inner[..self.end] 26 | } 27 | 28 | fn as_mut_slice(&mut self) -> &mut [u8] { 29 | &mut self.inner[..self.end] 30 | } 31 | 32 | /// Copies data to mmap and modifies data's end cursor. 33 | fn write(&mut self, data: &[u8]) -> error::BackendResult<()> { 34 | if data.len() > self.len { 35 | return Err(error::BackendError::Internal(format!( 36 | "Unexpected write beyond mmap's backend capacity." 37 | ))); 38 | } 39 | self.end = data.len(); 40 | self.as_mut_slice().copy_from_slice(data); 41 | Ok(()) 42 | } 43 | 44 | fn flush(&mut self) -> io::Result<()> { 45 | self.inner.flush() 46 | } 47 | 48 | /// Increases mmap size by `max(old_size*2, new_size)`. 49 | /// 50 | /// Note that it doesn't copy original data 51 | fn resize_no_copy(&mut self, new_size: usize) -> io::Result<()> { 52 | let len = cmp::max(self.len + self.len, new_size); 53 | // Make sure we don't discard old mmap before creating new one; 54 | let new_mmap = Self::new(len)?; 55 | *self = new_mmap; 56 | Ok(()) 57 | } 58 | } 59 | 60 | /// A backend that uses an nonymous mmap. 61 | /// 62 | /// The `Backend` automatically creates bigger map 63 | /// on demand using following strategy: 64 | /// 65 | /// - If new data size allows, multiply size by 2. 66 | /// - Otherwise new data size is used. 67 | /// 68 | /// Note that mmap is never shrink back. 69 | /// 70 | /// Use `Backend` methods to read and write into it. 71 | #[derive(Debug)] 72 | pub struct MmapStorage { 73 | mmap: Mmap, 74 | } 75 | 76 | impl MmapStorage { 77 | /// Creates new storage with 1024 bytes. 78 | pub fn new() -> error::BackendResult { 79 | Self::with_size(1024) 80 | } 81 | 82 | /// Creates new storage with custom size. 83 | pub fn with_size(len: usize) -> error::BackendResult { 84 | let mmap = Mmap::new(len)?; 85 | 86 | Ok(Self { mmap }) 87 | } 88 | } 89 | 90 | impl Backend for MmapStorage { 91 | fn get_data(&mut self) -> error::BackendResult> { 92 | let mmap = self.mmap.as_slice(); 93 | let mut buffer = Vec::with_capacity(mmap.len()); 94 | buffer.extend_from_slice(mmap); 95 | Ok(buffer) 96 | } 97 | 98 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { 99 | if self.mmap.len < data.len() { 100 | self.mmap.resize_no_copy(data.len())?; 101 | } 102 | self.mmap.write(data)?; 103 | self.mmap.flush()?; 104 | Ok(()) 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::{Backend, MmapStorage}; 111 | 112 | #[test] 113 | #[cfg_attr(miri, ignore)] 114 | fn test_mmap_storage() { 115 | let data = [4, 5, 1, 6, 8, 1]; 116 | let mut storage = MmapStorage::new().expect("To crate mmap storage"); 117 | 118 | storage.put_data(&data).expect("To put data"); 119 | assert_eq!(storage.mmap.end, data.len()); 120 | assert_eq!(storage.get_data().expect("To get data"), data); 121 | } 122 | 123 | #[test] 124 | #[cfg_attr(miri, ignore)] 125 | fn test_mmap_storage_extend() { 126 | let data = [4, 5, 1, 6, 8, 1]; 127 | let mut storage = MmapStorage::with_size(4).expect("To crate mmap storage"); 128 | 129 | storage.put_data(&data).expect("To put data"); 130 | assert_eq!(storage.mmap.end, data.len()); 131 | assert_eq!(storage.mmap.len, 8); 132 | assert_eq!(storage.get_data().expect("To get data"), data); 133 | } 134 | 135 | #[test] 136 | #[cfg_attr(miri, ignore)] 137 | fn test_mmap_storage_increase_by_new_data_size() { 138 | let data = [4, 5, 1, 6, 8, 1]; 139 | let mut storage = MmapStorage::with_size(1).expect("To crate mmap storage"); 140 | 141 | storage.put_data(&data).expect("To put data"); 142 | assert_eq!(storage.mmap.end, data.len()); 143 | assert_eq!(storage.mmap.len, data.len()); 144 | assert_eq!(storage.get_data().expect("To get data"), data); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | //! The persistence backends of the Database. 6 | //! 7 | //! A file is a `Backend` through the `FileBackend`, so is a `Vec` with a 8 | //! `MemoryBackend`. 9 | //! 10 | //! Implementing your own Backend should be straightforward. Check the `Backend` 11 | //! documentation for details. 12 | 13 | use crate::error; 14 | 15 | /// The Backend Trait. 16 | /// 17 | /// It should always read and save in full the data that it is passed. This 18 | /// means that a write to the backend followed by a read __must__ return the 19 | /// same dataset. 20 | /// 21 | /// **Important**: You can only return custom errors if the `other_errors` feature is enabled 22 | pub trait Backend { 23 | /// Read the all data from the backend. 24 | fn get_data(&mut self) -> error::BackendResult>; 25 | 26 | /// Write the whole slice to the backend. 27 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()>; 28 | } 29 | 30 | impl Backend for Box { 31 | fn get_data(&mut self) -> error::BackendResult> { 32 | use std::ops::DerefMut; 33 | self.deref_mut().get_data() 34 | } 35 | 36 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { 37 | use std::ops::DerefMut; 38 | self.deref_mut().put_data(data) 39 | } 40 | } 41 | 42 | impl Backend for Box { 43 | fn get_data(&mut self) -> error::BackendResult> { 44 | use std::ops::DerefMut; 45 | self.deref_mut().get_data() 46 | } 47 | 48 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { 49 | use std::ops::DerefMut; 50 | self.deref_mut().put_data(data) 51 | } 52 | } 53 | 54 | #[cfg(feature = "mmap")] 55 | mod mmap; 56 | #[cfg(feature = "mmap")] 57 | pub use mmap::MmapStorage; 58 | 59 | mod path; 60 | pub use path::PathBackend; 61 | 62 | /// A backend using a file. 63 | #[derive(Debug)] 64 | pub struct FileBackend(std::fs::File); 65 | 66 | impl Backend for FileBackend { 67 | fn get_data(&mut self) -> error::BackendResult> { 68 | use std::io::{Read, Seek, SeekFrom}; 69 | 70 | let mut buffer = vec![]; 71 | self.0.seek(SeekFrom::Start(0))?; 72 | self.0.read_to_end(&mut buffer)?; 73 | Ok(buffer) 74 | } 75 | 76 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { 77 | use std::io::{Seek, SeekFrom, Write}; 78 | 79 | self.0.seek(SeekFrom::Start(0))?; 80 | self.0.set_len(0)?; 81 | self.0.write_all(data)?; 82 | self.0.sync_all()?; 83 | Ok(()) 84 | } 85 | } 86 | 87 | impl FileBackend { 88 | /// Use an already open [`File`](std::fs::File) as the backend. 89 | #[must_use] 90 | pub fn from_file(file: std::fs::File) -> Self { 91 | Self(file) 92 | } 93 | 94 | /// Return the inner File. 95 | #[must_use] 96 | pub fn into_inner(self) -> std::fs::File { 97 | self.0 98 | } 99 | } 100 | 101 | impl FileBackend { 102 | /// Opens a new [`FileBackend`] for a given path. 103 | /// Errors when the file doesn't yet exist. 104 | pub fn from_path_or_fail>(path: P) -> error::BackendResult { 105 | use std::fs::OpenOptions; 106 | 107 | Ok(Self(OpenOptions::new().read(true).write(true).open(path)?)) 108 | } 109 | 110 | /// Opens a new [`FileBackend`] for a given path. 111 | /// Creates a file if it doesn't yet exist. 112 | /// 113 | /// Returns the [`FileBackend`] and whether the file already existed. 114 | pub fn from_path_or_create>( 115 | path: P, 116 | ) -> error::BackendResult<(Self, bool)> { 117 | use std::fs::OpenOptions; 118 | 119 | let exists = path.as_ref().is_file(); 120 | Ok(( 121 | Self( 122 | OpenOptions::new() 123 | .read(true) 124 | .write(true) 125 | .create(true) 126 | .open(path)?, 127 | ), 128 | exists, 129 | )) 130 | } 131 | 132 | /// Opens a new [`FileBackend`] for a given path. 133 | /// Creates a file if it doesn't yet exist, and calls `closure` with it. 134 | pub fn from_path_or_create_and(path: P, closure: C) -> error::BackendResult 135 | where 136 | C: FnOnce(&mut std::fs::File), 137 | P: AsRef, 138 | { 139 | Self::from_path_or_create(path).map(|(mut b, exists)| { 140 | if !exists { 141 | closure(&mut b.0) 142 | } 143 | b 144 | }) 145 | } 146 | } 147 | 148 | /// An in memory backend. 149 | /// 150 | /// It is backed by a byte vector (`Vec`). 151 | #[derive(Debug, Default)] 152 | pub struct MemoryBackend(Vec); 153 | 154 | impl MemoryBackend { 155 | /// Construct a new Memory Database. 156 | #[must_use] 157 | pub fn new() -> Self { 158 | Self::default() 159 | } 160 | } 161 | 162 | impl Backend for MemoryBackend { 163 | fn get_data(&mut self) -> error::BackendResult> { 164 | println!("Returning data: {:?}", &self.0); 165 | Ok(self.0.clone()) 166 | } 167 | 168 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { 169 | println!("Writing data: {:?}", data); 170 | self.0 = data.to_owned(); 171 | Ok(()) 172 | } 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use super::{Backend, FileBackend, MemoryBackend}; 178 | use std::io::{Read, Seek, SeekFrom, Write}; 179 | use tempfile::NamedTempFile; 180 | 181 | #[test] 182 | fn test_memory_backend() { 183 | let mut backend = MemoryBackend::new(); 184 | let data = [4, 5, 1, 6, 8, 1]; 185 | 186 | backend.put_data(&data).expect("could not put data"); 187 | assert_eq!(backend.get_data().expect("could not get data"), data); 188 | } 189 | 190 | #[test] 191 | #[cfg_attr(miri, ignore)] 192 | fn test_file_backend_from_file() { 193 | let file = tempfile::tempfile().expect("could not create temporary file"); 194 | let mut backend = FileBackend::from_file(file); 195 | let data = [4, 5, 1, 6, 8, 1]; 196 | let data2 = [3, 99, 127, 6]; 197 | 198 | backend.put_data(&data).expect("could not put data"); 199 | assert_eq!(backend.get_data().expect("could not get data"), data); 200 | 201 | backend.put_data(&data2).expect("could not put data"); 202 | assert_eq!(backend.get_data().expect("could not get data"), data2); 203 | } 204 | 205 | #[test] 206 | #[cfg_attr(miri, ignore)] 207 | fn test_file_backend_from_path_existing() { 208 | let file = NamedTempFile::new().expect("could not create temporary file"); 209 | let (mut backend, existed) = 210 | FileBackend::from_path_or_create(file.path()).expect("could not create backend"); 211 | assert!(existed); 212 | let data = [4, 5, 1, 6, 8, 1]; 213 | 214 | backend.put_data(&data).expect("could not put data"); 215 | assert_eq!(backend.get_data().expect("could not get data"), data); 216 | } 217 | 218 | #[test] 219 | #[cfg_attr(miri, ignore)] 220 | fn test_file_backend_from_path_new() { 221 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 222 | let mut file_path = dir.path().to_owned(); 223 | file_path.push("rustbreak_path_db.db"); 224 | let (mut backend, existed) = 225 | FileBackend::from_path_or_create(file_path).expect("could not create backend"); 226 | assert!(!existed); 227 | let data = [4, 5, 1, 6, 8, 1]; 228 | 229 | backend.put_data(&data).expect("could not put data"); 230 | assert_eq!(backend.get_data().expect("could not get data"), data); 231 | dir.close().expect("Error while deleting temp directory!"); 232 | } 233 | 234 | #[test] 235 | #[cfg_attr(miri, ignore)] 236 | fn test_file_backend_from_path_nofail() { 237 | let file = NamedTempFile::new().expect("could not create temporary file"); 238 | let file_path = file.path().to_owned(); 239 | let mut backend = FileBackend::from_path_or_fail(file_path).expect("should not fail"); 240 | let data = [4, 5, 1, 6, 8, 1]; 241 | 242 | backend.put_data(&data).expect("could not put data"); 243 | assert_eq!(backend.get_data().expect("could not get data"), data); 244 | } 245 | 246 | #[test] 247 | #[cfg_attr(miri, ignore)] 248 | fn test_file_backend_from_path_fail_notfound() { 249 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 250 | let mut file_path = dir.path().to_owned(); 251 | file_path.push("rustbreak_path_db.db"); 252 | let err = 253 | FileBackend::from_path_or_fail(file_path).expect_err("should fail with file not found"); 254 | if let crate::error::BackendError::Io(io_err) = &err { 255 | assert_eq!(std::io::ErrorKind::NotFound, io_err.kind()); 256 | } else { 257 | panic!("Wrong kind of error returned: {}", err); 258 | }; 259 | dir.close().expect("Error while deleting temp directory!"); 260 | } 261 | 262 | #[test] 263 | #[cfg_attr(miri, ignore)] 264 | fn test_file_backend_into_inner() { 265 | let file = tempfile::tempfile().expect("could not create temporary file"); 266 | let mut backend = FileBackend::from_file(file); 267 | let data = [4, 5, 1, 6, 8, 1]; 268 | 269 | backend.put_data(&data).expect("could not put data"); 270 | assert_eq!(backend.get_data().expect("could not get data"), data); 271 | 272 | let mut file = backend.into_inner(); 273 | file.seek(SeekFrom::Start(0)).unwrap(); 274 | let mut contents = Vec::new(); 275 | assert_eq!(file.read_to_end(&mut contents).unwrap(), 6); 276 | assert_eq!(&contents[..], &data[..]); 277 | } 278 | 279 | #[test] 280 | fn allow_boxed_backends() { 281 | let mut backend = Box::new(MemoryBackend::new()); 282 | let data = [4, 5, 1, 6, 8, 1]; 283 | 284 | backend.put_data(&data).unwrap(); 285 | assert_eq!(backend.get_data().unwrap(), data); 286 | } 287 | 288 | // If the file already exists, the closure shouldn't be called. 289 | #[test] 290 | #[cfg_attr(miri, ignore)] 291 | fn test_file_backend_create_and_existing_nocall() { 292 | let file = NamedTempFile::new().expect("could not create temporary file"); 293 | let mut backend = FileBackend::from_path_or_create_and(file.path(), |_| { 294 | panic!("Closure called but file already existed"); 295 | }) 296 | .expect("could not create backend"); 297 | let data = [4, 5, 1, 6, 8, 1]; 298 | 299 | backend.put_data(&data).expect("could not put data"); 300 | assert_eq!(backend.get_data().expect("could not get data"), data); 301 | } 302 | 303 | // If the file does not yet exist, the closure should be called. 304 | #[test] 305 | #[cfg_attr(miri, ignore)] 306 | fn test_file_backend_create_and_new() { 307 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 308 | let mut file_path = dir.path().to_owned(); 309 | file_path.push("rustbreak_path_db.db"); 310 | let mut backend = FileBackend::from_path_or_create_and(file_path, |f| { 311 | f.write_all(b"this is a new file") 312 | .expect("could not write to file") 313 | }) 314 | .expect("could not create backend"); 315 | assert_eq!( 316 | backend.get_data().expect("could not get data"), 317 | b"this is a new file" 318 | ); 319 | let data = [4, 5, 1, 6, 8, 1]; 320 | 321 | backend.put_data(&data).expect("could not put data"); 322 | assert_eq!(backend.get_data().expect("could not get data"), data); 323 | dir.close().expect("Error while deleting temp directory!"); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/backend/path.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | //! Module which implements the [`PathBackend`], storing data in a file on the 6 | //! file system (with a path) and featuring atomic saves. 7 | 8 | use super::Backend; 9 | use crate::error; 10 | use std::fs::OpenOptions; 11 | use std::path::{Path, PathBuf}; 12 | use tempfile::NamedTempFile; 13 | 14 | /// A [`Backend`] using a file given the path. 15 | /// 16 | /// Features atomic saves, so that the database file won't be corrupted or 17 | /// deleted if the program panics during the save. 18 | #[derive(Debug)] 19 | pub struct PathBackend { 20 | path: PathBuf, 21 | } 22 | 23 | impl PathBackend { 24 | /// Opens a new [`PathBackend`] for a given path. 25 | /// Errors when the file doesn't yet exist. 26 | pub fn from_path_or_fail(path: PathBuf) -> error::BackendResult { 27 | OpenOptions::new().read(true).open(path.as_path())?; 28 | Ok(Self { path }) 29 | } 30 | 31 | /// Opens a new [`PathBackend`] for a given path. 32 | /// Creates a file if it doesn't yet exist. 33 | /// 34 | /// Returns the [`PathBackend`] and whether the file already existed. 35 | pub fn from_path_or_create(path: PathBuf) -> error::BackendResult<(Self, bool)> { 36 | let exists = path.as_path().is_file(); 37 | OpenOptions::new() 38 | .write(true) 39 | .create(true) 40 | .open(path.as_path())?; 41 | Ok((Self { path }, exists)) 42 | } 43 | 44 | /// Opens a new [`PathBackend`] for a given path. 45 | /// Creates a file if it doesn't yet exist, and calls `closure` with it. 46 | pub fn from_path_or_create_and(path: PathBuf, closure: C) -> error::BackendResult 47 | where 48 | C: FnOnce(&mut std::fs::File), 49 | { 50 | let exists = path.as_path().is_file(); 51 | let mut file = OpenOptions::new() 52 | .read(true) 53 | .write(true) 54 | .create(true) 55 | .open(path.as_path())?; 56 | if !exists { 57 | closure(&mut file) 58 | } 59 | Ok(Self { path }) 60 | } 61 | } 62 | 63 | impl Backend for PathBackend { 64 | fn get_data(&mut self) -> error::BackendResult> { 65 | use std::io::Read; 66 | 67 | let mut file = OpenOptions::new().read(true).open(self.path.as_path())?; 68 | let mut buffer = vec![]; 69 | file.read_to_end(&mut buffer)?; 70 | Ok(buffer) 71 | } 72 | 73 | /// Write the byte slice to the backend. This uses and atomic save. 74 | /// 75 | /// This won't corrupt the existing database file if the program panics 76 | /// during the save. 77 | fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> { 78 | use std::io::Write; 79 | 80 | #[allow(clippy::or_fun_call)] // `Path::new` is a zero cost conversion 81 | let mut tempf = NamedTempFile::new_in(self.path.parent().unwrap_or(Path::new(".")))?; 82 | tempf.write_all(data)?; 83 | tempf.as_file().sync_all()?; 84 | tempf.persist(self.path.as_path())?; 85 | Ok(()) 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use super::{Backend, PathBackend}; 92 | use std::io::Write; 93 | use tempfile::NamedTempFile; 94 | 95 | #[test] 96 | #[cfg_attr(miri, ignore)] 97 | fn test_path_backend_existing() { 98 | let file = NamedTempFile::new().expect("could not create temporary file"); 99 | let (mut backend, existed) = PathBackend::from_path_or_create(file.path().to_owned()) 100 | .expect("could not create backend"); 101 | assert!(existed); 102 | let data = [4, 5, 1, 6, 8, 1]; 103 | 104 | backend.put_data(&data).expect("could not put data"); 105 | assert_eq!(backend.get_data().expect("could not get data"), data); 106 | } 107 | 108 | #[test] 109 | #[cfg_attr(miri, ignore)] 110 | fn test_path_backend_new() { 111 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 112 | let mut file_path = dir.path().to_owned(); 113 | file_path.push("rustbreak_path_db.db"); 114 | let (mut backend, existed) = 115 | PathBackend::from_path_or_create(file_path).expect("could not create backend"); 116 | assert!(!existed); 117 | let data = [4, 5, 1, 6, 8, 1]; 118 | 119 | backend.put_data(&data).expect("could not put data"); 120 | assert_eq!(backend.get_data().expect("could not get data"), data); 121 | dir.close().expect("Error while deleting temp directory!"); 122 | } 123 | 124 | #[test] 125 | #[cfg_attr(miri, ignore)] 126 | fn test_path_backend_nofail() { 127 | let file = NamedTempFile::new().expect("could not create temporary file"); 128 | let file_path = file.path().to_owned(); 129 | let mut backend = PathBackend::from_path_or_fail(file_path).expect("should not fail"); 130 | let data = [4, 5, 1, 6, 8, 1]; 131 | 132 | backend.put_data(&data).expect("could not put data"); 133 | assert_eq!(backend.get_data().expect("could not get data"), data); 134 | } 135 | 136 | #[test] 137 | #[cfg_attr(miri, ignore)] 138 | fn test_path_backend_fail_notfound() { 139 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 140 | let mut file_path = dir.path().to_owned(); 141 | file_path.push("rustbreak_path_db.db"); 142 | let err = 143 | PathBackend::from_path_or_fail(file_path).expect_err("should fail with file not found"); 144 | if let crate::error::BackendError::Io(io_err) = &err { 145 | assert_eq!(std::io::ErrorKind::NotFound, io_err.kind()); 146 | } else { 147 | panic!("Wrong kind of error returned: {}", err); 148 | }; 149 | dir.close().expect("Error while deleting temp directory!"); 150 | } 151 | 152 | // If the file already exists, the closure shouldn't be called. 153 | #[test] 154 | #[cfg_attr(miri, ignore)] 155 | fn test_path_backend_create_and_existing_nocall() { 156 | let file = NamedTempFile::new().expect("could not create temporary file"); 157 | let mut backend = PathBackend::from_path_or_create_and(file.path().to_owned(), |_| { 158 | panic!("Closure called but file already existed"); 159 | }) 160 | .expect("could not create backend"); 161 | let data = [4, 5, 1, 6, 8, 1]; 162 | 163 | backend.put_data(&data).expect("could not put data"); 164 | assert_eq!(backend.get_data().expect("could not get data"), data); 165 | } 166 | 167 | // If the file does not yet exist, the closure should be called. 168 | #[test] 169 | #[cfg_attr(miri, ignore)] 170 | fn test_path_backend_create_and_new() { 171 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 172 | let mut file_path = dir.path().to_owned(); 173 | file_path.push("rustbreak_path_db.db"); 174 | let mut backend = PathBackend::from_path_or_create_and(file_path, |f| { 175 | f.write_all(b"this is a new file") 176 | .expect("could not write to file") 177 | }) 178 | .expect("could not create backend"); 179 | assert_eq!( 180 | backend.get_data().expect("could not get data"), 181 | b"this is a new file" 182 | ); 183 | let data = [4, 5, 1, 6, 8, 1]; 184 | 185 | backend.put_data(&data).expect("could not put data"); 186 | assert_eq!(backend.get_data().expect("could not get data"), data); 187 | dir.close().expect("Error while deleting temp directory!"); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/deser.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | use crate::error; 5 | use std::io::Read; 6 | 7 | use serde::de::DeserializeOwned; 8 | use serde::Serialize; 9 | 10 | #[cfg(feature = "ron_enc")] 11 | pub use self::ron::Ron; 12 | 13 | #[cfg(feature = "yaml_enc")] 14 | pub use self::yaml::Yaml; 15 | 16 | #[cfg(feature = "bin_enc")] 17 | pub use self::bincode::Bincode; 18 | 19 | /// A trait to bundle serializer and deserializer in a simple struct 20 | /// 21 | /// It should preferably be an struct: one that does not have any members. 22 | /// 23 | /// # Example 24 | /// 25 | /// For an imaginary serde compatible encoding scheme 'Frobnar', an example 26 | /// implementation can look like this: 27 | /// 28 | /// ```rust 29 | /// extern crate rustbreak; 30 | /// extern crate thiserror; 31 | /// extern crate serde; 32 | /// #[macro_use] 33 | /// 34 | /// use serde::de::Deserialize; 35 | /// use serde::Serialize; 36 | /// use std::io::Read; 37 | /// 38 | /// use rustbreak::deser::DeSerializer; 39 | /// use rustbreak::error; 40 | /// 41 | /// #[derive(Clone, Debug, thiserror::Error)] 42 | /// #[error("A frobnarizer could not splagrle.")] 43 | /// struct FrobnarError; 44 | /// 45 | /// fn to_frobnar(input: &T) -> Vec { 46 | /// unimplemented!(); // implementation not specified 47 | /// } 48 | /// 49 | /// fn from_frobnar<'r, T: Deserialize<'r> + 'r, R: Read>(input: &R) -> Result { 50 | /// unimplemented!(); // implementation not specified 51 | /// } 52 | /// 53 | /// #[derive(Debug, Default, Clone)] 54 | /// struct Frobnar; 55 | /// 56 | /// impl DeSerializer for Frobnar 57 | /// where 58 | /// for<'de> T: Deserialize<'de>, 59 | /// { 60 | /// fn serialize(&self, val: &T) -> rustbreak::DeSerResult> { 61 | /// Ok(to_frobnar(val)) 62 | /// } 63 | /// 64 | /// fn deserialize(&self, s: R) -> rustbreak::DeSerResult { 65 | /// Ok(from_frobnar(&s).map_err(|e| error::DeSerError::Other(e.into()))?) 66 | /// } 67 | /// } 68 | /// 69 | /// fn main() {} 70 | /// ``` 71 | /// 72 | /// **Important**: You can only return custom errors if the `other_errors` feature is enabled 73 | pub trait DeSerializer: 74 | std::default::Default + Send + Sync + Clone 75 | { 76 | /// Serializes a given value to a [`String`]. 77 | fn serialize(&self, val: &T) -> error::DeSerResult>; 78 | /// Deserializes a [`String`] to a value. 79 | fn deserialize(&self, s: R) -> error::DeSerResult; 80 | } 81 | 82 | #[cfg(feature = "ron_enc")] 83 | mod ron { 84 | use std::io::Read; 85 | 86 | use serde::de::DeserializeOwned; 87 | use serde::Serialize; 88 | 89 | use ron::de::from_reader as from_ron_string; 90 | use ron::ser::to_string_pretty as to_ron_string; 91 | use ron::ser::PrettyConfig; 92 | 93 | use crate::deser::DeSerializer; 94 | use crate::error; 95 | 96 | /// The Struct that allows you to use `ron` the Rusty Object Notation. 97 | #[derive(Debug, Default, Clone)] 98 | pub struct Ron; 99 | 100 | impl DeSerializer for Ron { 101 | fn serialize(&self, val: &T) -> error::DeSerResult> { 102 | Ok(to_ron_string(val, PrettyConfig::default()).map(String::into_bytes)?) 103 | } 104 | fn deserialize(&self, s: R) -> error::DeSerResult { 105 | Ok(from_ron_string(s)?) 106 | } 107 | } 108 | } 109 | 110 | #[cfg(feature = "yaml_enc")] 111 | mod yaml { 112 | use std::io::Read; 113 | 114 | use serde::de::DeserializeOwned; 115 | use serde::Serialize; 116 | use serde_yaml::{from_reader as from_yaml_string, to_string as to_yaml_string}; 117 | 118 | use crate::deser::DeSerializer; 119 | use crate::error; 120 | 121 | /// The struct that allows you to use yaml. 122 | #[derive(Debug, Default, Clone)] 123 | pub struct Yaml; 124 | 125 | impl DeSerializer for Yaml { 126 | fn serialize(&self, val: &T) -> error::DeSerResult> { 127 | Ok(to_yaml_string(val).map(String::into_bytes)?) 128 | } 129 | fn deserialize(&self, s: R) -> error::DeSerResult { 130 | Ok(from_yaml_string(s)?) 131 | } 132 | } 133 | } 134 | 135 | #[cfg(feature = "bin_enc")] 136 | mod bincode { 137 | use std::io::Read; 138 | 139 | use bincode::{deserialize_from, serialize}; 140 | use serde::de::DeserializeOwned; 141 | use serde::Serialize; 142 | 143 | use crate::deser::DeSerializer; 144 | use crate::error; 145 | 146 | /// The struct that allows you to use bincode 147 | #[derive(Debug, Default, Clone)] 148 | pub struct Bincode; 149 | 150 | impl DeSerializer for Bincode { 151 | fn serialize(&self, val: &T) -> error::DeSerResult> { 152 | Ok(serialize(val)?) 153 | } 154 | fn deserialize(&self, s: R) -> error::DeSerResult { 155 | Ok(deserialize_from(s)?) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /// An error returned by a `DeSer` implementor 6 | #[derive(Debug, thiserror::Error)] 7 | #[non_exhaustive] 8 | #[allow(clippy::empty_enum)] // This can occur when no desers have beeen enabled 9 | pub enum DeSerError { 10 | #[cfg(feature = "yaml_enc")] 11 | /// An error occured with Yaml 12 | #[error("An error with yaml occured")] 13 | Yaml(#[from] serde_yaml::Error), 14 | #[cfg(feature = "ron_enc")] 15 | /// An error occured with Ron 16 | #[error("An error with Ron occured")] 17 | Ron(#[from] ron::Error), 18 | #[cfg(feature = "bin_enc")] 19 | /// An error occured with Bincode 20 | #[error("An error with Bincode occured")] 21 | Bincode(#[from] std::boxed::Box), 22 | /// An internal error to Rustbreak occured 23 | #[error("An internal error to rustbreak occured, please report it to the maintainers")] 24 | Internal(String), 25 | #[cfg(feature = "other_errors")] 26 | /// A dynamic error occured 27 | /// 28 | /// Most likely the custom `DeSer` implementation has thrown an error, consult its documentation 29 | /// for more information 30 | /// 31 | /// **Important**: This can only be used if the `other_errors` feature is enabled 32 | #[error(transparent)] 33 | Other(#[from] anyhow::Error), 34 | } 35 | 36 | /// An error returned by a Backend implementor 37 | #[derive(Debug, thiserror::Error)] 38 | #[non_exhaustive] 39 | pub enum BackendError { 40 | /// An error occured from the tempfile 41 | #[error("An error while persisting the file occured")] 42 | TempFile(#[from] tempfile::PersistError), 43 | /// An I/O Error occured 44 | #[error("An I/O Error occured")] 45 | Io(#[from] std::io::Error), 46 | /// An internal error to Rustbreak occured 47 | #[error("An internal error to rustbreak occured, please report it to the maintainers")] 48 | Internal(String), 49 | #[cfg(feature = "other_errors")] 50 | /// A dynamic error occured 51 | /// 52 | /// Most likely the custom `Backend` implementation has thrown an error, consult its documentation 53 | /// for more information 54 | /// 55 | /// **Important**: This can only be used if the `other_errors` feature is enabled 56 | #[error(transparent)] 57 | Other(#[from] anyhow::Error), 58 | } 59 | 60 | /// The different kinds of errors that can be returned 61 | #[derive(Debug, thiserror::Error)] 62 | #[non_exhaustive] 63 | pub enum RustbreakError { 64 | /// A context error when a DeSerialization failed 65 | #[error("Could not deserialize the value")] 66 | DeSerialization(#[from] DeSerError), 67 | /// This error is returned if the `Database` is poisoned. See 68 | /// `Database::write` for details 69 | #[error("The database has been poisoned")] 70 | Poison, 71 | /// An error in the backend happened 72 | #[error("The backend has encountered an error")] 73 | Backend(#[from] BackendError), 74 | /// If `Database::write_safe` is used and the closure panics, this error is 75 | /// returned 76 | #[error("The write operation paniced but got caught")] 77 | WritePanic, 78 | } 79 | 80 | /// A simple type alias for errors 81 | pub type Result = std::result::Result; 82 | /// The type alias used for backends 83 | pub type BackendResult = std::result::Result; 84 | /// The type alias used for `DeSer`s 85 | pub type DeSerResult = std::result::Result; 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #![deny( 6 | missing_docs, 7 | non_camel_case_types, 8 | non_snake_case, 9 | path_statements, 10 | trivial_casts, 11 | trivial_numeric_casts, 12 | unsafe_code, 13 | unstable_features, 14 | unused_allocation, 15 | unused_import_braces, 16 | unused_imports, 17 | unused_must_use, 18 | unused_mut, 19 | while_true, 20 | clippy::panic, 21 | clippy::print_stdout, 22 | clippy::todo, 23 | //clippy::unwrap_used, // not yet in stable 24 | clippy::wrong_pub_self_convention 25 | )] 26 | #![warn(clippy::pedantic)] 27 | // part of `clippy::pedantic`, causing many warnings 28 | #![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] 29 | 30 | //! # Rustbreak 31 | //! 32 | //! Rustbreak was a [Daybreak][daybreak] inspired single file Database. 33 | //! It has now since evolved into something else. Please check v1 for a more 34 | //! similar version. 35 | //! 36 | //! You will find an overview here in the docs, but to give you a more complete 37 | //! tale of how this is used please check the [examples][examples]. 38 | //! 39 | //! At its core, Rustbreak is an attempt at making a configurable 40 | //! general-purpose store Database. It features the possibility of: 41 | //! 42 | //! - Choosing what kind of Data is stored in it 43 | //! - Which kind of Serialization is used for persistence 44 | //! - Which kind of persistence is used 45 | //! 46 | //! This means you can take any struct you can serialize and deserialize and 47 | //! stick it into this Database. It is then encoded with Ron, Yaml, JSON, 48 | //! Bincode, anything really that uses Serde operations! 49 | //! 50 | //! There are three helper type aliases [`MemoryDatabase`], [`FileDatabase`], 51 | //! and [`PathDatabase`], each backed by their respective backend. 52 | //! 53 | //! The [`MemoryBackend`] saves its data into a `Vec`, which is not that 54 | //! useful on its own, but is needed for compatibility with the rest of the 55 | //! Library. 56 | //! 57 | //! The [`FileDatabase`] is a classical file based database. You give it a path 58 | //! or a file, and it will use it as its storage. You still get to pick what 59 | //! encoding it uses. 60 | //! 61 | //! The [`PathDatabase`] is very similar, but always requires a path for 62 | //! creation. It features atomic saves, so that the old database contents won't 63 | //! be lost when panicing during the save. It should therefore be preferred to a 64 | //! [`FileDatabase`]. 65 | //! 66 | //! Using the [`Database::with_deser`] and [`Database::with_backend`] one can 67 | //! switch between the representations one needs. Even at runtime! However this 68 | //! is only useful in a few scenarios. 69 | //! 70 | //! If you have any questions feel free to ask at the main [repo][repo]. 71 | //! 72 | //! ## Quickstart 73 | //! 74 | //! Add this to your `Cargo.toml`: 75 | //! 76 | //! ```toml 77 | //! [dependencies.rustbreak] 78 | //! version = "2" 79 | //! features = ["ron_enc"] # You can also use "yaml_enc" or "bin_enc" 80 | //! # Check the documentation to add your own! 81 | //! ``` 82 | //! 83 | //! ```rust 84 | //! # extern crate rustbreak; 85 | //! # use std::collections::HashMap; 86 | //! use rustbreak::{deser::Ron, MemoryDatabase}; 87 | //! 88 | //! # fn main() { 89 | //! # let func = || -> Result<(), Box> { 90 | //! let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; 91 | //! 92 | //! println!("Writing to Database"); 93 | //! db.write(|db| { 94 | //! db.insert(0, String::from("world")); 95 | //! db.insert(1, String::from("bar")); 96 | //! }); 97 | //! 98 | //! db.read(|db| { 99 | //! // db.insert("foo".into(), String::from("bar")); 100 | //! // The above line will not compile since we are only reading 101 | //! println!("Hello: {:?}", db.get(&0)); 102 | //! })?; 103 | //! # return Ok(()); }; 104 | //! # func().unwrap(); 105 | //! # } 106 | //! ``` 107 | //! 108 | //! Or alternatively: 109 | //! ```rust 110 | //! # extern crate rustbreak; 111 | //! # use std::collections::HashMap; 112 | //! use rustbreak::{deser::Ron, MemoryDatabase}; 113 | //! 114 | //! # fn main() { 115 | //! # let func = || -> Result<(), Box> { 116 | //! let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; 117 | //! 118 | //! println!("Writing to Database"); 119 | //! { 120 | //! let mut data = db.borrow_data_mut()?; 121 | //! data.insert(0, String::from("world")); 122 | //! data.insert(1, String::from("bar")); 123 | //! } 124 | //! 125 | //! let data = db.borrow_data()?; 126 | //! println!("Hello: {:?}", data.get(&0)); 127 | //! # return Ok(()); }; 128 | //! # func().unwrap(); 129 | //! # } 130 | //! ``` 131 | //! 132 | //! ## Error Handling 133 | //! 134 | //! Handling errors in Rustbreak is straightforward. Every `Result` has as its 135 | //! fail case as [`error::RustbreakError`]. This means that you can now either 136 | //! continue bubbling up said error case, or handle it yourself. 137 | //! 138 | //! ```rust 139 | //! use rustbreak::{deser::Ron, error::RustbreakError, MemoryDatabase}; 140 | //! let db = match MemoryDatabase::::memory(0) { 141 | //! Ok(db) => db, 142 | //! Err(e) => { 143 | //! // Do something with `e` here 144 | //! std::process::exit(1); 145 | //! } 146 | //! }; 147 | //! ``` 148 | //! 149 | //! ## Panics 150 | //! 151 | //! This Database implementation uses [`RwLock`] and [`Mutex`] under the hood. 152 | //! If either the closures given to [`Database::write`] or any of the Backend 153 | //! implementation methods panic the respective objects are then poisoned. This 154 | //! means that you *cannot panic* under any circumstances in your closures or 155 | //! custom backends. 156 | //! 157 | //! Currently there is no way to recover from a poisoned `Database` other than 158 | //! re-creating it. 159 | //! 160 | //! ## Examples 161 | //! 162 | //! There are several more or less in-depth example programs you can check out! 163 | //! Check them out here: [Examples][examples] 164 | //! 165 | //! - `config.rs` shows you how a possible configuration file could be managed 166 | //! with rustbreak 167 | //! - `full.rs` shows you how the database can be used as a hashmap store 168 | //! - `switching.rs` show you how you can easily swap out different parts of the 169 | //! Database *Note*: To run this example you need to enable the feature `yaml` 170 | //! like so: `cargo run --example switching --features yaml` 171 | //! - `server/` is a fully fledged example app written with the Rocket framework 172 | //! to make a form of micro-blogging website. You will need rust nightly to 173 | //! start it. 174 | //! 175 | //! ## Features 176 | //! 177 | //! Rustbreak comes with following optional features: 178 | //! 179 | //! - `ron_enc` which enables the [Ron][ron] de/serialization 180 | //! - `yaml_enc` which enables the Yaml de/serialization 181 | //! - `bin_enc` which enables the Bincode de/serialization 182 | //! - 'mmap' whhich enables memory map backend. 183 | //! 184 | //! [Enable them in your `Cargo.toml` file to use them.][features] You can 185 | //! safely have them all turned on per-default. 186 | //! 187 | //! 188 | //! [repo]: https://github.com/TheNeikos/rustbreak 189 | //! [daybreak]: https://propublica.github.io/daybreak 190 | //! [examples]: https://github.com/TheNeikos/rustbreak/tree/master/examples 191 | //! [ron]: https://github.com/ron-rs/ron 192 | //! [features]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features 193 | 194 | pub mod backend; 195 | /// Different serialization and deserialization methods one can use 196 | pub mod deser; 197 | /// The rustbreak errors that can be returned 198 | pub mod error; 199 | 200 | /// The `DeSerializer` trait used by serialization structs 201 | pub use crate::deser::DeSerializer; 202 | /// The general error used by the Rustbreak Module 203 | use std::fmt::Debug; 204 | use std::ops::Deref; 205 | use std::path::PathBuf; 206 | use std::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; 207 | 208 | use serde::de::DeserializeOwned; 209 | use serde::Serialize; 210 | 211 | #[cfg(feature = "mmap")] 212 | use crate::backend::MmapStorage; 213 | use crate::backend::{Backend, FileBackend, MemoryBackend, PathBackend}; 214 | 215 | pub use crate::error::*; 216 | 217 | /// The Central Database to Rustbreak. 218 | /// 219 | /// It has 3 Type Generics: 220 | /// 221 | /// - `Data`: Is the Data, you must specify this 222 | /// - `Back`: The storage backend. 223 | /// - `DeSer`: The Serializer/Deserializer or short `DeSer`. Check the [`deser`] 224 | /// module for other strategies. 225 | /// 226 | /// # Panics 227 | /// 228 | /// If the backend or the de/serialization panics, the database is poisoned. 229 | /// This means that any subsequent writes/reads will fail with an 230 | /// [`error::RustbreakError::Poison`]. You can only recover from this by 231 | /// re-creating the Database Object. 232 | #[derive(Debug)] 233 | pub struct Database { 234 | data: RwLock, 235 | backend: Mutex, 236 | deser: DeSer, 237 | } 238 | 239 | impl Database 240 | where 241 | Data: Serialize + DeserializeOwned + Clone + Send, 242 | Back: Backend, 243 | DeSer: DeSerializer + Send + Sync + Clone, 244 | { 245 | /// Write lock the database and get write access to the `Data` container. 246 | /// 247 | /// This gives you an exclusive lock on the memory object. Trying to open 248 | /// the database in writing will block if it is currently being written 249 | /// to. 250 | /// 251 | /// # Panics 252 | /// 253 | /// If you panic in the closure, the database is poisoned. This means that 254 | /// any subsequent writes/reads will fail with an 255 | /// [`error::RustbreakError::Poison`]. You can only recover from 256 | /// this by re-creating the Database Object. 257 | /// 258 | /// If you do not have full control over the code being written, and cannot 259 | /// incur the cost of having a single operation panicking then use 260 | /// [`Database::write_safe`]. 261 | /// 262 | /// # Examples 263 | /// 264 | /// ```rust 265 | /// # #[macro_use] extern crate serde_derive; 266 | /// # extern crate rustbreak; 267 | /// # extern crate serde; 268 | /// # extern crate tempfile; 269 | /// use rustbreak::{deser::Ron, FileDatabase}; 270 | /// 271 | /// #[derive(Debug, Serialize, Deserialize, Clone)] 272 | /// struct Data { 273 | /// level: u32, 274 | /// } 275 | /// 276 | /// # fn main() { 277 | /// # let func = || -> Result<(), Box> { 278 | /// # let file = tempfile::tempfile()?; 279 | /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; 280 | /// 281 | /// db.write(|db| { 282 | /// db.level = 42; 283 | /// })?; 284 | /// 285 | /// // You can also return from a `.read()`. But don't forget that you cannot return references 286 | /// // into the structure 287 | /// let value = db.read(|db| db.level)?; 288 | /// assert_eq!(42, value); 289 | /// # return Ok(()); 290 | /// # }; 291 | /// # func().unwrap(); 292 | /// # } 293 | /// ``` 294 | pub fn write(&self, task: T) -> error::Result 295 | where 296 | T: FnOnce(&mut Data) -> R, 297 | { 298 | let mut lock = self.data.write().map_err(|_| RustbreakError::Poison)?; 299 | Ok(task(&mut lock)) 300 | } 301 | 302 | /// Write lock the database and get write access to the `Data` container in 303 | /// a safe way. 304 | /// 305 | /// This gives you an exclusive lock on the memory object. Trying to open 306 | /// the database in writing will block if it is currently being written 307 | /// to. 308 | /// 309 | /// This differs to `Database::write` in that a clone of the internal data 310 | /// is made, which is then passed to the closure. Only if the closure 311 | /// doesn't panic is the internal model updated. 312 | /// 313 | /// Depending on the size of the database this can be very costly. This is a 314 | /// tradeoff to make for panic safety. 315 | /// 316 | /// You should read the documentation about this: 317 | /// [`UnwindSafe`](https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html) 318 | /// 319 | /// # Panics 320 | /// 321 | /// When the closure panics, it is caught and a 322 | /// [`error::RustbreakError::WritePanic`] will be returned. 323 | /// 324 | /// # Examples 325 | /// 326 | /// ```rust 327 | /// # #[macro_use] extern crate serde_derive; 328 | /// # extern crate rustbreak; 329 | /// # extern crate serde; 330 | /// # extern crate tempfile; 331 | /// use rustbreak::{ 332 | /// deser::Ron, 333 | /// error::RustbreakError, 334 | /// FileDatabase, 335 | /// }; 336 | /// 337 | /// #[derive(Debug, Serialize, Deserialize, Clone)] 338 | /// struct Data { 339 | /// level: u32, 340 | /// } 341 | /// 342 | /// # fn main() { 343 | /// # let func = || -> Result<(), Box> { 344 | /// # let file = tempfile::tempfile()?; 345 | /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; 346 | /// 347 | /// let result = db 348 | /// .write_safe(|db| { 349 | /// db.level = 42; 350 | /// panic!("We panic inside the write code."); 351 | /// }) 352 | /// .expect_err("This should have been caught"); 353 | /// 354 | /// match result { 355 | /// RustbreakError::WritePanic => { 356 | /// // We can now handle this, in this example we will just ignore it 357 | /// } 358 | /// e => { 359 | /// println!("{:#?}", e); 360 | /// // You should always have generic error catching here. 361 | /// // This future-proofs your code, and makes your code more robust. 362 | /// // In this example this is unreachable though, and to assert that we have this 363 | /// // macro here 364 | /// unreachable!(); 365 | /// } 366 | /// } 367 | /// 368 | /// // We read it back out again, it has not changed 369 | /// let value = db.read(|db| db.level)?; 370 | /// assert_eq!(0, value); 371 | /// # return Ok(()); 372 | /// # }; 373 | /// # func().unwrap(); 374 | /// # } 375 | /// ``` 376 | pub fn write_safe(&self, task: T) -> error::Result<()> 377 | where 378 | T: FnOnce(&mut Data) + std::panic::UnwindSafe, 379 | { 380 | let mut lock = self.data.write().map_err(|_| RustbreakError::Poison)?; 381 | let mut data = lock.clone(); 382 | std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { 383 | task(&mut data); 384 | })) 385 | .map_err(|_| RustbreakError::WritePanic)?; 386 | *lock = data; 387 | Ok(()) 388 | } 389 | 390 | /// Read lock the database and get read access to the `Data` container. 391 | /// 392 | /// This gives you a read-only lock on the database. You can have as many 393 | /// readers in parallel as you wish. 394 | /// 395 | /// # Errors 396 | /// 397 | /// May return: 398 | /// 399 | /// - [`error::RustbreakError::Backend`] 400 | /// 401 | /// # Panics 402 | /// 403 | /// If you panic in the closure, the database is poisoned. This means that 404 | /// any subsequent writes/reads will fail with an 405 | /// [`error::RustbreakError::Poison`]. You can only recover from 406 | /// this by re-creating the Database Object. 407 | pub fn read(&self, task: T) -> error::Result 408 | where 409 | T: FnOnce(&Data) -> R, 410 | { 411 | let mut lock = self.data.read().map_err(|_| RustbreakError::Poison)?; 412 | Ok(task(&mut lock)) 413 | } 414 | 415 | /// Read lock the database and get access to the underlying struct. 416 | /// 417 | /// This gives you access to the underlying struct, allowing for simple read 418 | /// only operations on it. 419 | /// 420 | /// # Examples 421 | /// 422 | /// ```rust 423 | /// # #[macro_use] extern crate serde_derive; 424 | /// # extern crate rustbreak; 425 | /// # extern crate serde; 426 | /// # extern crate tempfile; 427 | /// use rustbreak::{deser::Ron, FileDatabase}; 428 | /// 429 | /// #[derive(Debug, Serialize, Deserialize, Clone)] 430 | /// struct Data { 431 | /// level: u32, 432 | /// } 433 | /// 434 | /// # fn main() { 435 | /// # let func = || -> Result<(), Box> { 436 | /// # let file = tempfile::tempfile()?; 437 | /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; 438 | /// 439 | /// db.write(|db| { 440 | /// db.level = 42; 441 | /// })?; 442 | /// 443 | /// let data = db.borrow_data()?; 444 | /// 445 | /// assert_eq!(42, data.level); 446 | /// # return Ok(()); 447 | /// # }; 448 | /// # func().unwrap(); 449 | /// # } 450 | /// ``` 451 | pub fn borrow_data<'a>(&'a self) -> error::Result> { 452 | self.data.read().map_err(|_| RustbreakError::Poison) 453 | } 454 | 455 | /// Write lock the database and get access to the underlying struct. 456 | /// 457 | /// This gives you access to the underlying struct, allowing you to modify 458 | /// it. 459 | /// 460 | /// # Panics 461 | /// 462 | /// If you panic while holding this reference, the database is poisoned. 463 | /// This means that any subsequent writes/reads will fail with an 464 | /// [`error::RustbreakError::Poison`]. You can only recover from 465 | /// this by re-creating the Database Object. 466 | /// 467 | /// If you do not have full control over the code being written, and cannot 468 | /// incur the cost of having a single operation panicking then use 469 | /// [`Database::write_safe`]. 470 | /// 471 | /// # Examples 472 | /// 473 | /// ```rust 474 | /// # #[macro_use] extern crate serde_derive; 475 | /// # extern crate rustbreak; 476 | /// # extern crate serde; 477 | /// # extern crate tempfile; 478 | /// use rustbreak::{deser::Ron, FileDatabase}; 479 | /// 480 | /// #[derive(Debug, Serialize, Deserialize, Clone)] 481 | /// struct Data { 482 | /// level: u32, 483 | /// } 484 | /// 485 | /// # fn main() { 486 | /// # let func = || -> Result<(), Box> { 487 | /// # let file = tempfile::tempfile()?; 488 | /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; 489 | /// 490 | /// { 491 | /// let mut data = db.borrow_data_mut()?; 492 | /// data.level = 42; 493 | /// } 494 | /// 495 | /// let data = db.borrow_data()?; 496 | /// 497 | /// assert_eq!(42, data.level); 498 | /// # return Ok(()); 499 | /// # }; 500 | /// # func().unwrap(); 501 | /// # } 502 | /// ``` 503 | pub fn borrow_data_mut<'a>(&'a self) -> error::Result> { 504 | self.data.write().map_err(|_| RustbreakError::Poison) 505 | } 506 | 507 | /// Load data from backend and return this data. 508 | fn load_from_backend(backend: &mut Back, deser: &DeSer) -> error::Result { 509 | let new_data = deser.deserialize(&backend.get_data()?[..])?; 510 | 511 | Ok(new_data) 512 | } 513 | 514 | /// Like [`Self::load`] but returns the write lock to data it used. 515 | fn load_get_data_lock(&self) -> error::Result> { 516 | let mut backend_lock = self.backend.lock().map_err(|_| RustbreakError::Poison)?; 517 | 518 | let fresh_data = Self::load_from_backend(&mut backend_lock, &self.deser)?; 519 | drop(backend_lock); 520 | 521 | let mut data_write_lock = self.data.write().map_err(|_| RustbreakError::Poison)?; 522 | *data_write_lock = fresh_data; 523 | Ok(data_write_lock) 524 | } 525 | 526 | /// Load the data from the backend. 527 | pub fn load(&self) -> error::Result<()> { 528 | self.load_get_data_lock().map(|_| ()) 529 | } 530 | 531 | /// Like [`Self::save`] but with explicit read (or write) lock to data. 532 | fn save_data_locked>(&self, lock: L) -> error::Result<()> { 533 | let ser = self.deser.serialize(lock.deref())?; 534 | drop(lock); 535 | 536 | let mut backend = self.backend.lock().map_err(|_| RustbreakError::Poison)?; 537 | backend.put_data(&ser)?; 538 | Ok(()) 539 | } 540 | 541 | /// Flush the data structure to the backend. 542 | pub fn save(&self) -> error::Result<()> { 543 | let data = self.data.read().map_err(|_| RustbreakError::Poison)?; 544 | self.save_data_locked(data) 545 | } 546 | 547 | /// Get a clone of the data as it is in memory right now. 548 | /// 549 | /// To make sure you have the latest data, call this method with `load` 550 | /// true. 551 | pub fn get_data(&self, load: bool) -> error::Result { 552 | let data = if load { 553 | self.load_get_data_lock()? 554 | } else { 555 | self.data.write().map_err(|_| RustbreakError::Poison)? 556 | }; 557 | Ok(data.clone()) 558 | } 559 | 560 | /// Puts the data as is into memory. 561 | /// 562 | /// To save the data afterwards, call with `save` true. 563 | pub fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> { 564 | let mut data = self.data.write().map_err(|_| RustbreakError::Poison)?; 565 | *data = new_data; 566 | if save { 567 | self.save_data_locked(data) 568 | } else { 569 | Ok(()) 570 | } 571 | } 572 | 573 | /// Create a database from its constituents. 574 | pub fn from_parts(data: Data, backend: Back, deser: DeSer) -> Self { 575 | Self { 576 | data: RwLock::new(data), 577 | backend: Mutex::new(backend), 578 | deser, 579 | } 580 | } 581 | 582 | /// Break a database into its individual parts. 583 | pub fn into_inner(self) -> error::Result<(Data, Back, DeSer)> { 584 | Ok(( 585 | self.data.into_inner().map_err(|_| RustbreakError::Poison)?, 586 | self.backend 587 | .into_inner() 588 | .map_err(|_| RustbreakError::Poison)?, 589 | self.deser, 590 | )) 591 | } 592 | 593 | /// Tries to clone the Data in the Database. 594 | /// 595 | /// This method returns a `MemoryDatabase` which has an empty vector as a 596 | /// backend initially. This means that the user is responsible for assigning 597 | /// a new backend if an alternative is wanted. 598 | /// 599 | /// # Examples 600 | /// 601 | /// ```rust 602 | /// # #[macro_use] extern crate serde_derive; 603 | /// # extern crate rustbreak; 604 | /// # extern crate serde; 605 | /// # extern crate tempfile; 606 | /// use rustbreak::{deser::Ron, FileDatabase}; 607 | /// 608 | /// #[derive(Debug, Serialize, Deserialize, Clone)] 609 | /// struct Data { 610 | /// level: u32, 611 | /// } 612 | /// 613 | /// # fn main() { 614 | /// # let func = || -> Result<(), Box> { 615 | /// # let file = tempfile::tempfile()?; 616 | /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; 617 | /// 618 | /// db.write(|db| { 619 | /// db.level = 42; 620 | /// })?; 621 | /// 622 | /// db.save()?; 623 | /// 624 | /// let other_db = db.try_clone()?; 625 | /// 626 | /// // You can also return from a `.read()`. But don't forget that you cannot return references 627 | /// // into the structure 628 | /// let value = other_db.read(|db| db.level)?; 629 | /// assert_eq!(42, value); 630 | /// # return Ok(()); 631 | /// # }; 632 | /// # func().unwrap(); 633 | /// # } 634 | /// ``` 635 | pub fn try_clone(&self) -> error::Result> { 636 | let lock = self.data.read().map_err(|_| RustbreakError::Poison)?; 637 | 638 | Ok(Database { 639 | data: RwLock::new(lock.clone()), 640 | backend: Mutex::new(MemoryBackend::new()), 641 | deser: self.deser.clone(), 642 | }) 643 | } 644 | } 645 | 646 | /// A database backed by a file. 647 | pub type FileDatabase = Database; 648 | 649 | impl Database 650 | where 651 | Data: Serialize + DeserializeOwned + Clone + Send, 652 | DeSer: DeSerializer + Send + Sync + Clone, 653 | { 654 | /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), 655 | /// and load the contents. 656 | pub fn load_from_path(path: S) -> error::Result 657 | where 658 | S: AsRef, 659 | { 660 | let mut backend = FileBackend::from_path_or_fail(path)?; 661 | let deser = DeSer::default(); 662 | let data = Self::load_from_backend(&mut backend, &deser)?; 663 | 664 | let db = Self { 665 | data: RwLock::new(data), 666 | backend: Mutex::new(backend), 667 | deser, 668 | }; 669 | Ok(db) 670 | } 671 | 672 | /// Load [`FileDatabase`] at `path` or initialise with `data`. 673 | /// 674 | /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), 675 | /// and load the contents. If the file does not exist, initialise with 676 | /// `data`. 677 | pub fn load_from_path_or(path: S, data: Data) -> error::Result 678 | where 679 | S: AsRef, 680 | { 681 | let (mut backend, exists) = FileBackend::from_path_or_create(path)?; 682 | let deser = DeSer::default(); 683 | if !exists { 684 | let ser = deser.serialize(&data)?; 685 | backend.put_data(&ser)?; 686 | } 687 | 688 | let db = Self { 689 | data: RwLock::new(data), 690 | backend: Mutex::new(backend), 691 | deser, 692 | }; 693 | 694 | if exists { 695 | db.load()?; 696 | } 697 | 698 | Ok(db) 699 | } 700 | 701 | /// Load [`FileDatabase`] at `path` or initialise with `closure`. 702 | /// 703 | /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), 704 | /// and load the contents. If the file does not exist, `closure` is 705 | /// called and the database is initialised with it's return value. 706 | pub fn load_from_path_or_else(path: S, closure: C) -> error::Result 707 | where 708 | S: AsRef, 709 | C: FnOnce() -> Data, 710 | { 711 | let (mut backend, exists) = FileBackend::from_path_or_create(path)?; 712 | let deser = DeSer::default(); 713 | let data = if exists { 714 | Self::load_from_backend(&mut backend, &deser)? 715 | } else { 716 | let data = closure(); 717 | 718 | let ser = deser.serialize(&data)?; 719 | backend.put_data(&ser)?; 720 | 721 | data 722 | }; 723 | 724 | let db = Self { 725 | data: RwLock::new(data), 726 | backend: Mutex::new(backend), 727 | deser, 728 | }; 729 | Ok(db) 730 | } 731 | 732 | /// Create [`FileDatabase`] at `path`. Initialise with `data` if the file 733 | /// doesn't exist. 734 | /// 735 | /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path). 736 | /// Contents are not loaded. If the file does not exist, it is 737 | /// initialised with `data`. Frontend is always initialised with `data`. 738 | pub fn create_at_path(path: S, data: Data) -> error::Result 739 | where 740 | S: AsRef, 741 | { 742 | let (mut backend, exists) = FileBackend::from_path_or_create(path)?; 743 | let deser = DeSer::default(); 744 | if !exists { 745 | let ser = deser.serialize(&data)?; 746 | backend.put_data(&ser)?; 747 | } 748 | 749 | let db = Self { 750 | data: RwLock::new(data), 751 | backend: Mutex::new(backend), 752 | deser, 753 | }; 754 | Ok(db) 755 | } 756 | 757 | /// Create new [`FileDatabase`] from a file. 758 | pub fn from_file(file: std::fs::File, data: Data) -> error::Result { 759 | let backend = FileBackend::from_file(file); 760 | 761 | Ok(Self { 762 | data: RwLock::new(data), 763 | backend: Mutex::new(backend), 764 | deser: DeSer::default(), 765 | }) 766 | } 767 | } 768 | 769 | impl Database 770 | where 771 | Data: Serialize + DeserializeOwned + Clone + Send + Default, 772 | DeSer: DeSerializer + Send + Sync + Clone, 773 | { 774 | /// Load [`FileDatabase`] at `path` or initialise with `Data::default()`. 775 | /// 776 | /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), 777 | /// and load the contents. If the file does not exist, initialise with 778 | /// `Data::default`. 779 | pub fn load_from_path_or_default(path: S) -> error::Result 780 | where 781 | S: AsRef, 782 | { 783 | Self::load_from_path_or_else(path, Data::default) 784 | } 785 | } 786 | 787 | /// A database backed by a file, using atomic saves. 788 | pub type PathDatabase = Database; 789 | 790 | impl Database 791 | where 792 | Data: Serialize + DeserializeOwned + Clone + Send, 793 | DeSer: DeSerializer + Send + Sync + Clone, 794 | { 795 | /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), 796 | /// and load the contents. 797 | pub fn load_from_path(path: PathBuf) -> error::Result { 798 | let mut backend = PathBackend::from_path_or_fail(path)?; 799 | let deser = DeSer::default(); 800 | let data = Self::load_from_backend(&mut backend, &deser)?; 801 | 802 | let db = Self { 803 | data: RwLock::new(data), 804 | backend: Mutex::new(backend), 805 | deser, 806 | }; 807 | Ok(db) 808 | } 809 | 810 | /// Load [`PathDatabase`] at `path` or initialise with `data`. 811 | /// 812 | /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), 813 | /// and load the contents. If the file does not exist, initialise with 814 | /// `data`. 815 | pub fn load_from_path_or(path: PathBuf, data: Data) -> error::Result { 816 | let (mut backend, exists) = PathBackend::from_path_or_create(path)?; 817 | let deser = DeSer::default(); 818 | if !exists { 819 | let ser = deser.serialize(&data)?; 820 | backend.put_data(&ser)?; 821 | } 822 | 823 | let db = Self { 824 | data: RwLock::new(data), 825 | backend: Mutex::new(backend), 826 | deser, 827 | }; 828 | 829 | if exists { 830 | db.load()?; 831 | } 832 | 833 | Ok(db) 834 | } 835 | 836 | /// Load [`PathDatabase`] at `path` or initialise with `closure`. 837 | /// 838 | /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), 839 | /// and load the contents. If the file does not exist, `closure` is 840 | /// called and the database is initialised with it's return value. 841 | pub fn load_from_path_or_else(path: PathBuf, closure: C) -> error::Result 842 | where 843 | C: FnOnce() -> Data, 844 | { 845 | let (mut backend, exists) = PathBackend::from_path_or_create(path)?; 846 | let deser = DeSer::default(); 847 | let data = if exists { 848 | Self::load_from_backend(&mut backend, &deser)? 849 | } else { 850 | let data = closure(); 851 | 852 | let ser = deser.serialize(&data)?; 853 | backend.put_data(&ser)?; 854 | 855 | data 856 | }; 857 | 858 | let db = Self { 859 | data: RwLock::new(data), 860 | backend: Mutex::new(backend), 861 | deser, 862 | }; 863 | Ok(db) 864 | } 865 | 866 | /// Create [`PathDatabase`] at `path`. Initialise with `data` if the file 867 | /// doesn't exist. 868 | /// 869 | /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path). 870 | /// Contents are not loaded. If the file does not exist, it is 871 | /// initialised with `data`. Frontend is always initialised with `data`. 872 | pub fn create_at_path(path: PathBuf, data: Data) -> error::Result { 873 | let (mut backend, exists) = PathBackend::from_path_or_create(path)?; 874 | let deser = DeSer::default(); 875 | if !exists { 876 | let ser = deser.serialize(&data)?; 877 | backend.put_data(&ser)?; 878 | } 879 | 880 | let db = Self { 881 | data: RwLock::new(data), 882 | backend: Mutex::new(backend), 883 | deser, 884 | }; 885 | Ok(db) 886 | } 887 | } 888 | 889 | impl Database 890 | where 891 | Data: Serialize + DeserializeOwned + Clone + Send + Default, 892 | DeSer: DeSerializer + Send + Sync + Clone, 893 | { 894 | /// Load [`PathDatabase`] at `path` or initialise with `Data::default()`. 895 | /// 896 | /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), 897 | /// and load the contents. If the file does not exist, initialise with 898 | /// `Data::default`. 899 | pub fn load_from_path_or_default(path: PathBuf) -> error::Result { 900 | Self::load_from_path_or_else(path, Data::default) 901 | } 902 | } 903 | 904 | /// A database backed by a byte vector (`Vec`). 905 | pub type MemoryDatabase = Database; 906 | 907 | impl Database 908 | where 909 | Data: Serialize + DeserializeOwned + Clone + Send, 910 | DeSer: DeSerializer + Send + Sync + Clone, 911 | { 912 | /// Create new in-memory database. 913 | pub fn memory(data: Data) -> error::Result { 914 | let backend = MemoryBackend::new(); 915 | 916 | Ok(Self { 917 | data: RwLock::new(data), 918 | backend: Mutex::new(backend), 919 | deser: DeSer::default(), 920 | }) 921 | } 922 | } 923 | 924 | /// A database backed by anonymous memory map. 925 | #[cfg(feature = "mmap")] 926 | pub type MmapDatabase = Database; 927 | 928 | #[cfg(feature = "mmap")] 929 | impl Database 930 | where 931 | Data: Serialize + DeserializeOwned + Clone + Send, 932 | DeSer: DeSerializer + Send + Sync + Clone, 933 | { 934 | /// Create new [`MmapDatabase`]. 935 | pub fn mmap(data: Data) -> error::Result { 936 | let backend = MmapStorage::new()?; 937 | 938 | Ok(Self { 939 | data: RwLock::new(data), 940 | backend: Mutex::new(backend), 941 | deser: DeSer::default(), 942 | }) 943 | } 944 | 945 | /// Create new [`MmapDatabase`] with specified initial size. 946 | pub fn mmap_with_size(data: Data, size: usize) -> error::Result { 947 | let backend = MmapStorage::with_size(size)?; 948 | 949 | Ok(Self { 950 | data: RwLock::new(data), 951 | backend: Mutex::new(backend), 952 | deser: DeSer::default(), 953 | }) 954 | } 955 | } 956 | 957 | impl Database { 958 | /// Exchanges the `DeSerialization` strategy with the new one. 959 | pub fn with_deser(self, deser: T) -> Database { 960 | Database { 961 | backend: self.backend, 962 | data: self.data, 963 | deser, 964 | } 965 | } 966 | } 967 | 968 | impl Database { 969 | /// Exchanges the `Backend` with the new one. 970 | /// 971 | /// The new backend does not necessarily have the latest data saved to it, 972 | /// so a `.save` should be called to make sure that it is saved. 973 | pub fn with_backend(self, backend: T) -> Database { 974 | Database { 975 | backend: Mutex::new(backend), 976 | data: self.data, 977 | deser: self.deser, 978 | } 979 | } 980 | } 981 | 982 | impl Database 983 | where 984 | Data: Serialize + DeserializeOwned + Clone + Send, 985 | Back: Backend, 986 | DeSer: DeSerializer + Send + Sync + Clone, 987 | { 988 | /// Converts from one data type to another. 989 | /// 990 | /// This method is useful to migrate from one datatype to another. 991 | pub fn convert_data( 992 | self, 993 | convert: C, 994 | ) -> error::Result> 995 | where 996 | OutputData: Serialize + DeserializeOwned + Clone + Send, 997 | C: FnOnce(Data) -> OutputData, 998 | DeSer: DeSerializer + Send + Sync, 999 | { 1000 | let (data, backend, deser) = self.into_inner()?; 1001 | Ok(Database { 1002 | data: RwLock::new(convert(data)), 1003 | backend: Mutex::new(backend), 1004 | deser, 1005 | }) 1006 | } 1007 | } 1008 | 1009 | #[cfg(test)] 1010 | mod tests { 1011 | use super::*; 1012 | use std::collections::HashMap; 1013 | use tempfile::NamedTempFile; 1014 | 1015 | type TestData = HashMap; 1016 | type TestDb = Database; 1017 | type TestMemDb = TestDb; 1018 | 1019 | fn test_data() -> TestData { 1020 | let mut data = HashMap::new(); 1021 | data.insert(1, "Hello World".to_string()); 1022 | data.insert(100, "Rustbreak".to_string()); 1023 | data 1024 | } 1025 | 1026 | /// Used to test that `Default::default` isn't called. 1027 | #[derive(Clone, Debug, Serialize, serde::Deserialize)] 1028 | struct PanicDefault; 1029 | impl Default for PanicDefault { 1030 | fn default() -> Self { 1031 | panic!("`default` was called but should not") 1032 | } 1033 | } 1034 | 1035 | #[test] 1036 | fn create_db_and_read() { 1037 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1038 | assert_eq!( 1039 | "Hello World", 1040 | db.read(|d| d.get(&1).cloned()) 1041 | .expect("Rustbreak read error") 1042 | .expect("Should be `Some` but was `None`") 1043 | ); 1044 | assert_eq!( 1045 | "Rustbreak", 1046 | db.read(|d| d.get(&100).cloned()) 1047 | .expect("Rustbreak read error") 1048 | .expect("Should be `Some` but was `None`") 1049 | ); 1050 | } 1051 | 1052 | #[test] 1053 | fn write_twice() { 1054 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1055 | db.write(|d| d.insert(3, "Write to db".to_string())) 1056 | .expect("Rustbreak write error"); 1057 | db.write(|d| d.insert(3, "Second write".to_string())) 1058 | .expect("Rustbreak write error"); 1059 | assert_eq!( 1060 | "Hello World", 1061 | db.read(|d| d.get(&1).cloned()) 1062 | .expect("Rustbreak read error") 1063 | .expect("Should be `Some` but was `None`") 1064 | ); 1065 | assert_eq!( 1066 | "Rustbreak", 1067 | db.read(|d| d.get(&100).cloned()) 1068 | .expect("Rustbreak read error") 1069 | .expect("Should be `Some` but was `None`") 1070 | ); 1071 | assert_eq!( 1072 | "Second write", 1073 | db.read(|d| d.get(&3).cloned()) 1074 | .expect("Rustbreak read error") 1075 | .expect("Should be `Some` but was `None`") 1076 | ); 1077 | } 1078 | 1079 | #[test] 1080 | fn save_load() { 1081 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1082 | db.save().expect("Rustbreak save error"); 1083 | db.write(|d| d.clear()).expect("Rustbreak write error"); 1084 | db.load().expect("Rustbreak load error"); 1085 | assert_eq!( 1086 | "Hello World", 1087 | db.read(|d| d.get(&1).cloned()) 1088 | .expect("Rustbreak read error") 1089 | .expect("Should be `Some` but was `None`") 1090 | ); 1091 | assert_eq!( 1092 | "Rustbreak", 1093 | db.read(|d| d.get(&100).cloned()) 1094 | .expect("Rustbreak read error") 1095 | .expect("Should be `Some` but was `None`") 1096 | ); 1097 | } 1098 | 1099 | #[test] 1100 | fn writesafe_twice() { 1101 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1102 | db.write_safe(|d| { 1103 | d.insert(3, "Write to db".to_string()); 1104 | }) 1105 | .expect("Rustbreak write error"); 1106 | db.write_safe(|d| { 1107 | d.insert(3, "Second write".to_string()); 1108 | }) 1109 | .expect("Rustbreak write error"); 1110 | assert_eq!( 1111 | "Hello World", 1112 | db.read(|d| d.get(&1).cloned()) 1113 | .expect("Rustbreak read error") 1114 | .expect("Should be `Some` but was `None`") 1115 | ); 1116 | assert_eq!( 1117 | "Rustbreak", 1118 | db.read(|d| d.get(&100).cloned()) 1119 | .expect("Rustbreak read error") 1120 | .expect("Should be `Some` but was `None`") 1121 | ); 1122 | assert_eq!( 1123 | "Second write", 1124 | db.read(|d| d.get(&3).cloned()) 1125 | .expect("Rustbreak read error") 1126 | .expect("Should be `Some` but was `None`") 1127 | ); 1128 | } 1129 | 1130 | #[test] 1131 | fn writesafe_panic() { 1132 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1133 | let err = db 1134 | .write_safe(|d| { 1135 | d.clear(); 1136 | panic!("Panic should be catched") 1137 | }) 1138 | .expect_err("Did not error on panic in safe write!"); 1139 | assert!(matches!(err, RustbreakError::WritePanic)); 1140 | 1141 | assert_eq!( 1142 | "Hello World", 1143 | db.read(|d| d.get(&1).cloned()) 1144 | .expect("Rustbreak read error") 1145 | .expect("Should be `Some` but was `None`") 1146 | ); 1147 | assert_eq!( 1148 | "Rustbreak", 1149 | db.read(|d| d.get(&100).cloned()) 1150 | .expect("Rustbreak read error") 1151 | .expect("Should be `Some` but was `None`") 1152 | ); 1153 | } 1154 | 1155 | #[test] 1156 | fn borrow_data_twice() { 1157 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1158 | let readlock1 = db.borrow_data().expect("Rustbreak readlock error"); 1159 | let readlock2 = db.borrow_data().expect("Rustbreak readlock error"); 1160 | assert_eq!( 1161 | "Hello World", 1162 | readlock1.get(&1).expect("Should be `Some` but was `None`") 1163 | ); 1164 | assert_eq!( 1165 | "Hello World", 1166 | readlock2.get(&1).expect("Should be `Some` but was `None`") 1167 | ); 1168 | assert_eq!( 1169 | "Rustbreak", 1170 | readlock1 1171 | .get(&100) 1172 | .expect("Should be `Some` but was `None`") 1173 | ); 1174 | assert_eq!( 1175 | "Rustbreak", 1176 | readlock2 1177 | .get(&100) 1178 | .expect("Should be `Some` but was `None`") 1179 | ); 1180 | assert_eq!(*readlock1, *readlock2); 1181 | } 1182 | 1183 | #[test] 1184 | fn borrow_data_mut() { 1185 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1186 | let mut writelock = db.borrow_data_mut().expect("Rustbreak writelock error"); 1187 | writelock.insert(3, "Write to db".to_string()); 1188 | drop(writelock); 1189 | assert_eq!( 1190 | "Hello World", 1191 | db.read(|d| d.get(&1).cloned()) 1192 | .expect("Rustbreak read error") 1193 | .expect("Should be `Some` but was `None`") 1194 | ); 1195 | assert_eq!( 1196 | "Rustbreak", 1197 | db.read(|d| d.get(&100).cloned()) 1198 | .expect("Rustbreak read error") 1199 | .expect("Should be `Some` but was `None`") 1200 | ); 1201 | assert_eq!( 1202 | "Write to db", 1203 | db.read(|d| d.get(&3).cloned()) 1204 | .expect("Rustbreak read error") 1205 | .expect("Should be `Some` but was `None`") 1206 | ); 1207 | } 1208 | 1209 | #[test] 1210 | fn get_data_mem() { 1211 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1212 | let data = db.get_data(false).expect("could not get data"); 1213 | assert_eq!(test_data(), data); 1214 | } 1215 | 1216 | #[test] 1217 | fn get_data_load() { 1218 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1219 | db.save().expect("Rustbreak save error"); 1220 | db.write(|d| d.clear()).expect("Rustbreak write error"); 1221 | let data = db.get_data(true).expect("could not get data"); 1222 | assert_eq!(test_data(), data); 1223 | } 1224 | 1225 | #[test] 1226 | fn put_data_mem() { 1227 | let db = TestMemDb::memory(TestData::default()).expect("Could not create database"); 1228 | db.put_data(test_data(), false).expect("could not put data"); 1229 | assert_eq!( 1230 | "Hello World", 1231 | db.read(|d| d.get(&1).cloned()) 1232 | .expect("Rustbreak read error") 1233 | .expect("Should be `Some` but was `None`") 1234 | ); 1235 | assert_eq!( 1236 | "Rustbreak", 1237 | db.read(|d| d.get(&100).cloned()) 1238 | .expect("Rustbreak read error") 1239 | .expect("Should be `Some` but was `None`") 1240 | ); 1241 | let data = db.get_data(false).expect("could not get data"); 1242 | assert_eq!(test_data(), data); 1243 | } 1244 | 1245 | #[test] 1246 | fn put_data_save() { 1247 | let db = TestMemDb::memory(TestData::default()).expect("Could not create database"); 1248 | db.put_data(test_data(), true).expect("could not put data"); 1249 | db.load().expect("Rustbreak load error"); 1250 | assert_eq!( 1251 | "Hello World", 1252 | db.read(|d| d.get(&1).cloned()) 1253 | .expect("Rustbreak read error") 1254 | .expect("Should be `Some` but was `None`") 1255 | ); 1256 | assert_eq!( 1257 | "Rustbreak", 1258 | db.read(|d| d.get(&100).cloned()) 1259 | .expect("Rustbreak read error") 1260 | .expect("Should be `Some` but was `None`") 1261 | ); 1262 | let data = db.get_data(false).expect("could not get data"); 1263 | assert_eq!(test_data(), data); 1264 | } 1265 | 1266 | #[test] 1267 | fn save_and_into_inner() { 1268 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1269 | db.save().expect("Rustbreak save error"); 1270 | let (data, mut backend, _) = db 1271 | .into_inner() 1272 | .expect("error calling `Database.into_inner`"); 1273 | assert_eq!(test_data(), data); 1274 | let parsed: TestData = 1275 | ron::de::from_reader(&backend.get_data().expect("could not get data from backend")[..]) 1276 | .expect("backend contains invalid RON"); 1277 | assert_eq!(test_data(), parsed); 1278 | } 1279 | 1280 | #[test] 1281 | fn clone() { 1282 | let db1 = TestMemDb::memory(test_data()).expect("Could not create database"); 1283 | let readlock1 = db1.borrow_data().expect("Rustbreak readlock error"); 1284 | let db2 = db1.try_clone().expect("Rustbreak clone error"); 1285 | let readlock2 = db2.borrow_data().expect("Rustbreak readlock error"); 1286 | assert_eq!(test_data(), *readlock1); 1287 | assert_eq!(*readlock1, *readlock2); 1288 | } 1289 | 1290 | #[test] 1291 | fn allow_databases_with_boxed_backend() { 1292 | let db = 1293 | MemoryDatabase::, crate::deser::Ron>::memory(vec![]).expect("To be created"); 1294 | let db: Database<_, Box, _> = db.with_backend(Box::new(MemoryBackend::new())); 1295 | db.put_data(vec![1, 2, 3], true) 1296 | .expect("Can save data in memory"); 1297 | assert_eq!( 1298 | &[1, 2, 3], 1299 | &db.get_data(true).expect("Can get data from memory")[..] 1300 | ); 1301 | } 1302 | 1303 | /// Since `save` only needs read-access to the data we should be able to 1304 | /// save while holding a readlock. 1305 | #[test] 1306 | fn save_holding_readlock() { 1307 | let db = TestMemDb::memory(test_data()).expect("Could not create database"); 1308 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1309 | db.save().expect("Rustbreak save error"); 1310 | assert_eq!(test_data(), *readlock); 1311 | } 1312 | 1313 | /// Test that if the file already exists, the closure won't be called. 1314 | #[test] 1315 | #[cfg_attr(miri, ignore)] 1316 | fn pathdb_from_path_or_else_existing_nocall() { 1317 | let file = NamedTempFile::new().expect("could not create temporary file"); 1318 | let path = file.path().to_owned(); 1319 | let _ = TestDb::::load_from_path_or_else(path, || { 1320 | panic!("closure called while file existed") 1321 | }); 1322 | } 1323 | 1324 | /// Test that if the file already exists, the closure won't be called. 1325 | #[test] 1326 | #[cfg_attr(miri, ignore)] 1327 | fn filedb_from_path_or_else_existing_nocall() { 1328 | let file = NamedTempFile::new().expect("could not create temporary file"); 1329 | let path = file.path(); 1330 | let _ = TestDb::::load_from_path_or_else(path, || { 1331 | panic!("closure called while file existed") 1332 | }); 1333 | } 1334 | 1335 | /// Test that if the file already exists, `default` won't be called. 1336 | #[test] 1337 | #[cfg_attr(miri, ignore)] 1338 | fn pathdb_from_path_or_default_existing_nocall() { 1339 | let file = NamedTempFile::new().expect("could not create temporary file"); 1340 | let path = file.path().to_owned(); 1341 | let _ = Database::::load_from_path_or_default( 1342 | path, 1343 | ); 1344 | } 1345 | 1346 | /// Test that if the file already exists, the closure won't be called. 1347 | #[test] 1348 | #[cfg_attr(miri, ignore)] 1349 | fn filedb_from_path_or_default_existing_nocall() { 1350 | let file = NamedTempFile::new().expect("could not create temporary file"); 1351 | let path = file.path(); 1352 | let _ = Database::::load_from_path_or_default( 1353 | path, 1354 | ); 1355 | } 1356 | 1357 | #[test] 1358 | #[cfg_attr(miri, ignore)] 1359 | fn pathdb_from_path_or_new() { 1360 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 1361 | let mut file_path = dir.path().to_owned(); 1362 | file_path.push("rustbreak_path_db.db"); 1363 | let db = TestDb::::load_from_path_or(file_path, test_data()) 1364 | .expect("could not load from path"); 1365 | db.load().expect("could not load"); 1366 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1367 | assert_eq!(test_data(), *readlock); 1368 | dir.close().expect("Error while deleting temp directory!"); 1369 | } 1370 | 1371 | #[test] 1372 | #[cfg_attr(miri, ignore)] 1373 | fn pathdb_from_path_or_else_new() { 1374 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 1375 | let mut file_path = dir.path().to_owned(); 1376 | file_path.push("rustbreak_path_db.db"); 1377 | let db = TestDb::::load_from_path_or_else(file_path, test_data) 1378 | .expect("could not load from path"); 1379 | db.load().expect("could not load"); 1380 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1381 | assert_eq!(test_data(), *readlock); 1382 | dir.close().expect("Error while deleting temp directory!"); 1383 | } 1384 | 1385 | #[test] 1386 | #[cfg_attr(miri, ignore)] 1387 | fn filedb_from_path_or_new() { 1388 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 1389 | let mut file_path = dir.path().to_owned(); 1390 | file_path.push("rustbreak_path_db.db"); 1391 | let db = TestDb::::load_from_path_or(file_path, test_data()) 1392 | .expect("could not load from path"); 1393 | db.load().expect("could not load"); 1394 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1395 | assert_eq!(test_data(), *readlock); 1396 | dir.close().expect("Error while deleting temp directory!"); 1397 | } 1398 | 1399 | #[test] 1400 | #[cfg_attr(miri, ignore)] 1401 | fn filedb_from_path_or_else_new() { 1402 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 1403 | let mut file_path = dir.path().to_owned(); 1404 | file_path.push("rustbreak_path_db.db"); 1405 | let db = TestDb::::load_from_path_or_else(file_path, test_data) 1406 | .expect("could not load from path"); 1407 | db.load().expect("could not load"); 1408 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1409 | assert_eq!(test_data(), *readlock); 1410 | dir.close().expect("Error while deleting temp directory!"); 1411 | } 1412 | 1413 | #[test] 1414 | #[cfg_attr(miri, ignore)] 1415 | fn pathdb_from_path_new_fail() { 1416 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 1417 | let mut file_path = dir.path().to_owned(); 1418 | file_path.push("rustbreak_path_db.db"); 1419 | let err = TestDb::::load_from_path(file_path) 1420 | .expect_err("should fail with file not found"); 1421 | if let RustbreakError::Backend(BackendError::Io(io_err)) = &err { 1422 | assert_eq!(std::io::ErrorKind::NotFound, io_err.kind()); 1423 | } else { 1424 | panic!("Wrong error: {}", err) 1425 | }; 1426 | 1427 | dir.close().expect("Error while deleting temp directory!"); 1428 | } 1429 | 1430 | #[test] 1431 | #[cfg_attr(miri, ignore)] 1432 | fn filedb_from_path_new_fail() { 1433 | let dir = tempfile::tempdir().expect("could not create temporary directory"); 1434 | let mut file_path = dir.path().to_owned(); 1435 | file_path.push("rustbreak_path_db.db"); 1436 | let err = TestDb::::load_from_path(file_path) 1437 | .expect_err("should fail with file not found"); 1438 | if let RustbreakError::Backend(BackendError::Io(io_err)) = &err { 1439 | assert_eq!(std::io::ErrorKind::NotFound, io_err.kind()); 1440 | } else { 1441 | panic!("Wrong error: {}", err) 1442 | }; 1443 | 1444 | dir.close().expect("Error while deleting temp directory!"); 1445 | } 1446 | 1447 | #[test] 1448 | #[cfg_attr(miri, ignore)] 1449 | fn pathdb_from_path_existing() { 1450 | let file = NamedTempFile::new().expect("could not create temporary file"); 1451 | let path = file.path().to_owned(); 1452 | // initialise the file 1453 | let db = TestDb::::create_at_path(path.clone(), test_data()) 1454 | .expect("could not create db"); 1455 | db.save().expect("could not save db"); 1456 | drop(db); 1457 | // test that loading now works 1458 | let db = TestDb::::load_from_path(path).expect("could not load"); 1459 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1460 | assert_eq!(test_data(), *readlock); 1461 | } 1462 | 1463 | #[test] 1464 | #[cfg_attr(miri, ignore)] 1465 | fn filedb_from_path_existing() { 1466 | let file = NamedTempFile::new().expect("could not create temporary file"); 1467 | let path = file.path(); 1468 | // initialise the file 1469 | let db = 1470 | TestDb::::create_at_path(path, test_data()).expect("could not create db"); 1471 | db.save().expect("could not save db"); 1472 | drop(db); 1473 | // test that loading now works 1474 | let db = TestDb::::load_from_path(path).expect("could not load"); 1475 | let readlock = db.borrow_data().expect("Rustbreak readlock error"); 1476 | assert_eq!(test_data(), *readlock); 1477 | } 1478 | } 1479 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use rustbreak::backend::Backend; 2 | use rustbreak::deser::{Bincode, DeSerializer, Ron, Yaml}; 3 | use rustbreak::{Database, FileDatabase, MemoryDatabase, MmapDatabase, PathDatabase}; 4 | use std::fmt::Debug; 5 | use std::ops::Deref; 6 | use tempfile::tempfile; 7 | 8 | type Data = std::collections::HashMap; 9 | 10 | fn conv(mut data: Data) -> Data { 11 | data.remove(&2); 12 | data.insert(0, "Newly inserted".to_string()); 13 | data.insert(16, "Convertion succesful".to_string()); 14 | data 15 | } 16 | 17 | fn test_basic_save_load + Debug>( 18 | db: &Database, 19 | ) { 20 | db.write(|db| { 21 | db.insert(2, "Hello world!".to_string()); 22 | }) 23 | .expect("rustbreak write error"); 24 | db.write_safe(|db| { 25 | db.insert(5, "Hello again".to_string()); 26 | }) 27 | .expect("rustbreak write error"); 28 | db.save().expect("error while saving"); 29 | let saved_state = db.get_data(false).expect("could not get data"); 30 | 31 | // test that loading correctly restores the data 32 | db.write(|db| { 33 | db.clear(); 34 | }) 35 | .expect("rustbreak write error"); 36 | db.load().expect("rustbreak load error"); 37 | 38 | let len = db.read(|db| db.len()).expect("rustbreak read error"); 39 | assert_eq!(len, 2); 40 | 41 | let second = db 42 | .read(|db| db.get(&2).cloned()) 43 | .expect("rustbreak read error"); 44 | assert_eq!(second, Some(String::from("Hello world!"))); 45 | 46 | let fith = db 47 | .read(|db| db.get(&5).cloned()) 48 | .expect("rustbreak read error"); 49 | assert_eq!(fith, Some(String::from("Hello again"))); 50 | 51 | let data = db.borrow_data().expect("rustbreak borrow error"); 52 | assert_eq!(&saved_state, data.deref()); 53 | } 54 | 55 | fn test_multi_borrow + Debug>(db: &Database) { 56 | let data1 = db.borrow_data().expect("rustbreak borrow error"); 57 | let data2 = db.borrow_data().expect("rustbreak borrow error"); 58 | let data3 = db.borrow_data().expect("rustbreak borrow error"); 59 | assert_eq!(data1.deref(), data2.deref()); 60 | assert_eq!(data1.deref(), data3.deref()); 61 | } 62 | 63 | fn test_put_data + Debug>(db: &Database) { 64 | let backup = db.get_data(true).expect("could not get data"); 65 | 66 | let mut other_state = Data::new(); 67 | other_state.insert(3, "Foo".to_string()); 68 | other_state.insert(7, "Bar".to_string()); 69 | other_state.insert(19, "Bazz".to_string()); 70 | 71 | db.put_data(other_state.clone(), true) 72 | .expect("could not put data"); 73 | let data = db.borrow_data().expect("rustbreak borrow error"); 74 | assert_eq!(&other_state, data.deref()); 75 | // If we do not explicitly drop `data` here, the subsequent write will freeze 76 | drop(data); 77 | 78 | db.write(|db| { 79 | db.clear(); 80 | }) 81 | .expect("rustbreak write error"); 82 | db.load().expect("rustbreak load error"); 83 | 84 | let data = db.borrow_data().expect("rustbreak borrow error"); 85 | assert_eq!(&other_state, data.deref()); 86 | drop(data); 87 | 88 | db.put_data(backup, false).expect("could not put data"); 89 | } 90 | 91 | fn test_convert_data + Debug>(db: Database) { 92 | let db = db.convert_data(conv).expect("Could not convert data"); 93 | 94 | let mut expected_state = Data::new(); 95 | expected_state.insert(0, "Newly inserted".to_string()); 96 | expected_state.insert(5, "Hello again".to_string()); 97 | expected_state.insert(16, "Convertion succesful".to_string()); 98 | assert_eq!( 99 | &expected_state, 100 | db.borrow_data().expect("rustbreak borrow error").deref() 101 | ); 102 | } 103 | 104 | fn create_filedb + Debug>() -> FileDatabase { 105 | FileDatabase::from_file(tempfile().expect("could not create file"), Data::default()) 106 | .expect("could not create database") 107 | } 108 | 109 | fn create_filedb_from_path + Debug>() -> FileDatabase { 110 | let file = tempfile::NamedTempFile::new().expect("could not create temporary file"); 111 | FileDatabase::create_at_path(file.path(), Data::default()).expect("could not create database") 112 | } 113 | 114 | fn create_memdb + Debug>() -> MemoryDatabase { 115 | MemoryDatabase::memory(Data::default()).expect("could not create database") 116 | } 117 | 118 | fn create_mmapdb + Debug>() -> MmapDatabase { 119 | MmapDatabase::mmap(Data::default()).expect("could not create database") 120 | } 121 | 122 | fn create_mmapdb_with_size + Debug>(size: usize) -> MmapDatabase { 123 | MmapDatabase::mmap_with_size(Data::default(), size).expect("could not create database") 124 | } 125 | 126 | fn create_pathdb + Debug>() -> PathDatabase { 127 | let file = tempfile::NamedTempFile::new().expect("could not create temporary file"); 128 | PathDatabase::create_at_path(file.path().to_owned(), Data::default()) 129 | .expect("could not create database") 130 | } 131 | 132 | macro_rules! test_basic_save_load { 133 | ($name:ident, $db:expr, $enc:ty) => { 134 | #[test] 135 | #[cfg_attr(miri, ignore)] 136 | fn $name() { 137 | let db: Database = $db; 138 | test_basic_save_load(&db); 139 | test_put_data(&db); 140 | test_multi_borrow(&db); 141 | test_convert_data(db); 142 | } 143 | }; 144 | ($name:ident, $db:expr, $enc:ty, miri=true) => { 145 | #[test] 146 | fn $name() { 147 | let db: Database = $db; 148 | test_basic_save_load(&db); 149 | test_put_data(&db); 150 | test_multi_borrow(&db); 151 | test_convert_data(db); 152 | } 153 | }; 154 | } 155 | 156 | test_basic_save_load!(file_ron, create_filedb(), Ron); 157 | test_basic_save_load!(file_yaml, create_filedb(), Yaml); 158 | test_basic_save_load!(file_bincode, create_filedb(), Bincode); 159 | 160 | test_basic_save_load!(filepath_ron, create_filedb_from_path(), Ron); 161 | test_basic_save_load!(filepath_yaml, create_filedb_from_path(), Yaml); 162 | test_basic_save_load!(filepath_bincode, create_filedb_from_path(), Bincode); 163 | 164 | test_basic_save_load!(mem_ron, create_memdb(), Ron, miri = true); 165 | test_basic_save_load!(mem_yaml, create_memdb(), Yaml, miri = true); 166 | test_basic_save_load!(mem_bincode, create_memdb(), Bincode, miri = true); 167 | 168 | test_basic_save_load!(mmap_ron, create_mmapdb(), Ron); 169 | test_basic_save_load!(mmap_yaml, create_mmapdb(), Yaml); 170 | test_basic_save_load!(mmap_bincode, create_mmapdb(), Bincode); 171 | 172 | test_basic_save_load!(mmapsize_ron, create_mmapdb_with_size(10), Ron); 173 | test_basic_save_load!(mmapsize_yaml, create_mmapdb_with_size(10), Yaml); 174 | test_basic_save_load!(mmapsize_bincode, create_mmapdb_with_size(10), Bincode); 175 | 176 | test_basic_save_load!(path_ron, create_pathdb(), Ron); 177 | test_basic_save_load!(path_yaml, create_pathdb(), Yaml); 178 | test_basic_save_load!(path_bincode, create_pathdb(), Bincode); 179 | --------------------------------------------------------------------------------