├── .gitignore ├── oxidizer ├── .gitignore ├── src │ ├── migrations │ │ ├── mod.rs │ │ └── V0001__entity.rs │ ├── db │ │ ├── mod.rs │ │ ├── error.rs │ │ ├── test_utils.rs │ │ ├── tests_db.rs │ │ ├── db.rs │ │ └── connections.rs │ ├── entity.rs │ ├── migration.rs │ ├── lib.rs │ └── tests_macro.rs └── Cargo.toml ├── .devcontainer ├── dev-pki │ ├── .gitignore │ ├── generate-pki.sh │ ├── openssl-server.cnf │ └── openssl-ca.cnf ├── init.sh ├── psql-tls.dockerfile ├── docker-compose-ci.yml ├── devcontainer.json ├── docker-compose.yml └── docker-compose-tls.yml ├── Cargo.toml ├── .github ├── workflows │ ├── fmt.yml │ ├── clippy.yml │ ├── security_audit.yml │ └── test.yml └── dependabot.yml ├── .mergify.yml ├── oxidizer-tests ├── Cargo.toml └── src │ └── main.rs ├── oxidizer-entity-macro ├── src │ ├── lib.rs │ ├── attrs.rs │ ├── sql_builder │ │ ├── mod.rs │ │ └── postgres.rs │ ├── field_extras.rs │ ├── props.rs │ ├── utils.rs │ └── entity_builder.rs └── Cargo.toml ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | -------------------------------------------------------------------------------- /oxidizer/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.devcontainer/dev-pki/.gitignore: -------------------------------------------------------------------------------- 1 | *.cert 2 | *.key 3 | *.pem 4 | *.txt* 5 | *.csr 6 | -------------------------------------------------------------------------------- /oxidizer/src/migrations/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::migration::*; 2 | 3 | include_migration_mods!(); 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "oxidizer", 5 | "oxidizer-entity-macro", 6 | "oxidizer-tests" 7 | ] -------------------------------------------------------------------------------- /oxidizer/src/db/mod.rs: -------------------------------------------------------------------------------- 1 | mod connections; 2 | pub mod db; 3 | pub use db::DB; 4 | pub mod error; 5 | pub use error::*; 6 | pub mod test_utils; 7 | 8 | #[cfg(test)] 9 | mod tests_db; 10 | -------------------------------------------------------------------------------- /oxidizer/src/migrations/V0001__entity.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::create_migration_module; 3 | use crate::entity::IEntity; 4 | 5 | use crate::tests_macro::TestEntity; 6 | 7 | create_migration_module!(TestEntity); -------------------------------------------------------------------------------- /.devcontainer/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | rustup component add clippy 7 | rustup component add rustfmt 8 | cargo install cargo-expand 9 | 10 | # Cargo expand requires nightly toolchain 11 | rustup toolchain install nightly -------------------------------------------------------------------------------- /.devcontainer/psql-tls.dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:13 2 | 3 | COPY --chown=postgres:postgres dev-pki/server.key /var/lib/postgresql/server.key 4 | COPY --chown=postgres:postgres dev-pki/server.cert /var/lib/postgresql/server.cert 5 | RUN chmod 600 /var/lib/postgresql/server.key 6 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | name: rustfmt check 9 | jobs: 10 | fmt_check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - run: rustup component add rustfmt 15 | - run: cargo fmt -- --check -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatic merge dependabot prs 3 | conditions: 4 | - author~=^dependabot(|-preview)\[bot\]$ 5 | - status-success=clippy_check 6 | - status-success=security_audit 7 | - status-success=fmt_check 8 | - status-success=test 9 | - status-success=clippy 10 | actions: 11 | merge: 12 | method: merge 13 | 14 | -------------------------------------------------------------------------------- /oxidizer-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxidizer-tests" 3 | version = "0.2.1" 4 | authors = ["Gustavo Sampaio "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "0.2", features = ["full"] } 11 | oxidizer = { version = "0.2.1", path = "../oxidizer" } -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | name: Clippy check 9 | jobs: 10 | clippy_check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - run: rustup component add clippy 15 | - uses: actions-rs/clippy-check@v1 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | args: --all-features -------------------------------------------------------------------------------- /.github/workflows/security_audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | push: 4 | paths: 5 | - '**/Cargo.toml' 6 | - '**/Cargo.lock' 7 | branches: 8 | - main 9 | schedule: 10 | - cron: '0 0 * * *' 11 | pull_request: 12 | branches: 13 | - main 14 | jobs: 15 | security_audit: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v1 19 | - uses: actions-rs/audit-check@v1 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | mod attrs; 4 | mod entity_builder; 5 | mod field_extras; 6 | mod props; 7 | mod sql_builder; 8 | mod utils; 9 | 10 | /// Entity derive macro 11 | #[proc_macro_derive( 12 | Entity, 13 | attributes( 14 | primary_key, 15 | indexed, 16 | relation, 17 | entity, 18 | index, 19 | has_many, 20 | field_ignore, 21 | custom_type, 22 | increments, 23 | ) 24 | )] 25 | pub fn entity_macro(item: TokenStream) -> TokenStream { 26 | entity_builder::EntityBuilder::new().build(item) 27 | } 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "07:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: tokio-postgres 11 | versions: 12 | - 0.7.0 13 | - 0.7.1 14 | - dependency-name: rustls 15 | versions: 16 | - 0.19.0 17 | - dependency-name: proc-macro2 18 | versions: 19 | - 1.0.26 20 | - dependency-name: mobc 21 | versions: 22 | - 0.7.0 23 | - 0.7.1 24 | - dependency-name: syn 25 | versions: 26 | - 1.0.62 27 | - dependency-name: tokio-postgres-rustls 28 | versions: 29 | - 0.7.0 30 | -------------------------------------------------------------------------------- /oxidizer/src/db/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | #[cfg(feature = "tls-openssl")] 4 | OpensslError(openssl::error::ErrorStack), 5 | #[cfg(feature = "tls-rustls")] 6 | RustlsError(String), 7 | 8 | PostgresError(tokio_postgres::Error), 9 | MobcError(mobc::Error), 10 | RefineryError(refinery::Error), 11 | DoesNotExist, 12 | ReferencedModelIsNotInDB, 13 | Other(String), 14 | } 15 | 16 | pub type DBResult = std::result::Result; 17 | 18 | impl std::convert::From for Error 19 | where 20 | R: std::fmt::Display, 21 | { 22 | fn from(v: R) -> Self { 23 | Error::Other(v.to_string()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /oxidizer/src/db/test_utils.rs: -------------------------------------------------------------------------------- 1 | use super::super::db::*; 2 | 3 | pub async fn create_test_db(name: &str) -> DB { 4 | let uri = "postgres://postgres:alkje2lkaj2e@db/postgres"; 5 | let db = DB::connect(&uri, 50, None).await.unwrap(); 6 | 7 | let query_str = format!("DROP DATABASE IF EXISTS db_test_{}", name.to_lowercase()); 8 | db.execute(&query_str, &[]).await.unwrap(); 9 | let query_str = format!("CREATE DATABASE db_test_{}", name.to_lowercase()); 10 | db.execute(&query_str, &[]).await.unwrap(); 11 | 12 | drop(db); 13 | 14 | let uri = format!("postgres://postgres:alkje2lkaj2e@db/db_test_{}", name); 15 | let db = DB::connect(&uri, 50, None).await.unwrap(); 16 | 17 | db 18 | } 19 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | version: '3' 7 | services: 8 | oxidizer: 9 | volumes: 10 | # Update this to wherever you want VS Code to mount the folder of your project 11 | - ..:/workspace:cached 12 | - ~/cargo_registry:/usr/local/cargo/registry 13 | - ~/cargo_index:/usr/local/cargo/git -------------------------------------------------------------------------------- /.devcontainer/dev-pki/generate-pki.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | # some openssl tooling 6 | touch index.txt 7 | echo '01' > serial.txt 8 | 9 | # create CA certificate 10 | openssl req -x509 -batch -config openssl-ca.cnf -newkey rsa:2048 -sha256 -nodes -out ca.cert -outform PEM 11 | 12 | # create server key & signing request 13 | openssl req -batch -config openssl-server.cnf -newkey rsa:2048 -sha256 -nodes -out server.csr -outform PEM 14 | 15 | # sign the signing request with the CA key 16 | openssl ca -batch -notext -config openssl-ca.cnf -policy signing_policy -extensions signing_req -out server_tmp.cert -infiles server.csr 17 | 18 | cat server_tmp.cert > server.cert 19 | cat ca.cert >> server.cert 20 | rm server_tmp.cert 01.pem 21 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxidizer-entity-macro" 3 | version = "0.2.2" 4 | authors = ["Gustavo Sampaio ", "davysson ", "Vandre Leal "] 5 | edition = "2018" 6 | keywords = ["orm", "tokio-postgres", "refinery", "postgres", "sql"] 7 | categories = ["database"] 8 | repository = "https://github.com/oxidizer-rs/oxidizer" 9 | description = "Oxidizer helps you reduce the boiler plate of writing entities, tables & migrations when using tokio-postgres and refinery." 10 | license = "MIT" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | quote = "1.0.9" 19 | syn = { version = "1.0.71", features = ["full", "extra-traits"] } 20 | proc-macro2 = "1.0.24" 21 | darling = "0.12.4" 22 | async-trait = "0.1.50" 23 | Inflector = "0.11.4" 24 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/attrs.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | 3 | #[derive(Debug, FromMeta)] 4 | pub struct PrimaryKeyAttr { 5 | #[darling(default)] 6 | pub increments: Option, 7 | } 8 | 9 | #[derive(Debug, FromMeta)] 10 | pub struct RelationAttr { 11 | pub model: String, 12 | pub key: String, 13 | } 14 | 15 | #[derive(Debug, FromMeta, Clone)] 16 | pub struct IndexAttr { 17 | pub name: String, 18 | pub columns: String, 19 | #[darling(default)] 20 | pub unique: bool, 21 | } 22 | 23 | #[derive(Debug, FromMeta, Clone)] 24 | pub struct EntityAttr { 25 | pub table_name: Option, 26 | } 27 | 28 | #[derive(Debug, FromMeta, Clone)] 29 | pub struct HasManyAttr { 30 | pub model: String, 31 | pub field: String, 32 | #[darling(default)] 33 | pub through: Option, 34 | } 35 | 36 | #[derive(Debug, FromMeta)] 37 | pub struct CustomTypeAttr { 38 | pub ty: String, 39 | } 40 | -------------------------------------------------------------------------------- /oxidizer/src/entity.rs: -------------------------------------------------------------------------------- 1 | use tokio_postgres::Row; 2 | 3 | use super::async_trait; 4 | use super::db::{DBResult, DB}; 5 | use super::db_types::ToSql; 6 | use super::migration::Migration; 7 | 8 | /// Trait implemented by all derived Entitities 9 | #[async_trait] 10 | pub trait IEntity: Sized { 11 | async fn save(&mut self, db: &DB) -> DBResult; 12 | async fn delete(&mut self, db: &DB) -> DBResult; 13 | 14 | fn is_synced_with_db(&self) -> bool; 15 | 16 | fn from_row(row: &Row) -> DBResult; 17 | fn create_migration() -> DBResult; 18 | fn get_table_name() -> String; 19 | 20 | async fn find( 21 | db: &DB, 22 | query: &str, 23 | params: &'_ [&'_ (dyn ToSql + Sync)], 24 | ) -> DBResult>; 25 | async fn first( 26 | db: &DB, 27 | query: &str, 28 | params: &'_ [&'_ (dyn ToSql + Sync)], 29 | ) -> DBResult>; 30 | } 31 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/sql_builder/mod.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use inflector::cases::snakecase::to_snake_case; 3 | use proc_macro::TokenStream; 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::{format_ident, quote, quote_spanned}; 6 | use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Type}; 7 | 8 | use super::attrs::HasManyAttr; 9 | use super::attrs::{EntityAttr, IndexAttr, RelationAttr}; 10 | use super::field_extras::*; 11 | use super::props::*; 12 | 13 | mod postgres; 14 | 15 | pub trait Builder { 16 | fn new() -> Self; 17 | 18 | fn build_save_query(props: &Props) -> TokenStream2; 19 | 20 | fn build_find_query(props: &Props) -> TokenStream2; 21 | 22 | fn build_first_query(props: &Props) -> TokenStream2; 23 | 24 | fn build_delete_query(props: &Props) -> TokenStream2; 25 | 26 | fn build_relation_get_query(props: &Props, relation: &RelationAttr) -> TokenStream2; 27 | 28 | fn build_relation_has_many_get_condition(props: &Props, attr: &HasManyAttr) -> TokenStream2; 29 | } 30 | 31 | pub type DefaultBuilder = postgres::PostgresBuilder; 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 oxidizer-rs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /oxidizer/src/db/tests_db.rs: -------------------------------------------------------------------------------- 1 | use chrono; 2 | 3 | #[tokio::test] 4 | async fn test_db_raw_query() { 5 | let db = super::test_utils::create_test_db("test_db_raw_query").await; 6 | 7 | let query = " 8 | CREATE TABLE films ( 9 | code char(5) CONSTRAINT firstkey PRIMARY KEY, 10 | title varchar(40) NOT NULL, 11 | date_prod date, 12 | kind varchar(10), 13 | nn integer 14 | ); 15 | "; 16 | db.execute(query, &[]).await.unwrap(); 17 | 18 | let query = " 19 | insert into films (code, title, date_prod, kind, nn) values ($1, $2, $3, $4, $5) 20 | "; 21 | let rows_changed = db 22 | .execute( 23 | query, 24 | &[ 25 | &"abcde", 26 | &"film title", 27 | &chrono::NaiveDate::from_ymd(2020, 8, 30), 28 | &"action", 29 | &(2 as i32), 30 | ], 31 | ) 32 | .await 33 | .unwrap(); 34 | assert_eq!(1, rows_changed); 35 | 36 | let query = "select * from films where nn = $1"; 37 | let row = db.query(query, &[&(2 as i32)]).await.unwrap(); 38 | assert_eq!(1, row.len()); 39 | assert_eq!("abcde", row[0].get::<&str, &str>("code")); 40 | } 41 | -------------------------------------------------------------------------------- /.devcontainer/dev-pki/openssl-server.cnf: -------------------------------------------------------------------------------- 1 | HOME = . 2 | RANDFILE = $ENV::HOME/.rnd 3 | 4 | #################################################################### 5 | [ req ] 6 | default_bits = 2048 7 | default_keyfile = server.key 8 | distinguished_name = server_distinguished_name 9 | req_extensions = server_req_extensions 10 | string_mask = utf8only 11 | 12 | #################################################################### 13 | [ server_distinguished_name ] 14 | organizationName = Organization Name (eg, company) 15 | organizationName_default = DEVELOPMENT 16 | 17 | organizationalUnitName = Organizational Unit (eg, division) 18 | organizationalUnitName_default = DEVELOPMENT 19 | 20 | commonName = Common Name (e.g. server FQDN or YOUR name) 21 | commonName_default = DEV Certificate 22 | 23 | emailAddress = Email Address 24 | emailAddress_default = test@example.com 25 | 26 | #################################################################### 27 | [ server_req_extensions ] 28 | 29 | subjectKeyIdentifier = hash 30 | basicConstraints = CA:FALSE 31 | keyUsage = digitalSignature, keyEncipherment 32 | subjectAltName = @alternate_names 33 | nsComment = "OpenSSL Generated Certificate" 34 | 35 | #################################################################### 36 | [ alternate_names ] 37 | 38 | DNS.1 = localhost 39 | DNS.2 = db 40 | -------------------------------------------------------------------------------- /oxidizer-tests/src/main.rs: -------------------------------------------------------------------------------- 1 | use oxidizer::*; 2 | 3 | // mod tmp; 4 | 5 | #[derive(Entity, Default)] 6 | pub struct TestCustomPrimaryKey { 7 | #[primary_key()] 8 | name: String, 9 | 10 | email: String, 11 | } 12 | 13 | #[derive(Entity, Default)] 14 | pub struct TestCustomPrimaryKey2 { 15 | #[primary_key(increments)] 16 | id: i32, 17 | 18 | email: String, 19 | } 20 | 21 | fn main() {} 22 | 23 | #[cfg(test)] 24 | mod test { 25 | use super::*; 26 | 27 | // #[tokio::test] 28 | // async fn test_abc() { 29 | // let mut abc = ABC::default(); 30 | // }| 31 | 32 | /// 33 | /// Integration test for postgres TLS support (manual invocation only) 34 | /// 35 | /// Requires: 36 | /// - run of ./generate-pki.sh in ./.devcontainer/dev-pki 37 | /// - start of ./.devcontainer/docker-compose-tls.yml db 38 | /// 39 | //#[tokio::test] 40 | async fn test_tls() { 41 | let db = DB::connect( 42 | "postgres://postgres:alkje2lkaj2e@localhost:5432/postgres", 43 | 50, 44 | Some("../.devcontainer/dev-pki/ca.cert"), 45 | ) 46 | .await 47 | .unwrap(); 48 | 49 | db.migrate_tables(&[TestCustomPrimaryKey::create_migration().unwrap()]) 50 | .await 51 | .unwrap(); 52 | 53 | let mut entity = TestCustomPrimaryKey::default(); 54 | let creating = entity.save(&db).await.unwrap(); 55 | assert_eq!(creating, true); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /oxidizer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "oxidizer" 4 | version = "0.2.2" 5 | authors = ["Gustavo Sampaio ", "davysson ", "Vandre Leal "] 6 | edition = "2018" 7 | keywords = ["orm", "tokio-postgres", "refinery", "postgres", "sql"] 8 | categories = ["database"] 9 | repository = "https://github.com/oxidizer-rs/oxidizer" 10 | description = "Oxidizer helps you reduce the boiler plate of writing entities, tables & migrations when using tokio-postgres and refinery." 11 | license = "MIT" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | default = ["tls-openssl"] 17 | tls-openssl = ["openssl", "postgres-openssl"] 18 | tls-rustls = ["rustls", "tokio-postgres-rustls"] 19 | 20 | [dependencies] 21 | chrono = "0.4.19" 22 | async-trait = "0.1.50" 23 | tokio = { version = "0.2", features = ["full"] } 24 | tokio-postgres = { version= "0.5.4", features = ["with-chrono-0_4"]} 25 | mobc = "0.5" 26 | barrel = { version = "0.6.5", features = ["pg"] } 27 | refinery = { version = "0.4.0", features = ["tokio-postgres"]} 28 | cfg-if = "1.0.0" 29 | 30 | openssl = { version = "0.10", features = ["vendored"] , optional = true} 31 | postgres-openssl = { version = "0.3.0", optional = true} 32 | 33 | rustls = { version = "0.18.0", optional = true} 34 | tokio-postgres-rustls = { version = "0.5.0", optional = true} 35 | 36 | oxidizer-entity-macro = { version = "0.2.1", path = "../oxidizer-entity-macro" } 37 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-in-docker-compose 3 | // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. 4 | { 5 | "name": "oxidizer", 6 | "dockerComposeFile": "docker-compose.yml", 7 | "service": "oxidizer", 8 | "workspaceFolder": "/workspace", 9 | 10 | "runArgs": [ 11 | "--cap-add=SYS_PTRACE", 12 | "--security-opt", 13 | "seccomp=unconfined" 14 | ], 15 | // Set *default* container specific settings.json values on container create. 16 | "settings": { 17 | "lldb.executable": "/usr/bin/lldb", 18 | "terminal.integrated.shell.linux": "/bin/bash", 19 | "rust-analyzer.updates.askBeforeDownload": false 20 | }, 21 | 22 | // Add the IDs of extensions you want installed when the container is created. 23 | "extensions": [ 24 | "ms-azuretools.vscode-docker", 25 | // "rust-lang.rust", 26 | "matklad.rust-analyzer", 27 | "bungcip.better-toml", 28 | "vadimcn.vscode-lldb", 29 | ], 30 | 31 | // Uncomment the next line if you want start specific services in your Docker Compose config. 32 | "runServices": ["db", "oxidizer"], 33 | 34 | // Use 'postCreateCommand' to run commands after the container is created. 35 | "postCreateCommand": "bash /workspace/.devcontainer/init.sh", 36 | 37 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 38 | // "remoteUser": "vscode" 39 | "shutdownAction": "stopCompose" 40 | } -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | version: '3' 7 | services: 8 | oxidizer: 9 | # Uncomment the next line to use a non-root user for all processes. You can also 10 | # simply use the "remoteUser" property in devcontainer.json if you just want VS Code 11 | # and its sub-processes (terminals, tasks, debugging) to execute as the user. On Linux, 12 | # you may need to update USER_UID and USER_GID in .devcontainer/Dockerfile to match your 13 | # user if not 1000. See https://aka.ms/vscode-remote/containers/non-root for details. 14 | # user: vscode 15 | 16 | image: rust 17 | environment: 18 | DATABASE_URL: 'postgres://postgres:alkje2lkaj2e@db/common' 19 | MAX_OPEN: 64 20 | 21 | volumes: 22 | # Update this to wherever you want VS Code to mount the folder of your project 23 | - ..:/workspace:cached 24 | 25 | # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. 26 | cap_add: 27 | - SYS_PTRACE 28 | security_opt: 29 | - seccomp:unconfined 30 | 31 | command: sleep infinity 32 | 33 | db: 34 | image: postgres:latest 35 | restart: always 36 | environment: 37 | POSTGRES_PASSWORD: alkje2lkaj2e 38 | POSTGRES_DB: common -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | schedule: 5 | - cron: '0 4 * * 6' # run build Sat at 4 pst 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | env: 14 | CACHE_VERSION: 1 15 | 16 | jobs: 17 | test: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Cache cargo registry 24 | uses: actions/cache@v1 25 | with: 26 | path: ~/cargo_registry 27 | key: ${{ runner.os }}-amd64-cargo-registry-oxidizer-${{ hashFiles('**/Cargo.lock') }}-${{env.CACHE_VERSION}} 28 | restore-keys: | 29 | ${{ runner.os }}-amd64-cargo-registry-oxidizer- 30 | - name: Cache cargo index 31 | uses: actions/cache@v1 32 | with: 33 | path: ~/cargo_index 34 | key: ${{ runner.os }}-amd64-cargo-index-oxidizer-${{ hashFiles('**/Cargo.lock') }}-${{env.CACHE_VERSION}} 35 | restore-keys: | 36 | ${{ runner.os }}-amd64-cargo-index-oxidizer- 37 | - name: Cache cargo build 38 | uses: actions/cache@v1 39 | with: 40 | path: target 41 | key: ${{ runner.os }}-amd64-cargo-build-target-oxidizer-${{ hashFiles('**/Cargo.lock') }}-${{env.CACHE_VERSION}} 42 | restore-keys: | 43 | ${{ runner.os }}-amd64-cargo-build-target-oxidizer- 44 | 45 | - name: Set up environment 46 | run: docker-compose -p oxidizer -f .devcontainer/docker-compose.yml -f .devcontainer/docker-compose-ci.yml up -d 47 | 48 | - name: Run unit tests 49 | run: docker exec oxidizer_oxidizer_1 bash -c "cd /workspace/oxidizer && cargo test" 50 | 51 | - name: Fix permissions 52 | run: sudo chown -R $USER:$USER . && sudo chown -vR $USER:$USER ~/cargo_registry && sudo chown -vR $USER:$USER ~/cargo_index 53 | 54 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose-tls.yml: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | version: '3' 7 | services: 8 | oxidizer: 9 | # Uncomment the next line to use a non-root user for all processes. You can also 10 | # simply use the "remoteUser" property in devcontainer.json if you just want VS Code 11 | # and its sub-processes (terminals, tasks, debugging) to execute as the user. On Linux, 12 | # you may need to update USER_UID and USER_GID in .devcontainer/Dockerfile to match your 13 | # user if not 1000. See https://aka.ms/vscode-remote/containers/non-root for details. 14 | # user: vscode 15 | 16 | image: rust 17 | environment: 18 | DATABASE_URL: 'postgres://postgres:alkje2lkaj2e@db/common' 19 | MAX_OPEN: 64 20 | 21 | volumes: 22 | # Update this to wherever you want VS Code to mount the folder of your project 23 | - ..:/workspace:cached 24 | 25 | # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. 26 | cap_add: 27 | - SYS_PTRACE 28 | security_opt: 29 | - seccomp:unconfined 30 | 31 | command: sleep infinity 32 | 33 | db: 34 | build: 35 | context: ./ 36 | dockerfile: psql-tls.dockerfile 37 | restart: always 38 | ports: 39 | - "5432:5432" 40 | command: -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.cert -c ssl_key_file=/var/lib/postgresql/server.key -c log_min_messages=DEBUG5 41 | environment: 42 | POSTGRES_PASSWORD: alkje2lkaj2e 43 | POSTGRES_DB: common -------------------------------------------------------------------------------- /oxidizer/src/migration.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Migrations 3 | //! 4 | //! Migrations are handled by [refinery](https://crates.io/crates/refinery) and barrel. Go ahead and read their docs. 5 | //! The derive macro generates the migration code 6 | //! for each entity automatically. The only thing needed is include each Entity's migration in a file (module) 7 | //! A macro to generate the necessary code for each migration module is provided. 8 | //! NOTE: Please take note that this is highly experimental and it can change frequently. 9 | //! 10 | //! ```ignore 11 | //! + src/ 12 | //! +-- entities/ 13 | //! +--+-- mod.rs 14 | //! +--+-- person.rs 15 | //! +--+-- account.rs 16 | //! +--migrations/ 17 | //! +--+-- mod.rs 18 | //! +--+-- V00001__person.rs 19 | //! +--+-- V00002__account.rs 20 | //! ``` 21 | //! 22 | //! 23 | //! - V00001__person.rs 24 | //! 25 | //! ```ignore 26 | //! use oxidizer::create_migration_module; 27 | //! use oxidizer::entity::IEntity; 28 | //! 29 | //! use crate::entities::Person; 30 | //! 31 | //! create_migration_module!(Person); 32 | //! ``` 33 | //! - migrations/mod.rs 34 | //! 35 | //! ```ignore 36 | //! use oxidizer::include_migration_mods; 37 | //! 38 | //! include_migration_mods!(); 39 | //! 40 | //! ``` 41 | //! 42 | //! With the correct file struct you can now create a runner and apply migrations with: 43 | //! 44 | //! ``` 45 | //! use oxidizer::*; 46 | //! #[tokio::test] 47 | //! async fn test_migrate() { 48 | //! let runner = crate::migrations::runner(); 49 | //! 50 | //! let uri = "postgres://postgres:alkje2lkaj2e@db/postgres"; 51 | //! let max_open = 50; // mobc 52 | //! let ca_file: Option<&str> = None; 53 | //! let db = DB::connect(&uri, max_open, ca_file).await.unwrap(); 54 | //! db.migrate(runner).await.unwrap(); 55 | //! } 56 | //! ``` 57 | //! 58 | 59 | use barrel::{backend::Pg, Migration as RawMigration}; 60 | 61 | pub use refinery::include_migration_mods; 62 | pub use refinery::*; 63 | 64 | /// Migration abstract layer 65 | pub struct Migration { 66 | pub name: String, 67 | 68 | pub raw: RawMigration, 69 | } 70 | 71 | impl Migration { 72 | /// Creates a new migration 73 | pub fn new(name: &str) -> Self { 74 | Migration { 75 | name: name.to_string(), 76 | 77 | raw: RawMigration::new(), 78 | } 79 | } 80 | 81 | /// Builds the raw query from the migration 82 | pub fn make(&self) -> String { 83 | self.raw.make::() 84 | } 85 | } 86 | 87 | /// Creates a new migration module 88 | #[macro_export] 89 | macro_rules! create_migration_module { 90 | ($entity:ident) => { 91 | pub fn migration() -> String { 92 | let m = <$entity>::create_migration().expect(concat!( 93 | "Could not create migration for ", 94 | stringify!($entity) 95 | )); 96 | m.make() 97 | } 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /.devcontainer/dev-pki/openssl-ca.cnf: -------------------------------------------------------------------------------- 1 | HOME = . 2 | RANDFILE = $ENV::HOME/.rnd 3 | 4 | #################################################################### 5 | [ ca ] 6 | default_ca = CA_default # The default ca section 7 | 8 | [ CA_default ] 9 | 10 | default_days = 5000 # How long to certify for 11 | default_crl_days = 30 # How long before next CRL 12 | default_md = sha256 # Use public key default MD 13 | preserve = no # Keep passed DN ordering 14 | 15 | x509_extensions = ca_extensions # The extensions to add to the cert 16 | 17 | email_in_dn = no # Don't concat the email in the DN 18 | copy_extensions = copy # Required to copy SANs from CSR to cert 19 | 20 | base_dir = . 21 | certificate = $base_dir/ca.cert # The CA certifcate 22 | private_key = $base_dir/ca.key # The CA private key 23 | new_certs_dir = $base_dir # Location for new certs after signing 24 | database = $base_dir/index.txt # Database index file 25 | serial = $base_dir/serial.txt # The current serial number 26 | 27 | unique_subject = no # Set to 'no' to allow creation of 28 | # several certificates with same subject. 29 | 30 | #################################################################### 31 | [ req ] 32 | default_bits = 2048 33 | default_keyfile = ca.key 34 | distinguished_name = ca_distinguished_name 35 | x509_extensions = ca_extensions 36 | string_mask = utf8only 37 | 38 | #################################################################### 39 | [ ca_distinguished_name ] 40 | organizationName = Organization Name (eg, company) 41 | organizationName_default = DEVELOPMENT 42 | 43 | organizationalUnitName = Organizational Unit (eg, division) 44 | organizationalUnitName_default = DEVELOPMENT 45 | 46 | commonName = Common Name (e.g. server FQDN or YOUR name) 47 | commonName_default = DEV CA 48 | 49 | emailAddress = Email Address 50 | emailAddress_default = test@example.com 51 | 52 | #################################################################### 53 | [ ca_extensions ] 54 | 55 | subjectKeyIdentifier = hash 56 | authorityKeyIdentifier = keyid:always, issuer 57 | basicConstraints = critical, CA:true 58 | keyUsage = keyCertSign, cRLSign 59 | 60 | #################################################################### 61 | [ signing_policy ] 62 | countryName = optional 63 | stateOrProvinceName = optional 64 | localityName = optional 65 | organizationName = optional 66 | organizationalUnitName = optional 67 | commonName = supplied 68 | emailAddress = optional 69 | 70 | #################################################################### 71 | [ signing_req ] 72 | subjectKeyIdentifier = hash 73 | authorityKeyIdentifier = keyid,issuer 74 | basicConstraints = CA:FALSE 75 | keyUsage = digitalSignature, keyEncipherment 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oxidizer 2 | 3 | [![Actions Status][ci-badge]][ci-url] 4 | [![Crates.io][crates-badge]][crates-url] 5 | [![API Docs][docs-badge]][docs-url] 6 | [![MIT licensed][mit-badge]][mit-url] 7 | [![Gitter](https://badges.gitter.im/oxidizer-rs/community.svg)][glitter-url] 8 | 9 | [ci-badge]: https://github.com/oxidizer-rs/oxidizer/workflows/test/badge.svg 10 | [ci-url]: https://github.com/oxidizer-rs/oxidizer/actions 11 | [crates-badge]: https://img.shields.io/crates/v/oxidizer.svg 12 | [crates-url]: https://crates.io/crates/oxidizer 13 | [docs-badge]: https://docs.rs/oxidizer/badge.svg 14 | [docs-url]: https://docs.rs/oxidizer 15 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 16 | [mit-url]: https://github.com/oxidizer-rs/oxidizer/blob/main/LICENSE 17 | [glitter-url]: https://gitter.im/oxidizer-rs/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge 18 | 19 | ## ⚠️ Important ⚠️ 20 | 21 | Oxidizer is now EOL. Unfortunately, we have not been able to allocate time and effort to push Oxidizer forward. the code will remain available in GitHub in archived mode and everyone is welcome to fork and work on features as they wish. 22 | 23 | ## Overview 24 | 25 | A Rust ORM based on [tokio-postgres](https://crates.io/crates/tokio-postgres) and [refinery](https://crates.io/crates/refinery) that helps you reduce the boilerplate of writing entities, tables & migrations when using tokio-postgres and refinery. 26 | 27 | - Asynchronous from the ground up. All the database operations are 28 | efficiently handled by tokio at runtime. 29 | - Oxidizer macros generate code to access relations between entities with ease. Forward and reverse relations are supported. 30 | 31 | > Note that, while functional and working, this is in early stages. Use with caution. 32 | 33 | ## Features/Roadmap 34 | 35 | - ⚙ - Work in progress 36 | - 🗒 - TODO 37 | - ⚗ - Implemented/Testing 38 | 39 | Name | Status | Issue 40 | --- | --- | --- 41 | non-integer primary keys | ⚗ 42 | rustls | 🗒| [#13](https://github.com/oxidizer-rs/oxidizer/issues/13) 43 | joins | 🗒 | [#12](https://github.com/oxidizer-rs/oxidizer/issues/12) 44 | mysql support | 🗒 | [#11](https://github.com/oxidizer-rs/oxidizer/issues/11) 45 | recursive queries | 🗒 46 | transactions | 🗒 47 | select subset of columns | 🗒 48 | 49 | ## Contributing 50 | 51 | There are a couple of ways in which you can contribute to Oxidizer, for example: 52 | 53 | - [Submit bugs and feature requests](https://github.com/oxidizer-rs/oxidizer/issues), and help us verify as they are checked in 54 | - Review the [documentation](https://oxidizer.rs/docs) and make pull requests for anything from typos to new content suggestion 55 | 56 | Unless you explicitly state otherwise, any contribution intentionally submitted 57 | for inclusion in Oxidizer by you, shall be licensed as MIT, without any additional 58 | terms or conditions. 59 | 60 | ## Feedback 61 | 62 | - Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/oxidizer-rs) 63 | - [Report an issue](https://github.com/oxidizer-rs/oxidizer/issues) 64 | - Up vote [popular feature requests](https://github.com/oxidizer-rs/oxidizer/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) 65 | 66 | ## License 67 | 68 | This project is licensed under the [MIT license]. 69 | 70 | [mit license]: [mit-url] 71 | -------------------------------------------------------------------------------- /oxidizer/src/db/db.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use mobc::Manager; 3 | use mobc::Pool; 4 | 5 | use super::connections; 6 | use connections::ConnectionProvider; 7 | 8 | use refinery::{Report, Runner}; 9 | use std::str::FromStr; 10 | 11 | use super::super::migration::Migration; 12 | use super::error::*; 13 | 14 | use barrel::backend::Pg; 15 | use tokio_postgres::{row::Row, types::ToSql, Client}; 16 | 17 | struct ConnectionManager { 18 | provider: Box, 19 | } 20 | 21 | #[async_trait] 22 | impl Manager for ConnectionManager { 23 | type Connection = Client; 24 | type Error = tokio_postgres::Error; 25 | 26 | async fn connect(&self) -> Result { 27 | self.provider.connect().await 28 | } 29 | 30 | async fn check(&self, conn: Self::Connection) -> Result { 31 | conn.simple_query("").await?; 32 | Ok(conn) 33 | } 34 | } 35 | 36 | #[derive(Clone)] 37 | pub struct DB { 38 | pool: Pool, 39 | } 40 | 41 | unsafe impl std::marker::Sync for DB {} 42 | 43 | impl DB { 44 | pub async fn connect(uri: &str, max_open: u64, ca_file: Option<&str>) -> Result { 45 | let config = tokio_postgres::Config::from_str(uri).map_err(Error::PostgresError)?; 46 | 47 | let provider = connections::create_connection_provider(config, ca_file).await?; 48 | let manager = ConnectionManager { provider }; 49 | 50 | Ok(DB { 51 | pool: Pool::builder().max_open(max_open).build(manager), 52 | }) 53 | } 54 | 55 | pub async fn create( 56 | &self, 57 | query: &str, 58 | params: &'_ [&'_ (dyn ToSql + Sync)], 59 | ) -> Result { 60 | self.execute(query, params).await 61 | } 62 | 63 | pub async fn execute( 64 | &self, 65 | query: &str, 66 | params: &'_ [&'_ (dyn ToSql + Sync)], 67 | ) -> Result { 68 | let client = self.pool.get().await.map_err(Error::MobcError)?; 69 | 70 | let insert = client.prepare(query).await.map_err(Error::PostgresError)?; 71 | 72 | client 73 | .execute(&insert, params) 74 | .await 75 | .map_err(Error::PostgresError) 76 | } 77 | 78 | pub async fn query( 79 | &self, 80 | query: &str, 81 | params: &'_ [&'_ (dyn ToSql + Sync)], 82 | ) -> Result, Error> { 83 | let client = self.pool.get().await.map_err(Error::MobcError)?; 84 | 85 | let insert = client.prepare(query).await.map_err(Error::PostgresError)?; 86 | 87 | client 88 | .query(&insert, params) 89 | .await 90 | .map_err(Error::PostgresError) 91 | } 92 | 93 | pub async fn migrate_tables(&self, ms: &[Migration]) -> Result { 94 | let ref_migrations: Vec = ms 95 | .as_ref() 96 | .iter() 97 | .enumerate() 98 | .filter_map(|(i, m)| { 99 | let sql = m.raw.make::(); 100 | 101 | let name = format!("V{}__{}.rs", i, m.name); 102 | 103 | let migration = refinery::Migration::unapplied(&name, &sql).unwrap(); 104 | 105 | Some(migration) 106 | }) 107 | .collect(); 108 | 109 | let runner = refinery::Runner::new(&ref_migrations); 110 | 111 | self.migrate(runner).await 112 | } 113 | 114 | pub async fn migrate(&self, runner: Runner) -> Result { 115 | let runner = runner.set_abort_divergent(false); 116 | let mut client = self.pool.get().await.map_err(Error::MobcError)?; 117 | Ok(runner 118 | .run_async(&mut *client) 119 | .await 120 | .map_err(Error::RefineryError)?) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/field_extras.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::{format_ident, quote, quote_spanned}; 4 | use syn::{spanned::Spanned, Field, Type}; 5 | 6 | use super::attrs::{CustomTypeAttr, PrimaryKeyAttr, RelationAttr}; 7 | use super::utils::search_attr_in_field; 8 | use super::utils::type_to_db_type; 9 | use super::utils::{check_type_order, is_integer_type}; 10 | 11 | pub trait FieldExtras { 12 | fn is_indexed(&self) -> bool; 13 | fn is_nullable(&self) -> bool; 14 | fn is_ignore(&self) -> bool; 15 | fn is_increments(&self) -> bool; 16 | fn parse_primary_key(&self) -> Option; 17 | fn parse_relation(&self) -> Option; 18 | fn parse_custom_type(&self) -> Option; 19 | fn get_db_type(&self) -> TokenStream2; 20 | fn get_type(&self) -> TokenStream2; 21 | } 22 | 23 | impl FieldExtras for Field { 24 | fn is_indexed(&self) -> bool { 25 | search_attr_in_field(self, "indexed") 26 | } 27 | 28 | fn is_ignore(&self) -> bool { 29 | search_attr_in_field(self, "field_ignore") 30 | } 31 | 32 | fn is_increments(&self) -> bool { 33 | if let Some(attr) = self.parse_primary_key() { 34 | return match attr.increments.as_ref() { 35 | Some(v) => *v, 36 | None => false, 37 | }; 38 | } 39 | 40 | search_attr_in_field(self, "increments") 41 | } 42 | 43 | fn parse_primary_key(&self) -> Option { 44 | for attr in (&self.attrs).into_iter() { 45 | let option = attr.parse_meta().unwrap(); 46 | if let Ok(relation) = PrimaryKeyAttr::from_meta(&option) { 47 | return Some(relation); 48 | } 49 | } 50 | None 51 | } 52 | 53 | fn parse_relation(&self) -> Option { 54 | for attr in (&self.attrs).into_iter() { 55 | let option = attr.parse_meta().unwrap(); 56 | if let Ok(relation) = RelationAttr::from_meta(&option) { 57 | return Some(relation); 58 | } 59 | } 60 | None 61 | } 62 | 63 | fn parse_custom_type(&self) -> Option { 64 | for attr in (&self.attrs).into_iter() { 65 | let option = attr.parse_meta().unwrap(); 66 | if let Ok(ct) = CustomTypeAttr::from_meta(&option) { 67 | return Some(ct); 68 | } 69 | } 70 | None 71 | } 72 | 73 | fn is_nullable(&self) -> bool { 74 | match &self.ty { 75 | syn::Type::Path(tp) => { 76 | let expected: Vec = vec!["Option".to_owned()]; 77 | check_type_order(&tp, &expected, 0) 78 | } 79 | _ => false, 80 | } 81 | } 82 | 83 | fn get_type(&self) -> TokenStream2 { 84 | if let Some(ct) = self.parse_custom_type() { 85 | let ty = ct.ty; 86 | 87 | let ident = format_ident!("{}", ty); 88 | 89 | return quote! { #ident }; 90 | } 91 | 92 | let ty = &self.ty; 93 | 94 | quote! { #ty } 95 | } 96 | 97 | fn get_db_type(&self) -> TokenStream2 { 98 | if self.is_increments() { 99 | let bigserial_types = vec!["i64"]; 100 | // TODO CURRENTLY BARREL ONLY SUPPORTS INTEGER (INT4) FOREIGN KEY TYPES 101 | let ty = match bigserial_types.contains(&is_integer_type(&self.ty).1) { 102 | true => "SERIAL", 103 | false => "SERIAL", 104 | }; 105 | return quote! { oxidizer::types::custom(#ty) }; 106 | } 107 | 108 | if let Some(relation) = self.parse_relation() { 109 | let model = relation.model; 110 | let key = relation.key; 111 | 112 | let model_ident = format_ident!("{}", model); 113 | let table_name_acessor = quote! { <#model_ident>::get_table_name() }; 114 | 115 | return quote! { 116 | oxidizer::types::foreign(#table_name_acessor, #key) 117 | }; 118 | } 119 | 120 | if let Some(ct) = self.parse_custom_type() { 121 | let ty = ct.ty; 122 | 123 | let ty: Type = match syn::parse_str(&ty) { 124 | Ok(t) => t, 125 | Err(_) => return quote_spanned! { ty.span() => compile_error!("Invalid type") }, 126 | }; 127 | 128 | return type_to_db_type(&ty); 129 | } 130 | 131 | type_to_db_type(&self.ty) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /oxidizer/src/db/connections.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use tokio_postgres::{Client, Config, NoTls}; 3 | 4 | use crate::Error; 5 | 6 | #[async_trait] 7 | pub trait ConnectionProvider: Send + Sync + 'static { 8 | async fn connect(&self) -> Result; 9 | } 10 | 11 | pub async fn create_connection_provider( 12 | config: Config, 13 | ca_file: Option<&str>, 14 | ) -> Result, Error> { 15 | let prov: Box = if let Some(ca_file) = ca_file { 16 | cfg_if::cfg_if! { 17 | if #[cfg(feature = "tls-rustls")] { 18 | tls_rustls::create_rustls_provider(config, ca_file).await? 19 | } else if #[cfg(feature = "tls-openssl")] { 20 | tls_openssl::create_openssl_provider(config, ca_file).await? 21 | } else { 22 | eprintln!("[WARN] no TLS provider found, reverting to unencrypted connection"); 23 | no_tls(config) 24 | } 25 | } 26 | } else { 27 | no_tls(config) 28 | }; 29 | 30 | Ok(prov) 31 | } 32 | 33 | struct NoTlsConnectionProvider { 34 | config: Config, 35 | } 36 | 37 | #[async_trait] 38 | impl ConnectionProvider for NoTlsConnectionProvider { 39 | async fn connect(&self) -> Result { 40 | let (client, conn) = self.config.connect(NoTls).await?; 41 | mobc::spawn(conn); 42 | Ok(client) 43 | } 44 | } 45 | 46 | fn no_tls(config: Config) -> Box { 47 | Box::new(NoTlsConnectionProvider { config }) 48 | } 49 | 50 | #[cfg(feature = "tls-openssl")] 51 | mod tls_openssl { 52 | use super::*; 53 | 54 | use openssl::ssl::{SslConnector, SslMethod}; 55 | 56 | use postgres_openssl::MakeTlsConnector; 57 | use tokio_postgres::{Client, Config}; 58 | 59 | struct OpensslConnectionProvider { 60 | config: Config, 61 | tls: MakeTlsConnector, 62 | } 63 | 64 | #[async_trait] 65 | impl ConnectionProvider for OpensslConnectionProvider { 66 | async fn connect(&self) -> Result { 67 | let (client, conn) = self.config.connect(self.tls.clone()).await?; 68 | mobc::spawn(conn); 69 | Ok(client) 70 | } 71 | } 72 | 73 | fn sync_build_ssl_connector(ca_file: String) -> Result { 74 | let mut builder = SslConnector::builder(SslMethod::tls()).map_err(Error::OpensslError)?; 75 | 76 | builder.set_ca_file(&ca_file).map_err(Error::OpensslError)?; 77 | 78 | Ok(builder.build()) 79 | } 80 | 81 | pub async fn create_openssl_provider( 82 | config: Config, 83 | ca_file: &str, 84 | ) -> Result, Error> { 85 | let file = ca_file.to_string(); 86 | let connector = 87 | tokio::task::spawn_blocking(move || sync_build_ssl_connector(file)).await??; 88 | 89 | Ok(Box::new(OpensslConnectionProvider { 90 | config, 91 | tls: MakeTlsConnector::new(connector), 92 | })) 93 | } 94 | } 95 | 96 | #[cfg(feature = "tls-rustls")] 97 | mod tls_rustls { 98 | use super::*; 99 | 100 | use std::fs::File; 101 | use std::io::BufReader; 102 | 103 | use rustls::{ClientConfig, RootCertStore}; 104 | use tokio_postgres_rustls::MakeRustlsConnect; 105 | 106 | struct RustlsConnectionProvider { 107 | config: Config, 108 | tls: MakeRustlsConnect, 109 | } 110 | 111 | #[async_trait] 112 | impl ConnectionProvider for RustlsConnectionProvider { 113 | async fn connect(&self) -> Result { 114 | let (client, conn) = self.config.connect(self.tls.clone()).await?; 115 | mobc::spawn(conn); 116 | Ok(client) 117 | } 118 | } 119 | 120 | fn sync_initialise_root_store(ca_file: String) -> Result { 121 | let file = File::open(&ca_file).map_err(|err| Error::Other(err.to_string()))?; 122 | let mut reader = BufReader::new(file); 123 | 124 | let mut root_store = RootCertStore::empty(); 125 | root_store 126 | .add_pem_file(&mut reader) 127 | .map_err(|_| Error::RustlsError("Failed to read certificate file".to_string()))?; 128 | 129 | Ok(root_store) 130 | } 131 | 132 | pub async fn create_rustls_provider( 133 | config: Config, 134 | ca_file: &str, 135 | ) -> Result, Error> { 136 | let mut tls_conf = ClientConfig::new(); 137 | 138 | let file = ca_file.to_string(); 139 | tls_conf.root_store = 140 | tokio::task::spawn_blocking(move || sync_initialise_root_store(file)).await??; 141 | 142 | Ok(Box::new(RustlsConnectionProvider { 143 | config, 144 | tls: MakeRustlsConnect::new(tls_conf), 145 | })) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/sql_builder/postgres.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use inflector::cases::snakecase::to_snake_case; 3 | use proc_macro::TokenStream; 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::{format_ident, quote, quote_spanned}; 6 | use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Type}; 7 | 8 | use crate::attrs::HasManyAttr; 9 | use crate::attrs::{EntityAttr, IndexAttr, RelationAttr}; 10 | use crate::field_extras::*; 11 | use crate::props::*; 12 | use crate::utils::is_integer_type; 13 | 14 | use super::Builder; 15 | 16 | pub struct PostgresBuilder {} 17 | 18 | impl Builder for PostgresBuilder { 19 | fn new() -> Self { 20 | PostgresBuilder {} 21 | } 22 | 23 | fn build_save_query(props: &crate::props::Props) -> TokenStream2 { 24 | let table_name = props.get_table_name(); 25 | 26 | let fields_ident: Vec<&Option> = 27 | props.get_fields_all().map(|field| &field.ident).collect(); 28 | let mut current_index = 1; 29 | let fields_query_values = props 30 | .get_fields_all() 31 | .map(|field| { 32 | let v = current_index; 33 | current_index += 1; 34 | 35 | match field.parse_primary_key().is_some() && field.is_increments() { 36 | true => { 37 | let bigserial_types = vec!["i64"]; 38 | let cast = match bigserial_types.contains(&is_integer_type(&field.ty).1) { 39 | true => "int8", 40 | false => "int4", 41 | }; 42 | 43 | format!( 44 | "COALESCE(${}, CAST(nextval(pg_get_serial_sequence('{}', '{}')) AS {}))", 45 | v, 46 | table_name, 47 | field.ident.as_ref().unwrap().to_string(), 48 | cast, 49 | ) 50 | } 51 | false => format!("${}", v), 52 | } 53 | }) 54 | .collect::>() 55 | .join(","); 56 | 57 | let mut current_index = 0; 58 | let mut comma_index = 0; 59 | let fields_plain_to_set: Vec = props 60 | .get_fields_all() 61 | .filter_map(|field| { 62 | //if field.is_increments() { 63 | //return None; 64 | //} 65 | 66 | current_index += 1; 67 | 68 | if field.parse_primary_key().is_some() { 69 | return None; 70 | } 71 | 72 | let ident = &field.ident; 73 | let v = format!("${}", current_index); 74 | let comma = match comma_index { 75 | 0 => quote! {}, 76 | _ => quote! {,}, 77 | }; 78 | comma_index += 1; 79 | 80 | Some(quote! { 81 | concat!(stringify!(#comma #ident =), #v) 82 | }) 83 | }) 84 | .collect(); 85 | 86 | let on_conflict_do = match fields_plain_to_set.len() { 87 | 0 => quote! {"NOTHING"}, 88 | _ => quote! {"UPDATE SET ", #(#fields_plain_to_set),* }, 89 | }; 90 | 91 | let primary_key = props.get_primary_key_field().unwrap(); 92 | let primary_key_ident = &primary_key.ident; 93 | 94 | quote! { 95 | let query = concat!("INSERT INTO \"", #table_name, "\"", 96 | " (", stringify!(#(#fields_ident),*), 97 | ") values (", #fields_query_values, 98 | ") ON CONFLICT (", stringify!(#primary_key_ident), ") DO ", #on_conflict_do, 99 | " RETURNING ", stringify!(#primary_key_ident), ";" 100 | ); 101 | } 102 | } 103 | 104 | fn build_find_query(props: &Props) -> TokenStream2 { 105 | let table_name = props.get_table_name(); 106 | quote! { 107 | let query = format!("SELECT * FROM \"{}\" WHERE {}", #table_name, condition) 108 | } 109 | } 110 | 111 | fn build_first_query(props: &Props) -> TokenStream2 { 112 | let table_name = props.get_table_name(); 113 | quote! { 114 | let query = format!("SELECT * FROM \"{}\" WHERE {} LIMIT 1", #table_name, condition); 115 | } 116 | } 117 | 118 | fn build_delete_query(props: &Props) -> TokenStream2 { 119 | let primary_key_ident = &props.get_primary_key_field().unwrap().ident; 120 | let table_name = props.get_table_name(); 121 | quote! { 122 | let condition = format!("{} = $1", stringify!(#primary_key_ident)); 123 | let query = format!("DELETE FROM \"{}\" WHERE {}", #table_name, condition); 124 | } 125 | } 126 | 127 | fn build_relation_get_query(props: &Props, relation: &RelationAttr) -> TokenStream2 { 128 | let model = format_ident!("{}", relation.model); 129 | let key = format_ident!("{}", relation.key); 130 | 131 | quote! { 132 | let table_name = <#model>::get_table_name(); 133 | let query = format!("select * from \"{}\" where {} = $1 limit 1", &table_name, stringify!(#key)); 134 | } 135 | } 136 | 137 | fn build_relation_has_many_get_condition(props: &Props, attr: &HasManyAttr) -> TokenStream2 { 138 | let field = &attr.field; 139 | 140 | quote! { 141 | let query = format!("{} = $1", #field); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/props.rs: -------------------------------------------------------------------------------- 1 | use inflector::cases::snakecase::to_snake_case; 2 | use proc_macro::TokenStream; 3 | use proc_macro2::TokenStream as TokenStream2; 4 | use quote::{quote, quote_spanned}; 5 | use syn::{ 6 | punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field, 7 | Fields, Ident, Meta, PathArguments, PathSegment, Type, 8 | }; 9 | 10 | use super::attrs::HasManyAttr; 11 | use super::attrs::{EntityAttr, IndexAttr, PrimaryKeyAttr}; 12 | use super::field_extras::*; 13 | use super::utils::is_integer_type; 14 | 15 | pub struct Props { 16 | input: DeriveInput, 17 | attrs: Option, 18 | indexes: Vec, 19 | has_many_attrs: Vec, 20 | } 21 | 22 | type GetFieldsIter<'a> = std::iter::Filter, fn(&&Field) -> bool>; 23 | 24 | impl Props { 25 | pub fn new( 26 | input: DeriveInput, 27 | attrs: Option, 28 | indexes: Vec, 29 | has_many_attrs: Vec, 30 | ) -> Self { 31 | Props { 32 | input: input, 33 | attrs: attrs, 34 | indexes: indexes, 35 | has_many_attrs: has_many_attrs, 36 | } 37 | } 38 | 39 | pub fn get_name(&self) -> &Ident { 40 | &self.input.ident 41 | } 42 | 43 | pub fn get_table_name(&self) -> String { 44 | let snaked_name = to_snake_case(&self.get_name().to_string()); 45 | 46 | match self.attrs.as_ref() { 47 | Some(attrs) => match attrs.table_name.as_ref() { 48 | Some(name) => name.to_string(), 49 | None => snaked_name, 50 | }, 51 | None => snaked_name, 52 | } 53 | } 54 | 55 | pub fn get_fields_all(&self) -> GetFieldsIter { 56 | let fields = match &self.input.data { 57 | Data::Struct(DataStruct { 58 | fields: Fields::Named(fields), 59 | .. 60 | }) => &fields.named, 61 | _ => panic!("expected a struct with named fields"), 62 | }; 63 | 64 | fields.iter().filter(|field| !field.is_ignore()) 65 | } 66 | 67 | pub fn get_ignored_fields(&self) -> GetFieldsIter { 68 | let fields = match &self.input.data { 69 | Data::Struct(DataStruct { 70 | fields: Fields::Named(fields), 71 | .. 72 | }) => &fields.named, 73 | _ => panic!("expected a struct with named fields"), 74 | }; 75 | 76 | fields.iter().filter(|field| field.is_ignore()) 77 | } 78 | 79 | pub fn get_fields_all_names(&self) -> Vec<&Option> { 80 | self.get_fields_all().map(|field| &field.ident).collect() 81 | } 82 | 83 | pub fn get_fields_all_types(&self) -> Vec { 84 | self.get_fields_all() 85 | .map(|field| field.get_type()) 86 | .collect() 87 | } 88 | 89 | pub fn get_fields_all_nullable(&self) -> Vec { 90 | self.get_fields_all() 91 | .map(|field| field.is_nullable()) 92 | .collect() 93 | } 94 | 95 | pub fn get_fields_all_indexed(&self) -> Vec { 96 | self.get_fields_all() 97 | .map(|field| field.is_indexed()) 98 | .collect() 99 | } 100 | 101 | pub fn get_fields_all_primary(&self) -> Vec> { 102 | self.get_fields_all() 103 | .map(|field| field.parse_primary_key()) 104 | .collect() 105 | } 106 | 107 | pub fn get_fields_all_increments(&self) -> Vec { 108 | self.get_fields_all() 109 | .map(|field| field.is_increments()) 110 | .collect() 111 | } 112 | 113 | fn build_db_types(&self, fields: GetFieldsIter) -> Vec { 114 | fields.map(|field| field.get_db_type()).collect() 115 | } 116 | 117 | pub fn get_fields_all_db_types(&self) -> Vec { 118 | self.build_db_types(self.get_fields_all()) 119 | } 120 | 121 | pub fn get_primary_key_field(&self) -> Option<&Field> { 122 | self.get_fields_all() 123 | .find(|field| field.parse_primary_key().is_some()) 124 | } 125 | 126 | pub fn get_fields_plain(&self) -> Vec<&Field> { 127 | self.get_fields_all() 128 | .filter(|field| field.parse_primary_key().is_none()) 129 | .collect() 130 | } 131 | 132 | pub fn get_fields_plain_names(&self) -> Vec<&Option> { 133 | self.get_fields_plain() 134 | .iter() 135 | .map(|field| &field.ident) 136 | .collect() 137 | } 138 | 139 | pub fn get_fields_plain_numbered(&self) -> Vec { 140 | self.get_fields_plain_names() 141 | .iter() 142 | .enumerate() 143 | .map(|(i, _)| "$".to_string() + &(i + 1).to_string()) 144 | .collect() 145 | } 146 | 147 | pub fn get_fields_plain_numbered_next_index(&self) -> String { 148 | (self.get_fields_plain_numbered().len() + 1).to_string() 149 | } 150 | 151 | pub fn check(&self) -> Option { 152 | if let None = self.get_primary_key_field() { 153 | return Some(TokenStream::from( 154 | quote! { compile_error!("No primary key defined") }, 155 | )); 156 | } 157 | 158 | // checks auto-increments 159 | for field in self.get_fields_all() { 160 | if !field.is_increments() { 161 | continue; 162 | } 163 | 164 | if field.parse_primary_key().is_none() { 165 | return Some(TokenStream::from(quote_spanned! { 166 | field.ty.span() => compile_error!( 167 | "Increments can only be used with primary keys for now" 168 | ) 169 | })); 170 | } 171 | 172 | let allowed_increments_types = vec!["i32"]; 173 | 174 | let (check, ty) = is_integer_type(&field.ty); 175 | if !check || !allowed_increments_types.contains(&ty) { 176 | return Some(TokenStream::from(quote_spanned! { 177 | field.ty.span() => compile_error!( 178 | "Increments can only be used with integer types: 'i32'" 179 | ) 180 | })); 181 | } 182 | } 183 | 184 | // TODO this limitation should go away eventually 185 | if self 186 | .get_fields_all() 187 | .filter(|field| field.parse_primary_key().is_some()) 188 | .count() 189 | == 1 190 | { 191 | return None; 192 | } 193 | 194 | let last_primary_key = self 195 | .get_fields_all() 196 | .filter(|field| field.parse_primary_key().is_some()) 197 | .last() 198 | .unwrap(); 199 | let expanded = quote_spanned! { 200 | last_primary_key.ident.as_ref().unwrap().span() => compile_error!("Multiple primary keys defined") 201 | }; 202 | Some(TokenStream::from(expanded)) 203 | } 204 | 205 | pub fn get_fields_foreign(&self) -> Vec<&Field> { 206 | self.get_fields_all() 207 | .filter(|field| field.parse_relation().is_some()) 208 | .collect() 209 | } 210 | 211 | pub fn get_indexes(&self) -> Vec { 212 | self.indexes.clone() 213 | } 214 | 215 | pub fn get_has_many_attrs(&self) -> Vec { 216 | self.has_many_attrs.clone() 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, quote_spanned}; 3 | use syn::{ 4 | spanned::Spanned, AngleBracketedGenericArguments, Field, GenericArgument, Meta, Path, 5 | PathArguments, PathSegment, Type, TypePath, 6 | }; 7 | 8 | pub fn iterate_angle_bracketed( 9 | ab: &AngleBracketedGenericArguments, 10 | expected: &Vec, 11 | index: usize, 12 | ) -> bool { 13 | let index = index; 14 | 15 | if expected.len() == index { 16 | return true; 17 | } 18 | 19 | for arg in &ab.args { 20 | let res = match arg { 21 | GenericArgument::Type(Type::Path(tp)) => check_type_order(tp, expected, index), 22 | _ => unimplemented!(), 23 | }; 24 | 25 | if res { 26 | return true; 27 | } 28 | } 29 | 30 | false 31 | } 32 | 33 | pub fn iterate_path_arguments(seg: &PathSegment, expected: &Vec, index: usize) -> bool { 34 | let mut index = index; 35 | 36 | if expected.len() == index { 37 | return true; 38 | } 39 | 40 | if seg.ident.to_string() == expected[index] { 41 | index += 1; 42 | } 43 | 44 | if expected.len() == index { 45 | return true; 46 | } 47 | 48 | match &seg.arguments { 49 | PathArguments::AngleBracketed(angle) => iterate_angle_bracketed(angle, expected, index), 50 | PathArguments::Parenthesized(_paren) => unimplemented!(), 51 | PathArguments::None => expected.len() == index, 52 | } 53 | } 54 | 55 | pub fn iterate_path_segments(p: &Path, expected: &Vec, index: usize) -> bool { 56 | let index = index; 57 | 58 | if expected.len() == index { 59 | return true; 60 | } 61 | 62 | for seg in p.segments.iter() { 63 | if iterate_path_arguments(seg, &expected, index) { 64 | return true; 65 | } 66 | } 67 | 68 | expected.len() == index 69 | } 70 | 71 | pub fn check_type_order(p: &TypePath, expected: &Vec, index: usize) -> bool { 72 | let mut index = index; 73 | 74 | if expected.len() == index { 75 | return true; 76 | } 77 | 78 | if let Some(ident) = p.path.get_ident() { 79 | if ident.to_string() == expected[0] { 80 | index += 1; 81 | } 82 | } 83 | 84 | iterate_path_segments(&p.path, expected, index) 85 | } 86 | 87 | pub fn is_typed_with(segment: &PathSegment, expected: Vec<&str>) -> bool { 88 | let expected = expected.iter().map(|v| v.to_string()).collect(); 89 | iterate_path_arguments(segment, &expected, 0) 90 | } 91 | 92 | pub fn is_chrono_option(segment: &PathSegment) -> bool { 93 | let expected: Vec<&str> = vec!["Option", "DateTime", "Utc"]; 94 | let no_option_expected: Vec<&str> = vec!["DateTime", "Utc"]; 95 | 96 | is_typed_with(segment, expected) || is_typed_with(segment, no_option_expected) 97 | } 98 | 99 | pub fn search_attr_in_field(field: &Field, attr: &str) -> bool { 100 | for option in (&field.attrs).into_iter() { 101 | let option = option.parse_meta().unwrap(); 102 | match option { 103 | Meta::Path(path) if path.get_ident().unwrap().to_string() == attr => { 104 | return true; 105 | } 106 | _ => {} 107 | } 108 | } 109 | return false; 110 | } 111 | 112 | /// is_integer_type returns tuple indicating whether the type is an integer type and the type 113 | /// itself cast to string 114 | pub fn is_integer_type(ty: &Type) -> (bool, &str) { 115 | let segments = match ty { 116 | syn::Type::Path(TypePath { 117 | path: Path { segments, .. }, 118 | .. 119 | }) => segments, 120 | _ => unimplemented!(), 121 | }; 122 | match segments.first().unwrap() { 123 | PathSegment { ident, .. } if ident.to_string() == "i8" => (true, "i8"), 124 | PathSegment { ident, .. } if ident.to_string() == "i16" => (true, "i16"), 125 | PathSegment { ident, .. } if ident.to_string() == "i32" => (true, "i32"), 126 | PathSegment { ident, .. } if ident.to_string() == "i64" => (true, "i64"), 127 | PathSegment { ident, .. } if ident.to_string() == "u32" => (true, "u32"), 128 | PathSegment { ident, .. } if ident.to_string() == "u64" => (true, "u64"), 129 | _ => (false, ""), 130 | } 131 | } 132 | 133 | pub fn type_to_db_type(ty: &Type) -> TokenStream { 134 | let segments = match ty { 135 | syn::Type::Path(TypePath { 136 | path: Path { segments, .. }, 137 | .. 138 | }) => segments, 139 | _ => unimplemented!(), 140 | }; 141 | 142 | match segments.first().unwrap() { 143 | PathSegment { ident, .. } if ident.to_string() == "String" => { 144 | quote! { oxidizer::types::text() } 145 | } 146 | segment if is_typed_with(segment, vec!["Option", "String"]) => { 147 | quote! { oxidizer::types::text() } 148 | } 149 | 150 | PathSegment { ident, .. } if ident.to_string() == "i8" => { 151 | quote! { oxidizer::types::custom("char") } 152 | } 153 | segment if is_typed_with(segment, vec!["Option", "i8"]) => { 154 | quote! { oxidizer::types::custom("char") } 155 | } 156 | 157 | PathSegment { ident, .. } if ident.to_string() == "i16" => { 158 | quote! { oxidizer::types::custom("SMALLINT") } 159 | } 160 | segment if is_typed_with(segment, vec!["Option", "i16"]) => { 161 | quote! { oxidizer::types::custom("SMALLINT") } 162 | } 163 | 164 | PathSegment { ident, .. } if ident.to_string() == "i32" => { 165 | quote! { oxidizer::types::integer() } 166 | } 167 | segment if is_typed_with(segment, vec!["Option", "i32"]) => { 168 | quote! { oxidizer::types::integer() } 169 | } 170 | 171 | PathSegment { ident, .. } if ident.to_string() == "u32" => { 172 | quote! { oxidizer::types::custom("OID") } 173 | } 174 | segment if is_typed_with(segment, vec!["Option", "u32"]) => { 175 | quote! { oxidizer::types::custom("OID") } 176 | } 177 | 178 | PathSegment { ident, .. } if ident.to_string() == "i64" => { 179 | quote! { oxidizer::types::custom("BIGINT") } 180 | } 181 | segment if is_typed_with(segment, vec!["Option", "i64"]) => { 182 | quote! { oxidizer::types::custom("BIGINT") } 183 | } 184 | 185 | PathSegment { ident, .. } if ident.to_string() == "f32" => { 186 | quote! { oxidizer::types::custom("REAL") } 187 | } 188 | segment if is_typed_with(segment, vec!["Option", "f32"]) => { 189 | quote! { oxidizer::types::custom("REAL") } 190 | } 191 | 192 | PathSegment { ident, .. } if ident.to_string() == "f64" => { 193 | quote! { oxidizer::types::custom("DOUBLE PRECISION") } 194 | } 195 | segment if is_typed_with(segment, vec!["Option", "f64"]) => { 196 | quote! { oxidizer::types::custom("DOUBLE PRECISION") } 197 | } 198 | 199 | PathSegment { ident, .. } if ident.to_string() == "bool" => { 200 | quote! { oxidizer::types::boolean() } 201 | } 202 | segment if is_typed_with(segment, vec!["Option", "bool"]) => { 203 | quote! { oxidizer::types::boolean() } 204 | } 205 | 206 | segment if is_chrono_option(segment) => { 207 | quote! { oxidizer::types::custom("timestamp with time zone") } 208 | } 209 | _ => quote_spanned! { ty.span() => compile_error!("Invalid type") }, 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /oxidizer/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Oxidizer 2 | //! A simple orm based on [tokio-postgres](https://crates.io/crates/tokio-postgres) and [refinery](https://crates.io/crates/refinery) 3 | //! ```ignore 4 | //! #[async_trait] 5 | //! pub trait Entity: Sized { 6 | //! async fn save(&mut self, db: &DB) -> DBResult; 7 | //! async fn delete(&mut self, db: &DB) -> DBResult; 8 | //! 9 | //! fn from_row(row: &Row) -> Self; 10 | //! fn create_migration() -> DBResult; 11 | //! fn get_table_name() -> String; 12 | //! 13 | //! async fn find(db: &DB, query: &str, params: &'_ [&'_ (dyn ToSql + Sync)]) -> DBResult>; 14 | //! async fn first(db: &DB, query: &str, params: &'_ [&'_ (dyn ToSql + Sync)]) -> DBResult>; 15 | //! } 16 | //! ``` 17 | //! ``` 18 | //! use oxidizer::*; 19 | //! use chrono::{DateTime, Utc}; 20 | //! 21 | //! #[derive(Entity)] 22 | //! #[derive(Default)] 23 | //! pub struct MyEntity { 24 | //! #[primary_key(increments)] 25 | //! id: i32, 26 | //! 27 | //! name: String, 28 | //! 29 | //! #[indexed] 30 | //! integer: i32, 31 | //! integer64: i64, 32 | //! 33 | //! float: f32, 34 | //! double: f64, 35 | //! 36 | //! boolean: bool, 37 | //! 38 | //! datetime: Option>, 39 | //! } 40 | //! 41 | //! #[tokio::test] 42 | //! async fn test_my_entity() { 43 | //! let uri = "postgres://postgres:alkje2lkaj2e@db/postgres"; 44 | //! let max_open = 50; // mobc 45 | //! let ca_file: Option<&str> = None; 46 | //! let db = DB::connect(&uri, max_open, ca_file).await.unwrap(); 47 | //! 48 | //! db.migrate_tables(&[MyEntity::create_migration().unwrap()]).await.unwrap(); 49 | //! 50 | //! let mut entity = MyEntity::default(); 51 | //! let creating = entity.save(&db).await.unwrap(); 52 | //! assert_eq!(creating, true); 53 | //! } 54 | //! 55 | //! ``` 56 | //! 57 | //! 58 | //! ## Attributes 59 | //! 60 | //! Derive attributes can be used to create indexes, change the default table name and 61 | //! create reverse relation accessors 62 | //! 63 | //! ### #[primary_key(increments)] 64 | //! Required 65 | //! Field attribute used to mark the field as the primary key. 66 | //! `increments` will make the field integer autoincrement 67 | //! 68 | //! ``` 69 | //! use oxidizer::*; 70 | //! #[derive(Entity)] 71 | //! struct Entity { 72 | //! #[primary_key(increments)] 73 | //! id: i32 74 | //! } 75 | //! ``` 76 | //! 77 | //! ``` 78 | //! use oxidizer::*; 79 | //! #[derive(Entity)] 80 | //! struct Entity { 81 | //! #[primary_key()] 82 | //! name: String 83 | //! } 84 | //! ``` 85 | //! 86 | //! ### #[indexed] 87 | //! Make the specified field indexed in the db 88 | //! 89 | //! ``` 90 | //! use oxidizer::*; 91 | //! #[derive(Entity)] 92 | //! struct Entity { 93 | //! #[primary_key(increments)] 94 | //! id: i32, 95 | //! #[indexed] 96 | //! name: String, 97 | //! } 98 | //! ``` 99 | //! 100 | //! ### #[relation] 101 | //! See [Relations](#Relations) 102 | //! 103 | //! ### #[has_many] 104 | //! See [Relations](#Relations) 105 | //! 106 | //! ### #[entity] 107 | //! General settings for the entity struct 108 | //! 109 | //! #### table_name: String; 110 | //! Allows one to change the table name of the entity 111 | //! 112 | //! ``` 113 | //! use oxidizer::*; 114 | //! #[derive(Entity)] 115 | //! #[entity(table_name="custom_table_name")] 116 | //! struct Entity { 117 | //! #[primary_key(increments)] 118 | //! id: i32 119 | //! } 120 | //! ``` 121 | //! 122 | //! ### #[index] 123 | //! Creates a custom index/constraint on one or more column 124 | //! 125 | //! ``` 126 | //! use oxidizer::*; 127 | //! #[derive(Default, Entity)] 128 | //! #[index(name="myindex", columns="name, email", unique)] 129 | //! struct MyEntity { 130 | //! #[primary_key(increments)] 131 | //! id: i32, 132 | //! 133 | //! name: String, 134 | //! email: String, 135 | //! } 136 | //! ``` 137 | //! 138 | //! ### #[field_ignore] 139 | //! Ignores the specified field. The field type must implement the `Default` trait. 140 | //! 141 | //! ``` 142 | //! use oxidizer::*; 143 | //! #[derive(Default, Entity)] 144 | //! struct MyEntity { 145 | //! #[primary_key(increments)] 146 | //! id: i32, 147 | //! 148 | //! name: String, 149 | //! #[field_ignore] 150 | //! email: String, 151 | //! } 152 | //! ``` 153 | //! 154 | //! ### #[custom_type] 155 | //! The custom type attribute lets you override the default type provided by oxidizer. 156 | //! 157 | //! ``` 158 | //! use oxidizer::*; 159 | //! pub enum MyEnum { 160 | //! Item1, 161 | //! Item2, 162 | //! } 163 | //! 164 | //! pub enum ConvertError { 165 | //! Error 166 | //! } 167 | //! 168 | //! impl std::fmt::Display for ConvertError { 169 | //! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 170 | //! f.write_str("Error trying to convert") 171 | //! } 172 | //! } 173 | //! 174 | //! impl TryFrom<&MyEnum> for i32 { 175 | //! type Error = ConvertError; 176 | //! 177 | //! fn try_from(v: &MyEnum) -> Result { 178 | //! match v { 179 | //! MyEnum::Item1 => Ok(0), 180 | //! MyEnum::Item2 => Ok(1), 181 | //! } 182 | //! } 183 | //! } 184 | //! 185 | //! impl TryFrom for MyEnum { 186 | //! type Error = ConvertError; 187 | //! 188 | //! fn try_from(v: i32) -> Result { 189 | //! match v { 190 | //! 0 => Ok(MyEnum::Item1), 191 | //! 1 => Ok(MyEnum::Item2), 192 | //! _ => Err(ConvertError::Error), 193 | //! } 194 | //! } 195 | //! } 196 | //! 197 | //! #[derive(Entity)] 198 | //! pub struct TestCustomType { 199 | //! #[primary_key(increments)] 200 | //! id: i32, 201 | //! 202 | //! #[custom_type(ty = "i32")] 203 | //! my_enum: MyEnum, 204 | //! } 205 | //! ``` 206 | //! The custom type requires you to explicity implement the related `TryFrom` trait functions to convert between the 207 | //! actual type and the overriden type. The error type from the `TryFrom` trait must implement the `std::fmt::Display` trait 208 | //! 209 | //! 210 | //! ## Relations 211 | //! 212 | //! ### #[relation] 213 | //! Relations can be created using the `relation` attribute as in the example: 214 | //! ``` 215 | //! use oxidizer::*; 216 | //! #[derive(Entity)] 217 | //! struct Entity { 218 | //! #[primary_key(increments)] 219 | //! id: i32, 220 | //! } 221 | //! 222 | //! #[derive(Entity)] 223 | //! struct TestRelation { 224 | //! #[primary_key(increments)] 225 | //! id: i32, 226 | //! device_id: String, 227 | //! 228 | //! #[relation(model="Entity", key="id")] 229 | //! entity_id: i32, 230 | //! } 231 | //! ``` 232 | //! 233 | //! This will implement for `TestRelation` the following generated trait: 234 | //! ```ignore 235 | //! #[oxidizer::async_trait] 236 | //! pub trait __AccessorTestRelationToEntity { 237 | //! async fn get_test_entity(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult; 238 | //! async fn set_test_entity(&mut self, db: &oxidizer::db::DB, v: &Entity) -> oxidizer::db::DBResult<()>; 239 | //! } 240 | //! ``` 241 | //! 242 | //! #[has_many] 243 | //! 1-to-many or many-to-many relations can be achieved using the `has_many` attribute 244 | //! 245 | //! ### basic (1-to-many) 246 | //! 247 | //! ``` 248 | //! use oxidizer::*; 249 | //! 250 | //! #[derive(Entity)] 251 | //! #[derive(Default)] 252 | //! #[has_many(model="TargetEntity", field="entity_id")] 253 | //! pub struct Entity { 254 | //! #[primary_key(increments)] 255 | //! id: i32, 256 | //! name: String 257 | //! } 258 | //! 259 | //! #[derive(Default, Entity)] 260 | //! pub struct TargetEntity { 261 | //! #[primary_key(increments)] 262 | //! id: i32, 263 | 264 | //! #[relation(model="Entity", key="id")] 265 | //! entity_id: i32 266 | //! } 267 | //! ``` 268 | //! This will create helper functions to access all the `TargetEntity` that Entity has. 269 | //! This is what the generated trait and implementation looks like (implementaion is also generated). 270 | //! 271 | //! ```ignore 272 | //! #[oxidizer::async_trait] 273 | //! pub trait __AccessorHasManyTargetEntityToEntity { 274 | //! async fn get_all_test_entity(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult>; 275 | //! } 276 | //! ``` 277 | //! 278 | //! ### With a through table (many-to-many) 279 | //! ``` 280 | //! use oxidizer::*; 281 | //! 282 | //! #[derive(Entity)] 283 | //! #[derive(Default)] 284 | //! pub struct Entity { 285 | //! #[primary_key(increments)] 286 | //! id: i32, 287 | //! name: String 288 | //! } 289 | //! 290 | //! #[derive(Default, Entity)] 291 | //! #[has_many(model="Entity", field="entity_id", through="TestManyToMany")] 292 | //! pub struct TargetEntity { 293 | //! #[primary_key(increments)] 294 | //! id: i32, 295 | //! } 296 | //! 297 | //! #[derive(Default, Entity)] 298 | //! pub struct TestManyToMany { 299 | //! #[primary_key(increments)] 300 | //! id: i32, 301 | //! 302 | //! #[relation(model="TargetEntity", key="id")] 303 | //! target_id: i32, 304 | //! 305 | //! #[relation(model="Entity", key="id")] 306 | //! entity_id: i32, 307 | //! } 308 | //! ``` 309 | //! This will create helper functions to access the related entities. This is what the generated trait looks like (implementaion is also generated): 310 | //! ```ignore 311 | //! #[oxidizer::async_trait] 312 | //! pub trait __AccessorHasManyTargetEntityToEntity { 313 | //! async fn get_all_test_entity(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult>; 314 | //! } 315 | //! ``` 316 | //! 317 | //! 318 | 319 | pub mod db; 320 | pub use db::*; 321 | 322 | pub mod entity; 323 | pub use entity::*; 324 | 325 | pub mod migration; 326 | 327 | /// Re-export of [async_trait::async_trait](https://crates.io/crates/async-trait) 328 | pub use async_trait::async_trait; 329 | pub use tokio_postgres; 330 | pub use tokio_postgres::types as db_types; 331 | 332 | pub use barrel::types; 333 | 334 | pub use oxidizer_entity_macro::*; 335 | 336 | #[cfg(test)] 337 | mod tests_macro; 338 | 339 | #[cfg(test)] 340 | mod migrations; 341 | 342 | pub use std::convert::TryFrom; 343 | -------------------------------------------------------------------------------- /oxidizer-entity-macro/src/entity_builder.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use inflector::cases::snakecase::to_snake_case; 3 | use proc_macro::TokenStream; 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::{format_ident, quote, quote_spanned}; 6 | use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Type}; 7 | 8 | use super::attrs::HasManyAttr; 9 | use super::attrs::{EntityAttr, IndexAttr}; 10 | use super::field_extras::*; 11 | use super::props::*; 12 | use super::sql_builder::{Builder, DefaultBuilder}; 13 | 14 | pub struct EntityBuilder {} 15 | 16 | impl EntityBuilder { 17 | pub fn new() -> Self { 18 | EntityBuilder {} 19 | } 20 | 21 | fn build_save_fn(&self, props: &Props) -> TokenStream2 { 22 | let query = DefaultBuilder::build_save_query(props); 23 | 24 | let fields_value_acessors: Vec = props 25 | .get_fields_all() 26 | .map(|field| { 27 | let name = &field.ident; 28 | if let Some(ct) = field.parse_custom_type() { 29 | let ty = ct.ty; 30 | 31 | let ty_ident = format_ident!("{}", ty); 32 | 33 | return quote! { &<#ty_ident>::try_from(&self.#name)? }; 34 | } 35 | 36 | if field.parse_primary_key().is_some() && field.is_increments() { 37 | let ty = &field.ty; 38 | return quote! { 39 | &match self.#name { v if v == <#ty>::default() => None, _ => Some(self.#name) } 40 | }; 41 | } 42 | 43 | quote! { &self.#name } 44 | }) 45 | .collect(); 46 | 47 | let primary_key = props.get_primary_key_field().unwrap(); 48 | let primary_key_ident = &primary_key.ident; 49 | let primary_key_type = &primary_key.ty; 50 | 51 | quote! { 52 | async fn save(&mut self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult { 53 | let mut creating = false; 54 | let primary_key_default: #primary_key_type = Default::default(); 55 | 56 | if self.#primary_key_ident == primary_key_default { 57 | creating = true; 58 | } 59 | 60 | #query; 61 | let rows = db.query( 62 | query, 63 | &[#( #fields_value_acessors ),*] 64 | ).await?; 65 | if let Some(first_row) = rows.first() { 66 | self.#primary_key_ident = first_row.get::<&str, #primary_key_type>(stringify!(#primary_key_ident)); 67 | } else if creating { 68 | return Err(oxidizer::db::Error::Other("Error while saving entity".to_string())); 69 | } 70 | 71 | Ok(creating) 72 | } 73 | } 74 | } 75 | 76 | fn build_from_row_fn(&self, props: &Props) -> TokenStream2 { 77 | let fields_all_loaders: Vec = props 78 | .get_fields_all() 79 | .map(|field| { 80 | let name = &field.ident; 81 | 82 | let ty = field.get_type(); 83 | 84 | let mut converter = quote! {}; 85 | let mut converter_pos = quote! {}; 86 | 87 | if let Some(_) = field.parse_custom_type() { 88 | let custom_ty = &field.ty; 89 | converter = quote! { <#custom_ty>::try_from }; 90 | converter_pos = quote! {?}; 91 | } 92 | 93 | quote! { 94 | #name: #converter(row.get::<&str, #ty>(concat!(stringify!(#name))))#converter_pos, 95 | } 96 | }) 97 | .collect(); 98 | 99 | let fields_ignored_names: Vec<&Option> = props 100 | .get_ignored_fields() 101 | .map(|field| &field.ident) 102 | .collect(); 103 | let fields_ignored_types: Vec<&syn::Type> = 104 | props.get_ignored_fields().map(|field| &field.ty).collect(); 105 | 106 | quote! { 107 | fn from_row(row: &oxidizer::tokio_postgres::Row) -> oxidizer::db::DBResult { 108 | let mut obj: Self = Self{ 109 | #( #fields_all_loaders )* 110 | #( 111 | #fields_ignored_names: <#fields_ignored_types>::default(), 112 | )* 113 | }; 114 | Ok(obj) 115 | } 116 | } 117 | } 118 | 119 | fn build_create_migration_fn(&self, props: &Props) -> TokenStream2 { 120 | let table_name = props.get_table_name(); 121 | let fields_all_names = props.get_fields_all_names(); 122 | let fields_all_db_types = props.get_fields_all_db_types(); 123 | let fields_all_nullable = props.get_fields_all_nullable(); 124 | let fields_all_indexed = props.get_fields_all_indexed(); 125 | let fields_all_primary: Vec = props 126 | .get_fields_all_primary() 127 | .iter() 128 | .map(|attr| attr.is_some()) 129 | .collect(); 130 | 131 | let indexes: Vec = props 132 | .get_indexes() 133 | .iter() 134 | .map(|index| { 135 | let index_name = &index.name; 136 | let columns: Vec<&str> = index.columns.split(",").map(|c| c.trim()).collect(); 137 | let unique = index.unique; 138 | quote! { 139 | t.add_index( 140 | #index_name, 141 | oxidizer::types::index(vec![ #(#columns),* ]).unique(#unique) 142 | ); 143 | } 144 | }) 145 | .collect(); 146 | 147 | quote! { 148 | fn create_migration() -> oxidizer::db::DBResult { 149 | let mut m = oxidizer::migration::Migration::new(#table_name); 150 | m.raw.create_table(#table_name, |t| { 151 | #(t 152 | .add_column( 153 | stringify!(#fields_all_names), 154 | #fields_all_db_types 155 | .nullable(#fields_all_nullable) 156 | .indexed(#fields_all_indexed) 157 | .primary(#fields_all_primary) 158 | ) 159 | ;)* 160 | 161 | #(#indexes)* 162 | }); 163 | 164 | Ok(m) 165 | } 166 | } 167 | } 168 | 169 | fn build_find_fn(&self, props: &Props) -> TokenStream2 { 170 | let name = props.get_name(); 171 | let query = DefaultBuilder::build_find_query(props); 172 | quote! { 173 | async fn find(db: &oxidizer::db::DB, condition: &str, params: &'_ [&'_ (dyn oxidizer::db_types::ToSql + Sync)]) -> oxidizer::db::DBResult> { 174 | #query; 175 | let rows = db.query(&query, params).await?; 176 | 177 | let mut results: Vec<#name> = Vec::with_capacity(rows.len()); 178 | 179 | for row in rows.iter() { 180 | results.push(Self::from_row(row)?); 181 | } 182 | 183 | Ok(results) 184 | } 185 | } 186 | } 187 | 188 | fn build_first_fn(&self, props: &Props) -> TokenStream2 { 189 | let name = props.get_name(); 190 | let query = DefaultBuilder::build_first_query(props); 191 | quote! { 192 | async fn first(db: &oxidizer::db::DB, condition: &str, params: &'_ [&'_ (dyn oxidizer::db_types::ToSql + Sync)]) -> oxidizer::db::DBResult> { 193 | #query; 194 | let rows = db.query(&query, params).await?; 195 | 196 | let mut results: Vec<#name> = Vec::with_capacity(rows.len()); 197 | for row in rows.iter() { 198 | results.push(Self::from_row(row)?); 199 | } 200 | 201 | match results.len() { 202 | 0 => Ok(None), 203 | _ => Ok(Some(results.remove(0))), 204 | } 205 | } 206 | } 207 | } 208 | 209 | fn build_delete_fn(&self, props: &Props) -> TokenStream2 { 210 | let primary_key_ident = &props.get_primary_key_field().unwrap().ident; 211 | let primary_key_type = &props.get_primary_key_field().unwrap().ty; 212 | let query = DefaultBuilder::build_delete_query(props); 213 | quote! { 214 | async fn delete(&mut self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult { 215 | let key_default: #primary_key_type = Default::default(); 216 | if self.#primary_key_ident == key_default { 217 | return Ok(false); 218 | } 219 | 220 | #query; 221 | 222 | match db.execute(&query, &[&self.#primary_key_ident]).await? { 223 | 0 => Ok(false), 224 | _ => { 225 | self.#primary_key_ident = key_default; 226 | Ok(true) 227 | }, 228 | } 229 | } 230 | } 231 | } 232 | 233 | fn build_is_synced_with_db_fn(&self, props: &Props) -> TokenStream2 { 234 | let primary_key_ident = &props.get_primary_key_field().unwrap().ident; 235 | let primary_key_type = &props.get_primary_key_field().unwrap().ty; 236 | quote! { 237 | fn is_synced_with_db(&self) -> bool { 238 | let key_default: #primary_key_type = Default::default(); 239 | self.#primary_key_ident != key_default 240 | } 241 | } 242 | } 243 | 244 | fn build_foreign_helpers(&self, props: &Props) -> Vec { 245 | let name = props.get_name(); 246 | 247 | let foreign_fields = props.get_fields_foreign(); 248 | 249 | foreign_fields.iter().map(|field| { 250 | let relation = field.parse_relation().unwrap(); 251 | let local_key = field.ident.clone().unwrap(); 252 | let local_key_type = &field.ty; 253 | let get_ident = format_ident!("get_{}", to_snake_case(&relation.model)); 254 | let set_ident = format_ident!("set_{}", to_snake_case(&relation.model)); 255 | let trait_ident = format_ident!("__Accessor{}To{}", name, relation.model); 256 | let model = format_ident!("{}", relation.model); 257 | let key = format_ident!("{}", relation.key); 258 | 259 | let local_key_set = match field.is_nullable() { 260 | true => quote! { 261 | self.#local_key = Some(v.#key); 262 | }, 263 | false => quote! { 264 | self.#local_key = v.#key; 265 | }, 266 | }; 267 | 268 | let query = DefaultBuilder::build_relation_get_query(props, &relation); 269 | 270 | quote! { 271 | #[oxidizer::async_trait] 272 | pub trait #trait_ident { 273 | async fn #get_ident(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult<#model>; 274 | async fn #set_ident(&mut self, db: &oxidizer::db::DB, v: &#model) -> oxidizer::db::DBResult<()>; 275 | } 276 | 277 | #[oxidizer::async_trait] 278 | impl #trait_ident for #name { 279 | async fn #get_ident(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult<#model> { 280 | if self.#local_key == <#local_key_type>::default() { 281 | return Err(oxidizer::db::Error::DoesNotExist); 282 | } 283 | 284 | #query; 285 | 286 | let results = db.query(&query, &[&self.#local_key]).await?; 287 | if results.len() == 0 { 288 | return Err(oxidizer::db::Error::DoesNotExist); 289 | } 290 | 291 | #model::from_row(&results[0]) 292 | } 293 | 294 | async fn #set_ident(&mut self, db: &oxidizer::db::DB, v: &#model) -> oxidizer::db::DBResult<()> { 295 | if !v.is_synced_with_db() { 296 | return Err(oxidizer::db::Error::ReferencedModelIsNotInDB); 297 | } 298 | 299 | #local_key_set 300 | self.save(db).await?; 301 | Ok(()) 302 | } 303 | } 304 | } 305 | }).collect() 306 | } 307 | 308 | fn build_has_many_helpers(&self, props: &Props) -> Vec { 309 | let name = props.get_name(); 310 | 311 | props.get_has_many_attrs().iter().map(|attr| { 312 | let model_snake_cased = to_snake_case(&attr.model); 313 | 314 | let get_ident = format_ident!("get_all_{}", model_snake_cased); 315 | 316 | let trait_ident = format_ident!("__AccessorHasMany{}To{}", name, attr.model); 317 | 318 | let model = match attr.through.as_ref() { 319 | Some(m) => format_ident!("{}", m), 320 | None => format_ident!("{}", attr.model), 321 | }; 322 | 323 | let pk = &props.get_primary_key_field().unwrap().ident; 324 | 325 | let query = DefaultBuilder::build_relation_has_many_get_condition(props, attr); 326 | 327 | quote! { 328 | #[oxidizer::async_trait] 329 | pub trait #trait_ident { 330 | async fn #get_ident(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult>; 331 | } 332 | 333 | #[oxidizer::async_trait] 334 | impl #trait_ident for #name { 335 | async fn #get_ident(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult> { 336 | 337 | #query; 338 | 339 | <#model>::find(db, &query, &[ &self.#pk ]).await 340 | } 341 | } 342 | } 343 | 344 | }).collect() 345 | } 346 | 347 | pub fn build(&self, item: TokenStream) -> TokenStream { 348 | let input = parse_macro_input!(item as DeriveInput); 349 | 350 | let mut attrs: Option = None; 351 | 352 | let mut indexes: Vec = vec![]; 353 | 354 | let mut has_many_attrs: Vec = vec![]; 355 | 356 | for option in input.attrs.iter() { 357 | let option = option.parse_meta().unwrap(); 358 | if let Ok(v) = EntityAttr::from_meta(&option) { 359 | attrs = Some(v); 360 | } 361 | 362 | if let Ok(v) = IndexAttr::from_meta(&option) { 363 | indexes.push(v); 364 | } 365 | 366 | if let Ok(v) = HasManyAttr::from_meta(&option) { 367 | has_many_attrs.push(v); 368 | } 369 | } 370 | 371 | // eprintln!("{:#?}", input); 372 | // eprintln!("{:#?}", attrs); 373 | 374 | let props = Props::new(input, attrs, indexes, has_many_attrs); 375 | 376 | if let Some(ts) = props.check() { 377 | return ts; 378 | } 379 | 380 | let save_fn = self.build_save_fn(&props); 381 | let delete_fn = self.build_delete_fn(&props); 382 | let is_synced_with_db = self.build_is_synced_with_db_fn(&props); 383 | let from_row_fn = self.build_from_row_fn(&props); 384 | let create_migration_fn = self.build_create_migration_fn(&props); 385 | let find_fn = self.build_find_fn(&props); 386 | let first_fn = self.build_first_fn(&props); 387 | 388 | let name = props.get_name(); 389 | let table_name = props.get_table_name(); 390 | 391 | let foreign_helpers = self.build_foreign_helpers(&props); 392 | 393 | let has_many_helpers = self.build_has_many_helpers(&props); 394 | 395 | let expanded = quote! { 396 | #[oxidizer::async_trait] 397 | impl oxidizer::entity::IEntity for #name { 398 | #save_fn 399 | 400 | #delete_fn 401 | 402 | #is_synced_with_db 403 | 404 | #find_fn 405 | 406 | #first_fn 407 | 408 | #from_row_fn 409 | 410 | #create_migration_fn 411 | 412 | fn get_table_name() -> String { 413 | #table_name.to_string() 414 | } 415 | } 416 | 417 | #(#foreign_helpers)* 418 | 419 | #(#has_many_helpers)* 420 | }; 421 | 422 | // Hand the output tokens back to the compiler 423 | let r = TokenStream::from(expanded); 424 | 425 | // println!("{}", r); 426 | 427 | r 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /oxidizer/src/tests_macro.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | mod oxidizer { 4 | pub use crate::*; 5 | } 6 | 7 | use chrono::{DateTime, Utc}; 8 | 9 | #[derive(Entity, Default)] 10 | struct TestPKNoIncrements { 11 | #[primary_key()] 12 | id: i32, 13 | } 14 | 15 | #[derive(Entity, Default)] 16 | pub struct TestEntity { 17 | #[primary_key(increments)] 18 | id: i32, 19 | name: String, 20 | 21 | #[indexed] 22 | integer: i32, 23 | integer64: i64, 24 | 25 | float: f32, 26 | double: f64, 27 | 28 | boolean: bool, 29 | 30 | datetime: Option>, 31 | } 32 | 33 | #[derive(Entity)] 34 | struct TestRelation { 35 | #[primary_key(increments)] 36 | id: i32, 37 | device_id: String, 38 | 39 | #[relation(model = "TestEntity", key = "id")] 40 | entity_id: i32, 41 | } 42 | 43 | #[derive(Entity, Default)] 44 | struct TestOnlyPK { 45 | #[primary_key(increments)] 46 | id: i32, 47 | } 48 | 49 | #[derive(Default, Entity)] 50 | struct TestNullable { 51 | #[primary_key(increments)] 52 | id: i32, 53 | 54 | name: Option, 55 | } 56 | 57 | #[derive(Default, Entity)] 58 | struct TestNullableRelation { 59 | #[primary_key(increments)] 60 | id: i32, 61 | 62 | #[relation(model = "TestEntity", key = "id")] 63 | entity_id: Option, 64 | } 65 | 66 | #[derive(Default, Entity)] 67 | #[entity(table_name = "custom")] 68 | struct TestCustomTableName { 69 | #[primary_key(increments)] 70 | id: i32, 71 | } 72 | 73 | #[derive(Default, Entity)] 74 | #[entity(table_name = "custom2")] 75 | #[index(name = "myindex", columns = "name, date", unique)] 76 | #[index(name = "myindex2", columns = "email", unique)] 77 | struct TestCustomIndexes { 78 | #[primary_key(increments)] 79 | id: i32, 80 | 81 | name: String, 82 | date: String, 83 | email: String, 84 | } 85 | 86 | #[derive(Default, Entity)] 87 | pub struct TestReverseRelation { 88 | #[primary_key(increments)] 89 | id: i32, 90 | 91 | #[relation(model = "TestReverseRelationTarget", key = "id")] 92 | entity_id: i32, 93 | } 94 | 95 | #[derive(Default, Entity)] 96 | #[has_many(model = "TestReverseRelation", field = "entity_id")] 97 | #[has_many(model = "TestEntity", field = "entity_id", through = "TestManyToMany")] 98 | pub struct TestReverseRelationTarget { 99 | #[primary_key(increments)] 100 | id: i32, 101 | } 102 | 103 | #[derive(Default, Entity)] 104 | pub struct TestManyToMany { 105 | #[primary_key(increments)] 106 | id: i32, 107 | 108 | #[relation(model = "TestReverseRelationTarget", key = "id")] 109 | target_id: i32, 110 | 111 | #[relation(model = "TestEntity", key = "id")] 112 | entity_id: i32, 113 | } 114 | 115 | #[derive(Default)] 116 | pub struct TestIgnoredType { 117 | data: i32, 118 | } 119 | 120 | #[derive(Entity, Default)] 121 | pub struct TestIgnoreField { 122 | #[primary_key(increments)] 123 | id: i32, 124 | name: String, 125 | 126 | #[field_ignore] 127 | ignored: TestIgnoredType, 128 | } 129 | 130 | #[derive(PartialEq, Debug)] 131 | pub enum MyEnum { 132 | Item1, 133 | Item2, 134 | } 135 | 136 | impl Default for MyEnum { 137 | fn default() -> Self { 138 | MyEnum::Item1 139 | } 140 | } 141 | 142 | pub enum ConvertError { 143 | Error, 144 | } 145 | 146 | impl std::fmt::Display for ConvertError { 147 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 148 | f.write_str("Error trying to convert") 149 | } 150 | } 151 | 152 | impl std::convert::TryFrom<&MyEnum> for i32 { 153 | type Error = ConvertError; 154 | 155 | fn try_from(v: &MyEnum) -> Result { 156 | match v { 157 | MyEnum::Item1 => Ok(0), 158 | MyEnum::Item2 => Ok(1), 159 | } 160 | } 161 | } 162 | 163 | impl std::convert::TryFrom for MyEnum { 164 | type Error = ConvertError; 165 | 166 | fn try_from(v: i32) -> Result { 167 | match v { 168 | 0 => Ok(MyEnum::Item1), 169 | 1 => Ok(MyEnum::Item2), 170 | _ => Err(ConvertError::Error), 171 | } 172 | } 173 | } 174 | 175 | impl std::convert::TryFrom<&MyEnum> for String { 176 | type Error = ConvertError; 177 | 178 | fn try_from(v: &MyEnum) -> Result { 179 | match v { 180 | MyEnum::Item1 => Ok("item1".into()), 181 | MyEnum::Item2 => Ok("item2".into()), 182 | } 183 | } 184 | } 185 | 186 | impl std::convert::TryFrom for MyEnum { 187 | type Error = ConvertError; 188 | 189 | fn try_from(v: String) -> Result { 190 | match v.as_str() { 191 | "item1" => Ok(MyEnum::Item1), 192 | "item2" => Ok(MyEnum::Item2), 193 | _ => Err(ConvertError::Error), 194 | } 195 | } 196 | } 197 | #[derive(Entity, Default)] 198 | pub struct TestCustomType { 199 | #[primary_key(increments)] 200 | id: i32, 201 | 202 | #[custom_type(ty = "i32")] 203 | my_enum: MyEnum, 204 | } 205 | 206 | #[derive(Entity, Default)] 207 | pub struct TestCustomType2 { 208 | #[primary_key(increments)] 209 | id: i32, 210 | 211 | #[custom_type(ty = "String")] 212 | my_enum: MyEnum, 213 | } 214 | 215 | #[derive(Entity, Default)] 216 | pub struct TestCustomPrimaryKey { 217 | #[primary_key(increments = "false")] 218 | name: String, 219 | 220 | email: String, 221 | } 222 | 223 | #[derive(Entity, Default)] 224 | pub struct TestIndexedField { 225 | #[primary_key(increments)] 226 | pub id: i32, 227 | pub display_name: String, 228 | 229 | #[indexed] 230 | pub email: String, 231 | } 232 | 233 | #[derive(Entity, Default)] 234 | pub struct User { 235 | #[primary_key(increments)] 236 | pub id: i32, 237 | pub display_name: String, 238 | } 239 | 240 | #[tokio::test] 241 | async fn test_entity_macro_clean() { 242 | let _obj = TestEntity { 243 | id: 0, 244 | name: "test".to_string(), 245 | integer: 0, 246 | integer64: 0, 247 | float: 0.0, 248 | double: 0.0, 249 | boolean: false, 250 | 251 | datetime: None, 252 | }; 253 | } 254 | 255 | mod migration_modules { 256 | use super::*; 257 | use crate::create_migration_module; 258 | 259 | create_migration_module!(TestEntity); 260 | } 261 | 262 | #[tokio::test] 263 | async fn test_entity_macro_save() { 264 | let db = super::db::test_utils::create_test_db("test_entity_macro_save").await; 265 | 266 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 267 | .await 268 | .unwrap(); 269 | 270 | let mut obj = TestEntity::default(); 271 | obj.name = "test".to_string(); 272 | let creating = obj.save(&db).await.unwrap(); 273 | assert_eq!(creating, true); 274 | 275 | let creating = obj.save(&db).await.unwrap(); 276 | assert_eq!(creating, false); 277 | } 278 | 279 | #[tokio::test] 280 | async fn test_entity_macro_save_update() { 281 | let db = super::db::test_utils::create_test_db("test_entity_macro_save_update").await; 282 | 283 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 284 | .await 285 | .unwrap(); 286 | 287 | let mut obj = TestEntity::default(); 288 | obj.integer = 42; 289 | let creating = obj.save(&db).await.unwrap(); 290 | assert_eq!(creating, true); 291 | assert_eq!(obj.id, 1); 292 | 293 | let mut obj2 = TestEntity::first(&db, "integer = $1", &[&obj.integer]) 294 | .await 295 | .unwrap() 296 | .unwrap(); 297 | 298 | obj2.integer = 43; 299 | let creating = obj2.save(&db).await.unwrap(); 300 | assert_eq!(creating, false); 301 | assert_eq!(obj2.id, 1); 302 | } 303 | 304 | #[tokio::test] 305 | async fn test_entity_macro_find() { 306 | let db = super::db::test_utils::create_test_db("test_entity_macro_find").await; 307 | 308 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 309 | .await 310 | .unwrap(); 311 | 312 | let mut obj = TestEntity::default(); 313 | obj.name = "test".to_string(); 314 | let creating = obj.save(&db).await.unwrap(); 315 | assert_eq!(creating, true); 316 | 317 | let result = TestEntity::find(&db, "id = $1", &[&obj.id]).await.unwrap(); 318 | assert_eq!(result.len(), 1); 319 | } 320 | 321 | #[tokio::test] 322 | async fn test_entity_macro_first() { 323 | let db = super::db::test_utils::create_test_db("test_entity_macro_first").await; 324 | 325 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 326 | .await 327 | .unwrap(); 328 | 329 | let mut obj = TestEntity::default(); 330 | obj.name = "test".to_string(); 331 | let creating = obj.save(&db).await.unwrap(); 332 | assert_eq!(creating, true); 333 | 334 | let result = TestEntity::first(&db, "id = $1", &[&obj.id]) 335 | .await 336 | .unwrap() 337 | .unwrap(); 338 | assert_eq!(result.id, obj.id); 339 | 340 | let id: i32 = 2; 341 | let result = TestEntity::first(&db, "id = $1", &[&id]).await.unwrap(); 342 | assert!(result.is_none()) 343 | } 344 | 345 | #[tokio::test] 346 | async fn test_entity_macro_delete() { 347 | let db = super::db::test_utils::create_test_db("test_entity_macro_delete").await; 348 | 349 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 350 | .await 351 | .unwrap(); 352 | 353 | let mut obj = TestEntity::default(); 354 | obj.name = "test".to_string(); 355 | let creating = obj.save(&db).await.unwrap(); 356 | assert_eq!(creating, true); 357 | 358 | assert!(obj.delete(&db).await.unwrap()); 359 | assert_eq!(obj.id, 0); 360 | obj.id = 1; 361 | 362 | assert_eq!(false, obj.delete(&db).await.unwrap()); 363 | obj.id = 1; 364 | 365 | obj.id = 0; 366 | assert_eq!(false, obj.delete(&db).await.unwrap()); 367 | 368 | let result = TestEntity::first(&db, "id = $1", &[&obj.id]).await.unwrap(); 369 | assert!(result.is_none()) 370 | } 371 | 372 | #[tokio::test] 373 | async fn test_relation() { 374 | let db = super::db::test_utils::create_test_db("test_relation").await; 375 | 376 | db.migrate_tables(&[ 377 | TestEntity::create_migration().unwrap(), 378 | TestRelation::create_migration().unwrap(), 379 | ]) 380 | .await 381 | .unwrap(); 382 | 383 | let mut entity = TestEntity::default(); 384 | entity.name = "test".to_string(); 385 | let creating = entity.save(&db).await.unwrap(); 386 | assert_eq!(creating, true); 387 | 388 | let mut entity2 = TestEntity::default(); 389 | entity2.name = "test 2".to_string(); 390 | let creating = entity2.save(&db).await.unwrap(); 391 | assert_eq!(creating, true); 392 | 393 | let mut obj = TestRelation { 394 | id: 0, 395 | device_id: "abc12".to_string(), 396 | entity_id: entity.id, 397 | }; 398 | let creating = obj.save(&db).await.unwrap(); 399 | assert_eq!(creating, true); 400 | 401 | let loaded = obj.get_test_entity(&db).await.unwrap(); 402 | assert_eq!(entity.id, loaded.id); 403 | 404 | obj.set_test_entity(&db, &entity2).await.unwrap(); 405 | 406 | let loaded = obj.get_test_entity(&db).await.unwrap(); 407 | assert_eq!(entity2.id, loaded.id); 408 | } 409 | 410 | #[tokio::test] 411 | async fn test_nullable() { 412 | let db = super::db::test_utils::create_test_db("test_nullable").await; 413 | 414 | db.migrate_tables(&[TestNullable::create_migration().unwrap()]) 415 | .await 416 | .unwrap(); 417 | 418 | let mut obj = TestNullable::default(); 419 | let creating = obj.save(&db).await.unwrap(); 420 | assert_eq!(creating, true); 421 | 422 | assert_eq!(None, obj.name); 423 | 424 | let loaded = TestNullable::first(&db, "id = $1", &[&obj.id]) 425 | .await 426 | .unwrap() 427 | .unwrap(); 428 | assert_eq!(None, loaded.name); 429 | 430 | obj.name = Some("test".to_string()); 431 | let creating = obj.save(&db).await.unwrap(); 432 | assert_eq!(creating, false); 433 | 434 | let loaded = TestNullable::first(&db, "id = $1", &[&obj.id]) 435 | .await 436 | .unwrap() 437 | .unwrap(); 438 | assert_eq!(Some("test".to_string()), loaded.name); 439 | } 440 | 441 | #[tokio::test] 442 | async fn test_relation_nullable() { 443 | let db = super::db::test_utils::create_test_db("test_relation_nullable").await; 444 | 445 | db.migrate_tables(&[ 446 | TestEntity::create_migration().unwrap(), 447 | TestNullableRelation::create_migration().unwrap(), 448 | ]) 449 | .await 450 | .unwrap(); 451 | 452 | let mut entity = TestEntity::default(); 453 | entity.name = "test".to_string(); 454 | let creating = entity.save(&db).await.unwrap(); 455 | assert_eq!(creating, true); 456 | 457 | let mut obj = TestNullableRelation { 458 | id: 0, 459 | entity_id: None, 460 | }; 461 | let creating = obj.save(&db).await.unwrap(); 462 | assert_eq!(creating, true); 463 | 464 | assert!(obj.get_test_entity(&db).await.is_err()); 465 | 466 | obj.set_test_entity(&db, &entity).await.unwrap(); 467 | 468 | let loaded = obj.get_test_entity(&db).await.unwrap(); 469 | assert_eq!(entity.id, loaded.id); 470 | } 471 | 472 | #[tokio::test] 473 | async fn test_custom_table_name() { 474 | assert_eq!("custom", TestCustomTableName::get_table_name()); 475 | } 476 | 477 | #[tokio::test] 478 | async fn test_indexes() { 479 | let db = super::db::test_utils::create_test_db("test_indexes").await; 480 | 481 | db.migrate_tables(&[TestCustomIndexes::create_migration().unwrap()]) 482 | .await 483 | .unwrap(); 484 | 485 | let mut obj = TestCustomIndexes { 486 | id: 0, 487 | name: "test".to_string(), 488 | date: "07/19/2020".to_string(), 489 | email: "me@example.com".to_string(), 490 | }; 491 | let creating = obj.save(&db).await.unwrap(); 492 | assert_eq!(true, creating); 493 | 494 | let mut obj2 = TestCustomIndexes { 495 | id: 0, 496 | name: "test".to_string(), 497 | date: "07/19/2020".to_string(), 498 | email: "me2@example.com".to_string(), 499 | }; 500 | assert!(obj2.save(&db).await.is_err()); 501 | 502 | let mut obj2 = TestCustomIndexes { 503 | id: 0, 504 | name: "test2".to_string(), 505 | date: "07/19/2020".to_string(), 506 | email: "me2@example.com".to_string(), 507 | }; 508 | assert!(obj2.save(&db).await.is_ok()); 509 | 510 | let mut obj2 = TestCustomIndexes { 511 | id: 0, 512 | name: "test3".to_string(), 513 | date: "07/19/2020".to_string(), 514 | email: "me2@example.com".to_string(), 515 | }; 516 | assert!(obj2.save(&db).await.is_err()); 517 | 518 | let mut obj2 = TestCustomIndexes { 519 | id: 0, 520 | name: "test3".to_string(), 521 | date: "07/19/2020".to_string(), 522 | email: "me3@example.com".to_string(), 523 | }; 524 | assert!(obj2.save(&db).await.is_ok()); 525 | } 526 | 527 | #[tokio::test] 528 | async fn test_safe_migrations() { 529 | let db = super::db::test_utils::create_test_db("test_safe_migrations").await; 530 | 531 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 532 | .await 533 | .unwrap(); 534 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 535 | .await 536 | .unwrap(); 537 | 538 | #[derive(Entity)] 539 | #[entity(table_name = "test_entity")] 540 | struct TestEntityChanged { 541 | #[primary_key(increments)] 542 | id: i32, 543 | name: String, 544 | 545 | #[indexed] 546 | integer: i32, 547 | integer64: i64, 548 | 549 | float: f32, 550 | double: f64, 551 | 552 | boolean: bool, 553 | 554 | datetime: Option>, 555 | } 556 | 557 | // Hash should match 558 | db.migrate_tables(&[TestEntityChanged::create_migration().unwrap()]) 559 | .await 560 | .unwrap(); 561 | } 562 | 563 | #[tokio::test] 564 | async fn test_migrations_changed() { 565 | let db = super::db::test_utils::create_test_db("test_migrations_changed").await; 566 | 567 | db.migrate_tables(&[TestEntity::create_migration().unwrap()]) 568 | .await 569 | .unwrap(); 570 | 571 | #[derive(Entity)] 572 | #[entity(table_name = "test_entity")] 573 | struct TestEntityChanged { 574 | #[primary_key(increments)] 575 | id: i32, 576 | name: String, 577 | 578 | #[indexed] 579 | integer: i32, 580 | integer64: i64, 581 | 582 | float: f32, 583 | double: f64, 584 | 585 | boolean: bool, 586 | 587 | datetime: Option>, 588 | 589 | new_field: bool, 590 | } 591 | 592 | db.migrate_tables(&[TestEntityChanged::create_migration().unwrap()]) 593 | .await 594 | .unwrap(); 595 | } 596 | 597 | #[tokio::test] 598 | async fn test_migrations_module() { 599 | let migration = migration_modules::migration(); 600 | assert_eq!(TestEntity::create_migration().unwrap().make(), migration); 601 | } 602 | 603 | #[tokio::test] 604 | async fn test_migrations_module_fs() { 605 | let db = super::db::test_utils::create_test_db("test_migrations_module_fs").await; 606 | 607 | let runner = super::migrations::runner(); 608 | let report = db.migrate(runner).await.unwrap(); 609 | assert_eq!(1, report.applied_migrations().len()); 610 | 611 | let mut entity = TestEntity::default(); 612 | entity.name = "test".to_string(); 613 | entity.save(&db).await.unwrap(); 614 | } 615 | 616 | #[tokio::test] 617 | async fn test_relation_has_many() { 618 | let db = super::db::test_utils::create_test_db("test_relation_has_many").await; 619 | 620 | db.migrate_tables(&[ 621 | TestReverseRelationTarget::create_migration().unwrap(), 622 | TestReverseRelation::create_migration().unwrap(), 623 | ]) 624 | .await 625 | .unwrap(); 626 | 627 | let mut target = TestReverseRelationTarget::default(); 628 | let creating = target.save(&db).await.unwrap(); 629 | assert_eq!(creating, true); 630 | 631 | let mut entity = TestReverseRelation::default(); 632 | entity.entity_id = target.id; 633 | let creating = entity.save(&db).await.unwrap(); 634 | assert_eq!(creating, true); 635 | 636 | let mut entity2 = TestReverseRelation::default(); 637 | entity2.entity_id = target.id; 638 | let creating = entity2.save(&db).await.unwrap(); 639 | assert_eq!(creating, true); 640 | 641 | let loaded = target.get_all_test_reverse_relation(&db).await.unwrap(); 642 | assert_eq!(2, loaded.len()); 643 | 644 | assert_eq!(entity.id, loaded[0].id); 645 | assert_eq!(entity2.id, loaded[1].id); 646 | } 647 | 648 | #[tokio::test] 649 | async fn test_many_to_many() { 650 | let db = super::db::test_utils::create_test_db("test_many_to_many").await; 651 | 652 | db.migrate_tables(&[ 653 | TestEntity::create_migration().unwrap(), 654 | TestReverseRelationTarget::create_migration().unwrap(), 655 | TestManyToMany::create_migration().unwrap(), 656 | ]) 657 | .await 658 | .unwrap(); 659 | 660 | let mut target = TestReverseRelationTarget::default(); 661 | let creating = target.save(&db).await.unwrap(); 662 | assert_eq!(creating, true); 663 | 664 | let mut entity = TestEntity::default(); 665 | let creating = entity.save(&db).await.unwrap(); 666 | assert_eq!(creating, true); 667 | 668 | let mut m2m = TestManyToMany::default(); 669 | m2m.entity_id = entity.id; 670 | m2m.target_id = target.id; 671 | let creating = m2m.save(&db).await.unwrap(); 672 | assert_eq!(creating, true); 673 | 674 | let loaded_entity = target.get_all_test_entity(&db).await.unwrap(); 675 | assert_eq!(1, loaded_entity.len()); 676 | 677 | assert_eq!(entity.id, loaded_entity[0].entity_id); 678 | } 679 | 680 | #[tokio::test] 681 | async fn test_entity_field_ignore() { 682 | let db = super::db::test_utils::create_test_db("test_entity_field_ignore").await; 683 | 684 | db.migrate_tables(&[TestIgnoreField::create_migration().unwrap()]) 685 | .await 686 | .unwrap(); 687 | 688 | let mut obj = TestIgnoreField::default(); 689 | obj.name = "test".to_string(); 690 | let creating = obj.save(&db).await.unwrap(); 691 | assert_eq!(creating, true); 692 | 693 | let creating = obj.save(&db).await.unwrap(); 694 | assert_eq!(creating, false); 695 | } 696 | 697 | #[tokio::test] 698 | async fn test_entity_custom_type() { 699 | let db = super::db::test_utils::create_test_db("test_entity_custom_type").await; 700 | 701 | db.migrate_tables(&[TestCustomType::create_migration().unwrap()]) 702 | .await 703 | .unwrap(); 704 | 705 | let mut obj = TestCustomType::default(); 706 | obj.my_enum = MyEnum::Item2; 707 | let creating = obj.save(&db).await.unwrap(); 708 | assert_eq!(creating, true); 709 | 710 | let creating = obj.save(&db).await.unwrap(); 711 | assert_eq!(creating, false); 712 | 713 | let result = TestCustomType::first(&db, "id = $1", &[&obj.id]) 714 | .await 715 | .unwrap() 716 | .unwrap(); 717 | assert_eq!(result.my_enum, MyEnum::Item2); 718 | } 719 | 720 | #[tokio::test] 721 | async fn test_entity_custom_type_error() { 722 | let db = super::db::test_utils::create_test_db("test_entity_custom_type_error").await; 723 | 724 | db.migrate_tables(&[TestCustomType::create_migration().unwrap()]) 725 | .await 726 | .unwrap(); 727 | 728 | let mut obj = TestCustomType::default(); 729 | obj.my_enum = MyEnum::Item2; 730 | let creating = obj.save(&db).await.unwrap(); 731 | assert_eq!(creating, true); 732 | 733 | let query = format!( 734 | "update {} set my_enum = $1", 735 | TestCustomType::get_table_name() 736 | ); 737 | let value: i32 = 33; 738 | db.execute(&query, &[&value]).await.unwrap(); 739 | 740 | let result = TestCustomType::first(&db, "id = $1", &[&obj.id]).await; 741 | assert_eq!(true, result.is_err()); 742 | 743 | let query = format!( 744 | "update {} set my_enum = $1", 745 | TestCustomType::get_table_name() 746 | ); 747 | let value: i32 = 0; 748 | db.execute(&query, &[&value]).await.unwrap(); 749 | 750 | let result = TestCustomType::first(&db, "id = $1", &[&obj.id]).await; 751 | assert_eq!(true, result.is_ok()); 752 | } 753 | 754 | #[tokio::test] 755 | async fn test_entity_custom_type_2() { 756 | let db = super::db::test_utils::create_test_db("test_entity_custom_type_2").await; 757 | 758 | db.migrate_tables(&[TestCustomType2::create_migration().unwrap()]) 759 | .await 760 | .unwrap(); 761 | 762 | let mut obj = TestCustomType2::default(); 763 | obj.my_enum = MyEnum::Item2; 764 | let creating = obj.save(&db).await.unwrap(); 765 | assert_eq!(creating, true); 766 | 767 | let creating = obj.save(&db).await.unwrap(); 768 | assert_eq!(creating, false); 769 | 770 | let result = TestCustomType2::first(&db, "id = $1", &[&obj.id]) 771 | .await 772 | .unwrap() 773 | .unwrap(); 774 | assert_eq!(result.my_enum, MyEnum::Item2); 775 | } 776 | 777 | #[tokio::test] 778 | async fn test_only_pk() { 779 | let db = super::db::test_utils::create_test_db("test_only_pk").await; 780 | 781 | db.migrate_tables(&[TestOnlyPK::create_migration().unwrap()]) 782 | .await 783 | .unwrap(); 784 | 785 | let mut obj = TestOnlyPK::default(); 786 | let creating = obj.save(&db).await.unwrap(); 787 | assert_eq!(creating, true); 788 | 789 | let creating = obj.save(&db).await.unwrap(); 790 | assert_eq!(creating, false); 791 | 792 | let result = TestOnlyPK::first(&db, "id = $1", &[&obj.id]) 793 | .await 794 | .unwrap() 795 | .unwrap(); 796 | assert_eq!(result.id, obj.id); 797 | } 798 | 799 | #[tokio::test] 800 | async fn test_custom_primary_key() { 801 | let db = super::db::test_utils::create_test_db("test_custom_primary_key").await; 802 | 803 | db.migrate_tables(&[TestCustomPrimaryKey::create_migration().unwrap()]) 804 | .await 805 | .unwrap(); 806 | 807 | let mut obj = TestCustomPrimaryKey::default(); 808 | obj.name = "hello".to_string(); 809 | obj.email = "world".to_string(); 810 | let creating = obj.save(&db).await.unwrap(); 811 | assert_eq!(creating, false); 812 | 813 | let creating = obj.save(&db).await.unwrap(); 814 | assert_eq!(creating, false); 815 | 816 | let result = TestCustomPrimaryKey::first(&db, "name = $1", &[&obj.name]) 817 | .await 818 | .unwrap() 819 | .unwrap(); 820 | assert_eq!(result.email, obj.email); 821 | } 822 | 823 | #[tokio::test] 824 | async fn test_pk_no_increments() { 825 | let db = super::db::test_utils::create_test_db("test_pk_no_increments").await; 826 | 827 | db.migrate_tables(&[TestPKNoIncrements::create_migration().unwrap()]) 828 | .await 829 | .unwrap(); 830 | 831 | let mut obj = TestPKNoIncrements::default(); 832 | obj.id = 1; 833 | obj.save(&db).await.unwrap(); 834 | 835 | obj.id = 2; 836 | obj.save(&db).await.unwrap(); 837 | 838 | let results = TestPKNoIncrements::find(&db, "true", &[]).await.unwrap(); 839 | assert_eq!(results.len(), 2); 840 | } 841 | 842 | #[tokio::test] 843 | async fn test_indexed_field() { 844 | let db = super::db::test_utils::create_test_db("test_indexed_field").await; 845 | 846 | db.migrate_tables(&[TestIndexedField::create_migration().unwrap()]) 847 | .await 848 | .unwrap(); 849 | 850 | let mut obj = TestIndexedField::default(); 851 | obj.id = 12345; 852 | obj.display_name = "test".to_string(); 853 | 854 | obj.save(&db).await.unwrap(); 855 | 856 | let obj2 = TestIndexedField::first(&db, "id = 12345", &[]) 857 | .await 858 | .unwrap() 859 | .unwrap(); 860 | 861 | assert_eq!(obj.display_name, obj2.display_name); 862 | } 863 | 864 | #[tokio::test] 865 | async fn test_reserved_table_names() { 866 | let db = super::db::test_utils::create_test_db("test_reserved_table_names").await; 867 | 868 | db.migrate_tables(&[User::create_migration().unwrap()]) 869 | .await 870 | .unwrap(); 871 | 872 | let mut obj = User::default(); 873 | obj.id = 12345; 874 | obj.display_name = "test".to_string(); 875 | 876 | obj.save(&db).await.unwrap(); 877 | 878 | let obj2 = User::first(&db, "id = 12345", &[]).await.unwrap().unwrap(); 879 | 880 | assert_eq!(obj.display_name, obj2.display_name); 881 | } 882 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "Inflector" 5 | version = "0.11.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 8 | dependencies = [ 9 | "lazy_static", 10 | "regex", 11 | ] 12 | 13 | [[package]] 14 | name = "aho-corasick" 15 | version = "0.7.13" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 18 | dependencies = [ 19 | "memchr", 20 | ] 21 | 22 | [[package]] 23 | name = "arc-swap" 24 | version = "0.4.7" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" 27 | 28 | [[package]] 29 | name = "async-trait" 30 | version = "0.1.50" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" 33 | dependencies = [ 34 | "proc-macro2", 35 | "quote", 36 | "syn", 37 | ] 38 | 39 | [[package]] 40 | name = "autocfg" 41 | version = "1.0.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 44 | 45 | [[package]] 46 | name = "barrel" 47 | version = "0.6.5" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "9d67c978b1322c8031145b1f6c236fc371292f52c565bc96018b2971afcbffe1" 50 | 51 | [[package]] 52 | name = "base64" 53 | version = "0.12.3" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 56 | 57 | [[package]] 58 | name = "bitflags" 59 | version = "1.2.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 62 | 63 | [[package]] 64 | name = "block-buffer" 65 | version = "0.9.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 68 | dependencies = [ 69 | "generic-array", 70 | ] 71 | 72 | [[package]] 73 | name = "bumpalo" 74 | version = "3.4.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 77 | 78 | [[package]] 79 | name = "byteorder" 80 | version = "1.3.4" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 83 | 84 | [[package]] 85 | name = "bytes" 86 | version = "0.5.6" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 89 | 90 | [[package]] 91 | name = "cc" 92 | version = "1.0.58" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" 95 | 96 | [[package]] 97 | name = "cfg-if" 98 | version = "0.1.10" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "1.0.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 107 | 108 | [[package]] 109 | name = "chrono" 110 | version = "0.4.19" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 113 | dependencies = [ 114 | "libc", 115 | "num-integer", 116 | "num-traits", 117 | "time", 118 | "winapi 0.3.9", 119 | ] 120 | 121 | [[package]] 122 | name = "cloudabi" 123 | version = "0.1.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" 126 | dependencies = [ 127 | "bitflags", 128 | ] 129 | 130 | [[package]] 131 | name = "cpuid-bool" 132 | version = "0.1.2" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 135 | 136 | [[package]] 137 | name = "crypto-mac" 138 | version = "0.8.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" 141 | dependencies = [ 142 | "generic-array", 143 | "subtle", 144 | ] 145 | 146 | [[package]] 147 | name = "darling" 148 | version = "0.12.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" 151 | dependencies = [ 152 | "darling_core", 153 | "darling_macro", 154 | ] 155 | 156 | [[package]] 157 | name = "darling_core" 158 | version = "0.12.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" 161 | dependencies = [ 162 | "fnv", 163 | "ident_case", 164 | "proc-macro2", 165 | "quote", 166 | "strsim", 167 | "syn", 168 | ] 169 | 170 | [[package]] 171 | name = "darling_macro" 172 | version = "0.12.4" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" 175 | dependencies = [ 176 | "darling_core", 177 | "quote", 178 | "syn", 179 | ] 180 | 181 | [[package]] 182 | name = "digest" 183 | version = "0.9.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 186 | dependencies = [ 187 | "generic-array", 188 | ] 189 | 190 | [[package]] 191 | name = "fallible-iterator" 192 | version = "0.2.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 195 | 196 | [[package]] 197 | name = "fnv" 198 | version = "1.0.7" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 201 | 202 | [[package]] 203 | name = "foreign-types" 204 | version = "0.3.2" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 207 | dependencies = [ 208 | "foreign-types-shared", 209 | ] 210 | 211 | [[package]] 212 | name = "foreign-types-shared" 213 | version = "0.1.1" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 216 | 217 | [[package]] 218 | name = "fuchsia-zircon" 219 | version = "0.3.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 222 | dependencies = [ 223 | "bitflags", 224 | "fuchsia-zircon-sys", 225 | ] 226 | 227 | [[package]] 228 | name = "fuchsia-zircon-sys" 229 | version = "0.3.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 232 | 233 | [[package]] 234 | name = "futures" 235 | version = "0.3.5" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" 238 | dependencies = [ 239 | "futures-channel", 240 | "futures-core", 241 | "futures-executor", 242 | "futures-io", 243 | "futures-sink", 244 | "futures-task", 245 | "futures-util", 246 | ] 247 | 248 | [[package]] 249 | name = "futures-channel" 250 | version = "0.3.5" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 253 | dependencies = [ 254 | "futures-core", 255 | "futures-sink", 256 | ] 257 | 258 | [[package]] 259 | name = "futures-core" 260 | version = "0.3.5" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 263 | 264 | [[package]] 265 | name = "futures-executor" 266 | version = "0.3.5" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" 269 | dependencies = [ 270 | "futures-core", 271 | "futures-task", 272 | "futures-util", 273 | ] 274 | 275 | [[package]] 276 | name = "futures-io" 277 | version = "0.3.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 280 | 281 | [[package]] 282 | name = "futures-macro" 283 | version = "0.3.5" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" 286 | dependencies = [ 287 | "proc-macro-hack", 288 | "proc-macro2", 289 | "quote", 290 | "syn", 291 | ] 292 | 293 | [[package]] 294 | name = "futures-sink" 295 | version = "0.3.5" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 298 | 299 | [[package]] 300 | name = "futures-task" 301 | version = "0.3.5" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 304 | dependencies = [ 305 | "once_cell", 306 | ] 307 | 308 | [[package]] 309 | name = "futures-timer" 310 | version = "3.0.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" 313 | 314 | [[package]] 315 | name = "futures-util" 316 | version = "0.3.5" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 319 | dependencies = [ 320 | "futures-channel", 321 | "futures-core", 322 | "futures-io", 323 | "futures-macro", 324 | "futures-sink", 325 | "futures-task", 326 | "memchr", 327 | "pin-project", 328 | "pin-utils", 329 | "proc-macro-hack", 330 | "proc-macro-nested", 331 | "slab", 332 | ] 333 | 334 | [[package]] 335 | name = "generic-array" 336 | version = "0.14.3" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63" 339 | dependencies = [ 340 | "typenum", 341 | "version_check", 342 | ] 343 | 344 | [[package]] 345 | name = "getrandom" 346 | version = "0.1.14" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 349 | dependencies = [ 350 | "cfg-if 0.1.10", 351 | "libc", 352 | "wasi", 353 | ] 354 | 355 | [[package]] 356 | name = "hermit-abi" 357 | version = "0.1.15" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 360 | dependencies = [ 361 | "libc", 362 | ] 363 | 364 | [[package]] 365 | name = "hmac" 366 | version = "0.8.1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" 369 | dependencies = [ 370 | "crypto-mac", 371 | "digest", 372 | ] 373 | 374 | [[package]] 375 | name = "ident_case" 376 | version = "1.0.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 379 | 380 | [[package]] 381 | name = "idna" 382 | version = "0.2.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 385 | dependencies = [ 386 | "matches", 387 | "unicode-bidi", 388 | "unicode-normalization", 389 | ] 390 | 391 | [[package]] 392 | name = "instant" 393 | version = "0.1.6" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" 396 | 397 | [[package]] 398 | name = "iovec" 399 | version = "0.1.4" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 402 | dependencies = [ 403 | "libc", 404 | ] 405 | 406 | [[package]] 407 | name = "js-sys" 408 | version = "0.3.45" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" 411 | dependencies = [ 412 | "wasm-bindgen", 413 | ] 414 | 415 | [[package]] 416 | name = "kernel32-sys" 417 | version = "0.2.2" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 420 | dependencies = [ 421 | "winapi 0.2.8", 422 | "winapi-build", 423 | ] 424 | 425 | [[package]] 426 | name = "lazy_static" 427 | version = "1.4.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 430 | 431 | [[package]] 432 | name = "libc" 433 | version = "0.2.74" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" 436 | 437 | [[package]] 438 | name = "lock_api" 439 | version = "0.4.1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" 442 | dependencies = [ 443 | "scopeguard", 444 | ] 445 | 446 | [[package]] 447 | name = "log" 448 | version = "0.4.11" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 451 | dependencies = [ 452 | "cfg-if 0.1.10", 453 | ] 454 | 455 | [[package]] 456 | name = "matches" 457 | version = "0.1.8" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 460 | 461 | [[package]] 462 | name = "md5" 463 | version = "0.7.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 466 | 467 | [[package]] 468 | name = "memchr" 469 | version = "2.3.3" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 472 | 473 | [[package]] 474 | name = "mio" 475 | version = "0.6.22" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 478 | dependencies = [ 479 | "cfg-if 0.1.10", 480 | "fuchsia-zircon", 481 | "fuchsia-zircon-sys", 482 | "iovec", 483 | "kernel32-sys", 484 | "libc", 485 | "log", 486 | "miow 0.2.1", 487 | "net2", 488 | "slab", 489 | "winapi 0.2.8", 490 | ] 491 | 492 | [[package]] 493 | name = "mio-named-pipes" 494 | version = "0.1.7" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" 497 | dependencies = [ 498 | "log", 499 | "mio", 500 | "miow 0.3.5", 501 | "winapi 0.3.9", 502 | ] 503 | 504 | [[package]] 505 | name = "mio-uds" 506 | version = "0.6.8" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 509 | dependencies = [ 510 | "iovec", 511 | "libc", 512 | "mio", 513 | ] 514 | 515 | [[package]] 516 | name = "miow" 517 | version = "0.2.1" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 520 | dependencies = [ 521 | "kernel32-sys", 522 | "net2", 523 | "winapi 0.2.8", 524 | "ws2_32-sys", 525 | ] 526 | 527 | [[package]] 528 | name = "miow" 529 | version = "0.3.5" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" 532 | dependencies = [ 533 | "socket2", 534 | "winapi 0.3.9", 535 | ] 536 | 537 | [[package]] 538 | name = "mobc" 539 | version = "0.5.12" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "d6813d7da9f2399927e4f83d741165869376b5b3b7d9a09945713b48ce4241cf" 542 | dependencies = [ 543 | "async-trait", 544 | "futures", 545 | "futures-timer", 546 | "log", 547 | "tokio", 548 | ] 549 | 550 | [[package]] 551 | name = "net2" 552 | version = "0.2.34" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 555 | dependencies = [ 556 | "cfg-if 0.1.10", 557 | "libc", 558 | "winapi 0.3.9", 559 | ] 560 | 561 | [[package]] 562 | name = "num-integer" 563 | version = "0.1.43" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 566 | dependencies = [ 567 | "autocfg", 568 | "num-traits", 569 | ] 570 | 571 | [[package]] 572 | name = "num-traits" 573 | version = "0.2.12" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 576 | dependencies = [ 577 | "autocfg", 578 | ] 579 | 580 | [[package]] 581 | name = "num_cpus" 582 | version = "1.13.0" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 585 | dependencies = [ 586 | "hermit-abi", 587 | "libc", 588 | ] 589 | 590 | [[package]] 591 | name = "once_cell" 592 | version = "1.4.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 595 | 596 | [[package]] 597 | name = "opaque-debug" 598 | version = "0.3.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 601 | 602 | [[package]] 603 | name = "openssl" 604 | version = "0.10.32" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" 607 | dependencies = [ 608 | "bitflags", 609 | "cfg-if 1.0.0", 610 | "foreign-types", 611 | "lazy_static", 612 | "libc", 613 | "openssl-sys", 614 | ] 615 | 616 | [[package]] 617 | name = "openssl-src" 618 | version = "111.10.2+1.1.1g" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "a287fdb22e32b5b60624d4a5a7a02dbe82777f730ec0dbc42a0554326fef5a70" 621 | dependencies = [ 622 | "cc", 623 | ] 624 | 625 | [[package]] 626 | name = "openssl-sys" 627 | version = "0.9.60" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" 630 | dependencies = [ 631 | "autocfg", 632 | "cc", 633 | "libc", 634 | "openssl-src", 635 | "pkg-config", 636 | "vcpkg", 637 | ] 638 | 639 | [[package]] 640 | name = "oxidizer" 641 | version = "0.2.2" 642 | dependencies = [ 643 | "async-trait", 644 | "barrel", 645 | "cfg-if 1.0.0", 646 | "chrono", 647 | "mobc", 648 | "openssl", 649 | "oxidizer-entity-macro", 650 | "postgres-openssl", 651 | "refinery", 652 | "rustls", 653 | "tokio", 654 | "tokio-postgres", 655 | "tokio-postgres-rustls", 656 | ] 657 | 658 | [[package]] 659 | name = "oxidizer-entity-macro" 660 | version = "0.2.2" 661 | dependencies = [ 662 | "Inflector", 663 | "async-trait", 664 | "darling", 665 | "proc-macro2", 666 | "quote", 667 | "syn", 668 | ] 669 | 670 | [[package]] 671 | name = "oxidizer-tests" 672 | version = "0.2.1" 673 | dependencies = [ 674 | "oxidizer", 675 | "tokio", 676 | ] 677 | 678 | [[package]] 679 | name = "parking_lot" 680 | version = "0.11.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" 683 | dependencies = [ 684 | "instant", 685 | "lock_api", 686 | "parking_lot_core", 687 | ] 688 | 689 | [[package]] 690 | name = "parking_lot_core" 691 | version = "0.8.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" 694 | dependencies = [ 695 | "cfg-if 0.1.10", 696 | "cloudabi", 697 | "instant", 698 | "libc", 699 | "redox_syscall", 700 | "smallvec", 701 | "winapi 0.3.9", 702 | ] 703 | 704 | [[package]] 705 | name = "percent-encoding" 706 | version = "2.1.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 709 | 710 | [[package]] 711 | name = "phf" 712 | version = "0.8.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 715 | dependencies = [ 716 | "phf_shared", 717 | ] 718 | 719 | [[package]] 720 | name = "phf_shared" 721 | version = "0.8.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 724 | dependencies = [ 725 | "siphasher", 726 | ] 727 | 728 | [[package]] 729 | name = "pin-project" 730 | version = "0.4.23" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" 733 | dependencies = [ 734 | "pin-project-internal", 735 | ] 736 | 737 | [[package]] 738 | name = "pin-project-internal" 739 | version = "0.4.23" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" 742 | dependencies = [ 743 | "proc-macro2", 744 | "quote", 745 | "syn", 746 | ] 747 | 748 | [[package]] 749 | name = "pin-project-lite" 750 | version = "0.1.7" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" 753 | 754 | [[package]] 755 | name = "pin-utils" 756 | version = "0.1.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 759 | 760 | [[package]] 761 | name = "pkg-config" 762 | version = "0.3.18" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" 765 | 766 | [[package]] 767 | name = "postgres-openssl" 768 | version = "0.3.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "9f10ea2d77c744e1846d03b58362679e51c6647acf0e6a65a1f1e5f0d5e99387" 771 | dependencies = [ 772 | "bytes", 773 | "futures", 774 | "openssl", 775 | "tokio", 776 | "tokio-openssl", 777 | "tokio-postgres", 778 | ] 779 | 780 | [[package]] 781 | name = "postgres-protocol" 782 | version = "0.5.2" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "81c5b25980f9a9b5ad36e9cdc855530575396d8a57f67e14691a2440ed0d9a90" 785 | dependencies = [ 786 | "base64", 787 | "byteorder", 788 | "bytes", 789 | "fallible-iterator", 790 | "hmac", 791 | "md5", 792 | "memchr", 793 | "rand", 794 | "sha2", 795 | "stringprep", 796 | ] 797 | 798 | [[package]] 799 | name = "postgres-types" 800 | version = "0.1.2" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "3d14b0a4f433b0e0b565bb0fbc0ac9fc3d79ca338ba265ad0e7eef0f3bcc5e94" 803 | dependencies = [ 804 | "bytes", 805 | "chrono", 806 | "fallible-iterator", 807 | "postgres-protocol", 808 | ] 809 | 810 | [[package]] 811 | name = "ppv-lite86" 812 | version = "0.2.8" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 815 | 816 | [[package]] 817 | name = "proc-macro-hack" 818 | version = "0.5.18" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" 821 | 822 | [[package]] 823 | name = "proc-macro-nested" 824 | version = "0.1.6" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" 827 | 828 | [[package]] 829 | name = "proc-macro2" 830 | version = "1.0.26" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" 833 | dependencies = [ 834 | "unicode-xid", 835 | ] 836 | 837 | [[package]] 838 | name = "quote" 839 | version = "1.0.9" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 842 | dependencies = [ 843 | "proc-macro2", 844 | ] 845 | 846 | [[package]] 847 | name = "rand" 848 | version = "0.7.3" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 851 | dependencies = [ 852 | "getrandom", 853 | "libc", 854 | "rand_chacha", 855 | "rand_core", 856 | "rand_hc", 857 | ] 858 | 859 | [[package]] 860 | name = "rand_chacha" 861 | version = "0.2.2" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 864 | dependencies = [ 865 | "ppv-lite86", 866 | "rand_core", 867 | ] 868 | 869 | [[package]] 870 | name = "rand_core" 871 | version = "0.5.1" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 874 | dependencies = [ 875 | "getrandom", 876 | ] 877 | 878 | [[package]] 879 | name = "rand_hc" 880 | version = "0.2.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 883 | dependencies = [ 884 | "rand_core", 885 | ] 886 | 887 | [[package]] 888 | name = "redox_syscall" 889 | version = "0.1.57" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 892 | 893 | [[package]] 894 | name = "refinery" 895 | version = "0.4.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "3d34d324a0627219489ebdda4ddad5397902fb9d38777bf499223373aeaaba91" 898 | dependencies = [ 899 | "refinery-core", 900 | "refinery-macros", 901 | ] 902 | 903 | [[package]] 904 | name = "refinery-core" 905 | version = "0.4.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "eed7c0812dfc4ec2596a474e175002cc99576e8c78dd0a2c41aea5a451ad0216" 908 | dependencies = [ 909 | "async-trait", 910 | "cfg-if 0.1.10", 911 | "chrono", 912 | "lazy_static", 913 | "log", 914 | "regex", 915 | "serde", 916 | "siphasher", 917 | "thiserror", 918 | "tokio-postgres", 919 | "toml", 920 | "url", 921 | "walkdir", 922 | ] 923 | 924 | [[package]] 925 | name = "refinery-macros" 926 | version = "0.4.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "4a80f45b9c4c99b4268ce14305f16b129f6c40b392c9fcaa350da5e805ad7c64" 929 | dependencies = [ 930 | "proc-macro2", 931 | "quote", 932 | "refinery-core", 933 | "regex", 934 | "syn", 935 | ] 936 | 937 | [[package]] 938 | name = "regex" 939 | version = "1.3.9" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 942 | dependencies = [ 943 | "aho-corasick", 944 | "memchr", 945 | "regex-syntax", 946 | "thread_local", 947 | ] 948 | 949 | [[package]] 950 | name = "regex-syntax" 951 | version = "0.6.18" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 954 | 955 | [[package]] 956 | name = "ring" 957 | version = "0.16.15" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4" 960 | dependencies = [ 961 | "cc", 962 | "libc", 963 | "once_cell", 964 | "spin", 965 | "untrusted", 966 | "web-sys", 967 | "winapi 0.3.9", 968 | ] 969 | 970 | [[package]] 971 | name = "rustls" 972 | version = "0.18.1" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" 975 | dependencies = [ 976 | "base64", 977 | "log", 978 | "ring", 979 | "sct", 980 | "webpki", 981 | ] 982 | 983 | [[package]] 984 | name = "same-file" 985 | version = "1.0.6" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 988 | dependencies = [ 989 | "winapi-util", 990 | ] 991 | 992 | [[package]] 993 | name = "scopeguard" 994 | version = "1.1.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 997 | 998 | [[package]] 999 | name = "sct" 1000 | version = "0.6.0" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" 1003 | dependencies = [ 1004 | "ring", 1005 | "untrusted", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "serde" 1010 | version = "1.0.114" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 1013 | dependencies = [ 1014 | "serde_derive", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "serde_derive" 1019 | version = "1.0.114" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 1022 | dependencies = [ 1023 | "proc-macro2", 1024 | "quote", 1025 | "syn", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "sha2" 1030 | version = "0.9.1" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" 1033 | dependencies = [ 1034 | "block-buffer", 1035 | "cfg-if 0.1.10", 1036 | "cpuid-bool", 1037 | "digest", 1038 | "opaque-debug", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "signal-hook-registry" 1043 | version = "1.2.0" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 1046 | dependencies = [ 1047 | "arc-swap", 1048 | "libc", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "siphasher" 1053 | version = "0.3.3" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" 1056 | 1057 | [[package]] 1058 | name = "slab" 1059 | version = "0.4.2" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1062 | 1063 | [[package]] 1064 | name = "smallvec" 1065 | version = "1.4.1" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" 1068 | 1069 | [[package]] 1070 | name = "socket2" 1071 | version = "0.3.12" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" 1074 | dependencies = [ 1075 | "cfg-if 0.1.10", 1076 | "libc", 1077 | "redox_syscall", 1078 | "winapi 0.3.9", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "spin" 1083 | version = "0.5.2" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1086 | 1087 | [[package]] 1088 | name = "stringprep" 1089 | version = "0.1.2" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1092 | dependencies = [ 1093 | "unicode-bidi", 1094 | "unicode-normalization", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "strsim" 1099 | version = "0.10.0" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1102 | 1103 | [[package]] 1104 | name = "subtle" 1105 | version = "2.2.3" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" 1108 | 1109 | [[package]] 1110 | name = "syn" 1111 | version = "1.0.71" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" 1114 | dependencies = [ 1115 | "proc-macro2", 1116 | "quote", 1117 | "unicode-xid", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "thiserror" 1122 | version = "1.0.20" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" 1125 | dependencies = [ 1126 | "thiserror-impl", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "thiserror-impl" 1131 | version = "1.0.20" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" 1134 | dependencies = [ 1135 | "proc-macro2", 1136 | "quote", 1137 | "syn", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "thread_local" 1142 | version = "1.0.1" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1145 | dependencies = [ 1146 | "lazy_static", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "time" 1151 | version = "0.1.43" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1154 | dependencies = [ 1155 | "libc", 1156 | "winapi 0.3.9", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "tinyvec" 1161 | version = "0.3.3" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" 1164 | 1165 | [[package]] 1166 | name = "tokio" 1167 | version = "0.2.22" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" 1170 | dependencies = [ 1171 | "bytes", 1172 | "fnv", 1173 | "futures-core", 1174 | "iovec", 1175 | "lazy_static", 1176 | "libc", 1177 | "memchr", 1178 | "mio", 1179 | "mio-named-pipes", 1180 | "mio-uds", 1181 | "num_cpus", 1182 | "pin-project-lite", 1183 | "signal-hook-registry", 1184 | "slab", 1185 | "tokio-macros", 1186 | "winapi 0.3.9", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "tokio-macros" 1191 | version = "0.2.5" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" 1194 | dependencies = [ 1195 | "proc-macro2", 1196 | "quote", 1197 | "syn", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "tokio-openssl" 1202 | version = "0.4.0" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "3c4b08c5f4208e699ede3df2520aca2e82401b2de33f45e96696a074480be594" 1205 | dependencies = [ 1206 | "openssl", 1207 | "tokio", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "tokio-postgres" 1212 | version = "0.5.5" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "55a2482c9fe4dd481723cf5c0616f34afc710e55dcda0944e12e7b3316117892" 1215 | dependencies = [ 1216 | "async-trait", 1217 | "byteorder", 1218 | "bytes", 1219 | "fallible-iterator", 1220 | "futures", 1221 | "log", 1222 | "parking_lot", 1223 | "percent-encoding", 1224 | "phf", 1225 | "pin-project-lite", 1226 | "postgres-protocol", 1227 | "postgres-types", 1228 | "tokio", 1229 | "tokio-util", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "tokio-postgres-rustls" 1234 | version = "0.5.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "09d6a25f824310d0ef3c34db1bf093640b4f9d57b693f29c7c15cf3b2513ab50" 1237 | dependencies = [ 1238 | "bytes", 1239 | "futures", 1240 | "ring", 1241 | "rustls", 1242 | "tokio", 1243 | "tokio-postgres", 1244 | "tokio-rustls", 1245 | "webpki", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "tokio-rustls" 1250 | version = "0.14.1" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" 1253 | dependencies = [ 1254 | "futures-core", 1255 | "rustls", 1256 | "tokio", 1257 | "webpki", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "tokio-util" 1262 | version = "0.3.1" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 1265 | dependencies = [ 1266 | "bytes", 1267 | "futures-core", 1268 | "futures-sink", 1269 | "log", 1270 | "pin-project-lite", 1271 | "tokio", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "toml" 1276 | version = "0.5.6" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 1279 | dependencies = [ 1280 | "serde", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "typenum" 1285 | version = "1.12.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 1288 | 1289 | [[package]] 1290 | name = "unicode-bidi" 1291 | version = "0.3.4" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1294 | dependencies = [ 1295 | "matches", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "unicode-normalization" 1300 | version = "0.1.13" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" 1303 | dependencies = [ 1304 | "tinyvec", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "unicode-xid" 1309 | version = "0.2.1" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1312 | 1313 | [[package]] 1314 | name = "untrusted" 1315 | version = "0.7.1" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1318 | 1319 | [[package]] 1320 | name = "url" 1321 | version = "2.1.1" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1324 | dependencies = [ 1325 | "idna", 1326 | "matches", 1327 | "percent-encoding", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "vcpkg" 1332 | version = "0.2.10" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 1335 | 1336 | [[package]] 1337 | name = "version_check" 1338 | version = "0.9.2" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1341 | 1342 | [[package]] 1343 | name = "walkdir" 1344 | version = "2.3.1" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1347 | dependencies = [ 1348 | "same-file", 1349 | "winapi 0.3.9", 1350 | "winapi-util", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "wasi" 1355 | version = "0.9.0+wasi-snapshot-preview1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1358 | 1359 | [[package]] 1360 | name = "wasm-bindgen" 1361 | version = "0.2.68" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" 1364 | dependencies = [ 1365 | "cfg-if 0.1.10", 1366 | "wasm-bindgen-macro", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "wasm-bindgen-backend" 1371 | version = "0.2.68" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" 1374 | dependencies = [ 1375 | "bumpalo", 1376 | "lazy_static", 1377 | "log", 1378 | "proc-macro2", 1379 | "quote", 1380 | "syn", 1381 | "wasm-bindgen-shared", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "wasm-bindgen-macro" 1386 | version = "0.2.68" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" 1389 | dependencies = [ 1390 | "quote", 1391 | "wasm-bindgen-macro-support", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "wasm-bindgen-macro-support" 1396 | version = "0.2.68" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" 1399 | dependencies = [ 1400 | "proc-macro2", 1401 | "quote", 1402 | "syn", 1403 | "wasm-bindgen-backend", 1404 | "wasm-bindgen-shared", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "wasm-bindgen-shared" 1409 | version = "0.2.68" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" 1412 | 1413 | [[package]] 1414 | name = "web-sys" 1415 | version = "0.3.45" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" 1418 | dependencies = [ 1419 | "js-sys", 1420 | "wasm-bindgen", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "webpki" 1425 | version = "0.21.3" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" 1428 | dependencies = [ 1429 | "ring", 1430 | "untrusted", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "winapi" 1435 | version = "0.2.8" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1438 | 1439 | [[package]] 1440 | name = "winapi" 1441 | version = "0.3.9" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1444 | dependencies = [ 1445 | "winapi-i686-pc-windows-gnu", 1446 | "winapi-x86_64-pc-windows-gnu", 1447 | ] 1448 | 1449 | [[package]] 1450 | name = "winapi-build" 1451 | version = "0.1.1" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1454 | 1455 | [[package]] 1456 | name = "winapi-i686-pc-windows-gnu" 1457 | version = "0.4.0" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1460 | 1461 | [[package]] 1462 | name = "winapi-util" 1463 | version = "0.1.5" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1466 | dependencies = [ 1467 | "winapi 0.3.9", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "winapi-x86_64-pc-windows-gnu" 1472 | version = "0.4.0" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1475 | 1476 | [[package]] 1477 | name = "ws2_32-sys" 1478 | version = "0.2.1" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1481 | dependencies = [ 1482 | "winapi 0.2.8", 1483 | "winapi-build", 1484 | ] 1485 | --------------------------------------------------------------------------------