├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── create_function.rs ├── create_index.rs ├── create_instance.rs ├── delete_instance.rs ├── get_instance.rs └── misc.rs ├── src ├── client.rs ├── client │ ├── response.rs │ ├── response │ │ ├── index.rs │ │ └── value.rs │ └── sync.rs ├── error.rs ├── expr.rs ├── expr │ ├── array.rs │ ├── number.rs │ ├── object.rs │ ├── permission.rs │ ├── reference.rs │ └── set.rs ├── lib.rs ├── macros.rs ├── prelude.rs ├── query.rs ├── query │ ├── auth.rs │ ├── basic.rs │ ├── collection.rs │ ├── conversion.rs │ ├── datetime.rs │ ├── logical.rs │ ├── math.rs │ ├── misc.rs │ ├── read.rs │ ├── set.rs │ ├── string.rs │ ├── write.rs │ └── write │ │ ├── create.rs │ │ ├── create_class.rs │ │ ├── create_database.rs │ │ ├── create_function.rs │ │ ├── create_index.rs │ │ ├── create_key.rs │ │ ├── insert.rs │ │ └── update.rs ├── serde.rs ├── serde │ └── base64_bytes.rs └── test_utils.rs └── todo.org /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@prisma.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "faunadb" 3 | version = "0.0.12" 4 | authors = ["Julius de Bruijn "] 5 | edition = "2018" 6 | readme = "README.md" 7 | license = "Apache-2.0" 8 | description = "A Client for Fauna Database" 9 | repository = "https://github.com/prisma/faunadb-rust/" 10 | homepage = "https://github.com/prisma/faunadb-rust/" 11 | keywords = ["fauna", "faunadb", "database", "async"] 12 | documentation = "https://docs.rs/faunadb/" 13 | 14 | [features] 15 | default = ["sync_client"] 16 | sync_client = ["tokio"] 17 | 18 | [dependencies] 19 | chrono = { version = "0.4", features = ["serde"] } 20 | serde = "1.0" 21 | serde_json = "1.0" 22 | serde_derive = "1.0" 23 | base64-serde = "0.3" 24 | base64 = "0.10" 25 | hyper = "0.12" 26 | hyper-tls = "0.3" 27 | tokio-timer = "0.2" 28 | futures = "0.1" 29 | failure = "0.1" 30 | failure_derive = "0.1" 31 | native-tls = "0.2" 32 | log = "0.4" 33 | http = "0.1" 34 | lazy_static = "1.3" 35 | tokio = { version = "0.1", optional = true } 36 | 37 | [dev-dependencies] 38 | clap = "2" 39 | pretty_env_logger = "0.3" 40 | tokio = "0.1" 41 | rand = "0.6" 42 | lazy_static = "1.3" 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FaunaDB Rust Client 2 | 3 | FaunaDB offers an asynchronous (and synchronous) client for communicating with 4 | the [Fauna](https://fauna.com) database. 5 | 6 | Goals: 7 | 8 | - Typesafe 9 | - Allocating only when really needed 10 | - Asynchronous using futures (and [Tokio](https://tokio.rs)) 11 | 12 | The crate is not yet tested on production so use at your own risk. 13 | 14 | ## Basic Usage 15 | 16 | ``` rust 17 | use faunadb::prelude::*; 18 | use tokio; 19 | use futures::{future::lazy, Future}; 20 | 21 | fn main() { 22 | let client = Client::builder("put-your-secret-here").build().unwrap(); 23 | let params = DatabaseParams::new("my-first-database"); 24 | 25 | tokio::run(lazy(move || { 26 | client 27 | .query(CreateDatabase::new(params)) 28 | .map(|response| { 29 | let res = response.resource; 30 | assert_eq!(Some("my-first-database"), res["name"].as_str()) 31 | }) 32 | .map_err(|error: faunadb::error::Error| { 33 | println!("Error: {:?}", error); 34 | }) 35 | })); 36 | } 37 | ``` 38 | 39 | ## Testing 40 | 41 | For tests to be successful, one must have the [default Fauna Docker 42 | image](https://github.com/fauna/faunadb-docker), using the default password 43 | `secret`. 44 | 45 | Run the tests with: 46 | 47 | ``` bash 48 | cargo test 49 | ``` 50 | 51 | ## License 52 | 53 | The faunadb-rust crate is licensed under the [Apache 2.0](./LICENSE) 54 | -------------------------------------------------------------------------------- /examples/create_function.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use faunadb::prelude::*; 3 | use futures::{lazy, Future}; 4 | 5 | fn main() { 6 | pretty_env_logger::init(); 7 | 8 | let matches = App::new("A Simple FaunaDB Client") 9 | .version("1.0") 10 | .author("Julius de Bruijn ") 11 | .about("For testing faunadb-rust client library") 12 | .arg( 13 | Arg::with_name("secret") 14 | .short("s") 15 | .long("secret") 16 | .value_name("STRING") 17 | .required(true) 18 | .help("The FaunaDB connection secret") 19 | .takes_value(true), 20 | ) 21 | .get_matches(); 22 | 23 | let secret = matches.value_of("secret").unwrap(); 24 | let client = Client::builder(secret).build().unwrap(); 25 | 26 | let params = FunctionParams::new( 27 | "double", 28 | Lambda::new( 29 | "x", 30 | Add::new(Array::from(vec![Var::new("x"), Var::new("x")])), 31 | ), 32 | ); 33 | 34 | tokio::run(lazy(move || { 35 | client 36 | .query(CreateFunction::new(params)) 37 | .map(|response| { 38 | println!("{:?}", response); 39 | }) 40 | .map_err(|error: faunadb::error::Error| { 41 | println!("Error: {:#?}", error); 42 | }) 43 | })); 44 | } 45 | -------------------------------------------------------------------------------- /examples/create_index.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use faunadb::prelude::*; 3 | use futures::{lazy, Future}; 4 | 5 | fn main() { 6 | pretty_env_logger::init(); 7 | 8 | let matches = App::new("A Simple FaunaDB Client") 9 | .version("1.0") 10 | .author("Julius de Bruijn ") 11 | .about("For testing faunadb-rust client library") 12 | .arg( 13 | Arg::with_name("secret") 14 | .short("s") 15 | .long("secret") 16 | .value_name("STRING") 17 | .required(true) 18 | .help("The FaunaDB connection secret") 19 | .takes_value(true), 20 | ) 21 | .get_matches(); 22 | 23 | let secret = matches.value_of("secret").unwrap(); 24 | let client = Client::builder(secret).build().unwrap(); 25 | 26 | let mut permission = IndexPermission::default(); 27 | permission.read(Level::public()); 28 | 29 | let mut params = IndexParams::new("new_meows", Ref::class("HouseCats")); 30 | params.permissions(permission); 31 | params.serialized(); 32 | params.partitions(8); 33 | 34 | let id_term = Term::field(vec!["data", "id"]); 35 | 36 | params.terms(vec![id_term]); 37 | 38 | let ref_value = IndexValue::field(vec!["ref"]); 39 | let name_value = IndexValue::field(vec!["data", "name"]); 40 | let mut age_value = IndexValue::field(vec!["data", "age"]); 41 | 42 | age_value.reverse(); 43 | params.values(vec![ref_value, age_value, name_value]); 44 | 45 | tokio::run(lazy(move || { 46 | client 47 | .query(CreateIndex::new(params)) 48 | .map(|response| { 49 | println!("{:?}", response); 50 | }) 51 | .map_err(|error: faunadb::error::Error| { 52 | println!("Error: {:#?}", error); 53 | }) 54 | })); 55 | } 56 | -------------------------------------------------------------------------------- /examples/create_instance.rs: -------------------------------------------------------------------------------- 1 | use chrono::{NaiveDate, Utc}; 2 | use clap::{App, Arg}; 3 | use faunadb::prelude::*; 4 | use futures::{ 5 | Future, 6 | {future::Either, lazy}, 7 | }; 8 | 9 | fn main() { 10 | pretty_env_logger::init(); 11 | 12 | let matches = App::new("A Simple FaunaDB Client") 13 | .version("1.0") 14 | .author("Julius de Bruijn ") 15 | .about("For testing faunadb-rust client library") 16 | .arg( 17 | Arg::with_name("secret") 18 | .short("s") 19 | .long("secret") 20 | .value_name("STRING") 21 | .required(true) 22 | .help("The FaunaDB connection secret") 23 | .takes_value(true), 24 | ) 25 | .arg( 26 | Arg::with_name("create_class") 27 | .short("c") 28 | .long("create_class") 29 | .required(false) 30 | .help("Create a new class called HouseCats") 31 | .takes_value(false), 32 | ) 33 | .get_matches(); 34 | 35 | let secret = matches.value_of("secret").unwrap(); 36 | let client = Client::builder(secret).build().unwrap(); 37 | 38 | let create_instance = { 39 | let mut obj = Object::default(); 40 | 41 | obj.insert("name", "Musti"); 42 | obj.insert("id", 1); 43 | obj.insert("age", 7); 44 | obj.insert("byte_data", Bytes::from(vec![0x1, 0x2, 0x3])); 45 | obj.insert( 46 | "nicknames", 47 | Array::from(vec!["mustu", "muspus", "mustikka"]), 48 | ); 49 | obj.insert("this_is_null", Expr::null()); 50 | obj.insert("am_i_cute", true); 51 | obj.insert("created_at", Utc::now()); 52 | obj.insert("birthday", NaiveDate::from_ymd(2011, 7, 7)); 53 | 54 | { 55 | let mut obj2 = Object::default(); 56 | obj2.insert("foo", "bar"); 57 | obj.insert("objective", obj2); 58 | } 59 | 60 | Create::new(Ref::class("HouseCats"), obj) 61 | }; 62 | 63 | let instance_query = client.query(create_instance); 64 | 65 | let query = if matches.is_present("create_class") { 66 | let mut perms = ClassPermission::default(); 67 | perms.read(Level::public()); 68 | 69 | let mut params = ClassParams::new("HouseCats"); 70 | params.history_days(3); 71 | params.ttl_days(3); 72 | params.permissions(perms); 73 | 74 | let class_query = client.query(CreateClass::new(params)); 75 | 76 | let query = class_query.and_then(|res| { 77 | println!("{:?}", res); 78 | instance_query 79 | }); 80 | 81 | Either::A(query) 82 | } else { 83 | Either::B(instance_query) 84 | }; 85 | 86 | tokio::run(lazy(move || { 87 | query 88 | .map(|response| { 89 | println!("{:?}", response); 90 | }) 91 | .map_err(|error: faunadb::error::Error| { 92 | println!("Error: {:#?}", error); 93 | }) 94 | })); 95 | } 96 | -------------------------------------------------------------------------------- /examples/delete_instance.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use faunadb::{prelude::*, query::write::Delete}; 3 | use futures::{future::lazy, Future}; 4 | 5 | fn main() { 6 | pretty_env_logger::init(); 7 | 8 | let matches = App::new("A Simple FaunaDB Client") 9 | .version("1.0") 10 | .author("Julius de Bruijn ") 11 | .about("For testing faunadb-rust client library") 12 | .arg( 13 | Arg::with_name("secret") 14 | .short("s") 15 | .long("secret") 16 | .value_name("STRING") 17 | .required(true) 18 | .help("The FaunaDB connection secret") 19 | .takes_value(true), 20 | ) 21 | .arg( 22 | Arg::with_name("id") 23 | .short("i") 24 | .long("id") 25 | .value_name("STRING") 26 | .required(true) 27 | .help("ID of the instance") 28 | .takes_value(true), 29 | ) 30 | .get_matches(); 31 | 32 | let secret = matches.value_of("secret").unwrap(); 33 | 34 | let mut builder = Client::builder(secret); 35 | builder.uri("http://localhost:8443"); 36 | let client = builder.build().unwrap(); 37 | 38 | tokio::run(lazy(move || { 39 | let instance = Ref::database(matches.value_of("id").unwrap()); 40 | 41 | let query = Delete::new(instance); 42 | 43 | client 44 | .query(query) 45 | .map(|response| { 46 | println!("{:?}", response); 47 | }) 48 | .map_err(|error: faunadb::error::Error| { 49 | println!("Error: {:?}", error); 50 | }) 51 | })); 52 | } 53 | -------------------------------------------------------------------------------- /examples/get_instance.rs: -------------------------------------------------------------------------------- 1 | use chrono::Utc; 2 | use clap::{App, Arg}; 3 | use faunadb::{prelude::*, query::read::Get}; 4 | use futures::{future::lazy, Future}; 5 | 6 | fn main() { 7 | pretty_env_logger::init(); 8 | 9 | let matches = App::new("A Simple FaunaDB Client") 10 | .version("1.0") 11 | .author("Julius de Bruijn ") 12 | .about("For testing faunadb-rust client library") 13 | .arg( 14 | Arg::with_name("secret") 15 | .short("s") 16 | .long("secret") 17 | .value_name("STRING") 18 | .required(true) 19 | .help("The FaunaDB connection secret") 20 | .takes_value(true), 21 | ) 22 | .arg( 23 | Arg::with_name("id") 24 | .short("i") 25 | .long("id") 26 | .value_name("STRING") 27 | .required(true) 28 | .help("ID of the instance") 29 | .takes_value(true), 30 | ) 31 | .get_matches(); 32 | 33 | let secret = matches.value_of("secret").unwrap(); 34 | let client = Client::builder(secret).build().unwrap(); 35 | 36 | tokio::run(lazy(move || { 37 | let mut instance = Ref::instance(matches.value_of("id").unwrap()); 38 | instance.set_class("HouseFats"); 39 | 40 | let mut query = Get::instance(instance); 41 | query.timestamp(Utc::now()); 42 | 43 | client 44 | .query(query) 45 | .map(|response| { 46 | println!("{:?}", response); 47 | }) 48 | .map_err(|error: faunadb::error::Error| { 49 | println!("Error: {:?}", error); 50 | }) 51 | })); 52 | } 53 | -------------------------------------------------------------------------------- /examples/misc.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use faunadb::prelude::*; 3 | use futures::{future::lazy, Future}; 4 | 5 | fn main() { 6 | pretty_env_logger::init(); 7 | 8 | let matches = App::new("A misc throwaway test client for development") 9 | .version("1.0") 10 | .author("Julius de Bruijn ") 11 | .about("For testing faunadb-rust client library") 12 | .arg( 13 | Arg::with_name("secret") 14 | .short("s") 15 | .long("secret") 16 | .value_name("STRING") 17 | .required(true) 18 | .help("The FaunaDB connection secret") 19 | .takes_value(true), 20 | ) 21 | .get_matches(); 22 | 23 | let secret = matches.value_of("secret").unwrap(); 24 | 25 | let mut builder = Client::builder(secret); 26 | builder.uri("http://localhost:8443"); 27 | 28 | let client = builder.build().unwrap(); 29 | let mut data = Object::default(); 30 | data.insert("foo", "bar"); 31 | 32 | let mut params = DatabaseParams::new("test"); 33 | params.priority(10).unwrap(); 34 | params.data(data); 35 | 36 | tokio::run(lazy(move || { 37 | client 38 | .query(CreateDatabase::new(params)) 39 | .map(|response| { 40 | println!("{:#?}", response); 41 | }) 42 | .map_err(|error: faunadb::error::Error| { 43 | println!("Error: {:#?}", error); 44 | }) 45 | })); 46 | } 47 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | //! Tools for communicating with Fauna. 2 | 3 | mod response; 4 | 5 | #[cfg(feature = "sync_client")] 6 | mod sync; 7 | 8 | pub use response::*; 9 | 10 | #[cfg(feature = "sync_client")] 11 | pub use sync::*; 12 | 13 | use crate::{ 14 | error::{Error, FaunaErrors}, 15 | expr::Expr, 16 | }; 17 | use futures::{future, stream::Stream, Future}; 18 | use http::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE}; 19 | use hyper::{client::HttpConnector, Body, StatusCode, Uri}; 20 | use hyper_tls::HttpsConnector; 21 | use serde_json; 22 | use std::{borrow::Cow, time::Duration}; 23 | use tokio_timer::Timeout; 24 | 25 | type Transport = hyper::Client>; 26 | 27 | /// For building a new Fauna client. 28 | pub struct ClientBuilder<'a> { 29 | uri: Cow<'a, str>, 30 | secret: Cow<'a, str>, 31 | timeout: Duration, 32 | } 33 | 34 | impl<'a> ClientBuilder<'a> { 35 | /// Change the uri if using dedicated Fauna servers. Default: 36 | /// `https://db.fauna.com`. 37 | pub fn uri(&mut self, uri: impl Into>) -> &mut Self { 38 | self.uri = uri.into(); 39 | self 40 | } 41 | 42 | /// Request timeout. Default: `60 seconds`. 43 | pub fn timeout(&mut self, timeout: Duration) -> &mut Self { 44 | self.timeout = timeout; 45 | self 46 | } 47 | 48 | /// Creates the client. 49 | pub fn build(self) -> crate::Result { 50 | let mut builder = hyper::Client::builder(); 51 | builder.keep_alive(true); 52 | 53 | let secret_b64 = base64::encode(&format!("{}:", self.secret)); 54 | 55 | Ok(Client { 56 | transport: builder.build(HttpsConnector::new(1)?), 57 | uri: self.uri.parse()?, 58 | timeout: self.timeout, 59 | authorization: format!("Basic {}", secret_b64), 60 | }) 61 | } 62 | 63 | #[cfg(feature = "sync_client")] 64 | pub fn build_sync(self) -> crate::Result { 65 | Ok(SyncClient::new(self.build()?)?) 66 | } 67 | } 68 | 69 | /// The client for Fauna. Should be created using the 70 | /// [ClientBuilder](struct.ClientBuilder.html). 71 | /// 72 | /// Do not create new clients for every request to prevent 73 | /// spamming Fauna servers with new connections. 74 | pub struct Client { 75 | transport: Transport, 76 | uri: Uri, 77 | timeout: Duration, 78 | authorization: String, 79 | } 80 | 81 | impl Client { 82 | /// Create a new client builder. Secret can be generated in [Fauna Cloud 83 | /// Console](https://dashboard.fauna.com/keys-new/@db/). 84 | pub fn builder<'a>(secret: impl Into>) -> ClientBuilder<'a> { 85 | ClientBuilder { 86 | uri: Cow::from("https://db.fauna.com"), 87 | secret: secret.into(), 88 | timeout: Duration::new(60, 0), 89 | } 90 | } 91 | 92 | /// Send a query to Fauna servers and parsing the response. 93 | pub fn query<'a, Q>(&self, query: Q) -> FutureResponse 94 | where 95 | Q: Into>, 96 | { 97 | let query = query.into(); 98 | let payload_json = serde_json::to_string(&query).unwrap(); 99 | 100 | trace!("Querying with: {:?}", &payload_json); 101 | 102 | self.request(self.build_request(payload_json), |body| { 103 | serde_json::from_str(&body).unwrap() 104 | }) 105 | } 106 | 107 | fn request(&self, request: hyper::Request, f: F) -> FutureResponse 108 | where 109 | T: Send + Sync + 'static, 110 | F: FnOnce(String) -> T + Send + Sync + 'static, 111 | { 112 | let send_request = self 113 | .transport 114 | .request(request) 115 | .map_err(|e| Error::ConnectionError(e.into())); 116 | 117 | let requesting = send_request.and_then(move |response| { 118 | trace!("Client::call got response status {}", response.status()); 119 | 120 | let status = response.status(); 121 | 122 | let get_body = response 123 | .into_body() 124 | .map_err(|e| Error::ConnectionError(e.into())) 125 | .concat2(); 126 | 127 | get_body.and_then(move |body_chunk| { 128 | if let Ok(body) = String::from_utf8(body_chunk.to_vec()) { 129 | trace!("Got response: {:?}", &body); 130 | 131 | match status { 132 | s if s.is_success() => future::ok(f(body)), 133 | StatusCode::UNAUTHORIZED => future::err(Error::Unauthorized), 134 | StatusCode::BAD_REQUEST => { 135 | let errors: FaunaErrors = serde_json::from_str(&body).unwrap(); 136 | future::err(Error::BadRequest(errors)) 137 | } 138 | StatusCode::NOT_FOUND => { 139 | let errors: FaunaErrors = serde_json::from_str(&body).unwrap(); 140 | future::err(Error::NotFound(errors)) 141 | } 142 | _ => future::err(Error::DatabaseError(body)), 143 | } 144 | } else { 145 | future::err(Error::EmptyResponse) 146 | } 147 | }) 148 | }); 149 | 150 | let with_timeout = Timeout::new(requesting, self.timeout).map_err(|e| { 151 | if e.is_timer() { 152 | Error::TimeoutError 153 | } else { 154 | match e.into_inner() { 155 | Some(error) => error, 156 | None => Error::Other, 157 | } 158 | } 159 | }); 160 | 161 | FutureResponse(Box::new(with_timeout)) 162 | } 163 | 164 | fn build_request(&self, payload: String) -> hyper::Request { 165 | let mut builder = hyper::Request::builder(); 166 | 167 | builder.uri(&self.uri); 168 | builder.method("POST"); 169 | 170 | builder.header(CONTENT_LENGTH, format!("{}", payload.len()).as_bytes()); 171 | builder.header(CONTENT_TYPE, "application/json"); 172 | builder.header(AUTHORIZATION, self.authorization.as_bytes()); 173 | builder.header("X-FaunaDB-API-Version", "2.1"); 174 | 175 | builder.body(Body::from(payload)).unwrap() 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/client/response.rs: -------------------------------------------------------------------------------- 1 | mod index; 2 | mod value; 3 | 4 | use crate::error::Error; 5 | use futures::{Future, Poll}; 6 | 7 | pub use index::*; 8 | pub use value::*; 9 | 10 | pub struct FutureResponse(pub Box + Send + 'static>); 11 | 12 | impl Future for FutureResponse { 13 | type Item = T; 14 | type Error = Error; 15 | 16 | fn poll(&mut self) -> Poll { 17 | self.0.poll() 18 | } 19 | } 20 | 21 | #[derive(Deserialize, Debug, PartialEq)] 22 | pub struct Response { 23 | pub resource: Value, 24 | } 25 | -------------------------------------------------------------------------------- /src/client/response/index.rs: -------------------------------------------------------------------------------- 1 | use super::{SimpleValue, Value}; 2 | use std::ops; 3 | 4 | /// Indexing to support square bracket syntax for the response values. 5 | /// 6 | /// Shamelessly taken from `serde_json`, extended to be used with Fauna values. 7 | /// 8 | /// Read the 9 | /// [docs](https://docs.rs/serde_json/1.0.39/serde_json/value/trait.Index.html) 10 | pub trait ValueIndex: private::Sealed { 11 | #[doc(hidden)] 12 | fn index_into<'a>(&self, v: &'a Value) -> Option<&'a Value>; 13 | #[doc(hidden)] 14 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value>; 15 | } 16 | 17 | // Prevent users from implementing the ValueIndex trait. 18 | mod private { 19 | pub trait Sealed {} 20 | impl Sealed for usize {} 21 | impl Sealed for str {} 22 | impl Sealed for String {} 23 | impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {} 24 | } 25 | 26 | impl ValueIndex for usize { 27 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 28 | match *v { 29 | Value::Simple(SimpleValue::Array(ref vec)) => vec.get(*self), 30 | _ => None, 31 | } 32 | } 33 | 34 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 35 | match *v { 36 | Value::Simple(SimpleValue::Array(ref mut vec)) => vec.get_mut(*self), 37 | _ => None, 38 | } 39 | } 40 | } 41 | 42 | impl ValueIndex for str { 43 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 44 | match *v { 45 | Value::Simple(SimpleValue::Object(ref map)) => map.get(self), 46 | _ => None, 47 | } 48 | } 49 | 50 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 51 | match *v { 52 | Value::Simple(SimpleValue::Object(ref mut map)) => map.get_mut(self), 53 | _ => None, 54 | } 55 | } 56 | } 57 | 58 | impl ValueIndex for String { 59 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 60 | self[..].index_into(v) 61 | } 62 | 63 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 64 | self[..].index_into_mut(v) 65 | } 66 | } 67 | 68 | impl<'a, T: ?Sized> ValueIndex for &'a T 69 | where 70 | T: ValueIndex, 71 | { 72 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 73 | (**self).index_into(v) 74 | } 75 | 76 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 77 | (**self).index_into_mut(v) 78 | } 79 | } 80 | 81 | impl ops::Index for Value { 82 | type Output = Value; 83 | 84 | fn index(&self, index: I) -> &Value { 85 | static NULL: Value = Value::null(); 86 | index.index_into(self).unwrap_or(&NULL) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/client/response/value.rs: -------------------------------------------------------------------------------- 1 | use super::ValueIndex; 2 | use crate::{ 3 | expr::{Bytes, Number, Ref}, 4 | serde::base64_bytes, 5 | }; 6 | use chrono::{DateTime, NaiveDate, Utc}; 7 | use std::collections::BTreeMap; 8 | 9 | /// Represents any value returned from Fauna. 10 | /// 11 | /// Read the 12 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/types) 13 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 14 | #[serde(untagged)] 15 | pub enum Value { 16 | /// A value with an annotation for its type definition. 17 | Annotated(AnnotatedValue), 18 | /// A value with a direct mapping to the types supported in JSON. 19 | Simple(SimpleValue), 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 23 | #[serde(untagged)] 24 | pub enum SimpleValue { 25 | /// String data types store any letters, numbers, whitespaces, and/or symbols in a fixed order. 26 | String(String), 27 | /// Numbers are any real number which are bounded by double precision (64-bit). 28 | Number(Number), 29 | /// The boolean data type can only store "true" or "false" values. These can 30 | /// be directly compared for equality or inequality. 31 | Boolean(bool), 32 | /// An array is a data structure that contains a group of elements. 33 | /// Typically the elements of an array are of the same or related type. 34 | Array(Vec), 35 | /// Object values are a collection of key/value pairs. 36 | Object(BTreeMap), 37 | /// Null is a special marker used to indicate that a data value does not 38 | /// exist. It is a representation of missing information. A null value 39 | /// indicates a lack of a value. A lack of a value is not the same thing as 40 | /// a value of zero, in the same way that a lack of an answer is not the 41 | /// same thing as an answer of "no". 42 | Null, 43 | } 44 | 45 | #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] 46 | pub enum AnnotatedValue { 47 | /// Denotes a resource ref. Refs may be extracted from instances, or 48 | /// constructed using the ref function. 49 | #[serde(rename = "@ref")] 50 | Ref(Ref<'static>), 51 | /// Denotes a query expression object. 52 | #[serde(rename = "@query")] 53 | Query(Box), 54 | /// Denotes a base64 encoded string representing a byte array. Decoded to 55 | /// bytes when deserialized. 56 | #[serde(rename = "@bytes", with = "base64_bytes")] 57 | Bytes(Bytes<'static>), 58 | /// Denotes a date, with no associated time zone. 59 | #[serde(rename = "@date")] 60 | Date(NaiveDate), 61 | /// Denotes a set identifier. 62 | #[serde(rename = "@set")] 63 | Set(Box), 64 | /// Stores an instant in time expressed as a calendar date and time of day 65 | /// in UTC. 66 | #[serde(rename = "@ts")] 67 | Timestamp(DateTime), 68 | } 69 | 70 | impl Default for Value { 71 | fn default() -> Self { 72 | Value::null() 73 | } 74 | } 75 | 76 | impl<'a> From<&'a str> for Value { 77 | fn from(s: &'a str) -> Self { 78 | Value::Simple(SimpleValue::String(s.to_string())) 79 | } 80 | } 81 | 82 | impl From for Value 83 | where 84 | T: Into, 85 | { 86 | fn from(t: T) -> Self { 87 | Value::Simple(SimpleValue::Number(t.into())) 88 | } 89 | } 90 | 91 | impl From> for Value { 92 | fn from(t: Ref<'static>) -> Self { 93 | Value::Annotated(AnnotatedValue::Ref(t)) 94 | } 95 | } 96 | 97 | impl From for Value { 98 | fn from(t: NaiveDate) -> Self { 99 | Value::Annotated(AnnotatedValue::Date(t)) 100 | } 101 | } 102 | 103 | impl From> for Value { 104 | fn from(t: DateTime) -> Self { 105 | Value::Annotated(AnnotatedValue::Timestamp(t)) 106 | } 107 | } 108 | 109 | impl From> for Value 110 | where 111 | V: Into, 112 | { 113 | fn from(t: Vec) -> Self { 114 | Value::Simple(SimpleValue::Array(t.into_iter().map(Into::into).collect())) 115 | } 116 | } 117 | 118 | impl From> for Value 119 | where 120 | S: Into, 121 | V: Into, 122 | { 123 | fn from(map: BTreeMap) -> Self { 124 | let obj = map.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); 125 | Value::Simple(SimpleValue::Object(obj)) 126 | } 127 | } 128 | 129 | impl Value { 130 | /// A helper to get a `Null` value. 131 | pub const fn null() -> Value { 132 | Value::Simple(SimpleValue::Null) 133 | } 134 | 135 | /// Index into a Fauna `Array` or `Object`. A string index can be used to 136 | /// access a value in an `Object`, and a usize index can be used to access 137 | /// an element of an `Array`. 138 | /// 139 | /// Returns `None` if the type of `self` does not match the type of the index 140 | /// or the given key does not exist in the map or the given index is not 141 | /// within the bounds of the array. 142 | /// 143 | /// ``` 144 | /// # use faunadb::prelude::*; 145 | /// # use std::collections::BTreeMap; 146 | /// # 147 | /// let mut obj = BTreeMap::new(); 148 | /// obj.insert("foo", "bar"); 149 | /// 150 | /// let value = Value::from(vec![obj]); 151 | /// assert_eq!(Some("bar"), value[0]["foo"].as_str()); 152 | /// ``` 153 | pub fn get(&self, index: I) -> Option<&Value> { 154 | index.index_into(self) 155 | } 156 | 157 | /// Mutably index into a Fauna `Array` or `Object`. A string index can be 158 | /// used to access a value in an `Object`, and a usize index can be used to 159 | /// access an element of an `Array`. 160 | /// 161 | /// Returns `None` if the type of `self` does not match the type of the index 162 | /// or the given key does not exist in the map or the given index is not 163 | /// within the bounds of the array. 164 | /// 165 | /// ``` 166 | /// # use faunadb::prelude::*; 167 | /// # use std::collections::BTreeMap; 168 | /// # 169 | /// let mut obj = BTreeMap::new(); 170 | /// obj.insert("cat", "purr"); 171 | /// 172 | /// let mut obj_value = Value::from(obj); 173 | /// *obj_value.get_mut("cat").unwrap() = Value::from("meow"); 174 | /// 175 | /// let mut ary_value = Value::from(vec!["meow"]); 176 | /// *ary_value.get_mut(0).unwrap() = Value::from("purr"); 177 | /// ``` 178 | pub fn get_mut(&mut self, index: I) -> Option<&mut Value> { 179 | index.index_into_mut(self) 180 | } 181 | 182 | /// `true` if the `Value` is a `String`. 183 | pub fn is_string(&self) -> bool { 184 | match self { 185 | Value::Simple(SimpleValue::String(_)) => true, 186 | _ => false, 187 | } 188 | } 189 | 190 | /// Returns a &str if the value is a `String`, otherwise `None`. 191 | pub fn as_str(&self) -> Option<&str> { 192 | match self { 193 | Value::Simple(SimpleValue::String(string)) => Some(string.as_str()), 194 | _ => None, 195 | } 196 | } 197 | 198 | /// Transforms the `Value` to a `String` if it's a string, otherwise `None`. 199 | pub fn into_string(self) -> Option { 200 | match self { 201 | Value::Simple(SimpleValue::String(string)) => Some(string), 202 | _ => None, 203 | } 204 | } 205 | 206 | /// `true` if the `Value` is a `Number`. 207 | pub fn is_number(&self) -> bool { 208 | match self { 209 | Value::Simple(SimpleValue::Number(_)) => true, 210 | _ => false, 211 | } 212 | } 213 | 214 | /// Returns a `Number` for number values, otherwise `None`. 215 | pub fn as_number(&self) -> Option { 216 | match self { 217 | Value::Simple(SimpleValue::Number(num)) => Some(*num), 218 | _ => None, 219 | } 220 | } 221 | 222 | /// `true` if the `Value` is a `u64`. 223 | pub fn is_u64(&self) -> bool { 224 | match self { 225 | Value::Simple(SimpleValue::Number(num)) => num.is_u64(), 226 | _ => false, 227 | } 228 | } 229 | 230 | /// Returns a `u64` for `u64` values, otherwise `None`. 231 | pub fn as_u64(&self) -> Option { 232 | match self { 233 | Value::Simple(SimpleValue::Number(num)) => num.as_u64(), 234 | _ => None, 235 | } 236 | } 237 | 238 | /// `true` if the `Value` is a `i64`. 239 | pub fn is_i64(&self) -> bool { 240 | match self { 241 | Value::Simple(SimpleValue::Number(num)) => num.is_i64(), 242 | _ => false, 243 | } 244 | } 245 | 246 | /// Returns a `i64` for `i64` values, otherwise `None`. 247 | pub fn as_i64(&self) -> Option { 248 | match self { 249 | Value::Simple(SimpleValue::Number(num)) => num.as_i64(), 250 | _ => None, 251 | } 252 | } 253 | 254 | /// `true` if the `Value` is a `f64`. 255 | pub fn is_f64(&self) -> bool { 256 | match self { 257 | Value::Simple(SimpleValue::Number(num)) => num.is_f64(), 258 | _ => false, 259 | } 260 | } 261 | 262 | /// Returns a `f64` for `f64` values, otherwise `None`. 263 | pub fn as_f64(&self) -> Option { 264 | match self { 265 | Value::Simple(SimpleValue::Number(num)) => num.as_f64(), 266 | _ => None, 267 | } 268 | } 269 | 270 | /// `true` if the `Value` is a `f32`. 271 | pub fn is_f32(&self) -> bool { 272 | match self { 273 | Value::Simple(SimpleValue::Number(num)) => num.is_f32(), 274 | _ => false, 275 | } 276 | } 277 | 278 | /// Returns a `f32` for `f32` values, otherwise `None`. 279 | pub fn as_f32(&self) -> Option { 280 | match self { 281 | Value::Simple(SimpleValue::Number(num)) => num.as_f32(), 282 | _ => None, 283 | } 284 | } 285 | 286 | /// `true` if the `Value` is a `bool`. 287 | pub fn is_bool(&self) -> bool { 288 | match self { 289 | Value::Simple(SimpleValue::Boolean(_)) => true, 290 | _ => false, 291 | } 292 | } 293 | 294 | /// Returns a `bool` for `bool` values, otherwise `None`. 295 | pub fn as_bool(&self) -> Option { 296 | match self { 297 | Value::Simple(SimpleValue::Boolean(b)) => Some(*b), 298 | _ => None, 299 | } 300 | } 301 | 302 | /// `true` if the `Value` is an `Array`. 303 | pub fn is_array(&self) -> bool { 304 | match self { 305 | Value::Simple(SimpleValue::Array(_)) => true, 306 | _ => false, 307 | } 308 | } 309 | 310 | /// Returns an `Array` for `Array` values, otherwise `None`. 311 | pub fn as_array(&self) -> Option<&Vec> { 312 | match self { 313 | Value::Simple(SimpleValue::Array(v)) => Some(v), 314 | _ => None, 315 | } 316 | } 317 | 318 | /// Transforms the `Value` into an `Array` if an array, otherwise `None`. 319 | pub fn into_array(self) -> Option> { 320 | match self { 321 | Value::Simple(SimpleValue::Array(v)) => Some(v), 322 | _ => None, 323 | } 324 | } 325 | 326 | /// Returns a mutable `Array` for `Array` values, otherwise `None`. 327 | pub fn as_array_mut(&mut self) -> Option<&Vec> { 328 | match self { 329 | Value::Simple(SimpleValue::Array(ref mut v)) => Some(v), 330 | _ => None, 331 | } 332 | } 333 | 334 | /// `true` if the `Value` is an `Object`. 335 | pub fn is_object(&self) -> bool { 336 | match self { 337 | Value::Simple(SimpleValue::Object(_)) => true, 338 | _ => false, 339 | } 340 | } 341 | 342 | /// Returns an `Object` for `Object` values, otherwise `None`. 343 | pub fn as_object(&self) -> Option<&BTreeMap> { 344 | match self { 345 | Value::Simple(SimpleValue::Object(obj)) => Some(obj), 346 | _ => None, 347 | } 348 | } 349 | 350 | /// Returns a mutable `Object` for `Object` values, otherwise `None`. 351 | pub fn as_object_mut(&mut self) -> Option<&mut BTreeMap> { 352 | match *self { 353 | Value::Simple(SimpleValue::Object(ref mut obj)) => Some(obj), 354 | _ => None, 355 | } 356 | } 357 | 358 | /// Transforms the `Value` into an `Object` if an object, otherwise `None`. 359 | pub fn into_object(self) -> Option> { 360 | match self { 361 | Value::Simple(SimpleValue::Object(obj)) => Some(obj), 362 | _ => None, 363 | } 364 | } 365 | 366 | /// `true` if the `Value` is `Null`. 367 | pub fn is_null(&self) -> bool { 368 | match self { 369 | Value::Simple(SimpleValue::Null) => true, 370 | _ => false, 371 | } 372 | } 373 | 374 | /// `true` if the `Value` is a `Ref`. 375 | pub fn is_reference(&self) -> bool { 376 | match self { 377 | Value::Annotated(AnnotatedValue::Ref(_)) => true, 378 | _ => false, 379 | } 380 | } 381 | 382 | /// Returns a `Ref` for `Ref` values, otherwise `None`. 383 | pub fn as_reference(&self) -> Option<&Ref<'static>> { 384 | match self { 385 | Value::Annotated(AnnotatedValue::Ref(reference)) => Some(&*reference), 386 | _ => None, 387 | } 388 | } 389 | 390 | /// Finds a nearest `Ref` if found and if taken from an `Object`, otherwise 391 | /// `None`. 392 | pub fn get_reference(&self) -> Option<&Ref<'static>> { 393 | self["ref"].as_reference() 394 | } 395 | 396 | /// `true` if the `Value` is a `Query`. 397 | pub fn is_query(&self) -> bool { 398 | match self { 399 | Value::Annotated(AnnotatedValue::Query(_)) => true, 400 | _ => false, 401 | } 402 | } 403 | 404 | /// Returns a `Query` for `Query` values, otherwise `None`. 405 | pub fn as_query(&self) -> Option<&Value> { 406 | match self { 407 | Value::Annotated(AnnotatedValue::Query(q)) => Some(&*q), 408 | _ => None, 409 | } 410 | } 411 | 412 | /// `true` if the `Value` is a set of `Bytes`. 413 | pub fn is_bytes(&self) -> bool { 414 | match self { 415 | Value::Annotated(AnnotatedValue::Bytes(_)) => true, 416 | _ => false, 417 | } 418 | } 419 | 420 | /// Returns `Bytes` for sets of `Bytes`, otherwise `None`. 421 | pub fn as_bytes(&self) -> Option<&Bytes<'static>> { 422 | match self { 423 | Value::Annotated(AnnotatedValue::Bytes(byt)) => Some(byt), 424 | _ => None, 425 | } 426 | } 427 | 428 | /// `true` if the `Value` is a `Date`. 429 | pub fn is_date(&self) -> bool { 430 | match self { 431 | Value::Annotated(AnnotatedValue::Date(_)) => true, 432 | _ => false, 433 | } 434 | } 435 | 436 | /// Returns a `NaiveDate` for `Date` values, otherwise `None`. 437 | pub fn as_date(&self) -> Option { 438 | match self { 439 | Value::Annotated(AnnotatedValue::Date(dat)) => Some(*dat), 440 | _ => None, 441 | } 442 | } 443 | 444 | /// `true` if the `Value` is a `Set`. 445 | pub fn is_set(&self) -> bool { 446 | match self { 447 | Value::Annotated(AnnotatedValue::Set(_)) => true, 448 | _ => false, 449 | } 450 | } 451 | 452 | /// Returns a `Set` for `Set` values, otherwise `None`. 453 | pub fn as_set(&self) -> Option<&Value> { 454 | match self { 455 | Value::Annotated(AnnotatedValue::Set(set)) => Some(&*set), 456 | _ => None, 457 | } 458 | } 459 | 460 | /// `true` if the `Value` is a `Timestamp`. 461 | pub fn is_timestamp(&self) -> bool { 462 | match self { 463 | Value::Annotated(AnnotatedValue::Timestamp(_)) => true, 464 | _ => false, 465 | } 466 | } 467 | 468 | /// Returns a `DateTime` for `Timestamp` values, otherwise `None`. 469 | pub fn as_timestamp(&self) -> Option> { 470 | match self { 471 | Value::Annotated(AnnotatedValue::Timestamp(ts)) => Some(*ts), 472 | _ => None, 473 | } 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/client/sync.rs: -------------------------------------------------------------------------------- 1 | use super::{Client, Response}; 2 | use crate::expr::Expr; 3 | use std::sync::Mutex; 4 | use tokio::runtime::Runtime; 5 | 6 | /// A synchronous wrapper for the asynchronous Fauna client. 7 | pub struct SyncClient { 8 | inner: Client, 9 | runtime: Mutex, 10 | } 11 | 12 | impl SyncClient { 13 | pub fn new(inner: Client) -> crate::Result { 14 | Ok(Self { 15 | inner, 16 | runtime: Mutex::new(Runtime::new()?), 17 | }) 18 | } 19 | 20 | pub fn query<'a, Q>(&self, query: Q) -> crate::Result 21 | where 22 | Q: Into>, 23 | { 24 | self.runtime 25 | .lock() 26 | .unwrap() 27 | .block_on(self.inner.query(query)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::client::Value; 2 | use failure::{self, Fail}; 3 | 4 | #[derive(Debug, Fail)] 5 | pub enum Error { 6 | #[fail(display = "Error connecting to the database: {}", _0)] 7 | ConnectionError(failure::Error), 8 | #[fail(display = "Configuration error: {}", _0)] 9 | ConfigurationError(failure::Error), 10 | #[fail(display = "Timed out")] 11 | TimeoutError, 12 | #[fail(display = "Unknown error")] 13 | Other, 14 | #[fail(display = "Unauthorized")] 15 | Unauthorized, 16 | #[fail(display = "Server sent no response")] 17 | EmptyResponse, 18 | #[fail(display = "Bad request: {}", _0)] 19 | BadRequest(FaunaErrors), 20 | #[fail(display = "Not found: {}", _0)] 21 | NotFound(FaunaErrors), 22 | #[fail(display = "Request data failure: {}", _0)] 23 | RequestDataFailure(&'static str), 24 | #[fail(display = "Response data failure: {}", _0)] 25 | ResponseDataFailure(&'static str), 26 | #[fail(display = "Fauna error: {}", _0)] 27 | DatabaseError(String), 28 | #[fail(display = "Couldn't convert data: {}", _0)] 29 | ConversionError(&'static str), 30 | #[cfg(feature = "sync_client")] 31 | #[fail(display = "IO Error: {}", _0)] 32 | IoError(failure::Error), 33 | } 34 | 35 | #[derive(Debug, Deserialize, Fail)] 36 | #[fail(display = "Errors in the request data: [{:?}]", errors)] 37 | pub struct FaunaErrors { 38 | pub errors: Vec, 39 | } 40 | 41 | #[derive(Debug, Deserialize, Fail)] 42 | #[fail( 43 | display = "{{position={:?},code={},description={}}}", 44 | position, code, description 45 | )] 46 | pub struct FaunaError { 47 | pub position: Vec, 48 | pub code: String, 49 | pub description: String, 50 | } 51 | 52 | impl From for Error { 53 | fn from(e: native_tls::Error) -> Self { 54 | Error::ConnectionError(e.into()) 55 | } 56 | } 57 | 58 | impl From for Error { 59 | fn from(e: http::uri::InvalidUri) -> Self { 60 | Error::ConfigurationError(e.into()) 61 | } 62 | } 63 | 64 | #[cfg(feature = "sync_client")] 65 | impl From for Error { 66 | fn from(e: std::io::Error) -> Self { 67 | Error::IoError(e.into()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/expr/array.rs: -------------------------------------------------------------------------------- 1 | use super::Expr; 2 | use std::borrow::Cow; 3 | 4 | #[derive(Debug, Clone, Serialize)] 5 | pub struct Array<'a>(pub Vec>); 6 | 7 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 8 | pub struct Bytes<'a>(pub Cow<'a, [u8]>); 9 | 10 | impl<'a> Array<'a> { 11 | pub fn reuse(self) -> Self { 12 | let reused = self.0.into_iter().map(|e| e.reuse()).collect(); 13 | Array(reused) 14 | } 15 | 16 | pub fn push(&mut self, e: impl Into>) -> &mut Self { 17 | self.0.push(e.into()); 18 | self 19 | } 20 | } 21 | 22 | impl<'a, E> From> for Array<'a> 23 | where 24 | E: Into>, 25 | { 26 | fn from(a: Vec) -> Self { 27 | Array(a.into_iter().map(Into::into).collect()) 28 | } 29 | } 30 | 31 | impl<'a, B> From for Bytes<'a> 32 | where 33 | B: Into>, 34 | { 35 | fn from(b: B) -> Self { 36 | Self(b.into()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/expr/number.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::{Expr, SimpleExpr}; 2 | 3 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] 4 | #[serde(untagged)] 5 | pub enum Number { 6 | UInt(u64), 7 | Int(i64), 8 | Double(f64), 9 | Float(f32), 10 | } 11 | 12 | impl Number { 13 | pub fn is_u64(&self) -> bool { 14 | match self { 15 | Number::UInt(_) => true, 16 | _ => false, 17 | } 18 | } 19 | 20 | pub fn as_u64(&self) -> Option { 21 | match self { 22 | Number::UInt(u) => Some(*u), 23 | _ => None, 24 | } 25 | } 26 | 27 | pub fn is_i64(&self) -> bool { 28 | match self { 29 | Number::Int(_) => true, 30 | _ => false, 31 | } 32 | } 33 | 34 | pub fn as_i64(&self) -> Option { 35 | match self { 36 | Number::Int(i) => Some(*i), 37 | _ => None, 38 | } 39 | } 40 | 41 | pub fn is_f64(&self) -> bool { 42 | match self { 43 | Number::Double(_) => true, 44 | _ => false, 45 | } 46 | } 47 | 48 | pub fn as_f64(&self) -> Option { 49 | match self { 50 | Number::Double(f) => Some(*f), 51 | _ => None, 52 | } 53 | } 54 | 55 | pub fn is_f32(&self) -> bool { 56 | match self { 57 | Number::Float(_) => true, 58 | _ => false, 59 | } 60 | } 61 | 62 | pub fn as_f32(&self) -> Option { 63 | match self { 64 | Number::Float(f) => Some(*f), 65 | _ => None, 66 | } 67 | } 68 | } 69 | 70 | int_expr!(i8, i16, i32, i64); 71 | uint_expr!(u8, u16, u32, u64); 72 | 73 | impl From for Number { 74 | fn from(f: f64) -> Number { 75 | Number::Double(f) 76 | } 77 | } 78 | 79 | impl<'a> From for Number { 80 | fn from(f: f32) -> Number { 81 | Number::Float(f) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/expr/object.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::Expr; 2 | use std::{borrow::Cow, collections::BTreeMap, fmt}; 3 | 4 | #[derive(Debug, Serialize, Clone, Default)] 5 | pub struct Object<'a>(pub(crate) BTreeMap, Expr<'a>>); 6 | 7 | impl<'a> From, Expr<'a>>> for Object<'a> { 8 | fn from(data: BTreeMap, Expr<'a>>) -> Self { 9 | Object(data) 10 | } 11 | } 12 | 13 | impl<'a> Object<'a> { 14 | pub fn insert(&mut self, key: &'a str, val: E) -> &mut Self 15 | where 16 | E: Into>, 17 | { 18 | self.0.insert(Cow::from(key), val.into()); 19 | self 20 | } 21 | 22 | pub fn len(&self) -> usize { 23 | self.0.len() 24 | } 25 | 26 | pub fn is_empty(&self) -> bool { 27 | self.0.is_empty() 28 | } 29 | 30 | pub fn reuse(self) -> Self { 31 | let reused = self.0.into_iter().map(|(k, v)| (k, v.reuse())).collect(); 32 | Object(reused) 33 | } 34 | } 35 | 36 | impl<'a> fmt::Display for Object<'a> { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | let pairs: Vec = self.0.iter().map(|(k, v)| format!("{}:{}", k, v)).collect(); 39 | 40 | write!(f, "{{{}}}", pairs.join(",")) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/expr/permission.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::{Expr, Ref}; 2 | 3 | #[derive(Serialize, Clone, Copy, Debug)] 4 | #[doc(hidden)] 5 | pub enum SimpleLevel { 6 | #[serde(rename = "public")] 7 | Public, 8 | } 9 | 10 | #[derive(Serialize, Clone, Debug)] 11 | #[serde(untagged)] 12 | #[doc(hidden)] 13 | pub enum AnnotatedLevel<'a> { 14 | Reference(Expr<'a>), 15 | } 16 | 17 | #[derive(Serialize, Clone, Debug)] 18 | #[serde(untagged)] 19 | /// Permission level definition. 20 | pub enum Level<'a> { 21 | Annotated(AnnotatedLevel<'a>), 22 | Simple(SimpleLevel), 23 | } 24 | 25 | impl<'a> Level<'a> { 26 | /// Only tokens belonging to the specified Ref are allowed. 27 | /// 28 | /// Can refer to a: 29 | /// 30 | /// - Class: Only tokens belonging to instances in the specified class are allowed. 31 | /// - Instance: Only tokens belonging to the specified instance are allowed. 32 | pub fn reference(reference: Ref<'a>) -> Self { 33 | Level::Annotated(AnnotatedLevel::Reference(Expr::from(reference))) 34 | } 35 | 36 | /// Any key is allowed. 37 | pub fn public() -> Self { 38 | Level::Simple(SimpleLevel::Public) 39 | } 40 | } 41 | 42 | #[derive(Serialize, Clone, Debug, Default)] 43 | #[doc(hidden)] 44 | pub struct ClassPermissionObject<'a> { 45 | #[serde(skip_serializing_if = "Option::is_none")] 46 | create: Option>, 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | read: Option>, 49 | #[serde(skip_serializing_if = "Option::is_none")] 50 | write: Option>, 51 | } 52 | 53 | #[derive(Serialize, Clone, Debug, Default)] 54 | /// Creating, reading, and modifying an instance in a class is controlled by the 55 | /// class’s permissions. 56 | /// 57 | /// See the [docs](https://docs.fauna.com/fauna/current/reference/security#class-permissions) 58 | pub struct ClassPermission<'a> { 59 | object: ClassPermissionObject<'a>, 60 | } 61 | 62 | impl<'a> ClassPermission<'a> { 63 | /// Creating an instance in the class. 64 | pub fn create(&mut self, level: Level<'a>) -> &mut Self { 65 | self.object.create = Some(level); 66 | self 67 | } 68 | 69 | /// Reading instances in the class. 70 | pub fn read(&mut self, level: Level<'a>) -> &mut Self { 71 | self.object.read = Some(level); 72 | self 73 | } 74 | 75 | /// Writing to instances in the class. 76 | pub fn write(&mut self, level: Level<'a>) -> &mut Self { 77 | self.object.write = Some(level); 78 | self 79 | } 80 | } 81 | 82 | #[derive(Serialize, Debug, Clone, Default)] 83 | #[doc(hidden)] 84 | pub struct InstancePermissionObject<'a> { 85 | #[serde(skip_serializing_if = "Option::is_none")] 86 | /// Reading this instance. 87 | read: Option>, 88 | #[serde(skip_serializing_if = "Option::is_none")] 89 | /// Writing to this instance. 90 | write: Option>, 91 | } 92 | 93 | #[derive(Serialize, Debug, Clone, Default)] 94 | /// An instance also has permissions, which are applied in addition to 95 | /// permissions defined on its class. 96 | /// 97 | /// See the [docs](https://docs.fauna.com/fauna/current/reference/security#instance-permissions) 98 | pub struct InstancePermission<'a> { 99 | object: InstancePermissionObject<'a>, 100 | } 101 | 102 | impl<'a> InstancePermission<'a> { 103 | /// Reading this instance. 104 | pub fn read(&mut self, level: Level<'a>) -> &mut Self { 105 | self.object.read = Some(level); 106 | self 107 | } 108 | 109 | /// Writing to this instance. 110 | pub fn write(&mut self, level: Level<'a>) -> &mut Self { 111 | self.object.write = Some(level); 112 | self 113 | } 114 | } 115 | 116 | #[derive(Serialize, Debug, Clone, Default)] 117 | #[doc(hidden)] 118 | pub struct FunctionPermissionObject<'a> { 119 | #[serde(skip_serializing_if = "Option::is_none")] 120 | /// Calling the function 121 | call: Option>, 122 | } 123 | 124 | #[derive(Serialize, Debug, Clone, Default)] 125 | /// Calling a function is controlled by its permissions. 126 | /// 127 | /// See the [docs](https://docs.fauna.com/fauna/current/reference/security#instance-permissions) 128 | pub struct FunctionPermission<'a> { 129 | object: FunctionPermissionObject<'a>, 130 | } 131 | 132 | impl<'a> FunctionPermission<'a> { 133 | /// Calling the function. 134 | pub fn call(&mut self, level: Level<'a>) -> &mut Self { 135 | self.object.call = Some(level); 136 | self 137 | } 138 | } 139 | 140 | #[derive(Serialize, Debug, Clone, Default)] 141 | #[doc(hidden)] 142 | pub struct IndexPermissionObject<'a> { 143 | #[serde(skip_serializing_if = "Option::is_none")] 144 | /// Querying the index 145 | read: Option>, 146 | } 147 | 148 | #[derive(Serialize, Debug, Clone, Default)] 149 | /// Query access to an index is controlled by its permissions. 150 | /// 151 | /// See the [docs](https://docs.fauna.com/fauna/current/reference/security#instance-permissions) 152 | pub struct IndexPermission<'a> { 153 | object: IndexPermissionObject<'a>, 154 | } 155 | 156 | impl<'a> IndexPermission<'a> { 157 | /// Querying the index. 158 | pub fn read(&mut self, level: Level<'a>) -> &mut Self { 159 | self.object.read = Some(level); 160 | self 161 | } 162 | } 163 | 164 | #[cfg(test)] 165 | mod tests { 166 | use crate::prelude::*; 167 | use serde_json::{self, json}; 168 | 169 | #[test] 170 | fn test_class_permission() { 171 | let mut instance_ref = Ref::instance("musti"); 172 | instance_ref.set_class("HouseCats"); 173 | 174 | let class_ref = Ref::class("HouseCats"); 175 | 176 | let mut perm = ClassPermission::default(); 177 | perm.create(Level::public()); 178 | perm.read(Level::reference(instance_ref)); 179 | perm.write(Level::reference(class_ref)); 180 | 181 | let expected = json!({ 182 | "object": { 183 | "create": "public", 184 | "read": { 185 | "@ref": { 186 | "class": { 187 | "@ref": { 188 | "class": { 189 | "@ref": { 190 | "id": "classes" 191 | } 192 | }, 193 | "id": "HouseCats" 194 | } 195 | }, 196 | "id": "musti" 197 | } 198 | }, 199 | "write": { 200 | "@ref": { 201 | "class": { 202 | "@ref": { 203 | "id": "classes" 204 | } 205 | }, 206 | "id": "HouseCats" 207 | }, 208 | }, 209 | } 210 | }); 211 | 212 | assert_eq!(expected, serde_json::to_value(&perm).unwrap(),) 213 | } 214 | 215 | #[test] 216 | fn test_instance_permission() { 217 | let mut instance_ref = Ref::instance("musti"); 218 | instance_ref.set_class("HouseCats"); 219 | 220 | let class_ref = Ref::class("HouseCats"); 221 | 222 | let mut perm = InstancePermission::default(); 223 | perm.read(Level::reference(instance_ref)); 224 | perm.write(Level::reference(class_ref)); 225 | 226 | let expected = json!({ 227 | "object": { 228 | "read": { 229 | "@ref": { 230 | "class": { 231 | "@ref": { 232 | "class": { 233 | "@ref": { 234 | "id": "classes" 235 | } 236 | }, 237 | "id": "HouseCats" 238 | } 239 | }, 240 | "id": "musti" 241 | } 242 | }, 243 | "write": { 244 | "@ref": { 245 | "class": { 246 | "@ref": { 247 | "id": "classes" 248 | } 249 | }, 250 | "id": "HouseCats" 251 | }, 252 | }, 253 | } 254 | }); 255 | 256 | assert_eq!(expected, serde_json::to_value(&perm).unwrap(),) 257 | } 258 | 259 | #[test] 260 | fn test_function_permission() { 261 | let mut perm = FunctionPermission::default(); 262 | perm.call(Level::public()); 263 | 264 | assert_eq!( 265 | json!({ "object": {"call": "public"} }), 266 | serde_json::to_value(&perm).unwrap(), 267 | ) 268 | } 269 | 270 | #[test] 271 | fn test_index_permission() { 272 | let class_ref = Ref::class("HouseCats"); 273 | 274 | let mut perm = IndexPermission::default(); 275 | perm.read(Level::reference(class_ref)); 276 | 277 | let expected = json!({ 278 | "object": { 279 | "read": { 280 | "@ref": { 281 | "class": { 282 | "@ref": { 283 | "id": "classes" 284 | } 285 | }, 286 | "id": "HouseCats" 287 | }, 288 | }, 289 | } 290 | }); 291 | 292 | assert_eq!(expected, serde_json::to_value(&perm).unwrap(),) 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/expr/reference.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, fmt}; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 4 | enum RefLocation<'a> { 5 | #[serde(rename = "class")] 6 | Class { 7 | #[serde(rename = "@ref")] 8 | location: Box>, 9 | }, 10 | #[serde(rename = "class")] 11 | Database { 12 | #[serde(rename = "@ref")] 13 | location: Box>, 14 | }, 15 | #[serde(rename = "index")] 16 | Index { 17 | #[serde(rename = "@ref")] 18 | location: Box>, 19 | }, 20 | #[serde(rename = "class")] 21 | Function { 22 | #[serde(rename = "@ref")] 23 | location: Box>, 24 | }, 25 | } 26 | 27 | impl<'a> RefLocation<'a> { 28 | fn path(&self) -> String { 29 | match self { 30 | RefLocation::Class { location } => location.path(), 31 | RefLocation::Index { location } => location.path(), 32 | RefLocation::Function { location } => location.path(), 33 | RefLocation::Database { location } => location.path(), 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 39 | /// Denotes a resource ref. 40 | pub struct Ref<'a> { 41 | pub id: Cow<'a, str>, 42 | #[serde(skip_serializing_if = "Option::is_none", flatten)] 43 | location: Option>, 44 | } 45 | 46 | impl<'a> fmt::Display for Ref<'a> { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | match self.location { 49 | Some(RefLocation::Class { ref location }) => { 50 | write!(f, "Ref(id={},class={})", self.id, location.path()) 51 | } 52 | Some(RefLocation::Index { ref location }) => { 53 | write!(f, "Ref(id={},index={})", self.id, location.path()) 54 | } 55 | Some(RefLocation::Function { ref location }) => { 56 | write!(f, "Ref(id={},class={})", self.id, location.path()) 57 | } 58 | Some(RefLocation::Database { ref location }) => { 59 | write!(f, "Ref(id={},database={})", self.id, location.path()) 60 | } 61 | None => write!(f, "Ref(id={})", self.id), 62 | } 63 | } 64 | } 65 | 66 | impl<'a> Ref<'a> { 67 | /// A ref to a singleton instance. 68 | pub fn instance(id: S) -> Self 69 | where 70 | S: Into>, 71 | { 72 | Self { 73 | id: id.into(), 74 | location: None, 75 | } 76 | } 77 | 78 | /// A ref to a class. 79 | pub fn class(id: S) -> Self 80 | where 81 | S: Into>, 82 | { 83 | Self { 84 | id: id.into(), 85 | location: Some(RefLocation::Class { 86 | location: Box::new(Self::instance("classes")), 87 | }), 88 | } 89 | } 90 | 91 | /// A ref to an index. 92 | pub fn index(id: S) -> Self 93 | where 94 | S: Into>, 95 | { 96 | Self { 97 | id: id.into(), 98 | location: Some(RefLocation::Index { 99 | location: Box::new(Self::instance("indexes")), 100 | }), 101 | } 102 | } 103 | 104 | /// A ref to a function. 105 | pub fn function(id: S) -> Self 106 | where 107 | S: Into>, 108 | { 109 | Self { 110 | id: id.into(), 111 | location: Some(RefLocation::Function { 112 | location: Box::new(Self::instance("functions")), 113 | }), 114 | } 115 | } 116 | 117 | /// A ref to a database. 118 | pub fn database(id: S) -> Self 119 | where 120 | S: Into>, 121 | { 122 | Self { 123 | id: id.into(), 124 | location: Some(RefLocation::Database { 125 | location: Box::new(Self::instance("databases")), 126 | }), 127 | } 128 | } 129 | 130 | /// Set the class for the singleton ref. 131 | pub fn set_class(&mut self, id: S) -> &mut Self 132 | where 133 | S: Into>, 134 | { 135 | self.location = Some(RefLocation::Class { 136 | location: Box::new(Self::class(id)), 137 | }); 138 | 139 | self 140 | } 141 | 142 | /// Set the index for the singleton ref. 143 | pub fn set_index(&mut self, id: S) -> &mut Self 144 | where 145 | S: Into>, 146 | { 147 | self.location = Some(RefLocation::Index { 148 | location: Box::new(Self::index(id)), 149 | }); 150 | 151 | self 152 | } 153 | 154 | /// Gets the fully qualified path. 155 | pub fn path(&self) -> String { 156 | match self.location { 157 | Some(ref location) => format!("{}/{}", location.path(), self.id), 158 | None => format!("{}", self.id), 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/expr/set.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::{Expr, Ref}; 2 | use std::fmt; 3 | 4 | #[derive(Debug, Serialize, Clone)] 5 | pub struct Set<'a> { 6 | #[serde(rename = "match")] 7 | matching: Expr<'a>, 8 | terms: Expr<'a>, 9 | } 10 | 11 | impl<'a> Set<'a> { 12 | pub fn matching(reference: Ref<'a>, terms: E) -> Self 13 | where 14 | E: Into>, 15 | { 16 | let matching = Expr::from(reference); 17 | let terms = terms.into(); 18 | 19 | Self { matching, terms } 20 | } 21 | } 22 | 23 | impl<'a> fmt::Display for Set<'a> { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | write!(f, "Set(match={},terms={})", self.matching, self.terms) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # FaunaDB 2 | //! 3 | //! `faunadb` is a client for the Fauna database in Rust. It provides the query 4 | //! and expression types, (de-)serialization and an asynchronous client. 5 | //! 6 | //! Additionally the crate holds a `SyncClient` wrapper for synchronous 7 | //! execution, enabled with the `sync_client` feature flag. 8 | //! 9 | //! Most of the type checks are handled in Fauna and the functions accept 10 | //! anything that can be converted to the [Expr](expr/struct.Expr.html) enum, 11 | //! allowing the usage of different Fauna types in a more dynamic manner. 12 | //! 13 | //! ## Asynchronous example: 14 | //! 15 | //! ```no_run 16 | //! use futures::{future::lazy, Future}; 17 | //! use faunadb::prelude::*; 18 | //! 19 | //! fn main() { 20 | //! let client = Client::builder("my_fauna_secret").build().unwrap(); 21 | //! 22 | //! let query = Filter::new( 23 | //! Lambda::new("x", Gt::new(Var::new("x"), 2)), 24 | //! Array::from(vec![1, 2, 3]), 25 | //! ); 26 | //! 27 | //! tokio::run(lazy(move || { 28 | //! client 29 | //! .query(query) 30 | //! .map(|response| { 31 | //! println!("{:#?}", response); 32 | //! }) 33 | //! .map_err(|error: faunadb::error::Error| { 34 | //! println!("Error: {:#?}", error); 35 | //! }) 36 | //! })); 37 | //! } 38 | //! ``` 39 | //! 40 | //! ## Synchronous example: 41 | //! 42 | //! ```no_run 43 | //! use faunadb::prelude::*; 44 | //! 45 | //! fn main() { 46 | //! let mut client = Client::builder("my_fauna_secret").build_sync().unwrap(); 47 | //! 48 | //! let query = Filter::new( 49 | //! Lambda::new("x", Gt::new(Var::new("x"), 2)), 50 | //! Array::from(vec![1, 2, 3]), 51 | //! ); 52 | //! 53 | //! match client.query(query) { 54 | //! Ok(response) => println!("{:#?}", response), 55 | //! Err(error) => println!("Error: {:#?}", error), 56 | //! } 57 | //! } 58 | //! ``` 59 | #[macro_use] 60 | extern crate serde_derive; 61 | 62 | #[macro_use] 63 | extern crate log; 64 | 65 | #[macro_use] 66 | mod macros; 67 | 68 | pub mod client; 69 | pub mod error; 70 | pub mod expr; 71 | pub mod prelude; 72 | pub mod query; 73 | 74 | mod serde; 75 | 76 | #[cfg(test)] 77 | mod test_utils; 78 | 79 | pub type Result = ::std::result::Result; 80 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// A helper macro to implement `From` trait from the given query type into the 2 | /// `Query` enum. 3 | #[macro_export] 4 | macro_rules! query { 5 | ($($kind:ident),*) => ( 6 | $( 7 | impl<'a> From<$kind<'a>> for Query<'a> { 8 | fn from(q: $kind<'a>) -> Self { 9 | Query::$kind(q) 10 | } 11 | } 12 | )* 13 | ); 14 | } 15 | 16 | /// A helper macro to implement `From` trait from the given query type into the 17 | /// `Query` enum, boxing the query. 18 | #[macro_export] 19 | macro_rules! boxed_query { 20 | ($($kind:ident),*) => ( 21 | $( 22 | impl<'a> From<$kind<'a>> for Query<'a> { 23 | fn from(q: $kind<'a>) -> Self { 24 | Query::$kind(Box::new(q)) 25 | } 26 | } 27 | )* 28 | ); 29 | } 30 | 31 | /// A convenience to convert a type of a signed integer into Fauna `Expr`. 32 | #[macro_export] 33 | macro_rules! int_expr { 34 | ($($kind:ident),*) => ( 35 | $( 36 | impl<'a> From<$kind> for Number { 37 | fn from(i: $kind) -> Number { 38 | Number::Int(i64::from(i)) 39 | } 40 | } 41 | 42 | impl<'a> From<$kind> for Expr<'a> { 43 | fn from(i: $kind) -> Expr<'a> { 44 | Expr::Simple(SimpleExpr::Number(i.into())) 45 | } 46 | } 47 | )* 48 | ); 49 | } 50 | 51 | /// A convenience to convert a type of a unsigned integer into Fauna `Expr`. 52 | #[macro_export] 53 | macro_rules! uint_expr { 54 | ($($kind:ident),*) => ( 55 | $( 56 | impl<'a> From<$kind> for Number { 57 | fn from(i: $kind) -> Number { 58 | Number::UInt(u64::from(i)) 59 | } 60 | } 61 | 62 | impl<'a> From<$kind> for Expr<'a> { 63 | fn from(i: $kind) -> Expr<'a> { 64 | Expr::Simple(SimpleExpr::Number(i.into())) 65 | } 66 | } 67 | )* 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::{ 2 | client::*, 3 | expr::*, 4 | query::{ 5 | auth::*, basic::*, collection::*, conversion::*, datetime::*, logical::*, math::*, misc::*, 6 | read::*, set::*, string::*, write::*, Query, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/query.rs: -------------------------------------------------------------------------------- 1 | //! A special case of an expression that needs to be evaluated to a value. 2 | pub mod auth; 3 | pub mod basic; 4 | pub mod collection; 5 | pub mod conversion; 6 | pub mod datetime; 7 | pub mod logical; 8 | pub mod math; 9 | pub mod misc; 10 | pub mod read; 11 | pub mod set; 12 | pub mod string; 13 | pub mod write; 14 | 15 | #[derive(Debug, Clone, Serialize)] 16 | #[serde(untagged)] 17 | pub enum Query<'a> { 18 | Difference(set::Difference<'a>), 19 | Distinct(set::Distinct<'a>), 20 | Intersection(set::Intersection<'a>), 21 | Join(set::Join<'a>), 22 | Match(set::Match<'a>), 23 | Union(set::Union<'a>), 24 | 25 | Date(datetime::Date<'a>), 26 | Epoch(datetime::Epoch<'a>), 27 | Time(datetime::Time<'a>), 28 | 29 | CaseFold(string::CaseFold<'a>), 30 | Concat(string::Concat<'a>), 31 | FindStr(string::FindStr<'a>), 32 | FindStrRegex(string::FindStrRegex<'a>), 33 | LTrim(string::LTrim<'a>), 34 | Length(string::Length<'a>), 35 | LowerCase(string::LowerCase<'a>), 36 | RTrim(string::RTrim<'a>), 37 | Repeat(string::Repeat<'a>), 38 | ReplaceStr(string::ReplaceStr<'a>), 39 | ReplaceStrRegex(string::ReplaceStrRegex<'a>), 40 | Space(string::Space<'a>), 41 | SubString(string::SubString<'a>), 42 | TitleCase(string::TitleCase<'a>), 43 | Trim(string::Trim<'a>), 44 | UpperCase(string::UpperCase<'a>), 45 | 46 | HasIdentity(auth::HasIdentity<'a>), 47 | Identify(auth::Identify<'a>), 48 | Identity(auth::Identity<'a>), 49 | Login(auth::Login<'a>), 50 | Logout(auth::Logout<'a>), 51 | 52 | ToDate(conversion::ToDate<'a>), 53 | ToNumber(conversion::ToNumber<'a>), 54 | ToString(conversion::ToString<'a>), 55 | ToTime(conversion::ToTime<'a>), 56 | 57 | At(basic::At<'a>), 58 | Call(basic::Call<'a>), 59 | Do(basic::Do<'a>), 60 | Let(basic::Let<'a>), 61 | Var(basic::Var<'a>), 62 | Lambda(basic::Lambda<'a>), 63 | If(basic::If<'a>), 64 | 65 | Append(collection::Append<'a>), 66 | Drop(collection::Drop<'a>), 67 | Filter(collection::Filter<'a>), 68 | Foreach(collection::Foreach<'a>), 69 | IsEmpty(collection::IsEmpty<'a>), 70 | IsNonEmpty(collection::IsNonEmpty<'a>), 71 | Map(collection::Map<'a>), 72 | Prepend(collection::Prepend<'a>), 73 | Take(collection::Take<'a>), 74 | 75 | And(logical::And<'a>), 76 | Or(logical::Or<'a>), 77 | Not(logical::Not<'a>), 78 | Lt(logical::Lt<'a>), 79 | Lte(logical::Lte<'a>), 80 | Gt(logical::Gt<'a>), 81 | Gte(logical::Gte<'a>), 82 | Contains(logical::Contains<'a>), 83 | Equals(logical::Equals<'a>), 84 | Exists(logical::Exists<'a>), 85 | 86 | Abs(math::Abs<'a>), 87 | Acos(math::Acos<'a>), 88 | Add(math::Add<'a>), 89 | Asin(math::Asin<'a>), 90 | Atan(math::Atan<'a>), 91 | BitAnd(math::BitAnd<'a>), 92 | BitNot(math::BitNot<'a>), 93 | BitOr(math::BitOr<'a>), 94 | BitXor(math::BitXor<'a>), 95 | Ceil(math::Ceil<'a>), 96 | Cos(math::Cos<'a>), 97 | Cosh(math::Cosh<'a>), 98 | Degrees(math::Degrees<'a>), 99 | Divide(math::Divide<'a>), 100 | Exp(math::Exp<'a>), 101 | Floor(math::Floor<'a>), 102 | Hypot(math::Hypot<'a>), 103 | Ln(math::Ln<'a>), 104 | Log(math::Log<'a>), 105 | Max(math::Max<'a>), 106 | Min(math::Min<'a>), 107 | Modulo(math::Modulo<'a>), 108 | Multiply(math::Multiply<'a>), 109 | Pow(math::Pow<'a>), 110 | Radians(math::Radians<'a>), 111 | Round(math::Round<'a>), 112 | Sign(math::Sign<'a>), 113 | Sin(math::Sin<'a>), 114 | Sinh(math::Sinh<'a>), 115 | Sqrt(math::Sqrt<'a>), 116 | Subtract(math::Subtract<'a>), 117 | Tan(math::Tan<'a>), 118 | Tanh(math::Tanh<'a>), 119 | Trunc(math::Trunc<'a>), 120 | 121 | CreateClass(Box>), 122 | CreateDatabase(write::CreateDatabase<'a>), 123 | CreateIndex(Box>), 124 | CreateFunction(Box>), 125 | CreateKey(Box>), 126 | Create(write::Create<'a>), 127 | Insert(Box>), 128 | Delete(write::Delete<'a>), 129 | Remove(write::Remove<'a>), 130 | Replace(write::Replace<'a>), 131 | Update(write::Update<'a>), 132 | 133 | Get(read::Get<'a>), 134 | KeyFromSecret(read::KeyFromSecret<'a>), 135 | Paginate(read::Paginate<'a>), 136 | Select(read::Select<'a>), 137 | SelectAll(read::SelectAll<'a>), 138 | 139 | Abort(misc::Abort<'a>), 140 | Class(misc::Class<'a>), 141 | Classes(misc::Classes<'a>), 142 | Database(misc::Database<'a>), 143 | Databases(misc::Databases<'a>), 144 | Function(misc::Function<'a>), 145 | Functions(misc::Functions<'a>), 146 | Index(misc::Index<'a>), 147 | Indexes(misc::Indexes<'a>), 148 | NewId(misc::NewId<'a>), 149 | } 150 | -------------------------------------------------------------------------------- /src/query/auth.rs: -------------------------------------------------------------------------------- 1 | //! Authentication functions 2 | use crate::{ 3 | expr::{Expr, Ref}, 4 | query::Query, 5 | }; 6 | 7 | query![HasIdentity, Identify, Identity, Login, Logout]; 8 | 9 | /// The `HasIdentity` function returns `true` if the current client 10 | /// authentication credentials have an associated identity, and `false` if they 11 | /// don’t. 12 | /// 13 | /// Read the 14 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/authentication/hasidentity). 15 | #[derive(Serialize, Clone, Debug, Default)] 16 | pub struct HasIdentity<'a> { 17 | has_identity: Expr<'a>, 18 | } 19 | 20 | impl<'a> HasIdentity<'a> { 21 | pub fn new() -> Self { 22 | Self::default() 23 | } 24 | } 25 | 26 | /// The `Identify` function checks the given password against the ref’s 27 | /// credentials, returning `true` if the credentials are valid, or false 28 | /// otherwise. 29 | /// 30 | /// Read the 31 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/authentication/identify). 32 | #[derive(Serialize, Clone, Debug)] 33 | pub struct Identify<'a> { 34 | identify: Expr<'a>, 35 | password: Expr<'a>, 36 | } 37 | 38 | impl<'a> Identify<'a> { 39 | pub fn new(identify: Ref<'a>, password: impl Into>) -> Self { 40 | Self { 41 | identify: Expr::from(identify), 42 | password: password.into(), 43 | } 44 | } 45 | } 46 | 47 | /// The `Identity` function returns the ref of the instance associated with the 48 | /// authentication token used for the request. If an instance does not exist, an 49 | /// error is returned. 50 | /// 51 | /// Read the 52 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/authentication/identity). 53 | #[derive(Serialize, Clone, Debug, Default)] 54 | pub struct Identity<'a> { 55 | identity: Expr<'a>, 56 | } 57 | 58 | impl<'a> Identity<'a> { 59 | pub fn new() -> Self { 60 | Self::default() 61 | } 62 | } 63 | 64 | /// The `Login` function creates an authentication token for the provided Ref, or 65 | /// Set of Refs. 66 | /// 67 | /// Read the 68 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/authentication/login). 69 | #[derive(Serialize, Clone, Debug)] 70 | pub struct Login<'a> { 71 | login: Expr<'a>, 72 | params: LoginParams<'a>, 73 | } 74 | 75 | #[derive(Serialize, Clone, Debug)] 76 | #[doc(hidden)] 77 | pub struct LoginObject<'a> { 78 | password: Expr<'a>, 79 | } 80 | 81 | #[derive(Serialize, Clone, Debug)] 82 | #[doc(hidden)] 83 | pub struct LoginParams<'a> { 84 | object: LoginObject<'a>, 85 | } 86 | 87 | impl<'a> Login<'a> { 88 | pub fn new(login: Ref<'a>, password: impl Into>) -> Self { 89 | Self { 90 | login: Expr::from(login), 91 | params: LoginParams { 92 | object: LoginObject { 93 | password: password.into(), 94 | }, 95 | }, 96 | } 97 | } 98 | } 99 | 100 | /// The `Logout` function deletes all tokens associated with the current session 101 | /// if its parameter is `true`, or just the token used in this request otherwise. 102 | /// 103 | /// Read the 104 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/authentication/logout). 105 | #[derive(Serialize, Clone, Debug)] 106 | pub struct Logout<'a> { 107 | logout: Expr<'a>, 108 | } 109 | 110 | impl<'a> Logout<'a> { 111 | pub fn new(all_tokens: bool) -> Self { 112 | Self { 113 | logout: Expr::from(all_tokens), 114 | } 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use crate::prelude::*; 121 | use serde_json::{self, json}; 122 | 123 | #[test] 124 | fn test_has_identity() { 125 | let fun = HasIdentity::new(); 126 | 127 | let query = Query::from(fun); 128 | let serialized = serde_json::to_value(&query).unwrap(); 129 | 130 | assert_eq!(json!({ "has_identity": null }), serialized); 131 | } 132 | 133 | #[test] 134 | fn test_identity() { 135 | let fun = Identity::new(); 136 | 137 | let query = Query::from(fun); 138 | let serialized = serde_json::to_value(&query).unwrap(); 139 | 140 | assert_eq!(json!({ "identity": null }), serialized); 141 | } 142 | 143 | #[test] 144 | fn test_identify() { 145 | let mut user_ref = Ref::instance("1234"); 146 | user_ref.set_class("characters"); 147 | 148 | let fun = Identify::new(user_ref, "Hunter2"); 149 | 150 | let query = Query::from(fun); 151 | let serialized = serde_json::to_value(&query).unwrap(); 152 | 153 | let expected = json!({ 154 | "identify": { 155 | "@ref": { 156 | "class": { 157 | "@ref": { 158 | "class": { 159 | "@ref": { 160 | "id": "classes" 161 | } 162 | }, 163 | "id": "characters" 164 | } 165 | }, 166 | "id": "1234" 167 | } 168 | }, 169 | "password": "Hunter2", 170 | }); 171 | 172 | assert_eq!(expected, serialized); 173 | } 174 | 175 | #[test] 176 | fn test_login() { 177 | let mut user_ref = Ref::instance("1234"); 178 | user_ref.set_class("characters"); 179 | 180 | let fun = Login::new(user_ref, "Hunter2"); 181 | 182 | let query = Query::from(fun); 183 | let serialized = serde_json::to_value(&query).unwrap(); 184 | 185 | let expected = json!({ 186 | "login": { 187 | "@ref": { 188 | "class": { 189 | "@ref": { 190 | "class": { 191 | "@ref": { 192 | "id": "classes" 193 | } 194 | }, 195 | "id": "characters" 196 | } 197 | }, 198 | "id": "1234" 199 | } 200 | }, 201 | "params": { 202 | "object": { 203 | "password": "Hunter2" 204 | } 205 | }, 206 | }); 207 | 208 | assert_eq!(expected, serialized); 209 | } 210 | 211 | #[test] 212 | fn test_logout() { 213 | let fun = Logout::new(false); 214 | 215 | let query = Query::from(fun); 216 | let serialized = serde_json::to_value(&query).unwrap(); 217 | 218 | let expected = json!({ 219 | "logout": false, 220 | }); 221 | 222 | assert_eq!(expected, serialized); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/query/basic.rs: -------------------------------------------------------------------------------- 1 | //! Basic functions 2 | use crate::{ 3 | expr::{Expr, Ref}, 4 | query::Query, 5 | }; 6 | use chrono::{DateTime, Utc}; 7 | use std::{borrow::Cow, collections::BTreeMap}; 8 | 9 | // Implements From for Query 10 | query![At, Call, If, Do, Let, Var, Lambda]; 11 | 12 | /// The At function executes a temporal query, a query which examines the data 13 | /// in the past. 14 | /// 15 | /// The `timestamp` parameter determines the data available for viewing by 16 | /// creating a virtual snapshot of the data which was current at that date and 17 | /// time. All reads from the associated `expression` is then executed on that 18 | /// virtual snapshot. In contrast, all write operations must be executed at the 19 | /// current time. Attempting a write operation at any other time produces an 20 | /// error. 21 | /// 22 | /// Read the 23 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/at); 24 | #[derive(Debug, Clone, Serialize)] 25 | pub struct At<'a> { 26 | #[serde(rename = "at")] 27 | timestamp: Expr<'a>, 28 | #[serde(rename = "expr")] 29 | expression: Expr<'a>, 30 | } 31 | 32 | impl<'a> At<'a> { 33 | pub fn new(timestamp: DateTime, expression: impl Into>) -> Self { 34 | Self { 35 | timestamp: Expr::from(timestamp), 36 | expression: expression.into(), 37 | } 38 | } 39 | } 40 | 41 | /// The `Call` function executes a user-defined function previously defined with 42 | /// the CreateFunction function. 43 | /// 44 | /// The Call function takes a variable length list of arguments which must match 45 | /// the type and number of the function being called. These arguments are 46 | /// provided to the function being executed by `Call`. 47 | /// 48 | /// Read the 49 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/call); 50 | #[derive(Debug, Clone, Serialize)] 51 | pub struct Call<'a> { 52 | call: Expr<'a>, 53 | arguments: Expr<'a>, 54 | } 55 | 56 | impl<'a> Call<'a> { 57 | /// The `arguments` must evaluate to the type and number of arguments of the 58 | /// function being called. 59 | pub fn new(function: Ref<'a>, arguments: impl Into>) -> Self { 60 | Self { 61 | call: Expr::from(function), 62 | arguments: arguments.into(), 63 | } 64 | } 65 | } 66 | 67 | /// The `If` function evaluates and returns `if_true` or `if_false` depending on 68 | /// the value of the `cond` expression. 69 | /// 70 | /// If the `cond` expression evaluates to 71 | /// anything other than a `Boolean`, `If` returns an `invalid argument` error. 72 | /// 73 | /// Read the 74 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/if); 75 | #[derive(Debug, Clone, Serialize)] 76 | pub struct If<'a> { 77 | #[serde(rename = "if")] 78 | cond: Expr<'a>, 79 | #[serde(rename = "then")] 80 | if_true: Expr<'a>, 81 | #[serde(rename = "else")] 82 | if_false: Expr<'a>, 83 | } 84 | 85 | impl<'a> If<'a> { 86 | /// Create a new `If` conditional. The `cond` parameter should always return 87 | /// a `Boolean` expression. 88 | pub fn cond( 89 | cond: impl Into>, 90 | if_true: impl Into>, 91 | if_false: impl Into>, 92 | ) -> Self { 93 | Self { 94 | cond: cond.into(), 95 | if_true: if_true.into(), 96 | if_false: if_false.into(), 97 | } 98 | } 99 | } 100 | 101 | /// The `Do` function evaluates a list of expressions which are provided as 102 | /// arguments. 103 | /// 104 | /// This evaluation occurs sequentially, from left to right, ensuring 105 | /// that modifications made by earlier expressions are seen by later 106 | /// expressions. 107 | /// 108 | /// If one of the expressions evaluated by 'Do' returns an error, the 109 | /// current transaction is terminated and none of the expressions' effects are 110 | /// persisted in the database. 111 | /// 112 | /// If all of the expressions executed by 'Do' succeed, 113 | /// only the results of the last statements executed are returned. 114 | /// 115 | /// Read the 116 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/do). 117 | #[derive(Serialize, Debug, Clone)] 118 | pub struct Do<'a> { 119 | #[serde(rename = "do")] 120 | queries: Vec>, 121 | } 122 | 123 | impl<'a> Do<'a> { 124 | /// Create a new `Do` query. 125 | pub fn new(first_expr: impl Into>) -> Self { 126 | Do { 127 | queries: vec![first_expr.into()], 128 | } 129 | } 130 | 131 | /// Add a query to the end of the execution pipeline. 132 | pub fn push(&mut self, q: impl Into>) -> &mut Self { 133 | self.queries.push(q.into()); 134 | self 135 | } 136 | } 137 | 138 | /// The `Lambda` function is an anonymous function that performs lazy execution 139 | /// of custom code. It allows you to organize and execute almost any of the 140 | /// Fauna Query Language statements. 141 | /// 142 | /// A `Lambda` can take zero or more arguments. `Lambda`s that 143 | /// define multiple parameters use a `params` array to define the arguments. In 144 | /// this case, the items inside the `params` array are the arguments, not the 145 | /// array itself. The `params` array must have the same number of elements as 146 | /// the Lambda function expects, or an `_` (i.e., underscore) argument to drop 147 | /// the extra arguments in the array. Otherwise, it will return an error. 148 | /// 149 | /// The `Lambda` arguments may be accessed inside the `Lambda` code using the 150 | /// [Var](struct.Var.html) statement. 151 | /// 152 | /// Read the 153 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/lambda). 154 | #[derive(Serialize, Debug, Clone)] 155 | pub struct Lambda<'a> { 156 | #[serde(rename = "lambda")] 157 | params: Expr<'a>, 158 | expr: Expr<'a>, 159 | } 160 | 161 | impl<'a> Lambda<'a> { 162 | pub fn new(params: impl Into>, expr: impl Into>) -> Self { 163 | Self { 164 | params: params.into(), 165 | expr: expr.into(), 166 | } 167 | } 168 | } 169 | 170 | /// The `Let` function binds one or more variables to a single value or 171 | /// expression. 172 | /// 173 | /// When multiple variables are defined, the evaluation is from left 174 | /// to right. Variables which have previously been defined may be used to define 175 | /// future variables. Variables are lexically scoped to the expression passed 176 | /// via the in parameter. The value of a variable can be referenced with 177 | /// Var(varname) syntax. 178 | /// 179 | /// Read the 180 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/let). 181 | #[derive(Debug, Clone, Serialize)] 182 | pub struct Let<'a> { 183 | #[serde(rename = "let")] 184 | bindings: BTreeMap, Expr<'a>>, 185 | #[serde(rename = "in")] 186 | in_expr: Expr<'a>, 187 | } 188 | 189 | /// A single binding to be used in a `Let` query. 190 | #[derive(Debug, Clone, Serialize)] 191 | pub struct Binding<'a>(Cow<'a, str>, Expr<'a>); 192 | 193 | impl<'a> Binding<'a> { 194 | pub fn new(variable: V, expr: E) -> Self 195 | where 196 | V: Into>, 197 | E: Into>, 198 | { 199 | Binding(variable.into(), expr.into()) 200 | } 201 | } 202 | 203 | impl<'a> Let<'a> { 204 | /// Set bindings to be available in the given `Expr`. 205 | pub fn bindings(bindings: B, in_expr: E) -> Self 206 | where 207 | B: IntoIterator>, 208 | E: Into>, 209 | { 210 | let bindings = bindings 211 | .into_iter() 212 | .map(|binding| (binding.0, binding.1)) 213 | .collect(); 214 | 215 | let in_expr = in_expr.into(); 216 | 217 | Self { bindings, in_expr } 218 | } 219 | } 220 | 221 | /// Evaluate and return the value stored in a named variable. 222 | /// 223 | /// The `Var` statement can only be used inside other statements, such 224 | /// as [Let](struct.Let.html) or [Lambda](struct.Lambda.html). 225 | /// 226 | /// Read the 227 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/basic/var) 228 | #[derive(Debug, Serialize, Clone)] 229 | pub struct Var<'a> { 230 | var: Cow<'a, str>, 231 | } 232 | 233 | impl<'a> Var<'a> { 234 | pub fn new(var: V) -> Self 235 | where 236 | V: Into>, 237 | { 238 | Self { var: var.into() } 239 | } 240 | } 241 | 242 | #[cfg(test)] 243 | mod tests { 244 | use crate::{ 245 | prelude::*, 246 | query::{misc::Classes, read::Get, write::Delete}, 247 | }; 248 | use chrono::{offset::TimeZone, Utc}; 249 | use serde_json::{self, json}; 250 | 251 | #[test] 252 | fn test_at() { 253 | let fun = At::new(Utc.timestamp(60, 0), Classes::all()); 254 | let query = Query::from(fun); 255 | let serialized = serde_json::to_value(&query).unwrap(); 256 | 257 | let expected = json!({ 258 | "at": {"@ts": "1970-01-01T00:01:00Z"}, 259 | "expr": {"classes": null} 260 | }); 261 | 262 | assert_eq!(expected, serialized); 263 | } 264 | 265 | #[test] 266 | fn test_do() { 267 | let mut do_many = Do::new(Get::instance(Ref::instance("musti"))); 268 | do_many.push(Delete::new(Ref::instance("musti"))); 269 | 270 | let query = Query::from(do_many); 271 | let serialized = serde_json::to_value(&query).unwrap(); 272 | 273 | let expected = json!({ 274 | "do": [ 275 | {"get": {"@ref": {"id": "musti"}}}, 276 | {"delete": {"@ref": {"id": "musti"}}}, 277 | ] 278 | }); 279 | 280 | assert_eq!(expected, serialized); 281 | } 282 | 283 | #[test] 284 | fn test_if() { 285 | let query = Query::from(If::cond(true, "is true", "is false")); 286 | let serialized = serde_json::to_value(&query).unwrap(); 287 | 288 | let expected = json!({ 289 | "if": true, 290 | "then": "is true", 291 | "else": "is false", 292 | }); 293 | 294 | assert_eq!(expected, serialized); 295 | } 296 | 297 | #[test] 298 | fn test_let_var() { 299 | let let_var = Let::bindings( 300 | vec![Binding::new("cat", If::cond(true, "Musti", "Naukio"))], 301 | Var::new("cat"), 302 | ); 303 | 304 | let query = Query::from(let_var); 305 | 306 | let serialized = serde_json::to_value(&query).unwrap(); 307 | 308 | let expected = json!({ 309 | "let": {"cat": {"if": true, "then": "Musti", "else": "Naukio"}}, 310 | "in": {"var": "cat"}, 311 | }); 312 | 313 | assert_eq!(expected, serialized); 314 | } 315 | 316 | #[test] 317 | fn test_lambda() { 318 | let lambda = Lambda::new("cat", Var::new("cat")); 319 | let query = Query::from(lambda); 320 | let serialized = serde_json::to_value(&query).unwrap(); 321 | 322 | let expected = json!({ 323 | "lambda": "cat", 324 | "expr": {"var": "cat"}, 325 | }); 326 | 327 | assert_eq!(expected, serialized); 328 | } 329 | 330 | #[test] 331 | fn test_call() { 332 | let fun = Call::new(Ref::function("double"), 5); 333 | let query = Query::from(fun); 334 | let serialized = serde_json::to_value(&query).unwrap(); 335 | 336 | let expected = json!({ 337 | "call": { 338 | "@ref": { 339 | "class": { 340 | "@ref": { 341 | "id": "functions" 342 | } 343 | }, 344 | "id": "double" 345 | } 346 | }, 347 | "arguments": 5 348 | }); 349 | 350 | assert_eq!(expected, serialized); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/query/collection.rs: -------------------------------------------------------------------------------- 1 | //! Collection functions 2 | use crate::{ 3 | expr::Expr, 4 | query::{basic::Lambda, Query}, 5 | }; 6 | 7 | query![Append, Drop, Filter, Foreach, IsEmpty, IsNonEmpty, Map, Prepend, Take]; 8 | 9 | /// The `Append` function creates a new array that is the result of combining the 10 | /// base Array followed by the `elems`. 11 | /// 12 | /// This is a specialized function, and only works with collections of type 13 | /// Array. 14 | /// 15 | /// Read the 16 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/append). 17 | #[derive(Serialize, Clone, Debug)] 18 | pub struct Append<'a> { 19 | append: Expr<'a>, 20 | collection: Expr<'a>, 21 | } 22 | 23 | impl<'a> Append<'a> { 24 | pub fn new(base: impl Into>, elems: impl Into>) -> Self { 25 | Self { 26 | append: base.into(), 27 | collection: elems.into(), 28 | } 29 | } 30 | } 31 | 32 | /// The `Drop` function returns a new collection of the same type that contains 33 | /// the remaining elements, after `num` have been removed from the head of the 34 | /// collection. 35 | /// 36 | /// If `num` is zero or negative, all elements of the collection are 37 | /// returned unmodified. 38 | /// 39 | /// When applied to a collection of type page, the returned page’s `before` cursor 40 | /// is adjusted to exclude the dropped elements. As special cases: 41 | /// 42 | /// * If `num` is negative, `before` does not change. 43 | /// * Otherwise if all elements from the original page were dropped (including 44 | /// the case where the page was already empty), `before` is set to same value as 45 | /// the original page’s `after`. 46 | /// 47 | /// Read the 48 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/drop). 49 | #[derive(Debug, Clone, Serialize)] 50 | pub struct Drop<'a> { 51 | drop: Expr<'a>, 52 | collection: Expr<'a>, 53 | } 54 | 55 | impl<'a> Drop<'a> { 56 | /// The `drop` parameter must evaluate to an integer and `collection` to a 57 | /// collection. 58 | pub fn new(drop: impl Into>, collection: impl Into>) -> Self { 59 | Self { 60 | drop: drop.into(), 61 | collection: collection.into(), 62 | } 63 | } 64 | } 65 | 66 | /// The `Filter` function applies the [Lambda](../basic/struct.Lambda.html) to 67 | /// each member of the collection and returns a new collection of the same type 68 | /// containing only those elements for which the lambda returns `true`. 69 | /// 70 | /// Providing a lambda function which does not return a `Boolean` results in an 71 | /// "invalid argument" error. If a `Page` is passed, its decorated fields are 72 | /// preserved in the result. 73 | /// 74 | /// Read the 75 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/filter). 76 | #[derive(Debug, Clone, Serialize)] 77 | pub struct Filter<'a> { 78 | filter: Expr<'a>, 79 | collection: Expr<'a>, 80 | } 81 | 82 | impl<'a> Filter<'a> { 83 | pub fn new(filter: Lambda<'a>, collection: impl Into>) -> Self { 84 | Self { 85 | filter: Expr::from(filter), 86 | collection: collection.into(), 87 | } 88 | } 89 | } 90 | 91 | /// The `Foreach` function applies the [Lambda](../basic/struct.Lambda.html) 92 | /// serially to each member of a "collection", and returns the original 93 | /// collection. 94 | /// 95 | /// The `Foreach` function is very useful when the original collection does not 96 | /// need to be modified, but a side effect is required for every value in a 97 | /// collection. Later invocations of the `Lambda` can see the side effects of 98 | /// earlier invocations of the `Lambda`. 99 | /// 100 | /// Read the 101 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/foreach). 102 | #[derive(Debug, Clone, Serialize)] 103 | pub struct Foreach<'a> { 104 | collection: Expr<'a>, 105 | foreach: Expr<'a>, 106 | } 107 | 108 | impl<'a> Foreach<'a> { 109 | pub fn new(collection: impl Into>, lambda: Lambda<'a>) -> Self { 110 | Self { 111 | collection: collection.into(), 112 | foreach: Expr::from(lambda), 113 | } 114 | } 115 | } 116 | 117 | /// The `IsEmpty` function returns `true` only if there are no elements in the 118 | /// collection. 119 | /// 120 | /// Read the 121 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/isempty). 122 | #[derive(Serialize, Clone, Debug)] 123 | pub struct IsEmpty<'a> { 124 | is_empty: Expr<'a>, 125 | } 126 | 127 | impl<'a> IsEmpty<'a> { 128 | pub fn new(collection: impl Into>) -> Self { 129 | Self { 130 | is_empty: collection.into(), 131 | } 132 | } 133 | } 134 | 135 | /// The `IsNonEmpty` function returns `true` only if there is at least one 136 | /// element in the collection. 137 | /// 138 | /// Read the 139 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/isnonempty). 140 | #[derive(Serialize, Clone, Debug)] 141 | pub struct IsNonEmpty<'a> { 142 | is_nonempty: Expr<'a>, 143 | } 144 | 145 | impl<'a> IsNonEmpty<'a> { 146 | pub fn new(collection: impl Into>) -> Self { 147 | Self { 148 | is_nonempty: collection.into(), 149 | } 150 | } 151 | } 152 | 153 | /// The `Map` function applies a [Lambda](../basic/struct.Lambda.html) serially to each 154 | /// member of the collection and returns the results of each application in a 155 | /// new collection of the same type. Later invocations of the `Lambda` function 156 | /// can see the results of earlier invocations. 157 | /// 158 | /// Read the 159 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/map). 160 | #[derive(Serialize, Clone, Debug)] 161 | pub struct Map<'a> { 162 | collection: Expr<'a>, 163 | map: Expr<'a>, 164 | } 165 | 166 | impl<'a> Map<'a> { 167 | pub fn new(collection: impl Into>, lambda: impl Into>) -> Self { 168 | Self { 169 | collection: collection.into(), 170 | map: lambda.into(), 171 | } 172 | } 173 | } 174 | 175 | /// The `Prepend` function creates a new `Array` that is the result of combining the 176 | /// `elems` followed by the `base` Array. This function only works with collections 177 | /// of type Array. 178 | /// 179 | /// Read the 180 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/prepend). 181 | #[derive(Serialize, Clone, Debug)] 182 | pub struct Prepend<'a> { 183 | prepend: Expr<'a>, 184 | collection: Expr<'a>, 185 | } 186 | 187 | impl<'a> Prepend<'a> { 188 | pub fn new(base: impl Into>, elems: impl Into>) -> Self { 189 | Self { 190 | prepend: base.into(), 191 | collection: elems.into(), 192 | } 193 | } 194 | } 195 | 196 | /// The `Take` function returns a new collection of the same type that contains 197 | /// num elements from the head of the collection. 198 | /// 199 | /// If num is zero or negative, the resulting collection is empty. 200 | /// 201 | /// When applied to a collection which is of type page, the returned page’s 202 | /// "after" cursor is adjusted to only cover the taken elements. As special 203 | /// cases: 204 | /// 205 | /// * If num is negative, after is set to the same value as the original page’s 206 | /// "before". 207 | /// * If all elements from the original page were taken, after does not change. 208 | /// 209 | /// Read the 210 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/collection/take). 211 | #[derive(Serialize, Clone, Debug)] 212 | pub struct Take<'a> { 213 | take: Expr<'a>, 214 | collection: Expr<'a>, 215 | } 216 | 217 | impl<'a> Take<'a> { 218 | /// The `take` parameter must evaluate to an integer and `collection` to a 219 | /// collection. 220 | pub fn new(take: impl Into>, collection: impl Into>) -> Self { 221 | Self { 222 | take: take.into(), 223 | collection: collection.into(), 224 | } 225 | } 226 | } 227 | 228 | #[cfg(test)] 229 | mod tests { 230 | use crate::prelude::*; 231 | use serde_json::{self, json}; 232 | 233 | #[test] 234 | fn test_map() { 235 | let map = Map::new( 236 | Array::from(vec!["Musti", "Naukio"]), 237 | Lambda::new("cat", Var::new("cat")), 238 | ); 239 | 240 | let query = Query::from(map); 241 | let serialized = serde_json::to_value(&query).unwrap(); 242 | 243 | let expected = json!({ 244 | "collection": ["Musti", "Naukio"], 245 | "map": { 246 | "lambda": "cat", 247 | "expr": {"var": "cat"}, 248 | } 249 | }); 250 | 251 | assert_eq!(expected, serialized); 252 | } 253 | 254 | #[test] 255 | fn test_append() { 256 | let fun = Append::new( 257 | Array::from(vec!["Musti", "Naukio"]), 258 | Array::from(vec!["Musmus", "Naunau"]), 259 | ); 260 | 261 | let query = Query::from(fun); 262 | let serialized = serde_json::to_value(&query).unwrap(); 263 | 264 | let expected = json!({ 265 | "append": ["Musti", "Naukio"], 266 | "collection": ["Musmus", "Naunau"], 267 | }); 268 | 269 | assert_eq!(expected, serialized); 270 | } 271 | 272 | #[test] 273 | fn test_prepend() { 274 | let fun = Prepend::new( 275 | Array::from(vec!["Musti", "Naukio"]), 276 | Array::from(vec!["Musmus", "Naunau"]), 277 | ); 278 | 279 | let query = Query::from(fun); 280 | let serialized = serde_json::to_value(&query).unwrap(); 281 | 282 | let expected = json!({ 283 | "prepend": ["Musti", "Naukio"], 284 | "collection": ["Musmus", "Naunau"], 285 | }); 286 | 287 | assert_eq!(expected, serialized); 288 | } 289 | 290 | #[test] 291 | fn test_drop() { 292 | let fun = Drop::new(2, Array::from(vec![1, 2, 3])); 293 | let query = Query::from(fun); 294 | let serialized = serde_json::to_value(&query).unwrap(); 295 | 296 | let expected = json!({ 297 | "drop": 2, 298 | "collection": [1, 2, 3], 299 | }); 300 | 301 | assert_eq!(expected, serialized); 302 | } 303 | 304 | #[test] 305 | fn test_take() { 306 | let fun = Take::new(2, Array::from(vec![1, 2, 3])); 307 | let query = Query::from(fun); 308 | let serialized = serde_json::to_value(&query).unwrap(); 309 | 310 | let expected = json!({ 311 | "take": 2, 312 | "collection": [1, 2, 3], 313 | }); 314 | 315 | assert_eq!(expected, serialized); 316 | } 317 | 318 | #[test] 319 | fn test_filter() { 320 | let fun = Filter::new( 321 | Lambda::new("x", Gt::new(Var::new("x"), 2)), 322 | Array::from(vec![1, 2, 3]), 323 | ); 324 | 325 | let query = Query::from(fun); 326 | let serialized = serde_json::to_value(&query).unwrap(); 327 | 328 | let expected = json!({ 329 | "filter": { 330 | "lambda": "x", 331 | "expr": {"gt": [{ "var": "x" }, 2]} 332 | }, 333 | "collection": [1, 2, 3], 334 | }); 335 | 336 | assert_eq!(expected, serialized); 337 | } 338 | 339 | #[test] 340 | fn test_foreach() { 341 | let fun = Foreach::new(Array::from(vec![1, 2, 3]), Lambda::new("_", Gt::new(1, 2))); 342 | 343 | let query = Query::from(fun); 344 | let serialized = serde_json::to_value(&query).unwrap(); 345 | 346 | let expected = json!({ 347 | "foreach": { 348 | "lambda": "_", 349 | "expr": {"gt": [1, 2]} 350 | }, 351 | "collection": [1, 2, 3], 352 | }); 353 | 354 | assert_eq!(expected, serialized); 355 | } 356 | 357 | #[test] 358 | fn test_is_empty() { 359 | let fun = IsEmpty::new(Array::from(vec![1, 2, 3])); 360 | 361 | let query = Query::from(fun); 362 | let serialized = serde_json::to_value(&query).unwrap(); 363 | 364 | assert_eq!(json!({"is_empty": [1, 2, 3]}), serialized); 365 | } 366 | 367 | #[test] 368 | fn test_is_nonempty() { 369 | let fun = IsNonEmpty::new(Array::from(vec![1, 2, 3])); 370 | 371 | let query = Query::from(fun); 372 | let serialized = serde_json::to_value(&query).unwrap(); 373 | 374 | assert_eq!(json!({"is_nonempty": [1, 2, 3]}), serialized); 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /src/query/conversion.rs: -------------------------------------------------------------------------------- 1 | //! Conversion functions 2 | use crate::{expr::Expr, query::Query}; 3 | 4 | query![ToDate, ToNumber, ToString, ToTime]; 5 | 6 | /// The `ToDate` function converts a value to a date type, if possible. 7 | /// 8 | /// Attempting to convert a value to a date which has no date representation 9 | /// results in an "invalid argument" error. 10 | /// 11 | /// Read the 12 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/conversion/todate). 13 | #[derive(Serialize, Debug, Clone)] 14 | pub struct ToDate<'a> { 15 | to_date: Expr<'a>, 16 | } 17 | 18 | impl<'a> ToDate<'a> { 19 | pub fn new(expr: impl Into>) -> Self { 20 | Self { 21 | to_date: expr.into(), 22 | } 23 | } 24 | } 25 | 26 | /// The `ToNumber` function converts a value to a numeric literal, if possible. 27 | /// 28 | /// Attempting to convert a value to a number which has no numeric 29 | /// representation results in an "invalid argument" error. 30 | /// 31 | /// Read the 32 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/conversion/tonumber). 33 | #[derive(Serialize, Debug, Clone)] 34 | pub struct ToNumber<'a> { 35 | to_number: Expr<'a>, 36 | } 37 | 38 | impl<'a> ToNumber<'a> { 39 | pub fn new(expr: impl Into>) -> Self { 40 | Self { 41 | to_number: expr.into(), 42 | } 43 | } 44 | } 45 | 46 | /// The `ToString` function converts a value to a string type, if possible. 47 | /// 48 | /// Attempting to convert a value to a string which has no string representation 49 | /// results in an "invalid argument" error. 50 | /// 51 | /// Read the 52 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/conversion/tostring). 53 | #[derive(Serialize, Debug, Clone)] 54 | pub struct ToString<'a> { 55 | to_string: Expr<'a>, 56 | } 57 | 58 | impl<'a> ToString<'a> { 59 | pub fn new(expr: impl Into>) -> Self { 60 | Self { 61 | to_string: expr.into(), 62 | } 63 | } 64 | } 65 | 66 | /// The `ToTime` function converts a value to a timestamp type, if possible. 67 | /// 68 | /// Attempting to convert a value to a timestamp which has no timestamp 69 | /// representation results in an "invalid argument" error. 70 | /// 71 | /// Read the 72 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/conversion/totime). 73 | #[derive(Serialize, Debug, Clone)] 74 | pub struct ToTime<'a> { 75 | to_time: Expr<'a>, 76 | } 77 | 78 | impl<'a> ToTime<'a> { 79 | pub fn new(expr: impl Into>) -> Self { 80 | Self { 81 | to_time: expr.into(), 82 | } 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use crate::prelude::*; 89 | use serde_json::{self, json}; 90 | 91 | #[test] 92 | fn test_to_date() { 93 | let fun = ToDate::new("2019-06-06"); 94 | let query = Query::from(fun); 95 | let serialized = serde_json::to_value(&query).unwrap(); 96 | 97 | let expected = json!({ 98 | "to_date": "2019-06-06", 99 | }); 100 | 101 | assert_eq!(expected, serialized); 102 | } 103 | 104 | #[test] 105 | fn test_to_number() { 106 | let fun = ToNumber::new("2"); 107 | let query = Query::from(fun); 108 | let serialized = serde_json::to_value(&query).unwrap(); 109 | 110 | let expected = json!({ 111 | "to_number": "2", 112 | }); 113 | 114 | assert_eq!(expected, serialized); 115 | } 116 | 117 | #[test] 118 | fn test_to_string() { 119 | let fun = ToString::new(false); 120 | let query = Query::from(fun); 121 | let serialized = serde_json::to_value(&query).unwrap(); 122 | 123 | let expected = json!({ 124 | "to_string": false, 125 | }); 126 | 127 | assert_eq!(expected, serialized); 128 | } 129 | 130 | #[test] 131 | fn test_to_time() { 132 | let fun = ToString::new("2015-02-20T06:30:00Z"); 133 | let query = Query::from(fun); 134 | let serialized = serde_json::to_value(&query).unwrap(); 135 | 136 | let expected = json!({ 137 | "to_string": "2015-02-20T06:30:00Z", 138 | }); 139 | 140 | assert_eq!(expected, serialized); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/query/datetime.rs: -------------------------------------------------------------------------------- 1 | //! Time and date functions 2 | use crate::{expr::Expr, query::Query}; 3 | 4 | query![Date, Epoch, Time]; 5 | 6 | /// The `Date` function constructs a Date from an ISO 8601 formatted string. 7 | /// 8 | /// Read the 9 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/time_date/date) 10 | #[derive(Serialize, Clone, Debug)] 11 | pub struct Date<'a> { 12 | date: Expr<'a>, 13 | } 14 | 15 | impl<'a> Date<'a> { 16 | pub fn new(dateish: impl Into>) -> Self { 17 | Self { 18 | date: dateish.into(), 19 | } 20 | } 21 | } 22 | 23 | #[derive(Serialize, Clone, Debug, Copy)] 24 | pub enum EpochUnit { 25 | #[serde(rename = "second")] 26 | Second, 27 | #[serde(rename = "millisecond")] 28 | Millisecond, 29 | #[serde(rename = "microsecond")] 30 | Microsecond, 31 | #[serde(rename = "nanosecond")] 32 | Nanosecond, 33 | } 34 | 35 | /// The `Epoch` function constructs a Timestamp relative to the epoch 36 | /// (1970-01-01T00:00:00Z). 37 | /// 38 | /// The num argument must be an integer value. Epoch adds num to offset defined 39 | /// in units and returns a timestamp. 40 | /// 41 | /// Read the 42 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/time_date/epoch) 43 | #[derive(Serialize, Clone, Debug)] 44 | pub struct Epoch<'a> { 45 | epoch: Expr<'a>, 46 | unit: EpochUnit, 47 | } 48 | 49 | impl<'a> Epoch<'a> { 50 | pub fn new(num: impl Into>, unit: EpochUnit) -> Self { 51 | Self { 52 | epoch: num.into(), 53 | unit, 54 | } 55 | } 56 | } 57 | 58 | /// The `Time` function constructs a Timestamp from an ISO 8601 string. 59 | /// 60 | /// The special string now may be used to construct a time from the current 61 | /// request’s transaction time. Multiple references to now within the same 62 | /// transaction produce the same timestamp. The current transaction time is the 63 | /// same on all nodes that participate in the transaction. When doing a temporal 64 | /// query, now means the current time of the query, not the current time. 65 | /// 66 | /// Read the 67 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/time_date/time) 68 | #[derive(Serialize, Clone, Debug)] 69 | pub struct Time<'a> { 70 | time: Expr<'a>, 71 | } 72 | 73 | impl<'a> Time<'a> { 74 | pub fn new(timeish: impl Into>) -> Self { 75 | Self { 76 | time: timeish.into(), 77 | } 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use crate::prelude::*; 84 | use serde_json::{self, json}; 85 | 86 | #[test] 87 | fn test_date() { 88 | let fun = Date::new("1970-01-01"); 89 | 90 | let query = Query::from(fun); 91 | let serialized = serde_json::to_value(&query).unwrap(); 92 | 93 | let expected = json!({ 94 | "date": "1970-01-01", 95 | }); 96 | 97 | assert_eq!(expected, serialized); 98 | } 99 | 100 | #[test] 101 | fn test_epoch() { 102 | let fun = Epoch::new(5, EpochUnit::Second); 103 | 104 | let query = Query::from(fun); 105 | let serialized = serde_json::to_value(&query).unwrap(); 106 | 107 | let expected = json!({ 108 | "epoch": 5, 109 | "unit": "second" 110 | }); 111 | 112 | assert_eq!(expected, serialized); 113 | } 114 | 115 | #[test] 116 | fn test_time() { 117 | let fun = Time::new("1970-01-01T00:00:00+00:00"); 118 | 119 | let query = Query::from(fun); 120 | let serialized = serde_json::to_value(&query).unwrap(); 121 | 122 | let expected = json!({ 123 | "time": "1970-01-01T00:00:00+00:00", 124 | }); 125 | 126 | assert_eq!(expected, serialized); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/query/logical.rs: -------------------------------------------------------------------------------- 1 | //! Logical functions 2 | use crate::{ 3 | expr::{Expr, Ref}, 4 | query::Query, 5 | }; 6 | use chrono::{DateTime, Utc}; 7 | 8 | query![And, Or, Not, Contains, Exists, Equals, Lt, Lte, Gt, Gte]; 9 | 10 | /// The `And` function computes the conjunction of a list of boolean values, 11 | /// returning `true` if all elements are "true", and `false` otherwise. 12 | /// 13 | /// Read the 14 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/and) 15 | #[derive(Serialize, Debug, Clone, Default)] 16 | pub struct And<'a> { 17 | and: Vec>, 18 | } 19 | 20 | impl<'a> And<'a> { 21 | /// A simple and with two expressions. For a vector comparison, use the 22 | /// `From` trait. 23 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 24 | Self { 25 | and: vec![left.into(), right.into()], 26 | } 27 | } 28 | } 29 | 30 | impl<'a, I, E> From for And<'a> 31 | where 32 | I: IntoIterator, 33 | E: Into>, 34 | { 35 | fn from(exprs: I) -> Self { 36 | Self { 37 | and: exprs.into_iter().map(Into::into).collect(), 38 | } 39 | } 40 | } 41 | 42 | /// The `Or` function operates on one or more values and returns true if at least 43 | /// one of the values is `true`. 44 | /// 45 | /// Read the 46 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/or) 47 | #[derive(Serialize, Debug, Clone, Default)] 48 | pub struct Or<'a> { 49 | or: Vec>, 50 | } 51 | 52 | impl<'a> Or<'a> { 53 | /// A simple and with two expressions. For a vector comparison, use the 54 | /// `From` trait. 55 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 56 | Self { 57 | or: vec![left.into(), right.into()], 58 | } 59 | } 60 | } 61 | 62 | impl<'a, I, E> From for Or<'a> 63 | where 64 | I: IntoIterator, 65 | E: Into>, 66 | { 67 | fn from(exprs: I) -> Self { 68 | Self { 69 | or: exprs.into_iter().map(Into::into).collect(), 70 | } 71 | } 72 | } 73 | 74 | /// The `Not` function computes the negation of a boolean value, returning true if 75 | /// its argument is `false`, or `false` if its argument is `true`. 76 | /// 77 | /// Read the 78 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/not) 79 | #[derive(Serialize, Debug, Clone)] 80 | pub struct Not<'a> { 81 | not: Expr<'a>, 82 | } 83 | 84 | impl<'a> Not<'a> { 85 | pub fn new(expr: impl Into>) -> Self { 86 | Self { not: expr.into() } 87 | } 88 | } 89 | 90 | /// The `Equals` function tests equivalence between a list of values. 91 | /// 92 | /// Read the 93 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/equals) 94 | #[derive(Serialize, Debug, Clone)] 95 | pub struct Equals<'a> { 96 | equals: Vec>, 97 | } 98 | 99 | impl<'a> Equals<'a> { 100 | /// A simple and with two expressions. For a vector comparison, use the 101 | /// `From` trait. 102 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 103 | Self { 104 | equals: vec![left.into(), right.into()], 105 | } 106 | } 107 | } 108 | 109 | impl<'a, I, E> From for Equals<'a> 110 | where 111 | I: IntoIterator, 112 | E: Into>, 113 | { 114 | fn from(exprs: I) -> Self { 115 | Self { 116 | equals: exprs.into_iter().map(Into::into).collect(), 117 | } 118 | } 119 | } 120 | 121 | /// The `Lt` function returns `true` if each specified value is less than the ones 122 | /// following it, and `false` otherwise. 123 | /// 124 | /// Read the 125 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/lt) 126 | #[derive(Serialize, Debug, Clone, Default)] 127 | pub struct Lt<'a> { 128 | lt: Vec>, 129 | } 130 | 131 | impl<'a> Lt<'a> { 132 | /// A simple and with two expressions. For a vector comparison, use the 133 | /// `From` trait. 134 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 135 | Self { 136 | lt: vec![left.into(), right.into()], 137 | } 138 | } 139 | } 140 | 141 | impl<'a, I, E> From for Lt<'a> 142 | where 143 | I: IntoIterator, 144 | E: Into>, 145 | { 146 | fn from(exprs: I) -> Self { 147 | Self { 148 | lt: exprs.into_iter().map(Into::into).collect(), 149 | } 150 | } 151 | } 152 | 153 | /// The `Lte` function returns `true` if each specified value is less than or 154 | /// equal to the ones following it, and `false` otherwise. 155 | /// 156 | /// Read the 157 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/lte) 158 | #[derive(Serialize, Debug, Clone, Default)] 159 | pub struct Lte<'a> { 160 | lte: Vec>, 161 | } 162 | 163 | impl<'a> Lte<'a> { 164 | /// A simple and with two expressions. For a vector comparison, use the 165 | /// `From` trait. 166 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 167 | Self { 168 | lte: vec![left.into(), right.into()], 169 | } 170 | } 171 | } 172 | 173 | impl<'a, I, E> From for Lte<'a> 174 | where 175 | I: IntoIterator, 176 | E: Into>, 177 | { 178 | fn from(exprs: I) -> Self { 179 | Self { 180 | lte: exprs.into_iter().map(Into::into).collect(), 181 | } 182 | } 183 | } 184 | 185 | /// The `Gt` function returns `true` if each specified value is greater than the 186 | /// ones following it, and `false` otherwise. 187 | /// 188 | /// Read the 189 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/gt) 190 | #[derive(Serialize, Debug, Clone, Default)] 191 | pub struct Gt<'a> { 192 | gt: Vec>, 193 | } 194 | 195 | impl<'a> Gt<'a> { 196 | /// A simple and with two expressions. For a vector comparison, use the 197 | /// `From` trait. 198 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 199 | Self { 200 | gt: vec![left.into(), right.into()], 201 | } 202 | } 203 | } 204 | 205 | impl<'a, I, E> From for Gt<'a> 206 | where 207 | I: IntoIterator, 208 | E: Into>, 209 | { 210 | fn from(exprs: I) -> Self { 211 | Self { 212 | gt: exprs.into_iter().map(Into::into).collect(), 213 | } 214 | } 215 | } 216 | 217 | /// The `Gte` function returns `true` if each specified value is greater than or 218 | /// equal to the ones following it, and `false` otherwise. 219 | /// 220 | /// Read the 221 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/gte) 222 | #[derive(Serialize, Debug, Clone, Default)] 223 | pub struct Gte<'a> { 224 | gte: Vec>, 225 | } 226 | 227 | impl<'a> Gte<'a> { 228 | /// A simple and with two expressions. For a vector comparison, use the 229 | /// `From` trait. 230 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 231 | Self { 232 | gte: vec![left.into(), right.into()], 233 | } 234 | } 235 | } 236 | 237 | impl<'a, I, E> From for Gte<'a> 238 | where 239 | I: IntoIterator, 240 | E: Into>, 241 | { 242 | fn from(exprs: I) -> Self { 243 | Self { 244 | gte: exprs.into_iter().map(Into::into).collect(), 245 | } 246 | } 247 | } 248 | 249 | /// The `Contains` function returns `true` if the argument passed as in contains a 250 | /// value at the specified path, and `false` otherwise. 251 | /// 252 | /// Read the 253 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/contains) 254 | #[derive(Serialize, Debug, Clone)] 255 | pub struct Contains<'a> { 256 | contains: Vec>, 257 | #[serde(rename = "in")] 258 | in_: Expr<'a>, 259 | } 260 | 261 | impl<'a> Contains<'a> { 262 | pub fn new(path: I, in_: F) -> Self 263 | where 264 | I: IntoIterator, 265 | E: Into>, 266 | F: Into>, 267 | { 268 | Self { 269 | contains: path.into_iter().map(|e| e.into()).collect(), 270 | in_: in_.into(), 271 | } 272 | } 273 | } 274 | 275 | /// The `Exists` function returns boolean `true` if the provided ref exists at the 276 | /// specified timestamp (in the case of an instance), or is non-empty (in the 277 | /// case of a set), and `false` otherwise. 278 | /// 279 | /// Read the 280 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/logical/exists) 281 | #[derive(Serialize, Debug, Clone)] 282 | pub struct Exists<'a> { 283 | exists: Expr<'a>, 284 | #[serde(rename = "ts", skip_serializing_if = "Option::is_none")] 285 | timestamp: Option>, 286 | } 287 | 288 | impl<'a> Exists<'a> { 289 | pub fn new(reference: Ref<'a>) -> Self { 290 | Self { 291 | exists: Expr::from(reference), 292 | timestamp: None, 293 | } 294 | } 295 | 296 | pub fn timestamp(&mut self, ts: DateTime) -> &mut Self { 297 | self.timestamp = Some(Expr::from(ts)); 298 | self 299 | } 300 | } 301 | 302 | #[cfg(test)] 303 | mod tests { 304 | use crate::prelude::*; 305 | use chrono::{offset::TimeZone, Utc}; 306 | use serde_json::{self, json}; 307 | 308 | #[test] 309 | fn test_and() { 310 | let aaaand = And::from(vec![true, true, false]); 311 | let query = Query::from(aaaand); 312 | let serialized = serde_json::to_value(&query).unwrap(); 313 | 314 | assert_eq!(json!({"and": [true, true, false]}), serialized); 315 | } 316 | 317 | #[test] 318 | fn test_or() { 319 | let oooor = Or::new(Var::new("x"), false); 320 | let query = Query::from(oooor); 321 | let serialized = serde_json::to_value(&query).unwrap(); 322 | 323 | assert_eq!(json!({"or": [{"var": "x"}, false]}), serialized); 324 | } 325 | 326 | #[test] 327 | fn test_not() { 328 | let noooot = Not::new(false); 329 | let query = Query::from(noooot); 330 | let serialized = serde_json::to_value(&query).unwrap(); 331 | 332 | assert_eq!(json!({"not": false}), serialized); 333 | } 334 | 335 | #[test] 336 | fn test_equals() { 337 | let equals = Equals::new("musti", "naukio"); 338 | let query = Query::from(equals); 339 | let serialized = serde_json::to_value(&query).unwrap(); 340 | 341 | assert_eq!(json!({"equals": ["musti", "naukio"]}), serialized); 342 | } 343 | 344 | #[test] 345 | fn test_exists() { 346 | let mut exists = Exists::new(Ref::instance("Musti")); 347 | exists.timestamp(Utc.timestamp(60, 0)); 348 | 349 | let query = Query::from(exists); 350 | let serialized = serde_json::to_value(&query).unwrap(); 351 | 352 | let expected = json!({ 353 | "exists": { 354 | "@ref": { 355 | "id": "Musti" 356 | } 357 | }, 358 | "ts": { 359 | "@ts": Utc.timestamp(60, 0) 360 | } 361 | }); 362 | 363 | assert_eq!(expected, serialized); 364 | } 365 | 366 | #[test] 367 | fn test_lt() { 368 | let lt = Lt::new(1, 2); 369 | let query = Query::from(lt); 370 | let serialized = serde_json::to_value(&query).unwrap(); 371 | 372 | assert_eq!(json!({"lt": [1, 2]}), serialized); 373 | } 374 | 375 | #[test] 376 | fn test_lte() { 377 | let lte = Lte::new(2, 3); 378 | let query = Query::from(lte); 379 | let serialized = serde_json::to_value(&query).unwrap(); 380 | 381 | assert_eq!(json!({"lte": [2, 3]}), serialized); 382 | } 383 | 384 | #[test] 385 | fn test_gt() { 386 | let gt = Gt::new(1, 2); 387 | let query = Query::from(gt); 388 | let serialized = serde_json::to_value(&query).unwrap(); 389 | 390 | assert_eq!(json!({"gt": [1, 2]}), serialized); 391 | } 392 | 393 | #[test] 394 | fn test_gte() { 395 | let gte = Gte::from(vec![1, 2, 3]); 396 | let query = Query::from(gte); 397 | let serialized = serde_json::to_value(&query).unwrap(); 398 | 399 | assert_eq!(json!({"gte": [1, 2, 3]}), serialized); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /src/query/misc.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous functions 2 | use crate::{expr::Expr, query::Query}; 3 | 4 | query![Abort, Class, Classes, Database, Databases, Function, Functions, Index, Indexes, NewId]; 5 | 6 | /// This `Abort` function terminates the current transaction and augments the 7 | /// returned error with the associated message. 8 | /// 9 | /// Any modifications to data or schema in the aborted transaction will be 10 | /// ignored, even if this modification took place before the abort function was 11 | /// executed. 12 | /// 13 | /// Read the 14 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/abort) 15 | #[derive(Serialize, Debug, Clone)] 16 | pub struct Abort<'a> { 17 | abort: Expr<'a>, 18 | } 19 | 20 | impl<'a> Abort<'a> { 21 | pub fn new(msg: impl Into>) -> Self { 22 | Self { abort: msg.into() } 23 | } 24 | } 25 | 26 | /// The `Class` function returns a valid `Ref` for the given class name. 27 | /// 28 | /// Read the 29 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/class) 30 | #[derive(Serialize, Debug, Clone)] 31 | pub struct Class<'a> { 32 | class: Expr<'a>, 33 | } 34 | 35 | impl<'a> Class<'a> { 36 | pub fn find(name: impl Into>) -> Self { 37 | Self { class: name.into() } 38 | } 39 | } 40 | 41 | /// The `Classes` function when executed with `Paginate` returns an array of Refs 42 | /// for all classes in the database specified. 43 | /// 44 | /// If no database is provided, it returns an array of references to all classes 45 | /// in the current database. 46 | /// 47 | /// Read the 48 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/classes) 49 | #[derive(Serialize, Debug, Clone, Default)] 50 | pub struct Classes<'a> { 51 | classes: Option>, 52 | } 53 | 54 | impl<'a> Classes<'a> { 55 | pub fn all() -> Self { 56 | Self::default() 57 | } 58 | 59 | pub fn from_database(database: impl Into>) -> Self { 60 | Self { 61 | classes: Some(database.into()), 62 | } 63 | } 64 | } 65 | 66 | /// The `Function` function returns a valid `Ref` for the given function name. 67 | /// 68 | /// Read the 69 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/function) 70 | #[derive(Serialize, Debug, Clone)] 71 | pub struct Function<'a> { 72 | function: Expr<'a>, 73 | } 74 | 75 | impl<'a> Function<'a> { 76 | pub fn find(name: impl Into>) -> Self { 77 | Self { 78 | function: name.into(), 79 | } 80 | } 81 | } 82 | 83 | /// The `Functions` function when executed with `Paginate` returns an array of Refs 84 | /// for all functions in the database specified. 85 | /// 86 | /// If no database is provided, it returns an array of references to all functions 87 | /// in the current database. 88 | /// 89 | /// Read the 90 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/functions) 91 | #[derive(Serialize, Debug, Clone, Default)] 92 | pub struct Functions<'a> { 93 | functions: Option>, 94 | } 95 | 96 | impl<'a> Functions<'a> { 97 | pub fn all() -> Self { 98 | Self::default() 99 | } 100 | 101 | pub fn from_database(database: impl Into>) -> Self { 102 | Self { 103 | functions: Some(database.into()), 104 | } 105 | } 106 | } 107 | 108 | /// The `Database` function returns a valid `Ref` for the given database name. 109 | /// 110 | /// Read the 111 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/database) 112 | #[derive(Serialize, Debug, Clone)] 113 | pub struct Database<'a> { 114 | database: Expr<'a>, 115 | } 116 | 117 | impl<'a> Database<'a> { 118 | pub fn find(name: impl Into>) -> Self { 119 | Self { 120 | database: name.into(), 121 | } 122 | } 123 | } 124 | 125 | /// The `Databases` function when executed with `Paginate` returns an array of Refs 126 | /// for sub-databases in the database specified. 127 | /// 128 | /// If no database is provided, it returns an array of references to 129 | /// sub-databases in the current database. 130 | /// 131 | /// Read the 132 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/databases) 133 | #[derive(Serialize, Debug, Clone, Default)] 134 | pub struct Databases<'a> { 135 | databases: Option>, 136 | } 137 | 138 | impl<'a> Databases<'a> { 139 | pub fn all() -> Self { 140 | Self::default() 141 | } 142 | 143 | pub fn from_database(database: impl Into>) -> Self { 144 | Self { 145 | databases: Some(database.into()), 146 | } 147 | } 148 | } 149 | 150 | /// The `Index` function returns a valid `Ref` for the given index name. 151 | /// 152 | /// Read the 153 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/index) 154 | #[derive(Serialize, Debug, Clone)] 155 | pub struct Index<'a> { 156 | index: Expr<'a>, 157 | } 158 | 159 | impl<'a> Index<'a> { 160 | pub fn find(name: impl Into>) -> Self { 161 | Self { index: name.into() } 162 | } 163 | } 164 | 165 | /// The `Indexes` function when executed with `Paginate` returns an array of Refs 166 | /// for indexes in the database specified. 167 | /// 168 | /// If no database is provided, it returns an array of references to indexes in 169 | /// the current database. 170 | /// 171 | /// Read the 172 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/databases) 173 | #[derive(Serialize, Debug, Clone, Default)] 174 | pub struct Indexes<'a> { 175 | indexes: Option>, 176 | } 177 | 178 | impl<'a> Indexes<'a> { 179 | pub fn all() -> Self { 180 | Self::default() 181 | } 182 | 183 | pub fn from_database(database: impl Into>) -> Self { 184 | Self { 185 | indexes: Some(database.into()), 186 | } 187 | } 188 | } 189 | 190 | /// This `NewId` function produces a unique number. 191 | /// 192 | /// This number is guaranteed to be unique across the entire cluster and once 193 | /// generated is never generated a second time. This identifier is suitable for 194 | /// constructing the id part of a reference. 195 | /// 196 | /// Read the 197 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/misc/newid) 198 | #[derive(Serialize, Debug, Clone, Default)] 199 | pub struct NewId<'a> { 200 | new_id: Expr<'a>, 201 | } 202 | 203 | impl<'a> NewId<'a> { 204 | pub fn new() -> Self { 205 | Self::default() 206 | } 207 | } 208 | 209 | #[cfg(test)] 210 | mod tests { 211 | use crate::prelude::*; 212 | use serde_json::{self, json}; 213 | 214 | #[test] 215 | fn test_abort() { 216 | let fun = Abort::new("BOOM"); 217 | 218 | let query = Query::from(fun); 219 | let serialized = serde_json::to_value(&query).unwrap(); 220 | 221 | let expected = json!({ 222 | "abort": "BOOM", 223 | }); 224 | 225 | assert_eq!(expected, serialized); 226 | } 227 | 228 | #[test] 229 | fn test_class() { 230 | let fun = Class::find("housecats"); 231 | 232 | let query = Query::from(fun); 233 | let serialized = serde_json::to_value(&query).unwrap(); 234 | 235 | let expected = json!({ 236 | "class": "housecats", 237 | }); 238 | 239 | assert_eq!(expected, serialized); 240 | } 241 | 242 | #[test] 243 | fn test_classes_all() { 244 | let fun = Classes::all(); 245 | 246 | let query = Query::from(fun); 247 | let serialized = serde_json::to_value(&query).unwrap(); 248 | 249 | let expected = json!({ 250 | "classes": null, 251 | }); 252 | 253 | assert_eq!(expected, serialized); 254 | } 255 | 256 | #[test] 257 | fn test_classes_database() { 258 | let fun = Classes::from_database(Ref::database("cats")); 259 | 260 | let query = Query::from(fun); 261 | let serialized = serde_json::to_value(&query).unwrap(); 262 | 263 | let expected = json!({ 264 | "classes": { 265 | "@ref": { 266 | "class": { 267 | "@ref": { 268 | "id": "databases" 269 | } 270 | }, 271 | "id": "cats" 272 | } 273 | }, 274 | }); 275 | 276 | assert_eq!(expected, serialized); 277 | } 278 | 279 | #[test] 280 | fn test_database() { 281 | let fun = Database::find("cats"); 282 | 283 | let query = Query::from(fun); 284 | let serialized = serde_json::to_value(&query).unwrap(); 285 | 286 | let expected = json!({ 287 | "database": "cats", 288 | }); 289 | 290 | assert_eq!(expected, serialized); 291 | } 292 | 293 | #[test] 294 | fn test_databases_all() { 295 | let fun = Databases::all(); 296 | 297 | let query = Query::from(fun); 298 | let serialized = serde_json::to_value(&query).unwrap(); 299 | 300 | let expected = json!({ 301 | "databases": null, 302 | }); 303 | 304 | assert_eq!(expected, serialized); 305 | } 306 | 307 | #[test] 308 | fn test_databases_atabase() { 309 | let fun = Databases::from_database(Ref::database("cats")); 310 | 311 | let query = Query::from(fun); 312 | let serialized = serde_json::to_value(&query).unwrap(); 313 | 314 | let expected = json!({ 315 | "databases": { 316 | "@ref": { 317 | "class": { 318 | "@ref": { 319 | "id": "databases" 320 | } 321 | }, 322 | "id": "cats" 323 | } 324 | }, 325 | }); 326 | 327 | assert_eq!(expected, serialized); 328 | } 329 | 330 | #[test] 331 | fn test_function() { 332 | let fun = Function::find("meow"); 333 | 334 | let query = Query::from(fun); 335 | let serialized = serde_json::to_value(&query).unwrap(); 336 | 337 | let expected = json!({ 338 | "function": "meow", 339 | }); 340 | 341 | assert_eq!(expected, serialized); 342 | } 343 | 344 | #[test] 345 | fn test_functions_all() { 346 | let fun = Functions::all(); 347 | 348 | let query = Query::from(fun); 349 | let serialized = serde_json::to_value(&query).unwrap(); 350 | 351 | let expected = json!({ 352 | "functions": null, 353 | }); 354 | 355 | assert_eq!(expected, serialized); 356 | } 357 | 358 | #[test] 359 | fn test_functions_database() { 360 | let fun = Functions::from_database(Ref::database("cats")); 361 | 362 | let query = Query::from(fun); 363 | let serialized = serde_json::to_value(&query).unwrap(); 364 | 365 | let expected = json!({ 366 | "functions": { 367 | "@ref": { 368 | "class": { 369 | "@ref": { 370 | "id": "databases" 371 | } 372 | }, 373 | "id": "cats" 374 | } 375 | }, 376 | }); 377 | 378 | assert_eq!(expected, serialized); 379 | } 380 | 381 | #[test] 382 | fn test_index() { 383 | let fun = Index::find("scratches"); 384 | 385 | let query = Query::from(fun); 386 | let serialized = serde_json::to_value(&query).unwrap(); 387 | 388 | let expected = json!({ 389 | "index": "scratches", 390 | }); 391 | 392 | assert_eq!(expected, serialized); 393 | } 394 | 395 | #[test] 396 | fn test_indexes_all() { 397 | let fun = Indexes::all(); 398 | 399 | let query = Query::from(fun); 400 | let serialized = serde_json::to_value(&query).unwrap(); 401 | 402 | let expected = json!({ 403 | "indexes": null, 404 | }); 405 | 406 | assert_eq!(expected, serialized); 407 | } 408 | 409 | #[test] 410 | fn test_indexes_database() { 411 | let fun = Indexes::from_database(Ref::database("cats")); 412 | 413 | let query = Query::from(fun); 414 | let serialized = serde_json::to_value(&query).unwrap(); 415 | 416 | let expected = json!({ 417 | "indexes": { 418 | "@ref": { 419 | "class": { 420 | "@ref": { 421 | "id": "databases" 422 | } 423 | }, 424 | "id": "cats" 425 | } 426 | }, 427 | }); 428 | 429 | assert_eq!(expected, serialized); 430 | } 431 | 432 | #[test] 433 | fn test_new_id() { 434 | let fun = NewId::new(); 435 | 436 | let query = Query::from(fun); 437 | let serialized = serde_json::to_value(&query).unwrap(); 438 | 439 | let expected = json!({ 440 | "new_id": null, 441 | }); 442 | 443 | assert_eq!(expected, serialized); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/query/read.rs: -------------------------------------------------------------------------------- 1 | //! Read functions 2 | use crate::{ 3 | expr::{Array, Expr}, 4 | query::Query, 5 | }; 6 | use chrono::{DateTime, Utc}; 7 | 8 | query![Get, KeyFromSecret, Paginate, Select, SelectAll]; 9 | 10 | /// The `Get` function retrieves a single instance identified by `ref`. 11 | /// 12 | /// An optional `timestamp` can be provided to retrieve the instance which 13 | /// existed at the specific date and time. If the timestamp is omitted the 14 | /// default is the current time. 15 | /// 16 | /// Read the 17 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/read/get) 18 | #[derive(Debug, Serialize, Clone)] 19 | pub struct Get<'a> { 20 | get: Expr<'a>, 21 | #[serde(rename = "ts", skip_serializing_if = "Option::is_none")] 22 | timestamp: Option>, 23 | } 24 | 25 | impl<'a> Get<'a> { 26 | pub fn instance(reference: impl Into>) -> Self { 27 | Self { 28 | get: reference.into(), 29 | timestamp: None, 30 | } 31 | } 32 | 33 | pub fn timestamp(&mut self, ts: DateTime) -> &mut Self { 34 | self.timestamp = Some(Expr::from(ts)); 35 | self 36 | } 37 | } 38 | 39 | /// The `KeyFromSecret` function retrieves a key instance given a key’s secret string. 40 | /// 41 | /// Read the 42 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/read/keyfromsecret) 43 | #[derive(Debug, Serialize, Clone)] 44 | pub struct KeyFromSecret<'a> { 45 | key_from_secret: Expr<'a>, 46 | } 47 | 48 | impl<'a> KeyFromSecret<'a> { 49 | pub fn new(secret: &'a str) -> Self { 50 | Self { 51 | key_from_secret: Expr::from(secret), 52 | } 53 | } 54 | } 55 | 56 | /// The `Paginate` function simplifies the traversal of a query’s results. 57 | /// 58 | /// It is best utilized when the result of a query returns more than one object 59 | /// or an unknown number of objects. It provides cursor like semantics allowing 60 | /// the caller to walk both forward and backward in configurable sized pages 61 | /// through the results. 62 | /// 63 | /// Read the 64 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/read/paginate) 65 | #[derive(Debug, Serialize, Clone)] 66 | pub struct Paginate<'a> { 67 | paginate: Expr<'a>, 68 | size: u32, 69 | events: bool, 70 | sources: bool, 71 | #[serde(rename = "ts", skip_serializing_if = "Option::is_none")] 72 | timestamp: Option>, 73 | #[serde(skip_serializing_if = "Option::is_none")] 74 | after: Option>, 75 | #[serde(skip_serializing_if = "Option::is_none")] 76 | before: Option>, 77 | } 78 | 79 | impl<'a> Paginate<'a> { 80 | /// Input `Set` or a `Ref` 81 | pub fn new(input: impl Into>) -> Self { 82 | Self { 83 | paginate: input.into(), 84 | size: 64, 85 | events: false, 86 | sources: false, 87 | timestamp: None, 88 | after: None, 89 | before: None, 90 | } 91 | } 92 | 93 | /// Maximum results to return in a single page. Default: `64`. 94 | pub fn size(&mut self, size: u32) -> &mut Self { 95 | self.size = size; 96 | self 97 | } 98 | 99 | /// If `true`, return a page from the event history of the set. Default: 100 | /// `false`. 101 | pub fn events(&mut self, events: bool) -> &mut Self { 102 | self.events = events; 103 | self 104 | } 105 | 106 | /// If `true`, includes the source of truth providing why this object was 107 | /// included in the result set. Default: `false`. 108 | pub fn sources(&mut self, sources: bool) -> &mut Self { 109 | self.sources = sources; 110 | self 111 | } 112 | 113 | /// Return the result set at the specified point in time. 114 | pub fn timestamp(&mut self, timestamp: impl Into>) -> &mut Self { 115 | self.timestamp = Some(timestamp.into()); 116 | self 117 | } 118 | 119 | /// Return the next page of results after this cursor (inclusive). 120 | /// 121 | /// Cursor may be one of: 122 | /// 123 | /// * An `Integer` representing a timestamp. 124 | /// * A `@ts` value. 125 | /// * A `@date` value. Dates are interpreted as midnight on that date, in UTC. 126 | /// * An partial Event object: `ts`, `ts` and `action`, or all of `ts`, 127 | /// `action`, and `resource` must be specified. 128 | pub fn after(&mut self, after: impl Into>) -> &mut Self { 129 | self.after = Some(after.into()); 130 | self 131 | } 132 | 133 | /// Return the previous page of results before this cursor (exclusive). 134 | /// 135 | /// Cursor may be one of: 136 | /// 137 | /// * An `Integer` representing a timestamp. 138 | /// * A `@ts` value. 139 | /// * A `@date` value. Dates are interpreted as midnight on that date, in UTC. 140 | /// * An partial Event object: `ts`, `ts` and `action`, or all of `ts`, 141 | /// `action`, and `resource` must be specified. 142 | pub fn before(&mut self, before: impl Into>) -> &mut Self { 143 | self.before = Some(before.into()); 144 | self 145 | } 146 | } 147 | 148 | /// The `Select` function extracts a single value from a document. 149 | /// 150 | /// It extracts the value specified by the `path` parameter out of the `from` 151 | /// parameter and returns the value. 152 | /// 153 | /// If the path does not exist, the optional default object is returned. If the 154 | /// default object is not provided, an error is returned. 155 | /// 156 | /// Read the 157 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/read/select) 158 | #[derive(Serialize, Debug, Clone)] 159 | pub struct Select<'a> { 160 | select: Array<'a>, 161 | from: Expr<'a>, 162 | #[serde(skip_serializing_if = "Option::is_none")] 163 | default: Option>, 164 | } 165 | 166 | impl<'a> Select<'a> { 167 | pub fn new(select: impl Into>, from: impl Into>) -> Self { 168 | Self { 169 | select: select.into(), 170 | from: from.into(), 171 | default: None, 172 | } 173 | } 174 | 175 | /// The value to be returned if the path does not exists. 176 | pub fn default(&mut self, default: impl Into>) -> &mut Self { 177 | self.default = Some(default.into()); 178 | self 179 | } 180 | } 181 | 182 | /// The `SelectAll` function extracts one or more values from a document. 183 | /// 184 | /// It is very useful when extracting multiple values in an array. It extracts 185 | /// all of the values specified by the `path` parameter out of the `from` parameter 186 | /// and returns the values as an Array. If the path does not exist an empty 187 | /// array is returned. 188 | /// 189 | /// Read the 190 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/read/selectall) 191 | #[derive(Serialize, Debug, Clone)] 192 | pub struct SelectAll<'a> { 193 | select_all: Array<'a>, 194 | from: Expr<'a>, 195 | } 196 | 197 | impl<'a> SelectAll<'a> { 198 | pub fn new(select: impl Into>, from: impl Into>) -> Self { 199 | Self { 200 | select_all: select.into(), 201 | from: from.into(), 202 | } 203 | } 204 | } 205 | 206 | #[cfg(test)] 207 | mod tests { 208 | use crate::prelude::*; 209 | use chrono::{offset::TimeZone, Utc}; 210 | use serde_json::{self, json}; 211 | 212 | #[test] 213 | fn test_get() { 214 | let mut get = Get::instance(Ref::instance("musti")); 215 | get.timestamp(Utc.timestamp(60, 0)); 216 | 217 | let query = Query::from(get); 218 | let serialized = serde_json::to_value(&query).unwrap(); 219 | 220 | let expected = json!({ 221 | "get": { 222 | "@ref": { 223 | "id": "musti" 224 | } 225 | }, 226 | "ts": { 227 | "@ts": Utc.timestamp(60, 0) 228 | } 229 | }); 230 | 231 | assert_eq!(expected, serialized); 232 | } 233 | 234 | #[test] 235 | fn test_key_from_secret() { 236 | let fun = KeyFromSecret::new("Hunter2"); 237 | let query = Query::from(fun); 238 | let serialized = serde_json::to_value(&query).unwrap(); 239 | 240 | let expected = json!({ 241 | "key_from_secret": "Hunter2" 242 | }); 243 | 244 | assert_eq!(expected, serialized); 245 | } 246 | 247 | #[test] 248 | fn test_paginate() { 249 | let mut fun = Paginate::new(Classes::all()); 250 | fun.before(Utc.timestamp(100, 0)); 251 | fun.after(Utc.timestamp(60, 0)); 252 | 253 | let query = Query::from(fun); 254 | let serialized = serde_json::to_value(&query).unwrap(); 255 | 256 | let expected = json!({ 257 | "paginate": { "classes": null }, 258 | "after": { "@ts": "1970-01-01T00:01:00Z" }, 259 | "before": { "@ts": "1970-01-01T00:01:40Z" }, 260 | "size": 64, 261 | "sources": false, 262 | "events": false, 263 | }); 264 | 265 | assert_eq!(expected, serialized); 266 | } 267 | 268 | #[test] 269 | fn test_select() { 270 | let mut path = Array::from(vec!["favorites", "foods"]); 271 | path.push(1); 272 | 273 | let mut fun = Select::new(path, Get::instance(Ref::instance("musti"))); 274 | fun.default("Chicken hearts"); 275 | 276 | let query = Query::from(fun); 277 | let serialized = serde_json::to_value(&query).unwrap(); 278 | 279 | let expected = json!({ 280 | "select": ["favorites", "foods", 1], 281 | "from": { 282 | "get": { 283 | "@ref": { 284 | "id": "musti" 285 | } 286 | }, 287 | }, 288 | "default": "Chicken hearts" 289 | }); 290 | 291 | assert_eq!(expected, serialized); 292 | } 293 | 294 | #[test] 295 | fn test_select_all() { 296 | let mut path = Array::from(vec!["favorites", "foods"]); 297 | path.push(1); 298 | 299 | let fun = SelectAll::new(path, Get::instance(Ref::instance("naukio"))); 300 | let query = Query::from(fun); 301 | let serialized = serde_json::to_value(&query).unwrap(); 302 | 303 | let expected = json!({ 304 | "select_all": ["favorites", "foods", 1], 305 | "from": { 306 | "get": { 307 | "@ref": { 308 | "id": "naukio" 309 | } 310 | }, 311 | } 312 | }); 313 | 314 | assert_eq!(expected, serialized); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/query/set.rs: -------------------------------------------------------------------------------- 1 | //! Set functions 2 | use crate::{ 3 | expr::{Array, Expr}, 4 | query::Query, 5 | }; 6 | 7 | query![Difference, Distinct, Intersection, Join, Match, Union]; 8 | 9 | /// The `Difference` function returns a `SetRef` object that represents all elements 10 | /// in the first `SetRef` which are not in the difference `SetRef`(s). 11 | /// 12 | /// Read the 13 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/set/difference) 14 | #[derive(Serialize, Debug, Clone)] 15 | pub struct Difference<'a> { 16 | difference: Array<'a>, 17 | } 18 | 19 | impl<'a> Difference<'a> { 20 | /// Get the difference that represents all elements in the `left` `SetRef` 21 | /// that are not in the `right` `SetRef`. 22 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 23 | Self { 24 | difference: Array::from(vec![left.into(), right.into()]), 25 | } 26 | } 27 | 28 | /// Add a `SetRef` expression to be used in the `Difference`. 29 | pub fn push(&mut self, e: impl Into>) -> &mut Self { 30 | self.difference.push(e.into()); 31 | self 32 | } 33 | } 34 | 35 | impl<'a, A> From for Difference<'a> 36 | where 37 | A: Into>, 38 | { 39 | fn from(a: A) -> Self { 40 | Self { 41 | difference: a.into(), 42 | } 43 | } 44 | } 45 | 46 | /// The `Distinct` function returns a SetRef object that represents all of the 47 | /// unique elements in the provided SetRef. 48 | /// 49 | /// Read the 50 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/set/distinct) 51 | #[derive(Serialize, Debug, Clone)] 52 | pub struct Distinct<'a> { 53 | distinct: Expr<'a>, 54 | } 55 | 56 | impl<'a> Distinct<'a> { 57 | pub fn new(e: impl Into>) -> Self { 58 | Self { distinct: e.into() } 59 | } 60 | } 61 | 62 | /// The `Intersection` function returns a `SetRef` object that contains the elements 63 | /// that appears in every input `SetRef`. 64 | /// 65 | /// Read the 66 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/set/intersection) 67 | #[derive(Serialize, Debug, Clone)] 68 | pub struct Intersection<'a> { 69 | intersection: Array<'a>, 70 | } 71 | 72 | impl<'a> Intersection<'a> { 73 | /// Get the intersection that represents all elements in the `left` `SetRef` 74 | /// that are in the `right` `SetRef`. 75 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 76 | Self { 77 | intersection: Array::from(vec![left.into(), right.into()]), 78 | } 79 | } 80 | 81 | /// Add a `SetRef` expression to be used in the `Intersection`. 82 | pub fn push(&mut self, e: impl Into>) -> &mut Self { 83 | self.intersection.push(e.into()); 84 | self 85 | } 86 | } 87 | 88 | impl<'a, A> From for Intersection<'a> 89 | where 90 | A: Into>, 91 | { 92 | fn from(a: A) -> Self { 93 | Self { 94 | intersection: a.into(), 95 | } 96 | } 97 | } 98 | 99 | /// The `Join` function finds all index tuples from the `source` SetRef and uses the 100 | /// source's values to be retrieved from the `detail` index terms. 101 | /// 102 | /// Read the 103 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/set/join) 104 | #[derive(Serialize, Debug, Clone)] 105 | pub struct Join<'a> { 106 | join: Expr<'a>, 107 | with: Expr<'a>, 108 | } 109 | 110 | impl<'a> Join<'a> { 111 | pub fn new(source: impl Into>, detail: impl Into>) -> Self { 112 | Self { 113 | join: source.into(), 114 | with: detail.into(), 115 | } 116 | } 117 | } 118 | 119 | /// The `Match` function finds the "search terms" provided to `Match` in the 120 | /// requested index. 121 | /// 122 | /// The `search_terms` must be identical to the terms in the index, including both 123 | /// the value of all terms and number of terms. If the index is configured with 124 | /// no terms, then the search_terms argument should be omitted. If the index is 125 | /// configured with multiple terms, then the "search terms" should be an array 126 | /// of values. 127 | /// 128 | /// When calling Match through paginate, the results are returned as a 129 | /// Collection of type Page. If no matching element is found an empty collection 130 | /// is returned. 131 | /// 132 | /// Read the 133 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/set/match) 134 | #[derive(Serialize, Debug, Clone)] 135 | pub struct Match<'a> { 136 | #[serde(rename = "match")] 137 | match_: Expr<'a>, 138 | #[serde(skip_serializing_if = "Option::is_none")] 139 | terms: Option>, 140 | } 141 | 142 | impl<'a> Match<'a> { 143 | pub fn new(match_: impl Into>) -> Self { 144 | Self { 145 | match_: match_.into(), 146 | terms: None, 147 | } 148 | } 149 | 150 | pub fn with_terms(mut self, terms: impl Into>) -> Self { 151 | self.terms = Some(terms.into()); 152 | self 153 | } 154 | } 155 | 156 | /// The `Union` function combines the results one or more `SetRef` objects. 157 | /// 158 | /// Read the 159 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/set/union) 160 | #[derive(Serialize, Debug, Clone)] 161 | pub struct Union<'a> { 162 | union: Array<'a>, 163 | } 164 | 165 | impl<'a> Union<'a> { 166 | /// Get the union that represents all elements in the `left` `SetRef` 167 | /// and all elements in the `right` `SetRef`. 168 | pub fn new(left: impl Into>, right: impl Into>) -> Self { 169 | Self { 170 | union: Array::from(vec![left.into(), right.into()]), 171 | } 172 | } 173 | 174 | /// Add a `SetRef` expression to be used in the `Union`. 175 | pub fn push(&mut self, e: impl Into>) -> &mut Self { 176 | self.union.push(e.into()); 177 | self 178 | } 179 | } 180 | 181 | impl<'a, A> From for Union<'a> 182 | where 183 | A: Into>, 184 | { 185 | fn from(a: A) -> Self { 186 | Self { union: a.into() } 187 | } 188 | } 189 | 190 | #[cfg(test)] 191 | mod tests { 192 | use crate::prelude::*; 193 | use serde_json::{self, json}; 194 | 195 | #[test] 196 | fn test_difference() { 197 | let fun = Difference::new( 198 | Match::new(Index::find("spells_by_element")).with_terms("fire"), 199 | Match::new(Index::find("spells_by_element")).with_terms("water"), 200 | ); 201 | 202 | let query = Query::from(fun); 203 | let serialized = serde_json::to_value(&query).unwrap(); 204 | 205 | let expected = json!({ 206 | "difference": [ 207 | {"match": {"index": "spells_by_element"}, "terms": "fire"}, 208 | {"match": {"index": "spells_by_element"}, "terms": "water"}, 209 | ] 210 | }); 211 | 212 | assert_eq!(expected, serialized); 213 | } 214 | 215 | #[test] 216 | fn test_distinct() { 217 | let fun = Distinct::new(Match::new(Index::find("spells_by_element"))); 218 | 219 | let query = Query::from(fun); 220 | let serialized = serde_json::to_value(&query).unwrap(); 221 | 222 | let expected = json!({ 223 | "distinct": {"match": {"index": "spells_by_element"}}, 224 | }); 225 | 226 | assert_eq!(expected, serialized); 227 | } 228 | 229 | #[test] 230 | fn test_intersection() { 231 | let fun = Intersection::new( 232 | Match::new(Index::find("spells_by_element")).with_terms("fire"), 233 | Match::new(Index::find("spells_by_element")).with_terms("water"), 234 | ); 235 | 236 | let query = Query::from(fun); 237 | let serialized = serde_json::to_value(&query).unwrap(); 238 | 239 | let expected = json!({ 240 | "intersection": [ 241 | {"match": {"index": "spells_by_element"}, "terms": "fire"}, 242 | {"match": {"index": "spells_by_element"}, "terms": "water"}, 243 | ] 244 | }); 245 | 246 | assert_eq!(expected, serialized); 247 | } 248 | 249 | #[test] 250 | fn test_join() { 251 | let mut owner = Ref::instance("wizard"); 252 | owner.set_class("characters"); 253 | 254 | let fun = Join::new( 255 | Match::new(Index::find("spellbooks_by_owner")).with_terms(owner), 256 | Index::find("spells_by_spellbook"), 257 | ); 258 | 259 | let query = Query::from(fun); 260 | let serialized = serde_json::to_value(&query).unwrap(); 261 | 262 | let expected = json!({ 263 | "join": { 264 | "match": {"index": "spellbooks_by_owner"}, 265 | "terms": { 266 | "@ref": { 267 | "class": { 268 | "@ref": { 269 | "class": { 270 | "@ref": { 271 | "id": "classes" 272 | } 273 | }, 274 | "id": "characters" 275 | } 276 | }, 277 | "id": "wizard" 278 | } 279 | }, 280 | }, 281 | "with": {"index": "spells_by_spellbook"}, 282 | }); 283 | 284 | assert_eq!(expected, serialized); 285 | } 286 | 287 | #[test] 288 | fn test_match() { 289 | let fun = Match::new(Index::find("spells_by_element")).with_terms("fire"); 290 | 291 | let query = Query::from(fun); 292 | let serialized = serde_json::to_value(&query).unwrap(); 293 | 294 | let expected = json!({ 295 | "match": {"index": "spells_by_element"}, "terms": "fire" 296 | }); 297 | 298 | assert_eq!(expected, serialized); 299 | } 300 | 301 | #[test] 302 | fn test_union() { 303 | let fun = Union::new( 304 | Match::new(Index::find("spells_by_element")).with_terms("fire"), 305 | Match::new(Index::find("spells_by_element")).with_terms("water"), 306 | ); 307 | 308 | let query = Query::from(fun); 309 | let serialized = serde_json::to_value(&query).unwrap(); 310 | 311 | let expected = json!({ 312 | "union": [ 313 | {"match": {"index": "spells_by_element"}, "terms": "fire"}, 314 | {"match": {"index": "spells_by_element"}, "terms": "water"}, 315 | ] 316 | }); 317 | 318 | assert_eq!(expected, serialized); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/query/write.rs: -------------------------------------------------------------------------------- 1 | //! Write functions 2 | use crate::{expr::Expr, query::Query}; 3 | use chrono::{DateTime, Utc}; 4 | 5 | mod create; 6 | mod create_class; 7 | mod create_database; 8 | mod create_function; 9 | mod create_index; 10 | mod create_key; 11 | mod insert; 12 | mod update; 13 | 14 | pub use create::*; 15 | pub use create_class::*; 16 | pub use create_database::*; 17 | pub use create_function::*; 18 | pub use create_index::*; 19 | pub use create_key::*; 20 | pub use insert::*; 21 | pub use update::*; 22 | 23 | query![Delete, Remove, Replace]; 24 | 25 | #[derive(Serialize, Debug, Clone, Copy)] 26 | pub enum Action { 27 | #[serde(rename = "create")] 28 | Create, 29 | #[serde(rename = "delete")] 30 | Delete, 31 | #[serde(rename = "update")] 32 | Update, 33 | } 34 | 35 | /// The delete function removes an object. Some of the common objects to delete 36 | /// are instances, classes, indexes and databases. 37 | /// 38 | /// Read the 39 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/delete) 40 | #[derive(Serialize, Debug, Clone)] 41 | pub struct Delete<'a> { 42 | delete: Expr<'a>, 43 | } 44 | 45 | impl<'a> Delete<'a> { 46 | pub fn new(reference: impl Into>) -> Self { 47 | Delete { 48 | delete: reference.into(), 49 | } 50 | } 51 | } 52 | 53 | /// The `Remove` function deletes an event from an instance’s history. 54 | /// 55 | /// The reference must refer to an instance of a user-defined class. 56 | /// 57 | /// Outstanding references result in an "invalid argument" error. 58 | /// 59 | /// Read the 60 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/remove) 61 | #[derive(Serialize, Debug, Clone)] 62 | pub struct Remove<'a> { 63 | remove: Expr<'a>, 64 | #[serde(rename = "ts")] 65 | timestamp: Expr<'a>, 66 | action: Action, 67 | } 68 | 69 | impl<'a> Remove<'a> { 70 | pub fn new(reference: impl Into>, timestamp: DateTime, action: Action) -> Self { 71 | Self { 72 | remove: reference.into(), 73 | timestamp: Expr::from(timestamp), 74 | action, 75 | } 76 | } 77 | } 78 | 79 | /// The `Replace` operation substitutes the user data pointed to by the reference 80 | /// with the data contained in the `param_object`. Values not specified in the 81 | /// `param_object` are removed. 82 | /// 83 | /// Read the 84 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/replace) 85 | #[derive(Serialize, Debug, Clone)] 86 | pub struct Replace<'a> { 87 | replace: Expr<'a>, 88 | params: Expr<'a>, 89 | } 90 | 91 | impl<'a> Replace<'a> { 92 | pub fn new(reference: impl Into>, params: impl Into>) -> Self { 93 | Self { 94 | replace: reference.into(), 95 | params: params.into(), 96 | } 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use crate::prelude::*; 103 | use chrono::{offset::TimeZone, Utc}; 104 | use serde_json::{self, json}; 105 | 106 | #[test] 107 | fn test_delete() { 108 | let delete = Delete::new(Ref::instance("musti")); 109 | let query = Query::from(delete); 110 | let serialized = serde_json::to_value(&query).unwrap(); 111 | 112 | let expected = json!({ 113 | "delete": { 114 | "@ref": { 115 | "id": "musti" 116 | } 117 | } 118 | }); 119 | 120 | assert_eq!(expected, serialized); 121 | } 122 | 123 | #[test] 124 | fn test_remove() { 125 | let fun = Remove::new( 126 | Ref::instance("naukio"), 127 | Utc.timestamp(60, 0), 128 | Action::Create, 129 | ); 130 | 131 | let query = Query::from(fun); 132 | let serialized = serde_json::to_value(&query).unwrap(); 133 | 134 | let expected = json!({ 135 | "remove": { 136 | "@ref": { 137 | "id": "naukio" 138 | } 139 | }, 140 | "ts": { 141 | "@ts": "1970-01-01T00:01:00Z" 142 | }, 143 | "action": "create", 144 | }); 145 | 146 | assert_eq!(expected, serialized); 147 | } 148 | 149 | #[test] 150 | fn test_replace() { 151 | let mut data = Object::default(); 152 | data.insert("pawpaw", "meowmeow"); 153 | 154 | let fun = Replace::new(Ref::instance("musti"), data); 155 | 156 | let query = Query::from(fun); 157 | let serialized = serde_json::to_value(&query).unwrap(); 158 | 159 | let expected = json!({ 160 | "replace": { 161 | "@ref": { 162 | "id": "musti" 163 | } 164 | }, 165 | "params": { 166 | "object": { 167 | "pawpaw": "meowmeow" 168 | } 169 | } 170 | }); 171 | 172 | assert_eq!(expected, serialized); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/query/write/create.rs: -------------------------------------------------------------------------------- 1 | use crate::{expr::Expr, query::Query}; 2 | 3 | query!(Create); 4 | 5 | /// The `Create` function adds a new instance to a class. 6 | /// 7 | /// The `class_ref` parameter indicates what class of instance should be 8 | /// created, while `params` contains the instance data and optional metadata. 9 | /// 10 | /// Read the 11 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/create) 12 | #[derive(Debug, Serialize, Clone)] 13 | pub struct Create<'a> { 14 | create: Expr<'a>, 15 | params: InstanceParams<'a>, 16 | } 17 | 18 | #[derive(Debug, Serialize, Clone)] 19 | #[doc(hidden)] 20 | pub struct InstanceData<'a> { 21 | data: Expr<'a>, 22 | } 23 | 24 | #[derive(Debug, Serialize, Clone)] 25 | struct InstanceParams<'a> { 26 | object: InstanceData<'a>, 27 | } 28 | 29 | impl<'a> Create<'a> { 30 | pub fn new(class_ref: impl Into>, data: impl Into>) -> Self { 31 | Self { 32 | create: class_ref.into(), 33 | params: InstanceParams::new(data), 34 | } 35 | } 36 | } 37 | 38 | impl<'a> InstanceParams<'a> { 39 | fn new(data: E) -> Self 40 | where 41 | E: Into>, 42 | { 43 | Self { 44 | object: InstanceData { data: data.into() }, 45 | } 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | use crate::{prelude::*, test_utils::*}; 53 | use chrono::{offset::TimeZone, NaiveDate, Utc}; 54 | use serde_json::{self, json}; 55 | 56 | #[test] 57 | fn test_create_expr() { 58 | let mut obj = Object::default(); 59 | obj.insert("test_field", "test_value"); 60 | 61 | let query = Query::from(Create::new(Ref::class("test"), obj)); 62 | let serialized = serde_json::to_value(&query).unwrap(); 63 | 64 | let expected = json!({ 65 | "params": { 66 | "object": { 67 | "data": { 68 | "object": { 69 | "test_field": "test_value" 70 | } 71 | } 72 | } 73 | }, 74 | "create": { 75 | "@ref": { 76 | "class": { 77 | "@ref": { 78 | "id": "classes" 79 | } 80 | }, 81 | "id": "test", 82 | } 83 | } 84 | }); 85 | 86 | assert_eq!(expected, serialized); 87 | } 88 | 89 | #[test] 90 | fn test_create_eval() { 91 | let mut obj = Object::default(); 92 | let nickname_vals = vec!["mustu", "muspus", "mustikka"]; 93 | 94 | obj.insert("name", "Musti"); 95 | obj.insert("id", 1); 96 | obj.insert("age", 7); 97 | obj.insert("byte_data", Bytes::from(vec![0x1, 0x2, 0x3])); 98 | obj.insert("nicknames", Array::from(nickname_vals.clone())); 99 | obj.insert("am_i_cute", true); 100 | obj.insert("created_at", Utc.timestamp(60, 0)); 101 | obj.insert("birthday", NaiveDate::from_ymd(2011, 7, 7)); 102 | 103 | with_class(|class_name| { 104 | let response = CLIENT 105 | .query(Create::new(Class::find(class_name), obj)) 106 | .unwrap(); 107 | 108 | let res = response.resource; 109 | 110 | assert_eq!(res["data"]["name"].as_str(), Some("Musti")); 111 | assert_eq!(res["data"]["id"].as_u64(), Some(1)); 112 | assert_eq!(res["data"]["age"].as_u64(), Some(7)); 113 | assert_eq!(res["data"]["am_i_cute"].as_bool(), Some(true)); 114 | 115 | assert_eq!( 116 | res["data"]["byte_data"].as_bytes(), 117 | Some(&Bytes::from(vec![0x1, 0x2, 0x3])) 118 | ); 119 | 120 | assert_eq!( 121 | res["data"]["created_at"].as_timestamp(), 122 | Some(Utc.timestamp(60, 0)) 123 | ); 124 | 125 | assert_eq!( 126 | res["data"]["birthday"].as_date(), 127 | Some(NaiveDate::from_ymd(2011, 7, 7)) 128 | ); 129 | 130 | assert_eq!(res["data"]["nicknames"][0].as_str(), Some("mustu")); 131 | assert_eq!(res["data"]["nicknames"][1].as_str(), Some("muspus")); 132 | assert_eq!(res["data"]["nicknames"][2].as_str(), Some("mustikka")); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/query/write/create_class.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{ClassPermission, Expr, Object}, 3 | query::Query, 4 | }; 5 | use std::borrow::Cow; 6 | 7 | boxed_query!(CreateClass); 8 | 9 | /// The `CreateClass` function is used to create a class which groups instance 10 | /// objects. 11 | /// 12 | /// Once the class has been created, it is possible to create instances 13 | /// in the class. You cannot create a class and insert instances into that class 14 | /// in the same transaction. 15 | /// 16 | /// Read the 17 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/createclass). 18 | #[derive(Debug, Serialize, Clone)] 19 | pub struct CreateClass<'a> { 20 | create_class: ClassParams<'a>, 21 | } 22 | 23 | impl<'a> CreateClass<'a> { 24 | pub fn new(params: ClassParams<'a>) -> Self { 25 | Self { 26 | create_class: params, 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug, Default, Serialize, Clone)] 32 | struct ClassParamsInternal<'a> { 33 | name: Cow<'a, str>, 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | data: Option>, 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | history_days: Option, 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | ttl_days: Option, 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | permissions: Option>, 42 | } 43 | 44 | #[derive(Debug, Default, Serialize, Clone)] 45 | pub struct ClassParams<'a> { 46 | object: ClassParamsInternal<'a>, 47 | } 48 | 49 | impl<'a> ClassParams<'a> { 50 | /// The name of a class. Classes cannot be named any of the following 51 | /// reserved words: `events`, `set`, `self`, `instances`, or `_.` 52 | pub fn new(name: S) -> Self 53 | where 54 | S: Into>, 55 | { 56 | Self { 57 | object: ClassParamsInternal { 58 | name: name.into(), 59 | ..Default::default() 60 | }, 61 | } 62 | } 63 | 64 | /// User-defined metadata for the class. It is provided for the 65 | /// developer to store information at the class level. 66 | pub fn data(&mut self, data: Object<'a>) -> &mut Self { 67 | self.object.data = Some(Expr::from(data)); 68 | self 69 | } 70 | 71 | /// The number of days instance history is retained for this class. Without 72 | /// setting the value retains this class' history forever. Not setting 73 | /// history_days retains this class’s history forever. 74 | pub fn history_days(&mut self, days: u64) -> &mut Self { 75 | self.object.history_days = Some(days); 76 | self 77 | } 78 | 79 | /// The number of days instances are retained for this class. Instances 80 | /// which have not been updated within the configured TTL duration are 81 | /// removed. Not setting the `ttl_days` retains instances forever. 82 | pub fn ttl_days(&mut self, days: u64) -> &mut Self { 83 | self.object.ttl_days = Some(days); 84 | self 85 | } 86 | 87 | /// Provides the ability to enable permissions at the class level. 88 | pub fn permissions(&mut self, permissions: ClassPermission<'a>) -> &mut Self { 89 | self.object.permissions = Some(permissions); 90 | self 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use crate::{prelude::*, test_utils::*}; 98 | use serde_json::{self, json}; 99 | 100 | #[test] 101 | fn test_create_class_expr() { 102 | let mut permission = ClassPermission::default(); 103 | permission.read(Level::public()); 104 | 105 | let mut params = ClassParams::new("test"); 106 | params.history_days(10); 107 | params.permissions(permission); 108 | 109 | let query = Query::from(CreateClass::new(params)); 110 | let serialized = serde_json::to_value(&query).unwrap(); 111 | 112 | let expected = json!({ 113 | "create_class": { 114 | "object": { 115 | "history_days": 10, 116 | "name": "test", 117 | "permissions": { "object": { "read": "public" } }, 118 | } 119 | } 120 | }); 121 | 122 | assert_eq!(expected, serialized); 123 | } 124 | 125 | #[test] 126 | fn test_create_class_eval() { 127 | let mut permission = ClassPermission::default(); 128 | permission.read(Level::public()); 129 | permission.write(Level::public()); 130 | 131 | let mut data = Object::default(); 132 | data.insert("meow", true); 133 | 134 | let class_name = gen_db_name(); 135 | 136 | let mut params = ClassParams::new(&class_name); 137 | params.history_days(10); 138 | params.ttl_days(3); 139 | params.permissions(permission); 140 | params.data(data); 141 | 142 | with_database(|_| { 143 | let response = CLIENT.query(CreateClass::new(params)).unwrap(); 144 | let res = response.resource; 145 | 146 | assert_eq!(res["history_days"].as_u64(), Some(10)); 147 | assert_eq!(res["ttl_days"].as_u64(), Some(3)); 148 | assert_eq!(res["name"].as_str(), Some(class_name.as_str())); 149 | assert_eq!(res["data"]["meow"].as_bool(), Some(true)); 150 | assert_eq!(res["permissions"]["read"].as_str(), Some("public")); 151 | }); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/query/write/create_database.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::Error, 3 | expr::{Expr, Object}, 4 | query::Query, 5 | }; 6 | use std::borrow::Cow; 7 | 8 | query!(CreateDatabase); 9 | 10 | /// The `CreateDatabase` function adds a new database to the cluster with the 11 | /// specified parameters. 12 | /// 13 | /// It requires an admin key for authentication. 14 | /// 15 | /// Read the 16 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/createdatabase) 17 | #[derive(Debug, Serialize, Clone)] 18 | pub struct CreateDatabase<'a> { 19 | create_database: DatabaseParams<'a>, 20 | } 21 | 22 | #[derive(Debug, Default, Serialize, Clone)] 23 | #[doc(hidden)] 24 | pub struct DatabaseParamsInternal<'a> { 25 | name: Cow<'a, str>, 26 | api_version: Cow<'a, str>, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | data: Option>, 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | priority: Option, 31 | } 32 | 33 | #[derive(Debug, Default, Serialize, Clone)] 34 | pub struct DatabaseParams<'a> { 35 | object: DatabaseParamsInternal<'a>, 36 | } 37 | 38 | impl<'a> CreateDatabase<'a> { 39 | pub fn new(params: DatabaseParams<'a>) -> Self { 40 | Self { 41 | create_database: params, 42 | } 43 | } 44 | } 45 | 46 | impl<'a> DatabaseParams<'a> { 47 | pub fn new(name: S) -> Self 48 | where 49 | S: Into>, 50 | { 51 | Self { 52 | object: DatabaseParamsInternal { 53 | name: name.into(), 54 | api_version: Cow::from("2.0"), 55 | ..Default::default() 56 | }, 57 | } 58 | } 59 | 60 | pub fn api_version(&mut self, version: impl Into>) -> &mut Self { 61 | self.object.api_version = version.into(); 62 | self 63 | } 64 | 65 | pub fn data(&mut self, data: Object<'a>) -> &mut Self { 66 | self.object.data = Some(Expr::from(data)); 67 | self 68 | } 69 | 70 | pub fn priority(&mut self, priority: u16) -> crate::Result<&mut Self> { 71 | if priority == 0 || priority > 500 { 72 | return Err(Error::RequestDataFailure( 73 | "Priority should be a number between 1 and 500", 74 | )); 75 | } 76 | 77 | self.object.priority = Some(priority); 78 | 79 | Ok(self) 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use crate::{prelude::*, test_utils::CLIENT}; 86 | use serde_json::{self, json}; 87 | use std::panic; 88 | 89 | #[test] 90 | fn test_create_database_expr() { 91 | let mut params = DatabaseParams::new("test"); 92 | params.priority(10).unwrap(); 93 | 94 | let query = Query::from(CreateDatabase::new(params)); 95 | let serialized = serde_json::to_value(&query).unwrap(); 96 | 97 | let expected = json!({ 98 | "create_database": { 99 | "object": { 100 | "name": "test", 101 | "api_version": "2.0", 102 | "priority": 10, 103 | } 104 | } 105 | }); 106 | 107 | assert_eq!(expected, serialized); 108 | } 109 | 110 | #[test] 111 | fn test_create_database_eval() { 112 | let mut data = Object::default(); 113 | data.insert("foo", "bar"); 114 | 115 | let db_name = "test"; 116 | let mut params = DatabaseParams::new(db_name); 117 | params.priority(10).unwrap(); 118 | params.data(data); 119 | 120 | let result = panic::catch_unwind(|| { 121 | let response = CLIENT.query(CreateDatabase::new(params)).unwrap(); 122 | let res = response.resource; 123 | 124 | assert_eq!(res["api_version"].as_str(), Some("2.0")); 125 | assert_eq!(res["name"].as_str(), Some(db_name)); 126 | assert_eq!(res["priority"].as_u64(), Some(10)); 127 | 128 | assert_eq!( 129 | res["ref"].as_reference().unwrap().path(), 130 | Ref::database(db_name).path() 131 | ); 132 | 133 | assert_eq!(res["data"]["foo"].as_str(), Some("bar")); 134 | 135 | assert!(res["ts"].is_number()); 136 | }); 137 | 138 | CLIENT.query(Delete::new(Ref::database(db_name))).unwrap(); 139 | 140 | result.unwrap(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/query/write/create_function.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{Expr, Object}, 3 | query::Query, 4 | }; 5 | use std::borrow::Cow; 6 | 7 | boxed_query!(CreateFunction); 8 | 9 | /// The `CreateFunction` operation adds a new user-defined function with the 10 | /// specified parameters. 11 | /// 12 | /// Read the 13 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/createfunction). 14 | #[derive(Debug, Serialize, Clone)] 15 | pub struct CreateFunction<'a> { 16 | create_function: FunctionParams<'a>, 17 | } 18 | 19 | impl<'a> CreateFunction<'a> { 20 | pub fn new(params: FunctionParams<'a>) -> Self { 21 | Self { 22 | create_function: params, 23 | } 24 | } 25 | } 26 | 27 | #[derive(Debug, Serialize, Clone)] 28 | struct FunctionParamsInternal<'a> { 29 | name: Cow<'a, str>, 30 | body: Expr<'a>, 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | data: Option>, 33 | } 34 | 35 | #[derive(Debug, Serialize, Clone)] 36 | pub struct FunctionParams<'a> { 37 | object: FunctionParamsInternal<'a>, 38 | } 39 | 40 | impl<'a> FunctionParams<'a> { 41 | pub fn new(name: S, body: E) -> Self 42 | where 43 | S: Into>, 44 | E: Into>, 45 | { 46 | Self { 47 | object: FunctionParamsInternal { 48 | name: name.into(), 49 | body: body.into().as_quoted(), 50 | data: None, 51 | }, 52 | } 53 | } 54 | 55 | /// User-defined metadata for the function. It is provided for the 56 | /// developer to store information at the function level. 57 | pub fn data(&mut self, data: Object<'a>) -> &mut Self { 58 | self.object.data = Some(Expr::from(data)); 59 | self 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use crate::prelude::*; 66 | use serde_json::{self, json}; 67 | 68 | #[test] 69 | fn test_create_function() { 70 | let params = FunctionParams::new( 71 | "double", 72 | Lambda::new( 73 | "x", 74 | Add::new(Array::from(vec![Var::new("x"), Var::new("x")])), 75 | ), 76 | ); 77 | 78 | let query = Query::from(CreateFunction::new(params)); 79 | let serialized = serde_json::to_value(&query).unwrap(); 80 | 81 | let expected = json!({ 82 | "create_function": { 83 | "object": { 84 | "body": { 85 | "@query": { 86 | "expr": { 87 | "add": [{"var": "x"}, {"var": "x"}] 88 | }, 89 | "lambda": "x" 90 | } 91 | }, 92 | "name": "double" 93 | } 94 | } 95 | }); 96 | 97 | assert_eq!(expected, serialized); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/query/write/create_index.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{Expr, IndexPermission, Object}, 3 | query::Query, 4 | }; 5 | use std::borrow::Cow; 6 | 7 | boxed_query!(CreateIndex); 8 | 9 | /// The `CreateIndex` function adds a new index to the cluster with the specified 10 | /// parameters. 11 | /// 12 | /// After the transaction containing the `CreateIndex` is completed, 13 | /// the index is immediately available for reads. (The index may not be used in 14 | /// the transaction it was created, and it may not be created in the same 15 | /// transaction as its source class(es).) The index may return incomplete 16 | /// results until it is fully built and marked as active. FaunaDB builds the 17 | /// index asynchronously by scanning over relevant instance objects of the 18 | /// source class(es). 19 | /// 20 | /// Read the 21 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/createindex) 22 | #[derive(Debug, Serialize, Clone)] 23 | pub struct CreateIndex<'a> { 24 | create_index: IndexParams<'a>, 25 | } 26 | 27 | impl<'a> CreateIndex<'a> { 28 | pub fn new(params: IndexParams<'a>) -> Self { 29 | Self { 30 | create_index: params, 31 | } 32 | } 33 | } 34 | 35 | #[derive(Debug, Serialize, Clone)] 36 | #[doc(hidden)] 37 | pub struct IndexField<'a>(Vec>); 38 | 39 | #[derive(Debug, Serialize, Clone)] 40 | #[doc(hidden)] 41 | pub struct IndexBinding<'a>(Cow<'a, str>); 42 | 43 | #[derive(Debug, Serialize, Clone)] 44 | #[doc(hidden)] 45 | pub enum TermObject<'a> { 46 | #[serde(rename = "field")] 47 | Field(IndexField<'a>), 48 | #[serde(rename = "binding")] 49 | Binding(IndexBinding<'a>), 50 | } 51 | 52 | /// Term objects describe the fields used to locate entries in the index. 53 | /// 54 | /// If multiple terms are provided, instances missing a value will emit a Null 55 | /// term in the index for that field. 56 | /// 57 | /// Read the 58 | /// [docs](https://docs.fauna.com/fauna/current/reference/indexconfig#term-objects) 59 | #[derive(Debug, Serialize, Clone)] 60 | pub struct Term<'a> { 61 | object: TermObject<'a>, 62 | } 63 | 64 | #[derive(Debug, Serialize, Clone)] 65 | #[doc(hidden)] 66 | pub struct ValueObject<'a> { 67 | #[serde(skip_serializing_if = "Option::is_none")] 68 | field: Option>, 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | binding: Option>, 71 | reverse: bool, 72 | } 73 | 74 | /// Value objects describe the data covered by the index, which are included in 75 | /// query results on the index and control ordering of entries having the same 76 | /// terms. 77 | /// 78 | /// By default, indexes cover only the refs of included instances. 79 | /// 80 | /// Read the 81 | /// [docs](https://docs.fauna.com/fauna/current/reference/indexconfig#value-objects) 82 | #[derive(Debug, Serialize, Clone)] 83 | pub struct IndexValue<'a> { 84 | object: ValueObject<'a>, 85 | } 86 | 87 | #[derive(Debug, Serialize, Clone)] 88 | #[doc(hidden)] 89 | pub struct IndexParamsInternal<'a> { 90 | name: Cow<'a, str>, 91 | source: Expr<'a>, 92 | active: bool, 93 | unique: bool, 94 | serialized: bool, 95 | #[serde(skip_serializing_if = "Option::is_none")] 96 | terms: Option>>, 97 | #[serde(skip_serializing_if = "Option::is_none")] 98 | values: Option>>, 99 | #[serde(skip_serializing_if = "Option::is_none")] 100 | partitions: Option, 101 | #[serde(skip_serializing_if = "Option::is_none")] 102 | permissions: Option>, 103 | #[serde(skip_serializing_if = "Option::is_none")] 104 | data: Option>, 105 | } 106 | 107 | #[derive(Debug, Serialize, Clone)] 108 | pub struct IndexParams<'a> { 109 | object: IndexParamsInternal<'a>, 110 | } 111 | 112 | impl<'a> Term<'a> { 113 | /// The path of the field within an instance to be indexed. 114 | pub fn field(path: Vec) -> Self 115 | where 116 | T: Into>, 117 | { 118 | let field = IndexField(path.into_iter().map(Into::into).collect()); 119 | 120 | Self { 121 | object: TermObject::Field(field), 122 | } 123 | } 124 | 125 | /// The name of a binding from a source object. 126 | pub fn binding(name: T) -> Self 127 | where 128 | T: Into>, 129 | { 130 | let binding = IndexBinding(name.into()); 131 | 132 | Self { 133 | object: TermObject::Binding(binding), 134 | } 135 | } 136 | } 137 | 138 | impl<'a> IndexValue<'a> { 139 | /// The path of the field within an instance to be indexed. 140 | pub fn field(path: Vec) -> Self 141 | where 142 | T: Into>, 143 | { 144 | let field = IndexField(path.into_iter().map(Into::into).collect()); 145 | 146 | Self { 147 | object: ValueObject { 148 | field: Some(field), 149 | binding: None, 150 | reverse: false, 151 | }, 152 | } 153 | } 154 | 155 | /// The name of a binding from a source object. 156 | pub fn binding(name: T) -> Self 157 | where 158 | T: Into>, 159 | { 160 | let binding = IndexBinding(name.into()); 161 | 162 | Self { 163 | object: ValueObject { 164 | field: None, 165 | binding: Some(binding), 166 | reverse: false, 167 | }, 168 | } 169 | } 170 | 171 | /// If set, the sort of the field's value is reversed. 172 | pub fn reverse(&mut self) -> &mut Self { 173 | self.object.reverse = true; 174 | self 175 | } 176 | } 177 | 178 | impl<'a> IndexParams<'a> { 179 | /// The name cannot be `events`, `sets`, `self`, `instances` or `_`. 180 | /// 181 | /// The source must evaluate to a class `Ref`. 182 | pub fn new(name: S, source: impl Into>) -> Self 183 | where 184 | S: Into>, 185 | { 186 | Self { 187 | object: IndexParamsInternal { 188 | name: name.into(), 189 | source: source.into(), 190 | active: false, 191 | unique: false, 192 | serialized: false, 193 | terms: None, 194 | values: None, 195 | partitions: None, 196 | permissions: None, 197 | data: None, 198 | }, 199 | } 200 | } 201 | 202 | /// If set, avoids building the index from relevant instances. 203 | pub fn active(&mut self) -> &mut Self { 204 | self.object.active = true; 205 | self 206 | } 207 | 208 | /// If set, maintains a unique constraint on combined `terms` and `values`. 209 | pub fn unique(&mut self) -> &mut Self { 210 | self.object.unique = true; 211 | self 212 | } 213 | 214 | /// If set, writes to the index are serialized with concurrent reads and 215 | /// writes. 216 | pub fn serialized(&mut self) -> &mut Self { 217 | self.object.serialized = true; 218 | self 219 | } 220 | 221 | /// An array of [Term objects](struct.Term.html) describing the fields to be 222 | /// indexed. 223 | pub fn terms(&mut self, terms: Vec>) -> &mut Self { 224 | self.object.terms = Some(terms); 225 | self 226 | } 227 | 228 | /// An array of [Value objects](struct.IndexValue.html) describing the fields to be 229 | /// covered. 230 | pub fn values(&mut self, values: Vec>) -> &mut Self { 231 | self.object.values = Some(values); 232 | self 233 | } 234 | 235 | /// The number of sub-partitions within each term. 236 | pub fn partitions(&mut self, partitions: u16) -> &mut Self { 237 | self.object.partitions = Some(partitions); 238 | self 239 | } 240 | 241 | /// Indicates who is allowed to read the index. 242 | pub fn permissions(&mut self, permissions: IndexPermission<'a>) -> &mut Self { 243 | self.object.permissions = Some(permissions); 244 | self 245 | } 246 | 247 | /// The user-defined metadata for the index. It is provided for the 248 | /// developer to store information at the index level. 249 | pub fn data(&mut self, data: Object<'a>) -> &mut Self { 250 | self.object.data = Some(Expr::from(data)); 251 | self 252 | } 253 | } 254 | 255 | #[cfg(test)] 256 | mod tests { 257 | use crate::prelude::*; 258 | use serde_json::{self, json}; 259 | 260 | #[test] 261 | fn test_create_index() { 262 | let mut permission = IndexPermission::default(); 263 | permission.read(Level::public()); 264 | 265 | let mut params = IndexParams::new("meows", Ref::class("cats")); 266 | params.permissions(permission); 267 | 268 | let age_term = Term::field(vec!["data", "age"]); 269 | let name_term = Term::binding("cats_name"); 270 | 271 | params.terms(vec![age_term, name_term]); 272 | 273 | let name_value = IndexValue::field(vec!["data", "name"]); 274 | 275 | let mut age_value = IndexValue::binding("cats_age"); 276 | age_value.reverse(); 277 | 278 | params.values(vec![age_value, name_value]); 279 | 280 | let query = Query::from(CreateIndex::new(params)); 281 | let serialized = serde_json::to_value(&query).unwrap(); 282 | 283 | let expected = json!({ 284 | "create_index": { 285 | "object": { 286 | "active": false, 287 | "name": "meows", 288 | "permissions": { 289 | "object": { 290 | "read": "public", 291 | } 292 | }, 293 | "serialized": false, 294 | "source": { 295 | "@ref": { 296 | "class": { 297 | "@ref": { 298 | "id": "classes", 299 | }, 300 | }, 301 | "id": "cats", 302 | }, 303 | }, 304 | "terms": [ 305 | { 306 | "object": { 307 | "field": ["data", "age"], 308 | } 309 | }, 310 | { 311 | "object": { 312 | "binding": "cats_name", 313 | } 314 | }, 315 | ], 316 | "unique": false, 317 | "values": [ 318 | { 319 | "object": { 320 | "binding": "cats_age", 321 | "reverse": true, 322 | } 323 | }, 324 | { 325 | "object": { 326 | "field": ["data", "name"], 327 | "reverse": false, 328 | } 329 | }, 330 | ] 331 | } 332 | } 333 | }); 334 | 335 | assert_eq!(expected, serialized); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/query/write/create_key.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{Expr, Object, Ref}, 3 | query::Query, 4 | }; 5 | 6 | boxed_query!(CreateKey); 7 | 8 | #[derive(Debug, Serialize, Clone, Copy)] 9 | pub enum Role { 10 | #[serde(rename = "admin")] 11 | Admin, 12 | #[serde(rename = "server")] 13 | Server, 14 | #[serde(rename = "server-readonly")] 15 | ServerReadOnly, 16 | #[serde(rename = "client")] 17 | Client, 18 | } 19 | 20 | /// `CreateKey` creates a new key to access a database with the specified 21 | /// `param_object`. It requires an admin key for authentication. 22 | /// 23 | /// Read the 24 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/createkey) 25 | #[derive(Debug, Serialize, Clone)] 26 | pub struct CreateKey<'a> { 27 | create_key: KeyParams<'a>, 28 | } 29 | 30 | impl<'a> CreateKey<'a> { 31 | pub fn new(params: KeyParams<'a>) -> Self { 32 | Self { create_key: params } 33 | } 34 | } 35 | 36 | #[derive(Debug, Serialize, Clone)] 37 | #[doc(hidden)] 38 | pub struct KeyParamsInternal<'a> { 39 | database: Expr<'a>, 40 | role: Role, 41 | priority: Expr<'a>, 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | data: Option>, 44 | } 45 | 46 | #[derive(Debug, Serialize, Clone)] 47 | pub struct KeyParams<'a> { 48 | object: KeyParamsInternal<'a>, 49 | } 50 | 51 | impl<'a> KeyParams<'a> { 52 | /// A new `param_object` with the required fields: 53 | /// 54 | /// * A reference to the database for which a key should be created. 55 | /// * The access role 56 | pub fn new(database: Ref<'a>, role: Role) -> Self { 57 | Self { 58 | object: KeyParamsInternal { 59 | database: Expr::from(database), 60 | role, 61 | priority: Expr::from(1), 62 | data: None, 63 | }, 64 | } 65 | } 66 | 67 | /// A relative weight between 1 and 500, inclusive, indicating how many 68 | /// resources this key will be allowed to utilize. Defaults to 1. A higher 69 | /// number means more resources. 70 | pub fn priority(&mut self, priority: u16) -> &mut Self { 71 | self.object.priority = Expr::from(priority); 72 | self 73 | } 74 | 75 | /// This is user-defined metadata for the key. It is provided for the 76 | /// developer to store information at the key level. 77 | pub fn data(&mut self, data: Object<'a>) -> &mut Self { 78 | self.object.data = Some(Expr::from(data)); 79 | self 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use crate::prelude::*; 86 | use serde_json::{self, json}; 87 | 88 | #[test] 89 | fn test_create_index() { 90 | let mut data = Object::default(); 91 | data.insert("foo", "bar"); 92 | 93 | let mut params = KeyParams::new(Ref::database("cats"), Role::Admin); 94 | params.priority(420); 95 | params.data(data); 96 | 97 | let query = Query::from(CreateKey::new(params)); 98 | let serialized = serde_json::to_value(&query).unwrap(); 99 | 100 | let expected = json!({ 101 | "create_key": { 102 | "object": { 103 | "database": { 104 | "@ref": { 105 | "class": { 106 | "@ref": { 107 | "id": "databases", 108 | }, 109 | }, 110 | "id": "cats", 111 | }, 112 | }, 113 | "role": "admin", 114 | "priority": 420, 115 | "data": { 116 | "object": { 117 | "foo": "bar" 118 | } 119 | } 120 | } 121 | } 122 | }); 123 | 124 | assert_eq!(expected, serialized); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/query/write/insert.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | expr::{Expr, Ref}, 3 | query::{write::Action, Query}, 4 | }; 5 | use chrono::{DateTime, Utc}; 6 | 7 | boxed_query!(Insert); 8 | 9 | /// The Insert function adds an event to an instance’s history at a specified 10 | /// timestamp. 11 | /// 12 | /// Read the 13 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/insert) 14 | #[derive(Serialize, Debug, Clone)] 15 | pub struct Insert<'a> { 16 | insert: Expr<'a>, 17 | #[serde(rename = "ts")] 18 | timestamp: Expr<'a>, 19 | action: Action, 20 | params: InsertParams<'a>, 21 | } 22 | 23 | #[derive(Serialize, Debug, Clone)] 24 | pub struct InsertParams<'a> { 25 | object: InsertObject<'a>, 26 | } 27 | 28 | #[derive(Serialize, Debug, Clone)] 29 | #[doc(hidden)] 30 | pub struct InsertObject<'a> { 31 | data: Expr<'a>, 32 | credentials: Expr<'a>, 33 | delegates: Expr<'a>, 34 | } 35 | 36 | impl<'a> Insert<'a> { 37 | pub fn new( 38 | reference: Ref<'a>, 39 | timestamp: DateTime, 40 | action: Action, 41 | params: InsertParams<'a>, 42 | ) -> Self { 43 | Insert { 44 | insert: Expr::from(reference), 45 | timestamp: Expr::from(timestamp), 46 | action, 47 | params, 48 | } 49 | } 50 | } 51 | 52 | impl<'a> InsertParams<'a> { 53 | pub fn new( 54 | data: impl Into>, 55 | credentials: impl Into>, 56 | delegates: impl Into>, 57 | ) -> Self { 58 | Self { 59 | object: InsertObject { 60 | data: data.into(), 61 | credentials: credentials.into(), 62 | delegates: delegates.into(), 63 | }, 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use crate::prelude::*; 71 | use chrono::{offset::TimeZone, Utc}; 72 | use serde_json::{self, json}; 73 | 74 | #[test] 75 | fn test_insert() { 76 | let mut data = Object::default(); 77 | data.insert("scratch", "moar"); 78 | 79 | let mut credentials = Object::default(); 80 | credentials.insert("push", "meowmeow"); 81 | 82 | let mut delegates = Object::default(); 83 | delegates.insert("pawpaw", "meow"); 84 | 85 | let params = InsertParams::new(data, credentials, delegates); 86 | 87 | let fun = Insert::new( 88 | Ref::instance("musti"), 89 | Utc.timestamp(60, 0), 90 | Action::Update, 91 | params, 92 | ); 93 | 94 | let query = Query::from(fun); 95 | let serialized = serde_json::to_value(&query).unwrap(); 96 | 97 | let expected = json!({ 98 | "params": { 99 | "object": { 100 | "data": { 101 | "object": { 102 | "scratch": "moar" 103 | } 104 | }, 105 | "credentials": { 106 | "object": { 107 | "push": "meowmeow" 108 | } 109 | }, 110 | "delegates": { 111 | "object": { 112 | "pawpaw": "meow" 113 | } 114 | }, 115 | } 116 | }, 117 | "ts": {"@ts": "1970-01-01T00:01:00Z"}, 118 | "action": "update", 119 | "insert": { 120 | "@ref": { 121 | "id": "musti" 122 | } 123 | } 124 | }); 125 | 126 | assert_eq!(expected, serialized); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/query/write/update.rs: -------------------------------------------------------------------------------- 1 | use crate::{expr::Expr, query::Query}; 2 | 3 | query!(Update); 4 | 5 | /// The `Update` operation only modifies the specified fields in the instances 6 | /// pointed to by `ref`. 7 | /// 8 | /// Updates are partial, and only modify values that are specified in the 9 | /// param_object. Changes to scalar values and arrays are entirely replaced by 10 | /// the new data. Modifications to objects are merged. Setting a value to `null` 11 | /// completely removes the value. Fields in the instance not specified in the 12 | /// `param_object` are not modified. 13 | /// 14 | /// Read the 15 | /// [docs](https://docs.fauna.com/fauna/current/reference/queryapi/write/update) 16 | #[derive(Serialize, Debug, Clone)] 17 | pub struct Update<'a> { 18 | update: Expr<'a>, 19 | params: UpdateParams<'a>, 20 | } 21 | 22 | #[derive(Serialize, Debug, Clone, Default)] 23 | pub struct UpdateParams<'a> { 24 | object: UpdateObject<'a>, 25 | } 26 | 27 | #[derive(Serialize, Debug, Clone, Default)] 28 | #[doc(hidden)] 29 | pub struct UpdateObject<'a> { 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | data: Option>, 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | credentials: Option>, 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | delegates: Option>, 36 | } 37 | 38 | impl<'a> Update<'a> { 39 | pub fn new(reference: impl Into>, params: UpdateParams<'a>) -> Self { 40 | Update { 41 | update: reference.into(), 42 | params, 43 | } 44 | } 45 | } 46 | 47 | impl<'a> UpdateParams<'a> { 48 | pub fn new() -> Self { 49 | Self::default() 50 | } 51 | 52 | pub fn data(&mut self, data: impl Into>) -> &mut Self { 53 | self.object.data = Some(data.into()); 54 | self 55 | } 56 | 57 | pub fn credentials(&mut self, credentials: impl Into>) -> &mut Self { 58 | self.object.credentials = Some(credentials.into()); 59 | self 60 | } 61 | 62 | pub fn delegates(&mut self, delegates: impl Into>) -> &mut Self { 63 | self.object.delegates = Some(delegates.into()); 64 | self 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use crate::prelude::*; 71 | use serde_json::{self, json}; 72 | 73 | #[test] 74 | fn test_insert() { 75 | let mut data = Object::default(); 76 | data.insert("scratch", "moar"); 77 | 78 | let mut credentials = Object::default(); 79 | credentials.insert("push", "meowmeow"); 80 | 81 | let mut delegates = Object::default(); 82 | delegates.insert("pawpaw", "meow"); 83 | 84 | let mut params = UpdateParams::new(); 85 | params.data(data); 86 | params.credentials(credentials); 87 | params.delegates(delegates); 88 | 89 | let fun = Update::new(Ref::instance("musti"), params); 90 | 91 | let query = Query::from(fun); 92 | let serialized = serde_json::to_value(&query).unwrap(); 93 | 94 | let expected = json!({ 95 | "params": { 96 | "object": { 97 | "data": { 98 | "object": { 99 | "scratch": "moar" 100 | } 101 | }, 102 | "credentials": { 103 | "object": { 104 | "push": "meowmeow" 105 | } 106 | }, 107 | "delegates": { 108 | "object": { 109 | "pawpaw": "meow" 110 | } 111 | }, 112 | } 113 | }, 114 | "update": { 115 | "@ref": { 116 | "id": "musti" 117 | } 118 | } 119 | }); 120 | 121 | assert_eq!(expected, serialized); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | //! Serde (de-)serializer functions for Fauna types. 2 | pub mod base64_bytes; 3 | -------------------------------------------------------------------------------- /src/serde/base64_bytes.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::Bytes; 2 | use serde::{de, ser}; 3 | use std::fmt; 4 | 5 | pub fn serialize<'a, S>(data: &Bytes<'a>, serializer: S) -> Result 6 | where 7 | S: ser::Serializer, 8 | { 9 | serializer.serialize_str(&base64::encode(&data.0)) 10 | } 11 | 12 | pub fn deserialize<'a, 'de, D>(d: D) -> Result, D::Error> 13 | where 14 | D: de::Deserializer<'de>, 15 | { 16 | Ok(d.deserialize_str(Base64BytesVisitor)?) 17 | } 18 | 19 | struct Base64BytesVisitor; 20 | 21 | impl<'de> de::Visitor<'de> for Base64BytesVisitor { 22 | type Value = Bytes<'static>; 23 | 24 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 25 | formatter.write_str("base64-encoded string of bytes") 26 | } 27 | 28 | fn visit_str(self, value: &str) -> Result 29 | where 30 | E: de::Error, 31 | { 32 | base64::decode(value) 33 | .map_err(|err| de::Error::custom(err.to_string())) 34 | .map(|bytes| Bytes::from(bytes.to_vec())) 35 | } 36 | 37 | fn visit_string(self, value: String) -> Result 38 | where 39 | E: de::Error, 40 | { 41 | base64::decode(value.as_str()) 42 | .map_err(|err| de::Error::custom(err.to_string())) 43 | .map(|bytes| Bytes::from(bytes.to_vec())) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use lazy_static::lazy_static; 3 | use rand::distributions::Alphanumeric; 4 | use rand::Rng; 5 | use std::panic; 6 | 7 | lazy_static! { 8 | pub static ref CLIENT: SyncClient = { 9 | let mut builder = Client::builder("secret"); 10 | builder.uri("http://localhost:8443"); 11 | 12 | builder.build_sync().unwrap() 13 | }; 14 | } 15 | 16 | pub fn gen_db_name() -> String { 17 | rand::thread_rng() 18 | .sample_iter(&Alphanumeric) 19 | .take(10) 20 | .collect() 21 | } 22 | 23 | pub fn with_database(f: F) 24 | where 25 | F: FnOnce(&str) -> () + panic::UnwindSafe, 26 | { 27 | let db_name = gen_db_name(); 28 | let params = DatabaseParams::new(&db_name); 29 | 30 | trace!("Creating a test database {}", &db_name); 31 | CLIENT.query(CreateDatabase::new(params)).unwrap(); 32 | 33 | let result = panic::catch_unwind(|| f(db_name.as_ref())); 34 | 35 | trace!("Deleting the test database {}", &db_name); 36 | CLIENT.query(Delete::new(Ref::database(&db_name))).unwrap(); 37 | 38 | result.unwrap(); 39 | } 40 | 41 | pub fn with_class(f: F) 42 | where 43 | F: FnOnce(&str) -> () + panic::UnwindSafe, 44 | { 45 | let mut permission = ClassPermission::default(); 46 | permission.read(Level::public()); 47 | permission.write(Level::public()); 48 | 49 | let mut data = Object::default(); 50 | data.insert("meow", true); 51 | 52 | let class_name = gen_db_name(); 53 | 54 | let mut params = ClassParams::new(&class_name); 55 | params.history_days(10); 56 | params.ttl_days(3); 57 | params.permissions(permission); 58 | params.data(data); 59 | 60 | with_database(|_| { 61 | trace!("Creating a test class {}", &class_name); 62 | CLIENT.query(CreateClass::new(params)).unwrap(); 63 | 64 | f(class_name.as_str()); 65 | 66 | trace!("Creating the test class {}", &class_name); 67 | CLIENT.query(Delete::new(Ref::class(&class_name))).unwrap(); 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Todo 2 | 3 | * DONE Functions 4 | ** DONE Lambda 5 | ** DONE At 6 | ** DONE Call 7 | ** DONE Append 8 | ** DONE Drop 9 | ** DONE Filter 10 | ** DONE Foreach 11 | ** DONE IsEmpty 12 | ** DONE IsNonEmpty 13 | ** DONE Prepend 14 | ** DONE Take 15 | ** DONE ToDate 16 | ** DONE ToNumber 17 | ** DONE ToString 18 | ** DONE ToTime 19 | ** DONE Logical (And, Contains ... (10 functions)) 20 | ** DONE Math (Abs, Acos, Add, Asin ... (34 functions)) 21 | ** DONE Abort 22 | ** DONE Class 23 | ** DONE Classes 24 | ** DONE Database 25 | ** DONE Databases 26 | ** DONE Function 27 | ** DONE Functions 28 | ** DONE Index 29 | ** DONE Indexes 30 | ** DONE NewId 31 | ** DONE KeyFromSecret 32 | ** DONE Paginate 33 | ** DONE Select 34 | ** DONE SelectAll 35 | ** DONE Difference 36 | ** DONE Distinct 37 | ** DONE Intersection 38 | ** DONE Join 39 | ** DONE Match 40 | ** DONE Union 41 | ** DONE String (CaseFold, Concat, FindStr ... (16 functions)) 42 | ** DONE Date 43 | ** DONE Epoch 44 | ** DONE Time 45 | ** DONE CreateFunction 46 | ** DONE CreateKey 47 | ** DONE Insert 48 | ** DONE Remove 49 | ** DONE Replace 50 | ** DONE Update 51 | ** DONE HasIdentity 52 | ** DONE Identify 53 | ** DONE Identity 54 | ** DONE Login 55 | ** DONE Logout 56 | * TODO Integration tests with dockerized Fauna 57 | * TODO Massage the documentation 58 | --------------------------------------------------------------------------------