Compare commits

...

10 commits

Author SHA1 Message Date
58150002e6
fix 2024-09-13 22:29:34 +02:00
b0bae64efc
update 2024-09-13 14:00:28 +02:00
949340a7f2
fix 2024-09-11 16:08:58 +02:00
f24e3f1e3c
fix 2024-09-11 11:29:20 +02:00
1e9efe4c12
fix 2024-09-11 11:18:31 +02:00
d830e677ba
builder update 2024-09-11 11:09:38 +02:00
9784c3cac6
add api 2024-08-30 11:53:10 +02:00
9b56a434e7
fix 2024-08-28 08:50:38 +02:00
05e09295a5
add unique + sort 2024-08-28 08:45:44 +02:00
228f113dbf
add sort 2024-08-16 20:38:16 +02:00
12 changed files with 657 additions and 218 deletions

187
Cargo.lock generated
View file

@ -4,18 +4,18 @@ version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
@ -56,13 +56,13 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.81"
version = "0.1.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
]
[[package]]
@ -73,17 +73,17 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.73"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
@ -159,10 +159,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytes"
version = "1.6.1"
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "case"
@ -172,9 +178,12 @@ checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c"
[[package]]
name = "cc"
version = "1.1.6"
version = "1.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
@ -204,15 +213,15 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.12"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
dependencies = [
"libc",
]
@ -297,8 +306,8 @@ dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"syn 2.0.72",
"rustc_version 0.4.1",
"syn 2.0.77",
]
[[package]]
@ -407,7 +416,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
]
[[package]]
@ -463,9 +472,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.29.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "hashbrown"
@ -563,9 +572,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
@ -585,9 +594,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.9.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
[[package]]
name = "itoa"
@ -597,9 +606,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [
"wasm-bindgen",
]
@ -612,9 +621,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.155"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "linked-hash-map"
@ -677,18 +686,18 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler",
"adler2",
]
[[package]]
name = "mio"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
"hermit-abi",
"libc",
@ -698,7 +707,7 @@ dependencies = [
[[package]]
name = "mongod"
version = "0.2.0"
version = "0.2.2"
dependencies = [
"chrono",
"futures",
@ -785,9 +794,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.2"
version = "0.36.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e"
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
dependencies = [
"memchr",
]
@ -856,9 +865,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
@ -877,9 +889,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
@ -931,9 +943,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.5"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -1000,9 +1012,9 @@ dependencies = [
[[package]]
name = "rustc_version"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver 1.0.23",
]
@ -1093,9 +1105,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.204"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
@ -1111,23 +1123,24 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.204"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
]
[[package]]
name = "serde_json"
version = "1.0.120"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"indexmap",
"itoa",
"memchr",
"ryu",
"serde",
]
@ -1176,6 +1189,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@ -1262,9 +1281,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.72"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
@ -1300,7 +1319,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
]
[[package]]
@ -1351,9 +1370,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.39.1"
version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
dependencies = [
"backtrace",
"bytes",
@ -1374,7 +1393,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
]
[[package]]
@ -1389,9 +1408,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
@ -1471,9 +1490,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
@ -1486,9 +1505,9 @@ dependencies = [
[[package]]
name = "unicode-properties"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
[[package]]
name = "untrusted"
@ -1519,9 +1538,9 @@ dependencies = [
[[package]]
name = "version_check"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
@ -1531,34 +1550,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1566,22 +1586,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "webpki-roots"
@ -1790,6 +1810,7 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
@ -1801,5 +1822,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.77",
]

View file

@ -1,6 +1,6 @@
[package]
name = "mongod"
version = "0.2.0"
version = "0.2.2"
edition = "2021"
[features]

View file

@ -11,9 +11,9 @@ You can derive the `Model` and `Referencable` traits for your struct. This will
use serde::{Deserialize, Serialize};
use mongod::Validate;
use mongod::Reference;
use mongod::derive::{Module, Referencable};
use mongod::derive::{Model, Referencable};
#[derive(Debug, Clone, Serialize, Deserialize, Module, Referencable)]
#[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)]
struct MyStruct {
_id: String,
name: String,
@ -105,3 +105,34 @@ let ms = MyStruct::get_partial("someid", &serde_json::json!({"other": 1})).await
let myref = ms.other.unwrap(); // will be there
let name = ms.name.unwrap() // will panic!
```
### Updating values
You can either update the values by passing a JSON object overwriting the current values or update the values using a builder pattern.
```rust
[...]
struct MyStruct {
_id: String,
name: String,
age: u32,
other: Option<Reference>,
}
let mut a = MyStruct::get("someid").await.unwrap();
// JSON
a.update(serde_json::json!({"name": "bye"})).await.unwrap();
// Builder
let mut changes = a.change();
// Set fields
changes = changes.name("bye");
// There are type specific functions
// Increment age by 1
changes = changes.age_increment(1);
// Finalize
let changed_model: MyStruct = changes.update().await.unwrap();
```

View file

@ -0,0 +1,213 @@
use quote::quote;
use syn::Field;
use crate::{extract_inner_type, is_one_of_type, is_type};
/// Generate the ChangeBuilder field fns
pub fn builder_change_fields(field: &Field) -> proc_macro2::TokenStream {
let field_name = &field.ident.as_ref().unwrap();
let field_type = &field.ty;
let field_name_str = field_name.to_string();
// Never update _id
if field_name_str == "_id" {
return quote! {};
}
let number_types = [
"u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize",
"f16", "f32", "f64", "f128",
];
// Number type fn
if is_one_of_type(field_type, &number_types) {
let documentation = format!("Set the value of `{field_name}`");
let inc_fn_name = syn::Ident::new(&format!("{}_increment", field_name), field_name.span());
let doc_inc = format!("Increment value of `{field_name}` by `value`. Consecutive calls to this function will not add up, they overwrite the increment.");
let mul_fn_name = syn::Ident::new(&format!("{}_multiply", field_name), field_name.span());
let doc_mul = format!("Multiply value of `{field_name}` by `value`. Consecutive calls to this function will not add up, they overwrite the multiply.");
return quote! {
#[doc = #documentation]
pub fn #field_name(mut self, value: #field_type)-> Self {
self.model.#field_name = value.into();
self.changeset.entry("$set".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&self.model.#field_name).unwrap(),
);
self
}
#[doc = #doc_inc]
pub fn #inc_fn_name(mut self, value: #field_type) -> Self {
self.model.#field_name += value;
self.changeset.entry("$inc".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap()
.insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&value).unwrap(),
);
self
}
#[doc = #doc_mul]
pub fn #mul_fn_name(mut self, value: #field_type) -> Self {
self.model.#field_name *= value;
self.changeset.entry("$mul".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap()
.insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&value).unwrap(),
);
self
}
};
}
if is_type(field_type, "Vec") {
let inner_field_type = extract_inner_type(field_type, "Vec").unwrap();
let push_fn_name = syn::Ident::new(&format!("{}_push", field_name), field_name.span());
let documentation = format!("Add a value to the Vec `{field_name}`");
let documentation2 = format!("Set the value of `{field_name}`");
return quote! {
#[doc = #documentation]
pub fn #push_fn_name<T>(mut self, value: T) -> Self where T: Into<#inner_field_type> + serde::Serialize {
let mut push = self.changeset.entry("$push".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap();
if push.contains_key(#field_name_str) {
let current = push.get_mut(#field_name_str.to_string()).unwrap();
if current.as_document().map(|x| !x.contains_key("$each")).unwrap_or(true) {
let each = mongod::mongodb::bson::doc! {
"$each": [current, mongod::mongodb::bson::to_bson(&value).unwrap()]
};
push.insert(#field_name_str.to_string(), each);
} else {
current.as_document_mut().unwrap().get_mut("$each").unwrap().as_array_mut().unwrap().push(mongod::mongodb::bson::to_bson(&value).unwrap());
}
} else {
push.insert(#field_name_str.to_string(), mongod::mongodb::bson::to_bson(&value).unwrap());
}
self.model.#field_name.push(value.into());
self
}
#[doc = #documentation2]
pub fn #field_name<T>(mut self, value: T)-> Self where T: Into<#field_type> + serde::Serialize {
self.model.#field_name = value.into();
self.changeset.entry("$set".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&self.model.#field_name).unwrap(),
);
self
}
};
}
if is_type(field_type, "Historic") {
let inner_field_type = extract_inner_type(field_type, "Historic").unwrap();
let documentation = format!(
"Update the value of `{field_name}`. This change will be recorded by the `Historic`"
);
// Code for Historic<T>
return quote! {
#[doc = #documentation]
pub fn #field_name<T>(mut self, value: T) -> Self where T: Into<#inner_field_type> + serde::Serialize {
self.model.#field_name.update(value.into());
self.changeset.entry("$set".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&self.model.#field_name).unwrap(),
);
self
}
};
}
if is_type(field_type, "Option") {
let inner_field_type = extract_inner_type(field_type, "Option").unwrap();
if is_type(inner_field_type, "Historic") {
let inner_field_type = extract_inner_type(inner_field_type, "Historic").unwrap();
let documentation = format!("Update the value of `{field_name}`. This change will be recorded by the `Historic`. If `{field_name}` is `None` a new `Historic` will be initialized.");
// Code for Option<Historic<T>>
return quote! {
#[doc = #documentation]
pub fn #field_name<T>(mut self, value: T) -> Self where T: Into<#inner_field_type> + serde::Serialize {
if let Some(mut opt) = self.model.#field_name.as_mut() {
opt.update(value.into());
} else {
self.model.#field_name = Some(mongod::Historic::new(value.into()));
}
self.changeset.entry("$set".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&self.model.#field_name).unwrap(),
);
self
}
};
}
let documentation = format!("Set the value of `{field_name}`. If `Some(_)` it will be updated or removed if it is `None`");
return quote! {
#[doc = #documentation]
pub fn #field_name(mut self, value: Option<#inner_field_type>) -> Self {
let is_some = value.is_some();
self.model.#field_name = value.into();
if is_some {
self.changeset.entry("$set".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&self.model.#field_name).unwrap(),
);
} else {
self.changeset.entry("$unset".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson("").unwrap(),
);
}
self
}
};
}
let documentation = format!("Set the value of `{field_name}`");
// Code for T
quote! {
#[doc = #documentation]
pub fn #field_name<T>(mut self, value: T)-> Self where T: Into<#field_type> + serde::Serialize {
self.model.#field_name = value.into();
self.changeset.entry("$set".to_string()).or_insert(mongod::mongodb::bson::doc! {}.into()).as_document_mut().unwrap().insert(
#field_name_str.to_string(),
mongod::mongodb::bson::to_bson(&self.model.#field_name).unwrap(),
);
self
}
}
}

View file

@ -3,40 +3,18 @@ use case::CaseExt;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Type, TypePath};
use syn::{parse_macro_input, Data, DeriveInput, Fields};
/// Get inner type. Example: Returns `T` for `Option<T>`.
fn extract_inner_type<'a>(ty: &'a Type, parent: &'a str) -> Option<&'a Type> {
if let Type::Path(type_path) = ty {
if type_path.path.segments.len() == 1 {
let segment = &type_path.path.segments[0];
if segment.ident == parent {
if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments {
if args.args.len() == 1 {
if let syn::GenericArgument::Type(ref inner_type) = args.args[0] {
return Some(inner_type);
}
}
}
}
}
}
None
}
fn type_path(ty: &syn::Type) -> TypePath {
if let syn::Type::Path(type_path) = ty {
return type_path.clone();
}
unreachable!();
}
fn is_type(ty: &syn::Type, t: &str) -> bool {
let type_path = type_path(ty);
let id = type_path.path.segments.first().unwrap().ident.to_string();
id == t
}
mod types;
use types::*;
mod partial_fields;
use partial_fields::partial_code_field;
mod update_fields;
use update_fields::update_code_field;
mod change_fields;
use change_fields::builder_change_fields;
/// #[derive(Model)]
#[proc_macro_derive(Model)]
pub fn model_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
@ -44,84 +22,37 @@ pub fn model_derive(input: TokenStream) -> TokenStream {
let name = input.ident;
let name_str = name.to_string().to_snake();
let partial_name = syn::Ident::new(&format!("Partial{}", name), name.span());
let changebuilder_name = syn::Ident::new(&format!("Change{}", name), name.span());
let update_doc = "Commit the builder changes to DB";
// Generate code for each field
let field_code = if let Data::Struct(data_struct) = input.data {
match data_struct.fields {
Fields::Named(fields_named) => {
let field_process_code: Vec<_> = fields_named.named.iter().map(|field| {
let field_name = &field.ident.as_ref().unwrap();
let field_type = &field.ty;
let field_name_str = field_name.to_string();
// Update code
let field_process_code: Vec<_> =
fields_named.named.iter().map(update_code_field).collect();
if field_name_str == "_id" {
return quote! {};
}
// Partial struct fields
let partial_struct: Vec<_> =
fields_named.named.iter().map(partial_code_field).collect();
if is_type(field_type, "Historic") {
let inner_field_type = extract_inner_type(field_type, "Historic").unwrap();
if is_type(inner_field_type, "Vec") {
return quote! {
mongod::update_historic_vec!(self, obj, #field_name_str, #field_name, update);
};
}
return quote! {
mongod::update_historic_str!(self, obj, #field_name_str, #field_name, update);
}
}
if is_type(field_type, "Option") {
let inner_field_type = extract_inner_type(field_type, "Option").unwrap();
if is_type(inner_field_type, "Historic") {
let sub_inner_field_type = extract_inner_type(inner_field_type, "Historic").unwrap();
if is_type(sub_inner_field_type, "Reference") {
return quote! {
mongod::update_historic_ref_option!(self, obj, #field_name_str, #field_name, update);
};
}
}
return quote! {
mongod::update_value_option!(self, obj, #field_name_str, #field_name, update, #inner_field_type);
};
}
quote! {
mongod::update_value!(self, obj, #field_name_str, #field_name, update, #field_type);
}
}).collect();
let partial_struct: Vec<_> = fields_named
// Builder functions
let builder_change_fields: Vec<_> = fields_named
.named
.iter()
.map(|field| {
let field_name = &field.ident.as_ref().unwrap();
let field_type = &field.ty;
let field_name_str = field_name.to_string();
if field_name_str == "_id" {
return quote! {
pub _id: String,
};
}
if is_type(field_type, "Option") {
return quote! {
pub #field_name: #field_type,
};
}
quote! {
pub #field_name: Option<#field_type>,
}
})
.map(builder_change_fields)
.collect();
quote! {
impl mongod::model::Model for #name {
type Partial = #partial_name;
type ChangeBuilder = #changebuilder_name;
fn change_builder(self) -> Self::ChangeBuilder {
#changebuilder_name::new(self)
}
async fn update_values(
&mut self,
@ -137,6 +68,47 @@ pub fn model_derive(input: TokenStream) -> TokenStream {
#( #partial_struct )*
}
#[derive(Debug)]
pub struct #changebuilder_name {
model: #name,
changeset: mongod::mongodb::bson::Document
}
impl #changebuilder_name {
pub fn new(model: #name) -> Self {
Self {
model,
changeset: mongod::mongodb::bson::doc! {}
}
}
#[doc = #update_doc]
pub async fn update(self) -> Result<#name, mongod::model::UpdateError> {
let db = mongod::get_mongo!();
let collection = mongod::col!(db, <#name as mongod::Referencable>::collection_name());
if let Err(msg) = mongod::Validate::validate(&self.model).await {
return Err(mongod::model::UpdateError::Validation(msg));
}
let changeset = self.changeset;
collection
.update_one(
mongod::id_of!(mongod::Referencable::id(&self.model)),
changeset,
None,
)
.await
.map_err(mongod::model::UpdateError::Database)?;
Ok(self.model)
}
#( #builder_change_fields )*
}
impl mongod::model::reference::Referencable for #partial_name {
fn collection_name() -> &'static str {
#name_str
@ -157,6 +129,7 @@ pub fn model_derive(input: TokenStream) -> TokenStream {
TokenStream::from(field_code)
}
/// #[derive(Referencable)]
#[proc_macro_derive(Referencable)]
pub fn referencable_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

View file

@ -0,0 +1,30 @@
use quote::quote;
use syn::Field;
use crate::is_type;
/// Generate struct fields for Partial Model
pub fn partial_code_field(field: &Field) -> proc_macro2::TokenStream {
let field_name = &field.ident.as_ref().unwrap();
let field_type = &field.ty;
let field_name_str = field_name.to_string();
// Keep _id
if field_name_str == "_id" {
return quote! {
pub _id: String,
};
}
// Leave Option<T> alone
if is_type(field_type, "Option") {
return quote! {
pub #field_name: #field_type,
};
}
// Turn every field into Option<T>
quote! {
pub #field_name: Option<#field_type>,
}
}

View file

@ -0,0 +1,42 @@
use syn::{Type, TypePath};
/// Get inner type. Example: Returns `T` for `Option<T>`.
pub fn extract_inner_type<'a>(ty: &'a Type, parent: &'a str) -> Option<&'a Type> {
if let Type::Path(type_path) = ty {
if type_path.path.segments.len() == 1 {
let segment = &type_path.path.segments[0];
if segment.ident == parent {
if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments {
if args.args.len() == 1 {
if let syn::GenericArgument::Type(ref inner_type) = args.args[0] {
return Some(inner_type);
}
}
}
}
}
}
None
}
pub fn type_path(ty: &syn::Type) -> TypePath {
if let syn::Type::Path(type_path) = ty {
return type_path.clone();
}
unreachable!();
}
pub fn is_one_of_type(ty: &syn::Type, t: &[&str]) -> bool {
for typ in t {
if is_type(ty, typ) {
return true;
}
}
false
}
pub fn is_type(ty: &syn::Type, t: &str) -> bool {
let type_path = type_path(ty);
let id = type_path.path.segments.first().unwrap().ident.to_string();
id == t
}

View file

@ -0,0 +1,55 @@
use quote::quote;
use syn::Field;
use crate::{extract_inner_type, is_type};
/// Generate code for the update fn of models
pub fn update_code_field(field: &Field) -> proc_macro2::TokenStream {
let field_name = &field.ident.as_ref().unwrap();
let field_type = &field.ty;
let field_name_str = field_name.to_string();
// Never update _id
if field_name_str == "_id" {
return quote! {};
}
if is_type(field_type, "Historic") {
let inner_field_type = extract_inner_type(field_type, "Historic").unwrap();
if is_type(inner_field_type, "Vec") {
// Custom code Historic<Vec<T>>
return quote! {
mongod::update_historic_vec!(self, obj, #field_name_str, #field_name, update);
};
}
// Code for Historic<T>
return quote! {
mongod::update_historic_str!(self, obj, #field_name_str, #field_name, update);
};
}
if is_type(field_type, "Option") {
let inner_field_type = extract_inner_type(field_type, "Option").unwrap();
if is_type(inner_field_type, "Historic") {
let sub_inner_field_type = extract_inner_type(inner_field_type, "Historic").unwrap();
if is_type(sub_inner_field_type, "Reference") {
// Code for Option<Historic<Reference>>
return quote! {
mongod::update_historic_ref_option!(self, obj, #field_name_str, #field_name, update);
};
}
}
// Code for Option<T>
return quote! {
mongod::update_value_option!(self, obj, #field_name_str, #field_name, update, #inner_field_type);
};
}
// Code for T
quote! {
mongod::update_value!(self, obj, #field_name_str, #field_name, update, #field_type);
}
}

View file

@ -3,8 +3,10 @@ pub use model::historic::Historic;
pub use model::reference::*;
pub use model::valid::Validate;
pub use model::Model;
pub use model::Sort;
pub use mongod_derive as derive;
pub use mongodb;
#[cfg(feature = "cache")]
pub mod cache;
#[cfg(feature = "cache")]
@ -55,7 +57,7 @@ macro_rules! get_mongo {
if let Some(client) = $crate::MONGO_CLIENT.get() {
client
} else {
let client = mongodb::Client::with_uri_str(&std::env::var("DB_URI").unwrap())
let client = $crate::mongodb::Client::with_uri_str(&std::env::var("DB_URI").unwrap())
.await
.unwrap();
$crate::MONGO_CLIENT.set(client).unwrap();
@ -95,3 +97,20 @@ macro_rules! id_of {
$crate::mongodb::bson::doc! { "_id": $id}
};
}
/// A trait to generate a Model API representation in JSON format.
pub trait ToAPI: Sized {
/// Generate public API JSON
fn api(&self) -> impl std::future::Future<Output = serde_json::Value>;
}
/// Converts a slice of items implementing the `ToAPI` trait into a `Vec` of JSON values.
pub async fn vec_to_api(items: &[impl ToAPI]) -> Vec<serde_json::Value> {
let mut ret = Vec::with_capacity(items.len());
for e in items {
ret.push(e.api().await);
}
ret
}

View file

@ -3,7 +3,7 @@ use std::{collections::HashMap, ops::Deref};
/// A struct to keep track of historical changes to a value.
/// This struct represents a value that has a current state and a history of previous states.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Historic<T> {
/// The current value
pub current: T,
@ -24,6 +24,13 @@ impl<T: Clone> Historic<T> {
}
}
impl<T: Default + Clone> Historic<T> {
/// Create a new tracked value initialized with Default
pub fn new_default() -> Historic<T> {
Self::new(T::default())
}
}
impl<T: Clone> Historic<T> {
/// Update the value. The change will be recorded.
/// Will record a change even if the value is the same as the current one.

View file

@ -8,15 +8,16 @@ use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use valid::Validate;
use crate::{cache_read, cache_write, col, collect_results, get_mongo, id_of};
#[cfg(feature = "cache")]
use crate::{cache_read, cache_write};
use crate::{col, collect_results, get_mongo, id_of};
pub mod historic;
pub mod reference;
pub mod update;
pub mod valid;
// todo : use mongodb projection to only get fields you actually use, maybe PartialModel shadow struct?
/// Error type when updating a model
#[derive(Debug)]
pub enum UpdateError {
@ -32,6 +33,7 @@ pub trait Model:
Sized + Referencable + Validate + serde::Serialize + for<'a> serde::Deserialize<'a>
{
type Partial: DeserializeOwned;
type ChangeBuilder;
/// Insert the `Model` into the database
fn insert(
@ -215,7 +217,7 @@ pub trait Model:
/// Get all `Model`s from the database
#[must_use]
fn find_all() -> impl std::future::Future<Output = Option<Vec<Self>>> {
Self::find(doc! {}, None)
Self::find(doc! {}, None, None)
}
/// Get all `Model`s partial from the database
@ -223,7 +225,7 @@ pub trait Model:
fn find_all_partial(
part: serde_json::Value,
) -> impl std::future::Future<Output = Option<Vec<Self::Partial>>> {
Self::find_partial(doc! {}, part, None)
Self::find_partial(doc! {}, part, None, None)
}
/// Get multiple `Model`s by using a filter from the database. Pass a `limit` parameter to limit the amount of `Model`s returned.
@ -231,17 +233,15 @@ pub trait Model:
fn find(
filter: mongodb::bson::Document,
limit: Option<i64>,
sort: Option<mongodb::bson::Document>,
) -> impl std::future::Future<Output = Option<Vec<Self>>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
let mut results = collection
.find(
filter,
limit.map(|x| FindOptions::builder().limit(x).build()),
)
.await
.ok()?;
let options = FindOptions::builder().limit(limit).sort(sort).build();
let mut results = collection.find(filter, options).await.ok()?;
let docs = collect_results!(results);
docs.into_iter()
.map(|x| mongodb::bson::from_document(x).unwrap())
@ -249,12 +249,28 @@ pub trait Model:
}
}
/// Get unique values of a Models field
fn unique<T: DeserializeOwned>(
filter: mongodb::bson::Document,
field: &str,
) -> impl std::future::Future<Output = Vec<T>> {
async move {
let db = get_mongo!();
let collection = col!(db, Self::collection_name());
let res = collection.distinct(field, filter, None).await.unwrap();
res.into_iter()
.filter_map(|x| mongodb::bson::from_bson(x).ok())
.collect()
}
}
/// Get multiple partial `Model`s by using a filter from the database. Pass a `limit` parameter to limit the amount of `Model`s returned.
#[must_use]
fn find_partial(
filter: mongodb::bson::Document,
mut part: serde_json::Value,
limit: Option<i64>,
sort: Option<mongodb::bson::Document>,
) -> impl std::future::Future<Output = Option<Vec<Self::Partial>>> {
async move {
let db = get_mongo!();
@ -262,18 +278,13 @@ pub trait Model:
part.as_object_mut()?.insert("_id".into(), 1.into());
let mut results = collection
.find(
filter,
Some(
FindOptions::builder()
.projection(Some(mongodb::bson::to_document(&part).unwrap()))
.limit(limit)
.build(),
),
)
.await
.ok()?;
let options = FindOptions::builder()
.limit(limit)
.sort(sort)
.projection(Some(mongodb::bson::to_document(&part).unwrap()))
.build();
let mut results = collection.find(filter, Some(options)).await.ok()?;
let docs = collect_results!(results);
docs.into_iter()
.map(|x| mongodb::bson::from_document(x).unwrap())
@ -281,6 +292,18 @@ pub trait Model:
}
}
fn change_builder(self) -> Self::ChangeBuilder;
/// Update values of `Model` using a builder pattern
fn change(self) -> Self::ChangeBuilder {
#[cfg(feature = "cache")]
{
cache_write!().invalidate(Self::collection_name(), self.id());
}
self.change_builder()
}
/// Update values of `Model` into database
fn update(
&mut self,
@ -333,3 +356,28 @@ pub trait Model:
update: &mut mongodb::bson::Document,
) -> impl std::future::Future<Output = ()> + Send;
}
/// Sorting Order
///
/// # Example
/// ```ignore
/// let m = MyModel::find(doc! {}, None, Some(doc! {
/// "sort_key": Sort::Ascending
/// })).await.unwrap();
/// ```
#[derive(Debug)]
pub enum Sort {
/// Sort in ascending order
Ascending,
/// Sort in descending order
Descending,
}
impl Into<mongodb::bson::Bson> for Sort {
fn into(self) -> mongodb::bson::Bson {
match self {
Sort::Ascending => mongodb::bson::bson!(1),
Sort::Descending => mongodb::bson::bson!(-1),
}
}
}

View file

@ -10,9 +10,14 @@ use super::{valid::Validate, Model};
pub struct Reference(String);
impl Reference {
/// Create a new reference from String
pub async fn new_raw(reference: &str) -> Option<Self> {
let r = Self(reference.to_string());
/// Create a new reference from String without checks
pub fn new_raw(reference: &str) -> Self {
Self(reference.to_string())
}
/// Create a new reference
pub async fn new(model: &str, id: &str) -> Option<Self> {
let r = Self(format!("{model}::{id}"));
if r.validate().await.is_ok() {
Some(r)
} else {
@ -20,11 +25,6 @@ impl Reference {
}
}
/// Create a new reference
pub async fn new(model: &str, id: &str) -> Option<Self> {
Self::new_raw(&format!("{model}::{id}")).await
}
/// Get just the ID of the referenced `Model`
pub fn id(&self) -> &str {
self.0.split_once("::").unwrap().1
@ -130,11 +130,11 @@ macro_rules! reference_of {
($model:ident, $id:ident) => {{
$model::get_partial($id, serde_json::json!({}))
.await
.map(|x| x.reference())
.map(|x| mongod::Referencable::reference(&x))
}};
($model:ident, $id:literal) => {{
$model::get_partial($id, serde_json::json!({}))
.await
.map(|x| x.reference())
.map(|x| mongod::Referencable::reference(&x))
}};
}