From e088ae12a5ade17d4415321a88c7382d17d1278a Mon Sep 17 00:00:00 2001 From: dekkku Date: Tue, 10 Sep 2024 23:43:54 +0530 Subject: [PATCH 01/39] base setup --- projects/dekkku/.gitignore | 9 + projects/dekkku/Cargo.lock | 2571 +++++++++++++++++++++++++++++++++++ projects/dekkku/Cargo.toml | 24 + projects/dekkku/run.sh | 3 + projects/dekkku/src/main.rs | 145 ++ 5 files changed, 2752 insertions(+) create mode 100644 projects/dekkku/.gitignore create mode 100644 projects/dekkku/Cargo.lock create mode 100644 projects/dekkku/Cargo.toml create mode 100644 projects/dekkku/run.sh create mode 100644 projects/dekkku/src/main.rs diff --git a/projects/dekkku/.gitignore b/projects/dekkku/.gitignore new file mode 100644 index 0000000..5874756 --- /dev/null +++ b/projects/dekkku/.gitignore @@ -0,0 +1,9 @@ +.vscode + +/target +node_modules + +.direnv +.envrc + +*.snap.new diff --git a/projects/dekkku/Cargo.lock b/projects/dekkku/Cargo.lock new file mode 100644 index 0000000..2ff68d8 --- /dev/null +++ b/projects/dekkku/Cargo.lock @@ -0,0 +1,2571 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" + +[[package]] +name = "ascii_utils" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" + +[[package]] +name = "async-graphql" +version = "7.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d37c3e9ba322eb00e9e5e997d58f08e8b6de037325b9367ac59bca8e3cd46af" +dependencies = [ + "async-graphql-derive", + "async-graphql-parser", + "async-graphql-value", + "async-stream", + "async-trait", + "base64 0.22.1", + "bytes", + "fast_chemail", + "fnv", + "futures-timer", + "futures-util", + "handlebars", + "http", + "indexmap", + "mime", + "multer", + "num-traits", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "static_assertions_next", + "tempfile", + "thiserror", +] + +[[package]] +name = "async-graphql-derive" +version = "7.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1141703c11c6ad4fa9b3b0e1e476dea01dbd18a44db00f949b804afaab2f344" +dependencies = [ + "Inflector", + "async-graphql-parser", + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "strum", + "syn", + "thiserror", +] + +[[package]] +name = "async-graphql-parser" +version = "7.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f66edcce4c38c18f7eb181fdf561c3d3aa2d644ce7358fc7a928c00a4ffef17" +dependencies = [ + "async-graphql-value", + "pest", + "serde", + "serde_json", +] + +[[package]] +name = "async-graphql-poem" +version = "7.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe30b69d7a661ff46b5df6e0e75e8ec85d712db9f0cc460c1fb8a534c82eb33b" +dependencies = [ + "async-graphql", + "futures-util", + "http", + "mime", + "poem", + "serde_json", + "tokio", + "tokio-stream", + "tokio-util", +] + +[[package]] +name = "async-graphql-value" +version = "7.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0206011cad065420c27988f17dd7fe201a0e056b20c262209b7bffcd6fa176" +dependencies = [ + "bytes", + "indexmap", + "serde", + "serde_json", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "command-group" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409" +dependencies = [ + "async-trait", + "nix 0.27.1", + "tokio", + "winapi", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "dekkku" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-graphql", + "async-graphql-poem", + "clap", + "command-group", + "diff_logger", + "easy_retry", + "mock_json", + "poem", + "reqwest", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "diff_logger" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3930952b1892f2be44237b353d34e46aab1cb7e929dd9cc59b4a37a1267257" +dependencies = [ + "chrono", + "colored", + "serde_json", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "easy_retry" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b685bff3804033ed63c4354cc74d98f4f9fa5c6565669b2685c95c1d9078dfe8" +dependencies = [ + "futures", + "tokio", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fast_chemail" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" +dependencies = [ + "ascii_utils", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "ipnet" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "mock_json" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d9cedfa3802606b21f6d48afca77b0bafe4205b6f3a4ab59bc044e92319fca" +dependencies = [ + "once_cell", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "poem" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5419c612a492fce4961c521dca0c2249b5c48dc46eb5c8048063843f37a711d" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-util", + "headers", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "nix 0.29.0", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "regex", + "rfc7239", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "sync_wrapper", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing", + "wildmatch", +] + +[[package]] +name = "poem-derive" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfed15c1102d2a9a51b9f1aba945628c72ccb52fc5d3e4ad4ffbbd222e11821" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfc7239" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b106a85eeb5b0336d16d6a20eab857f92861d4fbb1eb9a239866fb98fb6a1063" +dependencies = [ + "uncased", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "static_assertions_next" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wildmatch" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/projects/dekkku/Cargo.toml b/projects/dekkku/Cargo.toml new file mode 100644 index 0000000..2f12265 --- /dev/null +++ b/projects/dekkku/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "dekkku" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.86" +clap = { version = "4.5.17", features = ["derive"] } +command-group = { version = "5.0.1", features = ["with-tokio"] } +diff_logger = "0.1.0" +easy_retry = { version = "0.1.0", features = ["async"] } +mock_json = "0.1.8" +reqwest = { version = "0.12.7", features = ["json"] } +serde_json = "1.0.127" +serde = "1.0.127" +tokio = { version = "1.40.0", features = ["full"] } +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +async-graphql = "7.0.8" +async-graphql-poem = "7.0.9" +poem = "3.1.0" + +[workspace] +members = [] \ No newline at end of file diff --git a/projects/dekkku/run.sh b/projects/dekkku/run.sh new file mode 100644 index 0000000..85b41c7 --- /dev/null +++ b/projects/dekkku/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cargo run -p dekkku diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs new file mode 100644 index 0000000..878ae0e --- /dev/null +++ b/projects/dekkku/src/main.rs @@ -0,0 +1,145 @@ +use async_graphql::http::GraphiQLSource; +use async_graphql::*; +use async_graphql_poem::*; +use poem::{listener::TcpListener, web::Html, *}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +const BASE_URL: &str = "http://localhost:3000"; + +struct Query; + +#[Object] +impl Query { + async fn posts(&self, ctx: &Context<'_>) -> anyhow::Result> { + let client = ctx.data_unchecked::>(); + let response = client.get(format!("{}/posts", BASE_URL)).send().await?; + let posts: serde_json::Value = response.json().await?; + let posts: Vec = serde_json::from_value(posts)?; + Ok(posts) + } + + async fn post(&self, ctx: &Context<'_>, id: i32) -> anyhow::Result> { + let client = ctx.data_unchecked::>(); + let response = client + .get(format!("{}/posts/{}", BASE_URL, id)) + .send() + .await?; + if response.status().is_success() { + let post: serde_json::Value = response.json().await?; + let post = serde_json::from_value(post)?; + Ok(Some(post)) + } else { + Ok(None) + } + } + + async fn users(&self, ctx: &Context<'_>) -> anyhow::Result> { + let client = ctx.data_unchecked::>(); + let response = client.get(format!("{}/users", BASE_URL)).send().await?; + let users: serde_json::Value = response.json().await?; + let users: Vec = serde_json::from_value(users)?; + Ok(users) + } + + async fn user(&self, ctx: &Context<'_>, id: i32) -> anyhow::Result> { + let client = ctx.data_unchecked::>(); + let response = client + .get(format!("{}/users/{}", BASE_URL, id)) + .send() + .await?; + if response.status().is_success() { + let user: serde_json::Value = response.json().await?; + let user = serde_json::from_value(user)?; + Ok(Some(user)) + } else { + Ok(None) + } + } +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug)] +#[graphql(complex)] +#[serde(rename_all = "camelCase")] +struct Post { + id: Option, + #[graphql(name = "userId")] + user_id: i32, + title: Option, + body: Option, +} + +#[ComplexObject] +impl Post { + async fn user(&self, ctx: &Context<'_>) -> anyhow::Result { + let client = ctx.data_unchecked::>(); + let response = client + .get(format!("{}/users/{}", BASE_URL, self.user_id)) + .send() + .await?; + let user: serde_json::Value = response.json().await?; + let user = serde_json::from_value(user)?; + Ok(user) + } +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug)] +struct User { + id: Option, + name: Option, + username: Option, + email: Option, + address: Option
, + phone: Option, + website: Option, +} + +#[ComplexObject] +impl User { + async fn posts(&self, ctx: &Context<'_>) -> anyhow::Result> { + let client = ctx.data_unchecked::>(); + let response = client + .get(format!("{}/posts?userId={}", BASE_URL, self.id.unwrap())) + .send() + .await?; + let posts: Vec = response.json().await?; + Ok(posts) + } +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug)] +struct Address { + zipcode: Option, + geo: Option, +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug)] +struct Geo { + lat: Option, + lng: Option, +} + +fn create_schema() -> Schema { + Schema::build(Query, EmptyMutation, EmptySubscription) + .data(Arc::new(Client::new())) + .finish() +} + +#[handler] +async fn graphiql() -> impl IntoResponse { + Html(GraphiQLSource::build().finish()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let schema = create_schema(); + + // start the http server + let app = Route::new().at("/", get(graphiql).post(GraphQL::new(schema))); + println!("GraphiQL: http://localhost:8000"); + Server::new(TcpListener::bind("0.0.0.0:8000")) + .run(app) + .await?; + Ok(()) +} From 32d3615169490e79430b8441bb9f4fb01b8c41f6 Mon Sep 17 00:00:00 2001 From: dekkku Date: Tue, 10 Sep 2024 23:46:20 +0530 Subject: [PATCH 02/39] clean up of not required deps --- projects/dekkku/Cargo.toml | 6 ------ projects/dekkku/run.sh | 0 2 files changed, 6 deletions(-) mode change 100644 => 100755 projects/dekkku/run.sh diff --git a/projects/dekkku/Cargo.toml b/projects/dekkku/Cargo.toml index 2f12265..ec71c96 100644 --- a/projects/dekkku/Cargo.toml +++ b/projects/dekkku/Cargo.toml @@ -5,17 +5,11 @@ edition = "2021" [dependencies] anyhow = "1.0.86" -clap = { version = "4.5.17", features = ["derive"] } -command-group = { version = "5.0.1", features = ["with-tokio"] } -diff_logger = "0.1.0" easy_retry = { version = "0.1.0", features = ["async"] } -mock_json = "0.1.8" reqwest = { version = "0.12.7", features = ["json"] } serde_json = "1.0.127" serde = "1.0.127" tokio = { version = "1.40.0", features = ["full"] } -tracing = "0.1.40" -tracing-subscriber = "0.3.18" async-graphql = "7.0.8" async-graphql-poem = "7.0.9" poem = "3.1.0" diff --git a/projects/dekkku/run.sh b/projects/dekkku/run.sh old mode 100644 new mode 100755 From fc5b0eee4246c54f310cafe41b86b08eec7e06d8 Mon Sep 17 00:00:00 2001 From: dekkku Date: Tue, 10 Sep 2024 23:50:25 +0530 Subject: [PATCH 03/39] update lock file --- projects/dekkku/Cargo.lock | 415 ++----------------------------------- 1 file changed, 15 insertions(+), 400 deletions(-) diff --git a/projects/dekkku/Cargo.lock b/projects/dekkku/Cargo.lock index 2ff68d8..f877295 100644 --- a/projects/dekkku/Cargo.lock +++ b/projects/dekkku/Cargo.lock @@ -36,70 +36,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" - -[[package]] -name = "anstyle-parse" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - [[package]] name = "anyhow" version = "1.0.87" @@ -261,7 +197,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -333,88 +269,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "clap" -version = "4.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - -[[package]] -name = "colorchoice" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - -[[package]] -name = "colored" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" -dependencies = [ - "lazy_static", - "windows-sys 0.48.0", -] - -[[package]] -name = "command-group" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409" -dependencies = [ - "async-trait", - "nix 0.27.1", - "tokio", - "winapi", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -498,29 +352,12 @@ dependencies = [ "anyhow", "async-graphql", "async-graphql-poem", - "clap", - "command-group", - "diff_logger", "easy_retry", - "mock_json", "poem", "reqwest", "serde", "serde_json", "tokio", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "diff_logger" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3930952b1892f2be44237b353d34e46aab1cb7e929dd9cc59b4a37a1267257" -dependencies = [ - "chrono", - "colored", - "serde_json", ] [[package]] @@ -930,29 +767,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -986,12 +800,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "itoa" version = "1.0.11" @@ -1074,18 +882,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mock_json" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d9cedfa3802606b21f6d48afca77b0bafe4205b6f3a4ab59bc044e92319fca" -dependencies = [ - "once_cell", - "rand", - "serde", - "serde_json", -] - [[package]] name = "multer" version = "3.1.0" @@ -1120,17 +916,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -1143,16 +928,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1221,12 +996,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" version = "0.12.3" @@ -1247,7 +1016,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1354,7 +1123,7 @@ dependencies = [ "hyper", "hyper-util", "mime", - "nix 0.29.0", + "nix", "parking_lot", "percent-encoding", "pin-project-lite", @@ -1732,15 +1501,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1901,16 +1661,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tinyvec" version = "1.8.0" @@ -2086,32 +1836,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", ] [[package]] @@ -2203,18 +1927,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" @@ -2325,37 +2037,6 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-registry" version = "0.2.0" @@ -2364,7 +2045,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2373,7 +2054,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2383,16 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -2401,7 +2073,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2410,22 +2082,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2434,46 +2091,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2486,48 +2125,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" From 927055645ac9b47c4be98feffdc0b73b356dbbd9 Mon Sep 17 00:00:00 2001 From: dekkku Date: Tue, 10 Sep 2024 23:53:18 +0530 Subject: [PATCH 04/39] fix the endpoint --- projects/dekkku/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 878ae0e..42fc998 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -136,7 +136,7 @@ async fn main() -> anyhow::Result<()> { let schema = create_schema(); // start the http server - let app = Route::new().at("/", get(graphiql).post(GraphQL::new(schema))); + let app = Route::new().at("/graphql", get(graphiql).post(GraphQL::new(schema))); println!("GraphiQL: http://localhost:8000"); Server::new(TcpListener::bind("0.0.0.0:8000")) .run(app) From db8b11cb7e61de88552f289d75dcf853339b6b66 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 01:08:49 +0530 Subject: [PATCH 05/39] use data loaders to load the data --- projects/dekkku/Cargo.lock | 34 +++++++ projects/dekkku/Cargo.toml | 6 +- projects/dekkku/src/main.rs | 175 ++++++++++++++++++++++-------------- 3 files changed, 147 insertions(+), 68 deletions(-) diff --git a/projects/dekkku/Cargo.lock b/projects/dekkku/Cargo.lock index f877295..1728be0 100644 --- a/projects/dekkku/Cargo.lock +++ b/projects/dekkku/Cargo.lock @@ -27,6 +27,18 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -36,6 +48,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anyhow" version = "1.0.87" @@ -63,11 +81,13 @@ dependencies = [ "bytes", "fast_chemail", "fnv", + "futures-channel", "futures-timer", "futures-util", "handlebars", "http", "indexmap", + "lru", "mime", "multer", "num-traits", @@ -353,6 +373,7 @@ dependencies = [ "async-graphql", "async-graphql-poem", "easy_retry", + "futures", "poem", "reqwest", "serde", @@ -610,6 +631,10 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "headers" @@ -849,6 +874,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" diff --git a/projects/dekkku/Cargo.toml b/projects/dekkku/Cargo.toml index ec71c96..50111c0 100644 --- a/projects/dekkku/Cargo.toml +++ b/projects/dekkku/Cargo.toml @@ -10,9 +10,13 @@ reqwest = { version = "0.12.7", features = ["json"] } serde_json = "1.0.127" serde = "1.0.127" tokio = { version = "1.40.0", features = ["full"] } -async-graphql = "7.0.8" +async-graphql = {version = "7.0.8", features = [ + "dynamic-schema", + "dataloader", +] } async-graphql-poem = "7.0.9" poem = "3.1.0" +futures = "0.3" [workspace] members = [] \ No newline at end of file diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 42fc998..b785c5f 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -4,6 +4,7 @@ use async_graphql_poem::*; use poem::{listener::TcpListener, web::Html, *}; use reqwest::Client; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::sync::Arc; const BASE_URL: &str = "http://localhost:3000"; @@ -12,55 +13,59 @@ struct Query; #[Object] impl Query { - async fn posts(&self, ctx: &Context<'_>) -> anyhow::Result> { + async fn posts( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); + let user_loader = ctx.data_unchecked::(); let response = client.get(format!("{}/posts", BASE_URL)).send().await?; - let posts: serde_json::Value = response.json().await?; - let posts: Vec = serde_json::from_value(posts)?; - Ok(posts) - } + let mut posts: Vec = serde_json::from_value(response.json().await?)?; - async fn post(&self, ctx: &Context<'_>, id: i32) -> anyhow::Result> { - let client = ctx.data_unchecked::>(); - let response = client - .get(format!("{}/posts/{}", BASE_URL, id)) - .send() + let user_ids: HashSet<_> = posts.iter().map(|post| post.user_id).collect(); + let users = user_loader + .load(&user_ids.into_iter().collect::>()) .await?; - if response.status().is_success() { - let post: serde_json::Value = response.json().await?; - let post = serde_json::from_value(post)?; - Ok(Some(post)) - } else { - Ok(None) + + for post in &mut posts { + post.user = users.get(&post.user_id).cloned(); } + Ok(posts) + } + + async fn post( + &self, + ctx: &Context<'_>, + id: i32, + ) -> std::result::Result, async_graphql::Error> { + let loader = ctx.data_unchecked::(); + let posts = loader.load(&[id]).await?; + Ok(posts.get(&id).cloned()) } - async fn users(&self, ctx: &Context<'_>) -> anyhow::Result> { + async fn users( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); let response = client.get(format!("{}/users", BASE_URL)).send().await?; - let users: serde_json::Value = response.json().await?; - let users: Vec = serde_json::from_value(users)?; + let users: Vec = serde_json::from_value(response.json().await?)?; Ok(users) } - async fn user(&self, ctx: &Context<'_>, id: i32) -> anyhow::Result> { - let client = ctx.data_unchecked::>(); - let response = client - .get(format!("{}/users/{}", BASE_URL, id)) - .send() - .await?; - if response.status().is_success() { - let user: serde_json::Value = response.json().await?; - let user = serde_json::from_value(user)?; - Ok(Some(user)) - } else { - Ok(None) - } + async fn user( + &self, + ctx: &Context<'_>, + id: i32, + ) -> std::result::Result, async_graphql::Error> { + let loader = ctx.data_unchecked::(); + let users = loader.load(&[id]).await?; + Ok(users.get(&id).cloned()) } } -#[derive(SimpleObject, Serialize, Deserialize, Debug)] -#[graphql(complex)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] +// #[graphql(complex)] #[serde(rename_all = "camelCase")] struct Post { id: Option, @@ -68,23 +73,10 @@ struct Post { user_id: i32, title: Option, body: Option, + user: Option, } -#[ComplexObject] -impl Post { - async fn user(&self, ctx: &Context<'_>) -> anyhow::Result { - let client = ctx.data_unchecked::>(); - let response = client - .get(format!("{}/users/{}", BASE_URL, self.user_id)) - .send() - .await?; - let user: serde_json::Value = response.json().await?; - let user = serde_json::from_value(user)?; - Ok(user) - } -} - -#[derive(SimpleObject, Serialize, Deserialize, Debug)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] struct User { id: Option, name: Option, @@ -95,34 +87,81 @@ struct User { website: Option, } -#[ComplexObject] -impl User { - async fn posts(&self, ctx: &Context<'_>) -> anyhow::Result> { - let client = ctx.data_unchecked::>(); - let response = client - .get(format!("{}/posts?userId={}", BASE_URL, self.id.unwrap())) - .send() - .await?; - let posts: Vec = response.json().await?; - Ok(posts) - } -} - -#[derive(SimpleObject, Serialize, Deserialize, Debug)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] struct Address { zipcode: Option, geo: Option, } -#[derive(SimpleObject, Serialize, Deserialize, Debug)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] struct Geo { lat: Option, lng: Option, } +use async_graphql::dataloader::Loader; + +struct PostLoader(Arc); +struct UserLoader(Arc); + +impl Loader for PostLoader { + type Value = Post; + type Error = async_graphql::Error; + + async fn load( + &self, + keys: &[i32], + ) -> std::result::Result, Self::Error> { + let mut result = std::collections::HashMap::new(); + for &id in keys { + let url = format!("{}/posts/{}", BASE_URL, id); + println!("http request: {}", url); + let response = self.0.get(url).send().await?; + if response.status().is_success() { + let post: Post = response.json().await?; + result.insert(id, post); + } + } + Ok(result) + } +} + +impl Loader for UserLoader { + type Value = User; + type Error = async_graphql::Error; + + async fn load( + &self, + keys: &[i32], + ) -> std::result::Result, Self::Error> { + let mut result = std::collections::HashMap::new(); + + let qp = keys + .iter() + .map(|id| format!("id={}", id)) + .collect::>() + .join("&"); + let url = format!("{}/users?{}", BASE_URL, qp); + println!("http request: {}", url); + let response = self.0.get(url).send().await?; + if response.status().is_success() { + let users: Vec = response.json().await?; + for user in users { + if let Some(id) = user.id { + result.insert(id, user); + } + } + } + Ok(result) + } +} + fn create_schema() -> Schema { + let client = Arc::new(Client::new()); Schema::build(Query, EmptyMutation, EmptySubscription) - .data(Arc::new(Client::new())) + .data(client.clone()) + .data(UserLoader(client.clone())) + .data(PostLoader(client.clone())) .finish() } @@ -136,8 +175,10 @@ async fn main() -> anyhow::Result<()> { let schema = create_schema(); // start the http server - let app = Route::new().at("/graphql", get(graphiql).post(GraphQL::new(schema))); - println!("GraphiQL: http://localhost:8000"); + let app = Route::new() + .at("/graphql", get(graphiql).post(GraphQL::new(schema.clone()))) + .at("/", get(graphiql).post(GraphQL::new(schema.clone()))); + println!("GraphiQL: http://localhost:8000/graphql"); Server::new(TcpListener::bind("0.0.0.0:8000")) .run(app) .await?; From 60438f9d07f64638231637a7d2e04f387af61ac0 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 01:13:29 +0530 Subject: [PATCH 06/39] removed logs --- projects/dekkku/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index b785c5f..6d45364 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -115,7 +115,7 @@ impl Loader for PostLoader { let mut result = std::collections::HashMap::new(); for &id in keys { let url = format!("{}/posts/{}", BASE_URL, id); - println!("http request: {}", url); + // println!("http request: {}", url); let response = self.0.get(url).send().await?; if response.status().is_success() { let post: Post = response.json().await?; @@ -142,7 +142,6 @@ impl Loader for UserLoader { .collect::>() .join("&"); let url = format!("{}/users?{}", BASE_URL, qp); - println!("http request: {}", url); let response = self.0.get(url).send().await?; if response.status().is_success() { let users: Vec = response.json().await?; @@ -178,7 +177,6 @@ async fn main() -> anyhow::Result<()> { let app = Route::new() .at("/graphql", get(graphiql).post(GraphQL::new(schema.clone()))) .at("/", get(graphiql).post(GraphQL::new(schema.clone()))); - println!("GraphiQL: http://localhost:8000/graphql"); Server::new(TcpListener::bind("0.0.0.0:8000")) .run(app) .await?; From ab4b23c5093d70bedd8a7eabcb418951297051d1 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 02:32:33 +0530 Subject: [PATCH 07/39] used dataloaders to solve N+1 problem --- projects/dekkku/src/main.rs | 59 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 6d45364..c18b00d 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -1,11 +1,11 @@ +use async_graphql::dataloader::*; use async_graphql::http::GraphiQLSource; use async_graphql::*; use async_graphql_poem::*; use poem::{listener::TcpListener, web::Html, *}; use reqwest::Client; use serde::{Deserialize, Serialize}; -use std::collections::HashSet; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; const BASE_URL: &str = "http://localhost:3000"; @@ -18,18 +18,8 @@ impl Query { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); - let user_loader = ctx.data_unchecked::(); let response = client.get(format!("{}/posts", BASE_URL)).send().await?; - let mut posts: Vec = serde_json::from_value(response.json().await?)?; - - let user_ids: HashSet<_> = posts.iter().map(|post| post.user_id).collect(); - let users = user_loader - .load(&user_ids.into_iter().collect::>()) - .await?; - - for post in &mut posts { - post.user = users.get(&post.user_id).cloned(); - } + let posts: Vec = serde_json::from_value(response.json().await?)?; Ok(posts) } @@ -38,9 +28,9 @@ impl Query { ctx: &Context<'_>, id: i32, ) -> std::result::Result, async_graphql::Error> { - let loader = ctx.data_unchecked::(); - let posts = loader.load(&[id]).await?; - Ok(posts.get(&id).cloned()) + let loader = ctx.data_unchecked::>(); + let post = loader.load_one(id).await?; + Ok(post) } async fn users( @@ -58,22 +48,33 @@ impl Query { ctx: &Context<'_>, id: i32, ) -> std::result::Result, async_graphql::Error> { - let loader = ctx.data_unchecked::(); - let users = loader.load(&[id]).await?; - Ok(users.get(&id).cloned()) + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(id).await?; + Ok(user) } } #[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] -// #[graphql(complex)] #[serde(rename_all = "camelCase")] +#[graphql(complex)] struct Post { id: Option, #[graphql(name = "userId")] user_id: i32, title: Option, body: Option, - user: Option, +} + +#[ComplexObject] +impl Post { + async fn user( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(self.user_id).await?; + Ok(user) + } } #[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] @@ -99,8 +100,6 @@ struct Geo { lng: Option, } -use async_graphql::dataloader::Loader; - struct PostLoader(Arc); struct UserLoader(Arc); @@ -115,7 +114,7 @@ impl Loader for PostLoader { let mut result = std::collections::HashMap::new(); for &id in keys { let url = format!("{}/posts/{}", BASE_URL, id); - // println!("http request: {}", url); + println!("[Finder]: url={}", url); let response = self.0.get(url).send().await?; if response.status().is_success() { let post: Post = response.json().await?; @@ -135,13 +134,13 @@ impl Loader for UserLoader { keys: &[i32], ) -> std::result::Result, Self::Error> { let mut result = std::collections::HashMap::new(); - let qp = keys .iter() .map(|id| format!("id={}", id)) .collect::>() .join("&"); let url = format!("{}/users?{}", BASE_URL, qp); + println!("[Finder]: url={}", url); let response = self.0.get(url).send().await?; if response.status().is_success() { let users: Vec = response.json().await?; @@ -157,10 +156,14 @@ impl Loader for UserLoader { fn create_schema() -> Schema { let client = Arc::new(Client::new()); + let user_loader = + DataLoader::new(UserLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); + let post_loader = + DataLoader::new(PostLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); Schema::build(Query, EmptyMutation, EmptySubscription) - .data(client.clone()) - .data(UserLoader(client.clone())) - .data(PostLoader(client.clone())) + .data(client) + .data(user_loader) + .data(post_loader) .finish() } From b031bc630faa4aa9a47ad3eaaabb7f44cf3fdea6 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 03:23:16 +0530 Subject: [PATCH 08/39] remove logs --- projects/dekkku/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index c18b00d..5a4347e 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -114,7 +114,6 @@ impl Loader for PostLoader { let mut result = std::collections::HashMap::new(); for &id in keys { let url = format!("{}/posts/{}", BASE_URL, id); - println!("[Finder]: url={}", url); let response = self.0.get(url).send().await?; if response.status().is_success() { let post: Post = response.json().await?; @@ -140,7 +139,6 @@ impl Loader for UserLoader { .collect::>() .join("&"); let url = format!("{}/users?{}", BASE_URL, qp); - println!("[Finder]: url={}", url); let response = self.0.get(url).send().await?; if response.status().is_success() { let users: Vec = response.json().await?; From dc83fdb51a8f27700f97e8e4179c9b7234e3e625 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 04:55:22 +0530 Subject: [PATCH 09/39] exploit reset property of mock server --- projects/dekkku/run.sh | 2 +- projects/dekkku/src/main.rs | 70 +++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/projects/dekkku/run.sh b/projects/dekkku/run.sh index 85b41c7..4a4a972 100755 --- a/projects/dekkku/run.sh +++ b/projects/dekkku/run.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -cargo run -p dekkku +cargo run -p dekkku --release diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 5a4347e..827b1db 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -3,12 +3,32 @@ use async_graphql::http::GraphiQLSource; use async_graphql::*; use async_graphql_poem::*; use poem::{listener::TcpListener, web::Html, *}; -use reqwest::Client; +use reqwest::{Client, ClientBuilder}; use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, + time::Duration, +}; const BASE_URL: &str = "http://localhost:3000"; +struct Store { + post: Mutex, + users: Mutex>, + is_post_same: Mutex, +} + +impl Default for Store { + fn default() -> Self { + Self { + post: Mutex::new(Post::default()), + users: Mutex::new(HashMap::default()), + is_post_same: Mutex::new(false), + } + } +} + struct Query; #[Object] @@ -18,8 +38,30 @@ impl Query { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); + let response = client.get(format!("{}/posts", BASE_URL)).send().await?; let posts: Vec = serde_json::from_value(response.json().await?)?; + + // exploit the `reset` property of Mock Server. + + let store = ctx.data_unchecked::>(); + + let is_same = { + let mut cached_post = store.post.lock().unwrap(); + let is_same = (*cached_post) == posts[0]; + if is_same { + true + } else { + *cached_post = posts[0].clone(); + false + } + }; + + { + let mut is_post_same = store.is_post_same.lock().unwrap(); + *is_post_same = is_same; + } + Ok(posts) } @@ -48,13 +90,14 @@ impl Query { ctx: &Context<'_>, id: i32, ) -> std::result::Result, async_graphql::Error> { + // TODO: cache the user for future purpose. let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; Ok(user) } } -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[graphql(complex)] struct Post { @@ -71,9 +114,19 @@ impl Post { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { - let loader = ctx.data_unchecked::>(); - let user = loader.load_one(self.user_id).await?; - Ok(user) + let store = ctx.data_unchecked::>(); + if *store.is_post_same.lock().unwrap() { + let user = store.users.lock().unwrap().get(&self.user_id).cloned(); + Ok(user) + } else { + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(self.user_id).await?; + if let Some(actual_user) = user.as_ref() { + let mut user_store = store.users.lock().unwrap(); + user_store.insert(self.user_id, actual_user.clone()); + } + Ok(user) + } } } @@ -162,6 +215,7 @@ fn create_schema() -> Schema { .data(client) .data(user_loader) .data(post_loader) + .data(Arc::new(Store::default())) .finish() } @@ -183,3 +237,7 @@ async fn main() -> anyhow::Result<()> { .await?; Ok(()) } + +// Ideas +// 1. Connection Pooling. +// 2. Caching based on POST's. From 0ad7e2cd046fdf7dca51fcf5aa1fd2fe65f5a038 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 04:55:43 +0530 Subject: [PATCH 10/39] make cache smart --- projects/dekkku/src/main.rs | 53 ++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 827b1db..5a02f64 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -3,7 +3,7 @@ use async_graphql::http::GraphiQLSource; use async_graphql::*; use async_graphql_poem::*; use poem::{listener::TcpListener, web::Html, *}; -use reqwest::{Client, ClientBuilder}; +use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -14,7 +14,7 @@ use std::{ const BASE_URL: &str = "http://localhost:3000"; struct Store { - post: Mutex, + post: Mutex>, users: Mutex>, is_post_same: Mutex, } @@ -22,13 +22,21 @@ struct Store { impl Default for Store { fn default() -> Self { Self { - post: Mutex::new(Post::default()), + post: Mutex::new(HashMap::default()), users: Mutex::new(HashMap::default()), is_post_same: Mutex::new(false), } } } +impl Store { + pub fn reset(&self) { + self.users.lock().unwrap().clear(); + self.post.lock().unwrap().clear(); + *self.is_post_same.lock().unwrap() = false; + } +} + struct Query; #[Object] @@ -42,17 +50,23 @@ impl Query { let response = client.get(format!("{}/posts", BASE_URL)).send().await?; let posts: Vec = serde_json::from_value(response.json().await?)?; - // exploit the `reset` property of Mock Server. - let store = ctx.data_unchecked::>(); let is_same = { - let mut cached_post = store.post.lock().unwrap(); - let is_same = (*cached_post) == posts[0]; - if is_same { + let cached_post = store.post.lock().unwrap(); + let post1_exists = cached_post + .get(&posts[1].id.unwrap()) + .map(|post1| *post1 == posts[1]) + .unwrap_or(false); + let post2_exists = cached_post + .get(&posts[2].id.unwrap()) + .map(|post2| post2 == &posts[2]) + .unwrap_or(false); + + if post1_exists && post2_exists { true } else { - *cached_post = posts[0].clone(); + store.reset(); false } }; @@ -72,6 +86,11 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; + if let Some(actual_post) = post.as_ref() { + // cache the post early + let store = ctx.data_unchecked::>(); + store.post.lock().unwrap().insert(id, actual_post.clone()); + } Ok(post) } @@ -79,6 +98,7 @@ impl Query { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { + // TODO: cache all the users here. let client = ctx.data_unchecked::>(); let response = client.get(format!("{}/users", BASE_URL)).send().await?; let users: Vec = serde_json::from_value(response.json().await?)?; @@ -90,9 +110,15 @@ impl Query { ctx: &Context<'_>, id: i32, ) -> std::result::Result, async_graphql::Error> { - // TODO: cache the user for future purpose. let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; + + if let Some(actual_user) = &user { + // cache the user early + let store = ctx.data_unchecked::>(); + store.users.lock().unwrap().insert(id, actual_user.clone()); + } + Ok(user) } } @@ -115,7 +141,9 @@ impl Post { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let store = ctx.data_unchecked::>(); - if *store.is_post_same.lock().unwrap() { + if *store.is_post_same.lock().unwrap() + && store.users.lock().unwrap().contains_key(&self.user_id) + { let user = store.users.lock().unwrap().get(&self.user_id).cloned(); Ok(user) } else { @@ -238,6 +266,5 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -// Ideas // 1. Connection Pooling. -// 2. Caching based on POST's. +// 2. reset property of Mock Server. From 9ed75501bdf22a127553dac0dc9e839f39a9f816 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 05:08:59 +0530 Subject: [PATCH 11/39] cache the users and posts --- projects/dekkku/src/main.rs | 71 ++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 5a02f64..7009906 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -7,33 +7,33 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - sync::{Arc, Mutex}, + sync::{Arc, RwLock}, // Change Mutex to RwLock time::Duration, }; const BASE_URL: &str = "http://localhost:3000"; struct Store { - post: Mutex>, - users: Mutex>, - is_post_same: Mutex, + post: RwLock>, // Change Mutex to RwLock + users: RwLock>, // Change Mutex to RwLock + is_post_same: RwLock, // Change Mutex to RwLock } impl Default for Store { fn default() -> Self { Self { - post: Mutex::new(HashMap::default()), - users: Mutex::new(HashMap::default()), - is_post_same: Mutex::new(false), + post: RwLock::new(HashMap::default()), // Change Mutex to RwLock + users: RwLock::new(HashMap::default()), // Change Mutex to RwLock + is_post_same: RwLock::new(false), // Change Mutex to RwLock } } } impl Store { pub fn reset(&self) { - self.users.lock().unwrap().clear(); - self.post.lock().unwrap().clear(); - *self.is_post_same.lock().unwrap() = false; + self.users.write().unwrap().clear(); // Change lock to write() + self.post.write().unwrap().clear(); // Change lock to write() + *self.is_post_same.write().unwrap() = false; // Change lock to write() } } @@ -53,26 +53,41 @@ impl Query { let store = ctx.data_unchecked::>(); let is_same = { - let cached_post = store.post.lock().unwrap(); - let post1_exists = cached_post - .get(&posts[1].id.unwrap()) - .map(|post1| *post1 == posts[1]) - .unwrap_or(false); - let post2_exists = cached_post - .get(&posts[2].id.unwrap()) - .map(|post2| post2 == &posts[2]) - .unwrap_or(false); - - if post1_exists && post2_exists { + // TODO: pick these ID's randomly to reduce the chance of false positives. + let are_posts_same = { + let cached_post = store.post.read().unwrap(); + let post1_exists = cached_post + .get(&posts[1].id.unwrap()) + .map(|post1| *post1 == posts[1]) + .unwrap_or(false); + let post2_exists = cached_post + .get(&posts[2].id.unwrap()) + .map(|post2| post2 == &posts[2]) + .unwrap_or(false); + + post1_exists && post2_exists + }; + + if are_posts_same { true } else { store.reset(); + store + .post + .write() + .unwrap() + .insert(posts[1].id.unwrap(), posts[1].clone()); + store + .post + .write() + .unwrap() + .insert(posts[2].id.unwrap(), posts[2].clone()); false } }; { - let mut is_post_same = store.is_post_same.lock().unwrap(); + let mut is_post_same = store.is_post_same.write().unwrap(); *is_post_same = is_same; } @@ -89,7 +104,7 @@ impl Query { if let Some(actual_post) = post.as_ref() { // cache the post early let store = ctx.data_unchecked::>(); - store.post.lock().unwrap().insert(id, actual_post.clone()); + store.post.write().unwrap().insert(id, actual_post.clone()); } Ok(post) } @@ -116,7 +131,7 @@ impl Query { if let Some(actual_user) = &user { // cache the user early let store = ctx.data_unchecked::>(); - store.users.lock().unwrap().insert(id, actual_user.clone()); + store.users.write().unwrap().insert(id, actual_user.clone()); } Ok(user) @@ -141,16 +156,16 @@ impl Post { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let store = ctx.data_unchecked::>(); - if *store.is_post_same.lock().unwrap() - && store.users.lock().unwrap().contains_key(&self.user_id) + if *store.is_post_same.read().unwrap() + && store.users.read().unwrap().contains_key(&self.user_id) { - let user = store.users.lock().unwrap().get(&self.user_id).cloned(); + let user = store.users.write().unwrap().get(&self.user_id).cloned(); Ok(user) } else { let loader = ctx.data_unchecked::>(); let user = loader.load_one(self.user_id).await?; if let Some(actual_user) = user.as_ref() { - let mut user_store = store.users.lock().unwrap(); + let mut user_store = store.users.write().unwrap(); user_store.insert(self.user_id, actual_user.clone()); } Ok(user) From 069d913d6de12af051f8bb3f51b0f0359c663c21 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 05:17:12 +0530 Subject: [PATCH 12/39] clean up --- projects/dekkku/src/main.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 7009906..fc25e2f 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -72,23 +72,15 @@ impl Query { true } else { store.reset(); - store - .post - .write() - .unwrap() - .insert(posts[1].id.unwrap(), posts[1].clone()); - store - .post - .write() - .unwrap() - .insert(posts[2].id.unwrap(), posts[2].clone()); + let store_post_writer = store.post.writer().unwrap(); + store_post_writer.insert(posts[1].id.unwrap(), posts[1].clone()); + store_post_writer.insert(posts[2].id.unwrap(), posts[2].clone()); false } }; { - let mut is_post_same = store.is_post_same.write().unwrap(); - *is_post_same = is_same; + *store.is_post_same.write().unwrap() = is_same; } Ok(posts) @@ -103,6 +95,7 @@ impl Query { let post = loader.load_one(id).await?; if let Some(actual_post) = post.as_ref() { // cache the post early + // if post already exists in store, check if it's the same or not. if not same then clean up the store. let store = ctx.data_unchecked::>(); store.post.write().unwrap().insert(id, actual_post.clone()); } @@ -113,7 +106,6 @@ impl Query { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { - // TODO: cache all the users here. let client = ctx.data_unchecked::>(); let response = client.get(format!("{}/users", BASE_URL)).send().await?; let users: Vec = serde_json::from_value(response.json().await?)?; @@ -131,6 +123,7 @@ impl Query { if let Some(actual_user) = &user { // cache the user early let store = ctx.data_unchecked::>(); + // if user already exists in store, check if it's the same or not. if not same then clean up the store. store.users.write().unwrap().insert(id, actual_user.clone()); } From deda9a32874e6b7b9cfa3d92cf3741487b2f1cd7 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 05:50:00 +0530 Subject: [PATCH 13/39] compile urls --- projects/dekkku/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index fc25e2f..77fa2ac 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -12,6 +12,8 @@ use std::{ }; const BASE_URL: &str = "http://localhost:3000"; +const ALL_USERS: &str = "http://localhost:3000/users"; +const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { post: RwLock>, // Change Mutex to RwLock @@ -47,7 +49,7 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); - let response = client.get(format!("{}/posts", BASE_URL)).send().await?; + let response = client.get(ALL_POSTS).send().await?; let posts: Vec = serde_json::from_value(response.json().await?)?; let store = ctx.data_unchecked::>(); @@ -107,7 +109,7 @@ impl Query { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); - let response = client.get(format!("{}/users", BASE_URL)).send().await?; + let response = client.get(ALL_USERS).send().await?; let users: Vec = serde_json::from_value(response.json().await?)?; Ok(users) } From f20a69916873c817792c7ad3f31b4d733d32bc3f Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 05:51:46 +0530 Subject: [PATCH 14/39] fix typo --- projects/dekkku/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 77fa2ac..0f5eee1 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -74,7 +74,7 @@ impl Query { true } else { store.reset(); - let store_post_writer = store.post.writer().unwrap(); + let mut store_post_writer = store.post.write().unwrap(); store_post_writer.insert(posts[1].id.unwrap(), posts[1].clone()); store_post_writer.insert(posts[2].id.unwrap(), posts[2].clone()); false From f581362af921fe1681949a35bea529fecf4267bf Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 11:09:56 +0530 Subject: [PATCH 15/39] clean up --- projects/dekkku/src/main.rs | 103 +++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 0f5eee1..0672038 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -7,7 +7,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - sync::{Arc, RwLock}, // Change Mutex to RwLock + sync::{Arc, RwLock}, time::Duration, }; @@ -16,26 +16,26 @@ const ALL_USERS: &str = "http://localhost:3000/users"; const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { - post: RwLock>, // Change Mutex to RwLock - users: RwLock>, // Change Mutex to RwLock - is_post_same: RwLock, // Change Mutex to RwLock + post: RwLock>, + users: RwLock>, + is_post_same: RwLock, // rename to is_dirty. } impl Default for Store { fn default() -> Self { Self { - post: RwLock::new(HashMap::default()), // Change Mutex to RwLock - users: RwLock::new(HashMap::default()), // Change Mutex to RwLock - is_post_same: RwLock::new(false), // Change Mutex to RwLock + post: RwLock::new(HashMap::default()), + users: RwLock::new(HashMap::default()), + is_post_same: RwLock::new(false), } } } impl Store { pub fn reset(&self) { - self.users.write().unwrap().clear(); // Change lock to write() - self.post.write().unwrap().clear(); // Change lock to write() - *self.is_post_same.write().unwrap() = false; // Change lock to write() + self.users.write().unwrap().clear(); + self.post.write().unwrap().clear(); + *self.is_post_same.write().unwrap() = false; } } @@ -54,35 +54,28 @@ impl Query { let store = ctx.data_unchecked::>(); - let is_same = { - // TODO: pick these ID's randomly to reduce the chance of false positives. - let are_posts_same = { - let cached_post = store.post.read().unwrap(); - let post1_exists = cached_post - .get(&posts[1].id.unwrap()) - .map(|post1| *post1 == posts[1]) - .unwrap_or(false); - let post2_exists = cached_post - .get(&posts[2].id.unwrap()) - .map(|post2| post2 == &posts[2]) - .unwrap_or(false); - - post1_exists && post2_exists - }; - - if are_posts_same { - true - } else { - store.reset(); - let mut store_post_writer = store.post.write().unwrap(); - store_post_writer.insert(posts[1].id.unwrap(), posts[1].clone()); - store_post_writer.insert(posts[2].id.unwrap(), posts[2].clone()); - false - } + let are_posts_same = { + let cached_post = store.post.read().unwrap(); + let post1_exists = cached_post + .get(&posts[1].id.unwrap()) + .map(|post1| *post1 == posts[1]) + .unwrap_or(false); + let post2_exists = cached_post + .get(&posts[2].id.unwrap()) + .map(|post2| post2 == &posts[2]) + .unwrap_or(false); + + post1_exists && post2_exists }; - { - *store.is_post_same.write().unwrap() = is_same; + if are_posts_same { + *store.is_post_same.write().unwrap() = true; + } else { + // store.reset(); // expensive operation but it's required. TODO: don't clean up posts, directly replace them. + *store.is_post_same.write().unwrap() = false; + let mut store_post_writer = store.post.write().unwrap(); + store_post_writer.insert(posts[1].id.unwrap(), posts[1].clone()); + store_post_writer.insert(posts[2].id.unwrap(), posts[2].clone()); } Ok(posts) @@ -96,9 +89,12 @@ impl Query { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; if let Some(actual_post) = post.as_ref() { - // cache the post early - // if post already exists in store, check if it's the same or not. if not same then clean up the store. let store = ctx.data_unchecked::>(); + if let Some(cached_post) = store.post.write().unwrap().get(&id) { + if actual_post != cached_post { + *store.is_post_same.write().unwrap() = false; + } + } store.post.write().unwrap().insert(id, actual_post.clone()); } Ok(post) @@ -125,7 +121,11 @@ impl Query { if let Some(actual_user) = &user { // cache the user early let store = ctx.data_unchecked::>(); - // if user already exists in store, check if it's the same or not. if not same then clean up the store. + if let Some(cached_user) = store.users.write().unwrap().get(&id) { + if cached_user != actual_user { + *store.is_post_same.write().unwrap() = false; + } + } store.users.write().unwrap().insert(id, actual_user.clone()); } @@ -154,21 +154,29 @@ impl Post { if *store.is_post_same.read().unwrap() && store.users.read().unwrap().contains_key(&self.user_id) { - let user = store.users.write().unwrap().get(&self.user_id).cloned(); + let user = store.users.read().unwrap().get(&self.user_id).cloned(); Ok(user) } else { let loader = ctx.data_unchecked::>(); let user = loader.load_one(self.user_id).await?; if let Some(actual_user) = user.as_ref() { - let mut user_store = store.users.write().unwrap(); - user_store.insert(self.user_id, actual_user.clone()); + if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { + if cached_user != actual_user { + *store.is_post_same.write().unwrap() = false; + } + } + store + .users + .write() + .unwrap() + .insert(self.user_id, actual_user.clone()); } Ok(user) } } } -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] struct User { id: Option, name: Option, @@ -179,13 +187,13 @@ struct User { website: Option, } -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] struct Address { zipcode: Option, geo: Option, } -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone)] +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] struct Geo { lat: Option, lng: Option, @@ -275,6 +283,3 @@ async fn main() -> anyhow::Result<()> { .await?; Ok(()) } - -// 1. Connection Pooling. -// 2. reset property of Mock Server. From bda8d1b2abdae0d2a7ec1831c994a6ff774e692e Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 11:12:00 +0530 Subject: [PATCH 16/39] removed reset --- projects/dekkku/src/main.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 0672038..828d5a7 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -31,14 +31,6 @@ impl Default for Store { } } -impl Store { - pub fn reset(&self) { - self.users.write().unwrap().clear(); - self.post.write().unwrap().clear(); - *self.is_post_same.write().unwrap() = false; - } -} - struct Query; #[Object] From a80b9f35609fb24d09f35dcb6f287496a80c439e Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 12:19:50 +0530 Subject: [PATCH 17/39] clean up --- projects/dekkku/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 828d5a7..0cc2eb7 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -42,7 +42,7 @@ impl Query { let client = ctx.data_unchecked::>(); let response = client.get(ALL_POSTS).send().await?; - let posts: Vec = serde_json::from_value(response.json().await?)?; + let posts: Vec = response.json().await?; let store = ctx.data_unchecked::>(); @@ -98,7 +98,7 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); let response = client.get(ALL_USERS).send().await?; - let users: Vec = serde_json::from_value(response.json().await?)?; + let users: Vec = response.json().await?; Ok(users) } @@ -248,7 +248,7 @@ fn create_schema() -> Schema { let user_loader = DataLoader::new(UserLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); let post_loader = - DataLoader::new(PostLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); + DataLoader::new(PostLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(5)); Schema::build(Query, EmptyMutation, EmptySubscription) .data(client) .data(user_loader) From 14f0dfc9e9e5bfc68f566989d5928a3aa859c1c8 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 12:30:10 +0530 Subject: [PATCH 18/39] use sampling with caching --- projects/dekkku/Cargo.lock | 1 + projects/dekkku/Cargo.toml | 1 + projects/dekkku/src/main.rs | 104 +++++++++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/projects/dekkku/Cargo.lock b/projects/dekkku/Cargo.lock index 1728be0..712d137 100644 --- a/projects/dekkku/Cargo.lock +++ b/projects/dekkku/Cargo.lock @@ -375,6 +375,7 @@ dependencies = [ "easy_retry", "futures", "poem", + "rand", "reqwest", "serde", "serde_json", diff --git a/projects/dekkku/Cargo.toml b/projects/dekkku/Cargo.toml index 50111c0..8ece86c 100644 --- a/projects/dekkku/Cargo.toml +++ b/projects/dekkku/Cargo.toml @@ -17,6 +17,7 @@ async-graphql = {version = "7.0.8", features = [ async-graphql-poem = "7.0.9" poem = "3.1.0" futures = "0.3" +rand = "0.8" [workspace] members = [] \ No newline at end of file diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 0cc2eb7..1613c90 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -3,6 +3,7 @@ use async_graphql::http::GraphiQLSource; use async_graphql::*; use async_graphql_poem::*; use poem::{listener::TcpListener, web::Html, *}; +use rand::seq::SliceRandom; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ @@ -18,7 +19,7 @@ const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { post: RwLock>, users: RwLock>, - is_post_same: RwLock, // rename to is_dirty. + is_db_same: RwLock, // rename to is_dirty. } impl Default for Store { @@ -26,7 +27,7 @@ impl Default for Store { Self { post: RwLock::new(HashMap::default()), users: RwLock::new(HashMap::default()), - is_post_same: RwLock::new(false), + is_db_same: RwLock::new(false), } } } @@ -47,28 +48,50 @@ impl Query { let store = ctx.data_unchecked::>(); let are_posts_same = { - let cached_post = store.post.read().unwrap(); - let post1_exists = cached_post - .get(&posts[1].id.unwrap()) - .map(|post1| *post1 == posts[1]) - .unwrap_or(false); - let post2_exists = cached_post - .get(&posts[2].id.unwrap()) - .map(|post2| post2 == &posts[2]) - .unwrap_or(false); - - post1_exists && post2_exists + // TODO: pick ID's + let is_cache_empty = { store.post.read().unwrap().is_empty() }; + + if is_cache_empty { + // Pick any two random posts from posts + let mut rng = rand::thread_rng(); + let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); + if selected_posts.len() == 2 { + let post1 = selected_posts[0]; + let post2 = selected_posts[1]; + let mut posts_writer = store.post.write().unwrap(); + posts_writer.insert(post1.id.unwrap(), post1.clone()); + posts_writer.insert(post2.id.unwrap(), post2.clone()); + } + false + } else { + // We've the posts, now check the posts from cache with the posts. + let mut are_posts_same = true; + let cached_posts = { + store + .post + .write() + .unwrap() + .values() + .cloned() + .collect::>() + }; + let mut posts_writer = store.post.write().unwrap(); + for post in cached_posts { + if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { + if post != *new_post { + are_posts_same = false; + posts_writer.insert(new_post.id.unwrap(), new_post.clone()); + } + } else { + are_posts_same = false; + break; + } + } + are_posts_same + } }; - if are_posts_same { - *store.is_post_same.write().unwrap() = true; - } else { - // store.reset(); // expensive operation but it's required. TODO: don't clean up posts, directly replace them. - *store.is_post_same.write().unwrap() = false; - let mut store_post_writer = store.post.write().unwrap(); - store_post_writer.insert(posts[1].id.unwrap(), posts[1].clone()); - store_post_writer.insert(posts[2].id.unwrap(), posts[2].clone()); - } + *store.is_db_same.write().unwrap() = are_posts_same; Ok(posts) } @@ -84,7 +107,7 @@ impl Query { let store = ctx.data_unchecked::>(); if let Some(cached_post) = store.post.write().unwrap().get(&id) { if actual_post != cached_post { - *store.is_post_same.write().unwrap() = false; + *store.is_db_same.write().unwrap() = false; } } store.post.write().unwrap().insert(id, actual_post.clone()); @@ -99,6 +122,35 @@ impl Query { let client = ctx.data_unchecked::>(); let response = client.get(ALL_USERS).send().await?; let users: Vec = response.json().await?; + + let store = ctx.data_unchecked::>(); + let mut rng = rand::thread_rng(); + let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); + + if selected_users.len() == 2 { + let user1 = selected_users[0]; + let user2 = selected_users[1]; + + let users_writer = store.users.read().unwrap(); + let mut are_users_same = true; + + for user in [user1, user2] { + if let Some(id) = user.id { + if let Some(cached_user) = users_writer.get(&id) { + if cached_user != user { + are_users_same = false; + } + } else { + are_users_same = false; + } + } + } + + if !are_users_same { + *store.is_db_same.write().unwrap() = false; + } + } + Ok(users) } @@ -115,7 +167,7 @@ impl Query { let store = ctx.data_unchecked::>(); if let Some(cached_user) = store.users.write().unwrap().get(&id) { if cached_user != actual_user { - *store.is_post_same.write().unwrap() = false; + *store.is_db_same.write().unwrap() = false; } } store.users.write().unwrap().insert(id, actual_user.clone()); @@ -143,7 +195,7 @@ impl Post { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let store = ctx.data_unchecked::>(); - if *store.is_post_same.read().unwrap() + if *store.is_db_same.read().unwrap() && store.users.read().unwrap().contains_key(&self.user_id) { let user = store.users.read().unwrap().get(&self.user_id).cloned(); @@ -154,7 +206,7 @@ impl Post { if let Some(actual_user) = user.as_ref() { if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { if cached_user != actual_user { - *store.is_post_same.write().unwrap() = false; + *store.is_db_same.write().unwrap() = false; } } store From bb72a185797bb80bdb0f7f48bfc134eb6c6fa759 Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 12:40:02 +0530 Subject: [PATCH 19/39] clean up --- projects/dekkku/src/main.rs | 110 +++++++++++------------------------- 1 file changed, 34 insertions(+), 76 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 1613c90..fd2e347 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -19,15 +19,15 @@ const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { post: RwLock>, users: RwLock>, - is_db_same: RwLock, // rename to is_dirty. + is_dirty: RwLock, } impl Default for Store { fn default() -> Self { Self { - post: RwLock::new(HashMap::default()), - users: RwLock::new(HashMap::default()), - is_db_same: RwLock::new(false), + post: RwLock::new(HashMap::new()), + users: RwLock::new(HashMap::new()), + is_dirty: RwLock::new(false), } } } @@ -36,78 +36,54 @@ struct Query; #[Object] impl Query { - async fn posts( - &self, - ctx: &Context<'_>, - ) -> std::result::Result, async_graphql::Error> { + async fn posts(&self, ctx: &Context<'_>) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); - let response = client.get(ALL_POSTS).send().await?; let posts: Vec = response.json().await?; - let store = ctx.data_unchecked::>(); let are_posts_same = { - // TODO: pick ID's - let is_cache_empty = { store.post.read().unwrap().is_empty() }; - + let is_cache_empty = store.post.read().unwrap().is_empty(); if is_cache_empty { - // Pick any two random posts from posts let mut rng = rand::thread_rng(); let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); if selected_posts.len() == 2 { - let post1 = selected_posts[0]; - let post2 = selected_posts[1]; let mut posts_writer = store.post.write().unwrap(); - posts_writer.insert(post1.id.unwrap(), post1.clone()); - posts_writer.insert(post2.id.unwrap(), post2.clone()); + for post in selected_posts { + posts_writer.insert(post.id.unwrap(), post.clone()); + } } false } else { - // We've the posts, now check the posts from cache with the posts. - let mut are_posts_same = true; - let cached_posts = { - store - .post - .write() - .unwrap() - .values() - .cloned() - .collect::>() - }; + let cached_posts: Vec = store.post.read().unwrap().values().cloned().collect(); let mut posts_writer = store.post.write().unwrap(); - for post in cached_posts { + cached_posts.iter().all(|post| { if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { - if post != *new_post { - are_posts_same = false; + if post != new_post { posts_writer.insert(new_post.id.unwrap(), new_post.clone()); + false + } else { + true } } else { - are_posts_same = false; - break; + false } - } - are_posts_same + }) } }; - *store.is_db_same.write().unwrap() = are_posts_same; - + *store.is_dirty.write().unwrap() = !are_posts_same; Ok(posts) } - async fn post( - &self, - ctx: &Context<'_>, - id: i32, - ) -> std::result::Result, async_graphql::Error> { + async fn post(&self, ctx: &Context<'_>, id: i32) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; if let Some(actual_post) = post.as_ref() { let store = ctx.data_unchecked::>(); - if let Some(cached_post) = store.post.write().unwrap().get(&id) { + if let Some(cached_post) = store.post.read().unwrap().get(&id) { if actual_post != cached_post { - *store.is_db_same.write().unwrap() = false; + *store.is_dirty.write().unwrap() = true; } } store.post.write().unwrap().insert(id, actual_post.clone()); @@ -115,10 +91,7 @@ impl Query { Ok(post) } - async fn users( - &self, - ctx: &Context<'_>, - ) -> std::result::Result, async_graphql::Error> { + async fn users(&self, ctx: &Context<'_>) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); let response = client.get(ALL_USERS).send().await?; let users: Vec = response.json().await?; @@ -128,51 +101,35 @@ impl Query { let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); if selected_users.len() == 2 { - let user1 = selected_users[0]; - let user2 = selected_users[1]; - let users_writer = store.users.read().unwrap(); - let mut are_users_same = true; - - for user in [user1, user2] { + let are_users_same = selected_users.iter().all(|user| { if let Some(id) = user.id { - if let Some(cached_user) = users_writer.get(&id) { - if cached_user != user { - are_users_same = false; - } - } else { - are_users_same = false; - } + users_writer.get(&id).map_or(false, |cached_user| cached_user == *user) + } else { + false } - } + }); if !are_users_same { - *store.is_db_same.write().unwrap() = false; + *store.is_dirty.write().unwrap() = true; } } Ok(users) } - async fn user( - &self, - ctx: &Context<'_>, - id: i32, - ) -> std::result::Result, async_graphql::Error> { + async fn user(&self, ctx: &Context<'_>, id: i32) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; - if let Some(actual_user) = &user { - // cache the user early let store = ctx.data_unchecked::>(); - if let Some(cached_user) = store.users.write().unwrap().get(&id) { + if let Some(cached_user) = store.users.read().unwrap().get(&id) { if cached_user != actual_user { - *store.is_db_same.write().unwrap() = false; + *store.is_dirty.write().unwrap() = true; } } store.users.write().unwrap().insert(id, actual_user.clone()); } - Ok(user) } } @@ -195,7 +152,7 @@ impl Post { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let store = ctx.data_unchecked::>(); - if *store.is_db_same.read().unwrap() + if !*store.is_dirty.read().unwrap() && store.users.read().unwrap().contains_key(&self.user_id) { let user = store.users.read().unwrap().get(&self.user_id).cloned(); @@ -203,10 +160,11 @@ impl Post { } else { let loader = ctx.data_unchecked::>(); let user = loader.load_one(self.user_id).await?; + if let Some(actual_user) = user.as_ref() { if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { if cached_user != actual_user { - *store.is_db_same.write().unwrap() = false; + *store.is_dirty.write().unwrap() = true; } } store From 28e0c5de4da1bf1db2b6c52d0f9a132447e4809a Mon Sep 17 00:00:00 2001 From: dekkku Date: Wed, 11 Sep 2024 13:18:56 +0530 Subject: [PATCH 20/39] - fix tests --- projects/dekkku/src/main.rs | 42 ++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index fd2e347..0eda444 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -36,7 +36,10 @@ struct Query; #[Object] impl Query { - async fn posts(&self, ctx: &Context<'_>) -> std::result::Result, async_graphql::Error> { + async fn posts( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); let response = client.get(ALL_POSTS).send().await?; let posts: Vec = response.json().await?; @@ -55,7 +58,8 @@ impl Query { } false } else { - let cached_posts: Vec = store.post.read().unwrap().values().cloned().collect(); + let cached_posts: Vec = + store.post.read().unwrap().values().cloned().collect(); let mut posts_writer = store.post.write().unwrap(); cached_posts.iter().all(|post| { if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { @@ -72,26 +76,44 @@ impl Query { } }; + if !are_posts_same { + // clean up the users. + store.users.write().unwrap().clear(); + } + *store.is_dirty.write().unwrap() = !are_posts_same; Ok(posts) } - async fn post(&self, ctx: &Context<'_>, id: i32) -> std::result::Result, async_graphql::Error> { + async fn post( + &self, + ctx: &Context<'_>, + id: i32, + ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; if let Some(actual_post) = post.as_ref() { let store = ctx.data_unchecked::>(); + let mut are_posts_same = true; if let Some(cached_post) = store.post.read().unwrap().get(&id) { if actual_post != cached_post { *store.is_dirty.write().unwrap() = true; + are_posts_same = false; } } + if !are_posts_same { + // clean up the users. + store.users.write().unwrap().clear(); + } store.post.write().unwrap().insert(id, actual_post.clone()); } Ok(post) } - async fn users(&self, ctx: &Context<'_>) -> std::result::Result, async_graphql::Error> { + async fn users( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { let client = ctx.data_unchecked::>(); let response = client.get(ALL_USERS).send().await?; let users: Vec = response.json().await?; @@ -104,7 +126,9 @@ impl Query { let users_writer = store.users.read().unwrap(); let are_users_same = selected_users.iter().all(|user| { if let Some(id) = user.id { - users_writer.get(&id).map_or(false, |cached_user| cached_user == *user) + users_writer + .get(&id) + .map_or(false, |cached_user| cached_user == *user) } else { false } @@ -118,7 +142,11 @@ impl Query { Ok(users) } - async fn user(&self, ctx: &Context<'_>, id: i32) -> std::result::Result, async_graphql::Error> { + async fn user( + &self, + ctx: &Context<'_>, + id: i32, + ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; if let Some(actual_user) = &user { @@ -160,7 +188,7 @@ impl Post { } else { let loader = ctx.data_unchecked::>(); let user = loader.load_one(self.user_id).await?; - + if let Some(actual_user) = user.as_ref() { if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { if cached_user != actual_user { From 7f90bd87baf9071684b4e5c4e2aac55d0dc838bd Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 11:02:49 +0530 Subject: [PATCH 21/39] batch concurrent requests into one. --- projects/dekkku/Cargo.lock | 37 +++++++- projects/dekkku/Cargo.toml | 4 +- projects/dekkku/src/main.rs | 171 ++++++++++++++++++++++++++---------- 3 files changed, 164 insertions(+), 48 deletions(-) diff --git a/projects/dekkku/Cargo.lock b/projects/dekkku/Cargo.lock index 712d137..2c0a22b 100644 --- a/projects/dekkku/Cargo.lock +++ b/projects/dekkku/Cargo.lock @@ -56,9 +56,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "ascii_utils" @@ -314,6 +314,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -359,6 +365,20 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -372,6 +392,7 @@ dependencies = [ "anyhow", "async-graphql", "async-graphql-poem", + "dashmap", "easy_retry", "futures", "poem", @@ -380,6 +401,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-retry", ] [[package]] @@ -1750,6 +1772,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" diff --git a/projects/dekkku/Cargo.toml b/projects/dekkku/Cargo.toml index 8ece86c..46c6d77 100644 --- a/projects/dekkku/Cargo.toml +++ b/projects/dekkku/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -anyhow = "1.0.86" +anyhow = "1.0.88" easy_retry = { version = "0.1.0", features = ["async"] } reqwest = { version = "0.12.7", features = ["json"] } serde_json = "1.0.127" @@ -18,6 +18,8 @@ async-graphql-poem = "7.0.9" poem = "3.1.0" futures = "0.3" rand = "0.8" +tokio-retry = "0.3" +dashmap = "6.1.0" [workspace] members = [] \ No newline at end of file diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 0eda444..5a9e839 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -2,6 +2,8 @@ use async_graphql::dataloader::*; use async_graphql::http::GraphiQLSource; use async_graphql::*; use async_graphql_poem::*; +use bytes::Bytes; +use dashmap::DashMap; use poem::{listener::TcpListener, web::Html, *}; use rand::seq::SliceRandom; use reqwest::Client; @@ -11,13 +13,16 @@ use std::{ sync::{Arc, RwLock}, time::Duration, }; +use tokio::sync::oneshot; +use tokio_retry::strategy::{jitter, ExponentialBackoff}; +use tokio_retry::Retry; const BASE_URL: &str = "http://localhost:3000"; const ALL_USERS: &str = "http://localhost:3000/users"; const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { - post: RwLock>, + posts: RwLock>, users: RwLock>, is_dirty: RwLock, } @@ -25,7 +30,7 @@ struct Store { impl Default for Store { fn default() -> Self { Self { - post: RwLock::new(HashMap::new()), + posts: RwLock::new(HashMap::new()), users: RwLock::new(HashMap::new()), is_dirty: RwLock::new(false), } @@ -40,31 +45,30 @@ impl Query { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { - let client = ctx.data_unchecked::>(); - let response = client.get(ALL_POSTS).send().await?; - let posts: Vec = response.json().await?; + let http_client = ctx.data_unchecked::>(); + let posts: Vec = http_client.request(ALL_POSTS).await?; let store = ctx.data_unchecked::>(); let are_posts_same = { - let is_cache_empty = store.post.read().unwrap().is_empty(); + let is_cache_empty = store.posts.read().unwrap().is_empty(); if is_cache_empty { let mut rng = rand::thread_rng(); let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); if selected_posts.len() == 2 { - let mut posts_writer = store.post.write().unwrap(); + let mut posts_writer = store.posts.write().unwrap(); for post in selected_posts { - posts_writer.insert(post.id.unwrap(), post.clone()); + posts_writer.insert(post.id, post.clone()); } } false } else { let cached_posts: Vec = - store.post.read().unwrap().values().cloned().collect(); - let mut posts_writer = store.post.write().unwrap(); + store.posts.read().unwrap().values().cloned().collect(); + let mut posts_writer = store.posts.write().unwrap(); cached_posts.iter().all(|post| { if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { if post != new_post { - posts_writer.insert(new_post.id.unwrap(), new_post.clone()); + posts_writer.insert(new_post.id, new_post.clone()); false } else { true @@ -95,7 +99,7 @@ impl Query { if let Some(actual_post) = post.as_ref() { let store = ctx.data_unchecked::>(); let mut are_posts_same = true; - if let Some(cached_post) = store.post.read().unwrap().get(&id) { + if let Some(cached_post) = store.posts.read().unwrap().get(&id) { if actual_post != cached_post { *store.is_dirty.write().unwrap() = true; are_posts_same = false; @@ -105,7 +109,7 @@ impl Query { // clean up the users. store.users.write().unwrap().clear(); } - store.post.write().unwrap().insert(id, actual_post.clone()); + store.posts.write().unwrap().insert(id, actual_post.clone()); } Ok(post) } @@ -114,9 +118,8 @@ impl Query { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { - let client = ctx.data_unchecked::>(); - let response = client.get(ALL_USERS).send().await?; - let users: Vec = response.json().await?; + let http_client = ctx.data_unchecked::>(); + let users: Vec = http_client.request(ALL_USERS).await?; let store = ctx.data_unchecked::>(); let mut rng = rand::thread_rng(); @@ -125,13 +128,9 @@ impl Query { if selected_users.len() == 2 { let users_writer = store.users.read().unwrap(); let are_users_same = selected_users.iter().all(|user| { - if let Some(id) = user.id { - users_writer - .get(&id) - .map_or(false, |cached_user| cached_user == *user) - } else { - false - } + users_writer + .get(&user.id) + .map_or(false, |cached_user| cached_user == *user) }); if !are_users_same { @@ -166,7 +165,7 @@ impl Query { #[serde(rename_all = "camelCase")] #[graphql(complex)] struct Post { - id: Option, + id: i32, #[graphql(name = "userId")] user_id: i32, title: Option, @@ -208,7 +207,7 @@ impl Post { #[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] struct User { - id: Option, + id: i32, name: Option, username: Option, email: Option, @@ -229,8 +228,8 @@ struct Geo { lng: Option, } -struct PostLoader(Arc); -struct UserLoader(Arc); +struct PostLoader(Arc); +struct UserLoader(Arc); impl Loader for PostLoader { type Value = Post; @@ -243,11 +242,8 @@ impl Loader for PostLoader { let mut result = std::collections::HashMap::new(); for &id in keys { let url = format!("{}/posts/{}", BASE_URL, id); - let response = self.0.get(url).send().await?; - if response.status().is_success() { - let post: Post = response.json().await?; - result.insert(id, post); - } + let post: Post = self.0.request(&url).await?; + result.insert(id, post); } Ok(result) } @@ -268,29 +264,114 @@ impl Loader for UserLoader { .collect::>() .join("&"); let url = format!("{}/users?{}", BASE_URL, qp); - let response = self.0.get(url).send().await?; - if response.status().is_success() { - let users: Vec = response.json().await?; - for user in users { - if let Some(id) = user.id { - result.insert(id, user); - } - } + let users: Vec = self.0.request(&url).await?; + for user in users { + result.insert(user.id, user); } Ok(result) } } +async fn fetch_with_retry( + client: &Client, + url: &str, +) -> std::result::Result { + let retry_strategy = ExponentialBackoff::from_millis(10).map(jitter).take(3); // Retry up to 3 times + + Retry::spawn(retry_strategy, || client.get(url).send()).await +} + +#[allow(dead_code)] +struct HttpClient { + client: Arc, +} + +impl HttpClient { + fn new(client: Arc) -> Self { + Self { client } + } + + #[allow(dead_code)] + async fn request Deserialize<'de>>( + &self, + url: &str, + ) -> std::result::Result { + let response = fetch_with_retry(&self.client, url).await?; + Ok(response.json().await?) + } +} + +struct RequestBatcher { + client: Arc, + on_going_req: + Arc>>>>, +} + +impl RequestBatcher { + fn new(client: Arc) -> Self { + Self { + client, + on_going_req: Default::default(), + } + } + + async fn request Deserialize<'de>>( + &self, + url: &str, + ) -> std::result::Result { + let (tx, rx) = oneshot::channel(); + + let should_fetch = { + let mut entry = self.on_going_req.entry(url.to_owned()).or_default(); + entry.push(tx); + entry.len() == 1 + }; + + if should_fetch { + let client = self.client.clone(); + let url = url.to_owned(); + let on_going_req = self.on_going_req.clone(); + + tokio::spawn(async move { + let result = fetch_with_retry(&client, &url).await; + + if let Ok(response) = result { + if let Ok(body) = response.bytes().await { + // let's say in queue, we've got around 10k requests and only one is executing right now. + // and if it fails then we won't reach here to remove the all requests from queue + // handle that scenario. + let senders = { on_going_req.remove(&url) }; + if let Some((_, senders)) = senders { + for sender in senders { + let _ = sender.send(Ok(body.clone())); + } + } + } + } + }); + } + + let response = rx.await??; + + Ok(serde_json::from_slice(&response)?) + } +} + fn create_schema() -> Schema { let client = Arc::new(Client::new()); - let user_loader = - DataLoader::new(UserLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); - let post_loader = - DataLoader::new(PostLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(5)); + let http_client = Arc::new(HttpClient::new(client.clone())); + let batch_client = Arc::new(RequestBatcher::new(client.clone())); + + let user_loader = DataLoader::new(UserLoader(batch_client.clone()), tokio::spawn) + .delay(Duration::from_millis(1)); + let post_loader = DataLoader::new(PostLoader(batch_client.clone()), tokio::spawn) + .delay(Duration::from_millis(5)); + Schema::build(Query, EmptyMutation, EmptySubscription) - .data(client) + .data(http_client) .data(user_loader) .data(post_loader) + .data(batch_client) .data(Arc::new(Store::default())) .finish() } From 2909ced1119bf05d3acaedbbd37d98a5801be68d Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 11:09:49 +0530 Subject: [PATCH 22/39] added bytes --- projects/dekkku/Cargo.lock | 12 ++++++++++++ projects/dekkku/Cargo.toml | 2 ++ 2 files changed, 14 insertions(+) diff --git a/projects/dekkku/Cargo.lock b/projects/dekkku/Cargo.lock index 2c0a22b..a6c3fa1 100644 --- a/projects/dekkku/Cargo.lock +++ b/projects/dekkku/Cargo.lock @@ -392,6 +392,7 @@ dependencies = [ "anyhow", "async-graphql", "async-graphql-poem", + "bytes", "dashmap", "easy_retry", "futures", @@ -400,6 +401,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_json_borrow", "tokio", "tokio-retry", ] @@ -1524,6 +1526,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_json_borrow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176a77dea19cf9b2cfe7f9e31966112ef8282a709af7c0a0fb28fc6347c7ba78" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" diff --git a/projects/dekkku/Cargo.toml b/projects/dekkku/Cargo.toml index 46c6d77..ea1937b 100644 --- a/projects/dekkku/Cargo.toml +++ b/projects/dekkku/Cargo.toml @@ -20,6 +20,8 @@ futures = "0.3" rand = "0.8" tokio-retry = "0.3" dashmap = "6.1.0" +serde_json_borrow = "0.6.0" +bytes = "1.7.1" [workspace] members = [] \ No newline at end of file From 0b6cb06bdfdaea83c2cec237bbd17510ad5f8113 Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 21:58:46 +0530 Subject: [PATCH 23/39] refactor cache impl --- projects/dekkku/src/main.rs | 201 ++++++++++++++++++------------------ 1 file changed, 99 insertions(+), 102 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 5a9e839..ae0992a 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -9,7 +9,6 @@ use rand::seq::SliceRandom; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, sync::{Arc, RwLock}, time::Duration, }; @@ -21,20 +20,90 @@ const BASE_URL: &str = "http://localhost:3000"; const ALL_USERS: &str = "http://localhost:3000/users"; const ALL_POSTS: &str = "http://localhost:3000/posts"; -struct Store { - posts: RwLock>, - users: RwLock>, +// can cache the POST and USER. +#[derive(Default)] +struct Cache { + posts: DashMap, + users: DashMap, is_dirty: RwLock, } -impl Default for Store { - fn default() -> Self { - Self { - posts: RwLock::new(HashMap::new()), - users: RwLock::new(HashMap::new()), - is_dirty: RwLock::new(false), +impl Cache { + fn validate_user(&self, user: &User) { + if let Some(cached_user) = self.users.get(&user.id) { + if *cached_user != *user { + *self.is_dirty.write().unwrap() = true; + } + } + self.users.insert(user.id, user.clone()); + } + + fn validate_users(&self, users: &[User]) { + if self.users.is_empty() { + return; + } + + let mut rng = rand::thread_rng(); + let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); + if selected_users.len() == 2 { + let are_users_same = selected_users.iter().all(|user| { + self.users + .get(&user.id) + .map_or(false, |cached_user| *cached_user == **user) + }); + + if !are_users_same { + *self.is_dirty.write().unwrap() = true; + } + } + } + + fn validate_posts(&self, posts: &[Post]) { + if self.users.is_empty() { + // fill the cache with users. + let mut rng = rand::thread_rng(); + let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); + if selected_posts.len() == 2 { + for post in selected_posts { + self.posts.insert(post.id, post.clone()); + } + } + } else { + let mut cache_dirty = false; + for post in self.posts.iter() { + if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { + if *post != *new_post { + cache_dirty = true; + break; + } + } + } + + if cache_dirty { + *self.is_dirty.write().unwrap() = true; + self.posts.clear(); + } + } + } + + fn validate_post(&self, post: &Post) { + let mut are_posts_same = true; + if let Some(cached_post) = self.posts.get(&post.id) { + if *post != *cached_post { + are_posts_same = false; + } + } + + if !are_posts_same { + // clean up the users. + self.users.clear(); + *self.is_dirty.write().unwrap() = true; } } + + fn should_fetch_users(&self, user_id: &i32) -> bool { + self.users.contains_key(user_id) && !*self.is_dirty.read().unwrap() + } } struct Query; @@ -47,45 +116,10 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let http_client = ctx.data_unchecked::>(); let posts: Vec = http_client.request(ALL_POSTS).await?; - let store = ctx.data_unchecked::>(); - - let are_posts_same = { - let is_cache_empty = store.posts.read().unwrap().is_empty(); - if is_cache_empty { - let mut rng = rand::thread_rng(); - let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); - if selected_posts.len() == 2 { - let mut posts_writer = store.posts.write().unwrap(); - for post in selected_posts { - posts_writer.insert(post.id, post.clone()); - } - } - false - } else { - let cached_posts: Vec = - store.posts.read().unwrap().values().cloned().collect(); - let mut posts_writer = store.posts.write().unwrap(); - cached_posts.iter().all(|post| { - if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { - if post != new_post { - posts_writer.insert(new_post.id, new_post.clone()); - false - } else { - true - } - } else { - false - } - }) - } - }; - if !are_posts_same { - // clean up the users. - store.users.write().unwrap().clear(); - } + let cache = ctx.data_unchecked::>(); + cache.validate_posts(&posts); - *store.is_dirty.write().unwrap() = !are_posts_same; Ok(posts) } @@ -96,21 +130,12 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; + if let Some(actual_post) = post.as_ref() { - let store = ctx.data_unchecked::>(); - let mut are_posts_same = true; - if let Some(cached_post) = store.posts.read().unwrap().get(&id) { - if actual_post != cached_post { - *store.is_dirty.write().unwrap() = true; - are_posts_same = false; - } - } - if !are_posts_same { - // clean up the users. - store.users.write().unwrap().clear(); - } - store.posts.write().unwrap().insert(id, actual_post.clone()); + let cache = ctx.data_unchecked::>(); + cache.validate_post(actual_post); } + Ok(post) } @@ -121,22 +146,8 @@ impl Query { let http_client = ctx.data_unchecked::>(); let users: Vec = http_client.request(ALL_USERS).await?; - let store = ctx.data_unchecked::>(); - let mut rng = rand::thread_rng(); - let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); - - if selected_users.len() == 2 { - let users_writer = store.users.read().unwrap(); - let are_users_same = selected_users.iter().all(|user| { - users_writer - .get(&user.id) - .map_or(false, |cached_user| cached_user == *user) - }); - - if !are_users_same { - *store.is_dirty.write().unwrap() = true; - } - } + let cache = ctx.data_unchecked::>(); + cache.validate_users(&users); Ok(users) } @@ -149,14 +160,10 @@ impl Query { let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; if let Some(actual_user) = &user { - let store = ctx.data_unchecked::>(); - if let Some(cached_user) = store.users.read().unwrap().get(&id) { - if cached_user != actual_user { - *store.is_dirty.write().unwrap() = true; - } - } - store.users.write().unwrap().insert(id, actual_user.clone()); + let cache = ctx.data_unchecked::>(); + cache.validate_user(actual_user); } + Ok(user) } } @@ -178,27 +185,19 @@ impl Post { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { - let store = ctx.data_unchecked::>(); - if !*store.is_dirty.read().unwrap() - && store.users.read().unwrap().contains_key(&self.user_id) - { - let user = store.users.read().unwrap().get(&self.user_id).cloned(); + let cache = ctx.data_unchecked::>(); + if cache.should_fetch_users(&self.user_id) { + let user = cache + .users + .get(&self.user_id) + .map(|ref_multi| ref_multi.clone()); Ok(user) } else { let loader = ctx.data_unchecked::>(); let user = loader.load_one(self.user_id).await?; if let Some(actual_user) = user.as_ref() { - if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { - if cached_user != actual_user { - *store.is_dirty.write().unwrap() = true; - } - } - store - .users - .write() - .unwrap() - .insert(self.user_id, actual_user.clone()); + cache.validate_user(actual_user); } Ok(user) } @@ -277,7 +276,6 @@ async fn fetch_with_retry( url: &str, ) -> std::result::Result { let retry_strategy = ExponentialBackoff::from_millis(10).map(jitter).take(3); // Retry up to 3 times - Retry::spawn(retry_strategy, || client.get(url).send()).await } @@ -287,6 +285,7 @@ struct HttpClient { } impl HttpClient { + #[allow(dead_code)] fn new(client: Arc) -> Self { Self { client } } @@ -359,7 +358,6 @@ impl RequestBatcher { fn create_schema() -> Schema { let client = Arc::new(Client::new()); - let http_client = Arc::new(HttpClient::new(client.clone())); let batch_client = Arc::new(RequestBatcher::new(client.clone())); let user_loader = DataLoader::new(UserLoader(batch_client.clone()), tokio::spawn) @@ -368,11 +366,10 @@ fn create_schema() -> Schema { .delay(Duration::from_millis(5)); Schema::build(Query, EmptyMutation, EmptySubscription) - .data(http_client) .data(user_loader) .data(post_loader) .data(batch_client) - .data(Arc::new(Store::default())) + .data(Arc::new(Cache::default())) .finish() } From 91f07555b270c514d6c8e8565c90b905d6587e68 Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:25:02 +0530 Subject: [PATCH 24/39] added old code back for benchmarking --- projects/dekkku/src/revert-main.rs | 315 +++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 projects/dekkku/src/revert-main.rs diff --git a/projects/dekkku/src/revert-main.rs b/projects/dekkku/src/revert-main.rs new file mode 100644 index 0000000..745c5fd --- /dev/null +++ b/projects/dekkku/src/revert-main.rs @@ -0,0 +1,315 @@ +use async_graphql::dataloader::*; +use async_graphql::http::GraphiQLSource; +use async_graphql::*; +use async_graphql_poem::*; +use poem::{listener::TcpListener, web::Html, *}; +use rand::seq::SliceRandom; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, + time::Duration, +}; + +const BASE_URL: &str = "http://localhost:3000"; +const ALL_USERS: &str = "http://localhost:3000/users"; +const ALL_POSTS: &str = "http://localhost:3000/posts"; + +struct Store { + post: RwLock>, + users: RwLock>, + is_dirty: RwLock, +} + +impl Default for Store { + fn default() -> Self { + Self { + post: RwLock::new(HashMap::new()), + users: RwLock::new(HashMap::new()), + is_dirty: RwLock::new(false), + } + } +} + +struct Query; + +#[Object] +impl Query { + async fn posts( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { + let client = ctx.data_unchecked::>(); + let response = client.get(ALL_POSTS).send().await?; + let posts: Vec = response.json().await?; + let store = ctx.data_unchecked::>(); + + let are_posts_same = { + let is_cache_empty = store.post.read().unwrap().is_empty(); + if is_cache_empty { + let mut rng = rand::thread_rng(); + let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); + if selected_posts.len() == 2 { + let mut posts_writer = store.post.write().unwrap(); + for post in selected_posts { + posts_writer.insert(post.id.unwrap(), post.clone()); + } + } + false + } else { + let cached_posts: Vec = + store.post.read().unwrap().values().cloned().collect(); + let mut posts_writer = store.post.write().unwrap(); + cached_posts.iter().all(|post| { + if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { + if post != new_post { + posts_writer.insert(new_post.id.unwrap(), new_post.clone()); + false + } else { + true + } + } else { + false + } + }) + } + }; + + if !are_posts_same { + // clean up the users. + store.users.write().unwrap().clear(); + } + + *store.is_dirty.write().unwrap() = !are_posts_same; + Ok(posts) + } + + async fn post( + &self, + ctx: &Context<'_>, + id: i32, + ) -> std::result::Result, async_graphql::Error> { + let loader = ctx.data_unchecked::>(); + let post = loader.load_one(id).await?; + if let Some(actual_post) = post.as_ref() { + let store = ctx.data_unchecked::>(); + let mut are_posts_same = true; + if let Some(cached_post) = store.post.read().unwrap().get(&id) { + if actual_post != cached_post { + *store.is_dirty.write().unwrap() = true; + are_posts_same = false; + } + } + if !are_posts_same { + // clean up the users. + store.users.write().unwrap().clear(); + } + store.post.write().unwrap().insert(id, actual_post.clone()); + } + Ok(post) + } + + async fn users( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { + let client = ctx.data_unchecked::>(); + let response = client.get(ALL_USERS).send().await?; + let users: Vec = response.json().await?; + + let store = ctx.data_unchecked::>(); + let mut rng = rand::thread_rng(); + let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); + + if selected_users.len() == 2 { + let users_writer = store.users.read().unwrap(); + let are_users_same = selected_users.iter().all(|user| { + if let Some(id) = user.id { + users_writer + .get(&id) + .map_or(false, |cached_user| cached_user == *user) + } else { + false + } + }); + + if !are_users_same { + *store.is_dirty.write().unwrap() = true; + } + } + + Ok(users) + } + + async fn user( + &self, + ctx: &Context<'_>, + id: i32, + ) -> std::result::Result, async_graphql::Error> { + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(id).await?; + if let Some(actual_user) = &user { + let store = ctx.data_unchecked::>(); + if let Some(cached_user) = store.users.read().unwrap().get(&id) { + if cached_user != actual_user { + *store.is_dirty.write().unwrap() = true; + } + } + store.users.write().unwrap().insert(id, actual_user.clone()); + } + Ok(user) + } +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq, Default)] +#[serde(rename_all = "camelCase")] +#[graphql(complex)] +struct Post { + id: Option, + #[graphql(name = "userId")] + user_id: i32, + title: Option, + body: Option, +} + +#[ComplexObject] +impl Post { + async fn user( + &self, + ctx: &Context<'_>, + ) -> std::result::Result, async_graphql::Error> { + let store = ctx.data_unchecked::>(); + if !*store.is_dirty.read().unwrap() + && store.users.read().unwrap().contains_key(&self.user_id) + { + let user = store.users.read().unwrap().get(&self.user_id).cloned(); + Ok(user) + } else { + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(self.user_id).await?; + + if let Some(actual_user) = user.as_ref() { + if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { + if cached_user != actual_user { + *store.is_dirty.write().unwrap() = true; + } + } + store + .users + .write() + .unwrap() + .insert(self.user_id, actual_user.clone()); + } + Ok(user) + } + } +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] +struct User { + id: Option, + name: Option, + username: Option, + email: Option, + address: Option
, + phone: Option, + website: Option, +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] +struct Address { + zipcode: Option, + geo: Option, +} + +#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] +struct Geo { + lat: Option, + lng: Option, +} + +struct PostLoader(Arc); +struct UserLoader(Arc); + +impl Loader for PostLoader { + type Value = Post; + type Error = async_graphql::Error; + + async fn load( + &self, + keys: &[i32], + ) -> std::result::Result, Self::Error> { + let mut result = std::collections::HashMap::new(); + for &id in keys { + let url = format!("{}/posts/{}", BASE_URL, id); + let response = self.0.get(url).send().await?; + if response.status().is_success() { + let post: Post = response.json().await?; + result.insert(id, post); + } + } + Ok(result) + } +} + +impl Loader for UserLoader { + type Value = User; + type Error = async_graphql::Error; + + async fn load( + &self, + keys: &[i32], + ) -> std::result::Result, Self::Error> { + let mut result = std::collections::HashMap::new(); + let qp = keys + .iter() + .map(|id| format!("id={}", id)) + .collect::>() + .join("&"); + let url = format!("{}/users?{}", BASE_URL, qp); + let response = self.0.get(url).send().await?; + if response.status().is_success() { + let users: Vec = response.json().await?; + for user in users { + if let Some(id) = user.id { + result.insert(id, user); + } + } + } + Ok(result) + } +} + +fn create_schema() -> Schema { + let client = Arc::new(Client::new()); + let user_loader = + DataLoader::new(UserLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); + let post_loader = + DataLoader::new(PostLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(5)); + Schema::build(Query, EmptyMutation, EmptySubscription) + .data(client) + .data(user_loader) + .data(post_loader) + .data(Arc::new(Store::default())) + .finish() +} + +#[handler] +async fn graphiql() -> impl IntoResponse { + Html(GraphiQLSource::build().finish()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let schema = create_schema(); + + // start the http server + let app = Route::new() + .at("/graphql", get(graphiql).post(GraphQL::new(schema.clone()))) + .at("/", get(graphiql).post(GraphQL::new(schema.clone()))); + Server::new(TcpListener::bind("0.0.0.0:8000")) + .run(app) + .await?; + Ok(()) +} \ No newline at end of file From 6a40a0e14863bed061d6544a83f01cafb9479a07 Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:27:49 +0530 Subject: [PATCH 25/39] move to bin folder --- projects/dekkku/src/{ => bin}/revert-main.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename projects/dekkku/src/{ => bin}/revert-main.rs (100%) diff --git a/projects/dekkku/src/revert-main.rs b/projects/dekkku/src/bin/revert-main.rs similarity index 100% rename from projects/dekkku/src/revert-main.rs rename to projects/dekkku/src/bin/revert-main.rs From 5311397a95191e6aa806c79ab11a1c93e60322e0 Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:28:27 +0530 Subject: [PATCH 26/39] update shell script --- projects/dekkku/run.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/dekkku/run.sh b/projects/dekkku/run.sh index 4a4a972..6b7889a 100755 --- a/projects/dekkku/run.sh +++ b/projects/dekkku/run.sh @@ -1,3 +1,5 @@ #!/usr/bin/env bash -cargo run -p dekkku --release +cargo run --bin revert-main --release + +# cargo run -p dekkku --release From 357a779444c755f07a9134174a4905eb962b1b66 Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:36:23 +0530 Subject: [PATCH 27/39] run only main.rs --- projects/dekkku/run.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/dekkku/run.sh b/projects/dekkku/run.sh index 6b7889a..4a4a972 100755 --- a/projects/dekkku/run.sh +++ b/projects/dekkku/run.sh @@ -1,5 +1,3 @@ #!/usr/bin/env bash -cargo run --bin revert-main --release - -# cargo run -p dekkku --release +cargo run -p dekkku --release From a534a781d9bd337bc0ebe86290e3cc13b48456de Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:38:36 +0530 Subject: [PATCH 28/39] revert --- projects/dekkku/src/bin/revert-main.rs | 315 ------------------------- 1 file changed, 315 deletions(-) delete mode 100644 projects/dekkku/src/bin/revert-main.rs diff --git a/projects/dekkku/src/bin/revert-main.rs b/projects/dekkku/src/bin/revert-main.rs deleted file mode 100644 index 745c5fd..0000000 --- a/projects/dekkku/src/bin/revert-main.rs +++ /dev/null @@ -1,315 +0,0 @@ -use async_graphql::dataloader::*; -use async_graphql::http::GraphiQLSource; -use async_graphql::*; -use async_graphql_poem::*; -use poem::{listener::TcpListener, web::Html, *}; -use rand::seq::SliceRandom; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - sync::{Arc, RwLock}, - time::Duration, -}; - -const BASE_URL: &str = "http://localhost:3000"; -const ALL_USERS: &str = "http://localhost:3000/users"; -const ALL_POSTS: &str = "http://localhost:3000/posts"; - -struct Store { - post: RwLock>, - users: RwLock>, - is_dirty: RwLock, -} - -impl Default for Store { - fn default() -> Self { - Self { - post: RwLock::new(HashMap::new()), - users: RwLock::new(HashMap::new()), - is_dirty: RwLock::new(false), - } - } -} - -struct Query; - -#[Object] -impl Query { - async fn posts( - &self, - ctx: &Context<'_>, - ) -> std::result::Result, async_graphql::Error> { - let client = ctx.data_unchecked::>(); - let response = client.get(ALL_POSTS).send().await?; - let posts: Vec = response.json().await?; - let store = ctx.data_unchecked::>(); - - let are_posts_same = { - let is_cache_empty = store.post.read().unwrap().is_empty(); - if is_cache_empty { - let mut rng = rand::thread_rng(); - let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); - if selected_posts.len() == 2 { - let mut posts_writer = store.post.write().unwrap(); - for post in selected_posts { - posts_writer.insert(post.id.unwrap(), post.clone()); - } - } - false - } else { - let cached_posts: Vec = - store.post.read().unwrap().values().cloned().collect(); - let mut posts_writer = store.post.write().unwrap(); - cached_posts.iter().all(|post| { - if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { - if post != new_post { - posts_writer.insert(new_post.id.unwrap(), new_post.clone()); - false - } else { - true - } - } else { - false - } - }) - } - }; - - if !are_posts_same { - // clean up the users. - store.users.write().unwrap().clear(); - } - - *store.is_dirty.write().unwrap() = !are_posts_same; - Ok(posts) - } - - async fn post( - &self, - ctx: &Context<'_>, - id: i32, - ) -> std::result::Result, async_graphql::Error> { - let loader = ctx.data_unchecked::>(); - let post = loader.load_one(id).await?; - if let Some(actual_post) = post.as_ref() { - let store = ctx.data_unchecked::>(); - let mut are_posts_same = true; - if let Some(cached_post) = store.post.read().unwrap().get(&id) { - if actual_post != cached_post { - *store.is_dirty.write().unwrap() = true; - are_posts_same = false; - } - } - if !are_posts_same { - // clean up the users. - store.users.write().unwrap().clear(); - } - store.post.write().unwrap().insert(id, actual_post.clone()); - } - Ok(post) - } - - async fn users( - &self, - ctx: &Context<'_>, - ) -> std::result::Result, async_graphql::Error> { - let client = ctx.data_unchecked::>(); - let response = client.get(ALL_USERS).send().await?; - let users: Vec = response.json().await?; - - let store = ctx.data_unchecked::>(); - let mut rng = rand::thread_rng(); - let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); - - if selected_users.len() == 2 { - let users_writer = store.users.read().unwrap(); - let are_users_same = selected_users.iter().all(|user| { - if let Some(id) = user.id { - users_writer - .get(&id) - .map_or(false, |cached_user| cached_user == *user) - } else { - false - } - }); - - if !are_users_same { - *store.is_dirty.write().unwrap() = true; - } - } - - Ok(users) - } - - async fn user( - &self, - ctx: &Context<'_>, - id: i32, - ) -> std::result::Result, async_graphql::Error> { - let loader = ctx.data_unchecked::>(); - let user = loader.load_one(id).await?; - if let Some(actual_user) = &user { - let store = ctx.data_unchecked::>(); - if let Some(cached_user) = store.users.read().unwrap().get(&id) { - if cached_user != actual_user { - *store.is_dirty.write().unwrap() = true; - } - } - store.users.write().unwrap().insert(id, actual_user.clone()); - } - Ok(user) - } -} - -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[serde(rename_all = "camelCase")] -#[graphql(complex)] -struct Post { - id: Option, - #[graphql(name = "userId")] - user_id: i32, - title: Option, - body: Option, -} - -#[ComplexObject] -impl Post { - async fn user( - &self, - ctx: &Context<'_>, - ) -> std::result::Result, async_graphql::Error> { - let store = ctx.data_unchecked::>(); - if !*store.is_dirty.read().unwrap() - && store.users.read().unwrap().contains_key(&self.user_id) - { - let user = store.users.read().unwrap().get(&self.user_id).cloned(); - Ok(user) - } else { - let loader = ctx.data_unchecked::>(); - let user = loader.load_one(self.user_id).await?; - - if let Some(actual_user) = user.as_ref() { - if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { - if cached_user != actual_user { - *store.is_dirty.write().unwrap() = true; - } - } - store - .users - .write() - .unwrap() - .insert(self.user_id, actual_user.clone()); - } - Ok(user) - } - } -} - -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] -struct User { - id: Option, - name: Option, - username: Option, - email: Option, - address: Option
, - phone: Option, - website: Option, -} - -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] -struct Address { - zipcode: Option, - geo: Option, -} - -#[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] -struct Geo { - lat: Option, - lng: Option, -} - -struct PostLoader(Arc); -struct UserLoader(Arc); - -impl Loader for PostLoader { - type Value = Post; - type Error = async_graphql::Error; - - async fn load( - &self, - keys: &[i32], - ) -> std::result::Result, Self::Error> { - let mut result = std::collections::HashMap::new(); - for &id in keys { - let url = format!("{}/posts/{}", BASE_URL, id); - let response = self.0.get(url).send().await?; - if response.status().is_success() { - let post: Post = response.json().await?; - result.insert(id, post); - } - } - Ok(result) - } -} - -impl Loader for UserLoader { - type Value = User; - type Error = async_graphql::Error; - - async fn load( - &self, - keys: &[i32], - ) -> std::result::Result, Self::Error> { - let mut result = std::collections::HashMap::new(); - let qp = keys - .iter() - .map(|id| format!("id={}", id)) - .collect::>() - .join("&"); - let url = format!("{}/users?{}", BASE_URL, qp); - let response = self.0.get(url).send().await?; - if response.status().is_success() { - let users: Vec = response.json().await?; - for user in users { - if let Some(id) = user.id { - result.insert(id, user); - } - } - } - Ok(result) - } -} - -fn create_schema() -> Schema { - let client = Arc::new(Client::new()); - let user_loader = - DataLoader::new(UserLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(1)); - let post_loader = - DataLoader::new(PostLoader(client.clone()), tokio::spawn).delay(Duration::from_millis(5)); - Schema::build(Query, EmptyMutation, EmptySubscription) - .data(client) - .data(user_loader) - .data(post_loader) - .data(Arc::new(Store::default())) - .finish() -} - -#[handler] -async fn graphiql() -> impl IntoResponse { - Html(GraphiQLSource::build().finish()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let schema = create_schema(); - - // start the http server - let app = Route::new() - .at("/graphql", get(graphiql).post(GraphQL::new(schema.clone()))) - .at("/", get(graphiql).post(GraphQL::new(schema.clone()))); - Server::new(TcpListener::bind("0.0.0.0:8000")) - .run(app) - .await?; - Ok(()) -} \ No newline at end of file From 355142093669c480360f33f7e57494d4d859b4b0 Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:56:49 +0530 Subject: [PATCH 29/39] revert back to RWLock --- projects/dekkku/src/main.rs | 58 ++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index ae0992a..68a8b22 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -9,6 +9,7 @@ use rand::seq::SliceRandom; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ + collections::HashMap, sync::{Arc, RwLock}, time::Duration, }; @@ -23,23 +24,25 @@ const ALL_POSTS: &str = "http://localhost:3000/posts"; // can cache the POST and USER. #[derive(Default)] struct Cache { - posts: DashMap, - users: DashMap, + posts: RwLock>, + users: RwLock>, is_dirty: RwLock, } impl Cache { fn validate_user(&self, user: &User) { - if let Some(cached_user) = self.users.get(&user.id) { + let mut user_writer = self.users.write().unwrap(); + if let Some(cached_user) = user_writer.get(&user.id) { if *cached_user != *user { *self.is_dirty.write().unwrap() = true; } } - self.users.insert(user.id, user.clone()); + user_writer.insert(user.id, user.clone()); } fn validate_users(&self, users: &[User]) { - if self.users.is_empty() { + let users_reader = self.users.read().unwrap(); + if users_reader.is_empty() { return; } @@ -47,7 +50,7 @@ impl Cache { let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); if selected_users.len() == 2 { let are_users_same = selected_users.iter().all(|user| { - self.users + users_reader .get(&user.id) .map_or(false, |cached_user| *cached_user == **user) }); @@ -59,50 +62,57 @@ impl Cache { } fn validate_posts(&self, posts: &[Post]) { - if self.users.is_empty() { + let users_empty = { self.users.read().unwrap().is_empty() }; + if users_empty { // fill the cache with users. + let mut posts_writer = self.posts.write().unwrap(); let mut rng = rand::thread_rng(); let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); if selected_posts.len() == 2 { for post in selected_posts { - self.posts.insert(post.id, post.clone()); + posts_writer.insert(post.id, post.clone()); } } } else { - let mut cache_dirty = false; - for post in self.posts.iter() { - if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { - if *post != *new_post { - cache_dirty = true; - break; + let cache_dirty = { + let mut cache_dirty = false; + for (_, post) in self.posts.read().unwrap().iter() { + if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { + if *post != *new_post { + cache_dirty = true; + break; + } } } - } + cache_dirty + }; if cache_dirty { *self.is_dirty.write().unwrap() = true; - self.posts.clear(); + self.posts.write().unwrap().clear(); } } } fn validate_post(&self, post: &Post) { - let mut are_posts_same = true; - if let Some(cached_post) = self.posts.get(&post.id) { - if *post != *cached_post { - are_posts_same = false; + let are_posts_same = { + let mut are_posts_same = true; + if let Some(cached_post) = self.posts.read().unwrap().get(&post.id) { + if *post != *cached_post { + are_posts_same = false; + } } - } + are_posts_same + }; if !are_posts_same { - // clean up the users. - self.users.clear(); + self.users.write().unwrap().clear(); *self.is_dirty.write().unwrap() = true; } } fn should_fetch_users(&self, user_id: &i32) -> bool { - self.users.contains_key(user_id) && !*self.is_dirty.read().unwrap() + self.users.read().unwrap().contains_key(user_id) && !*self.is_dirty.read().unwrap() } } From 6dcd04891d7e9d2306d9a171dca06c6171397deb Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 22:58:40 +0530 Subject: [PATCH 30/39] fix --- projects/dekkku/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 68a8b22..671b8e4 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -197,10 +197,7 @@ impl Post { ) -> std::result::Result, async_graphql::Error> { let cache = ctx.data_unchecked::>(); if cache.should_fetch_users(&self.user_id) { - let user = cache - .users - .get(&self.user_id) - .map(|ref_multi| ref_multi.clone()); + let user = cache.users.read().unwrap().get(&self.user_id).cloned(); Ok(user) } else { let loader = ctx.data_unchecked::>(); From 572998981bb86d347dfae4b99e634ea69a3bfffd Mon Sep 17 00:00:00 2001 From: dekkku Date: Thu, 12 Sep 2024 23:11:47 +0530 Subject: [PATCH 31/39] restore to Store --- projects/dekkku/src/main.rs | 202 ++++++++++++++++++------------------ 1 file changed, 99 insertions(+), 103 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 671b8e4..5a9e839 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -21,99 +21,20 @@ const BASE_URL: &str = "http://localhost:3000"; const ALL_USERS: &str = "http://localhost:3000/users"; const ALL_POSTS: &str = "http://localhost:3000/posts"; -// can cache the POST and USER. -#[derive(Default)] -struct Cache { +struct Store { posts: RwLock>, users: RwLock>, is_dirty: RwLock, } -impl Cache { - fn validate_user(&self, user: &User) { - let mut user_writer = self.users.write().unwrap(); - if let Some(cached_user) = user_writer.get(&user.id) { - if *cached_user != *user { - *self.is_dirty.write().unwrap() = true; - } - } - user_writer.insert(user.id, user.clone()); - } - - fn validate_users(&self, users: &[User]) { - let users_reader = self.users.read().unwrap(); - if users_reader.is_empty() { - return; - } - - let mut rng = rand::thread_rng(); - let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); - if selected_users.len() == 2 { - let are_users_same = selected_users.iter().all(|user| { - users_reader - .get(&user.id) - .map_or(false, |cached_user| *cached_user == **user) - }); - - if !are_users_same { - *self.is_dirty.write().unwrap() = true; - } - } - } - - fn validate_posts(&self, posts: &[Post]) { - let users_empty = { self.users.read().unwrap().is_empty() }; - if users_empty { - // fill the cache with users. - let mut posts_writer = self.posts.write().unwrap(); - let mut rng = rand::thread_rng(); - let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); - if selected_posts.len() == 2 { - for post in selected_posts { - posts_writer.insert(post.id, post.clone()); - } - } - } else { - let cache_dirty = { - let mut cache_dirty = false; - for (_, post) in self.posts.read().unwrap().iter() { - if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { - if *post != *new_post { - cache_dirty = true; - break; - } - } - } - cache_dirty - }; - - if cache_dirty { - *self.is_dirty.write().unwrap() = true; - self.posts.write().unwrap().clear(); - } - } - } - - fn validate_post(&self, post: &Post) { - let are_posts_same = { - let mut are_posts_same = true; - if let Some(cached_post) = self.posts.read().unwrap().get(&post.id) { - if *post != *cached_post { - are_posts_same = false; - } - } - are_posts_same - }; - - if !are_posts_same { - self.users.write().unwrap().clear(); - *self.is_dirty.write().unwrap() = true; +impl Default for Store { + fn default() -> Self { + Self { + posts: RwLock::new(HashMap::new()), + users: RwLock::new(HashMap::new()), + is_dirty: RwLock::new(false), } } - - fn should_fetch_users(&self, user_id: &i32) -> bool { - self.users.read().unwrap().contains_key(user_id) && !*self.is_dirty.read().unwrap() - } } struct Query; @@ -126,10 +47,45 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let http_client = ctx.data_unchecked::>(); let posts: Vec = http_client.request(ALL_POSTS).await?; + let store = ctx.data_unchecked::>(); - let cache = ctx.data_unchecked::>(); - cache.validate_posts(&posts); + let are_posts_same = { + let is_cache_empty = store.posts.read().unwrap().is_empty(); + if is_cache_empty { + let mut rng = rand::thread_rng(); + let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); + if selected_posts.len() == 2 { + let mut posts_writer = store.posts.write().unwrap(); + for post in selected_posts { + posts_writer.insert(post.id, post.clone()); + } + } + false + } else { + let cached_posts: Vec = + store.posts.read().unwrap().values().cloned().collect(); + let mut posts_writer = store.posts.write().unwrap(); + cached_posts.iter().all(|post| { + if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { + if post != new_post { + posts_writer.insert(new_post.id, new_post.clone()); + false + } else { + true + } + } else { + false + } + }) + } + }; + if !are_posts_same { + // clean up the users. + store.users.write().unwrap().clear(); + } + + *store.is_dirty.write().unwrap() = !are_posts_same; Ok(posts) } @@ -140,12 +96,21 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; - if let Some(actual_post) = post.as_ref() { - let cache = ctx.data_unchecked::>(); - cache.validate_post(actual_post); + let store = ctx.data_unchecked::>(); + let mut are_posts_same = true; + if let Some(cached_post) = store.posts.read().unwrap().get(&id) { + if actual_post != cached_post { + *store.is_dirty.write().unwrap() = true; + are_posts_same = false; + } + } + if !are_posts_same { + // clean up the users. + store.users.write().unwrap().clear(); + } + store.posts.write().unwrap().insert(id, actual_post.clone()); } - Ok(post) } @@ -156,8 +121,22 @@ impl Query { let http_client = ctx.data_unchecked::>(); let users: Vec = http_client.request(ALL_USERS).await?; - let cache = ctx.data_unchecked::>(); - cache.validate_users(&users); + let store = ctx.data_unchecked::>(); + let mut rng = rand::thread_rng(); + let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); + + if selected_users.len() == 2 { + let users_writer = store.users.read().unwrap(); + let are_users_same = selected_users.iter().all(|user| { + users_writer + .get(&user.id) + .map_or(false, |cached_user| cached_user == *user) + }); + + if !are_users_same { + *store.is_dirty.write().unwrap() = true; + } + } Ok(users) } @@ -170,10 +149,14 @@ impl Query { let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; if let Some(actual_user) = &user { - let cache = ctx.data_unchecked::>(); - cache.validate_user(actual_user); + let store = ctx.data_unchecked::>(); + if let Some(cached_user) = store.users.read().unwrap().get(&id) { + if cached_user != actual_user { + *store.is_dirty.write().unwrap() = true; + } + } + store.users.write().unwrap().insert(id, actual_user.clone()); } - Ok(user) } } @@ -195,16 +178,27 @@ impl Post { &self, ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { - let cache = ctx.data_unchecked::>(); - if cache.should_fetch_users(&self.user_id) { - let user = cache.users.read().unwrap().get(&self.user_id).cloned(); + let store = ctx.data_unchecked::>(); + if !*store.is_dirty.read().unwrap() + && store.users.read().unwrap().contains_key(&self.user_id) + { + let user = store.users.read().unwrap().get(&self.user_id).cloned(); Ok(user) } else { let loader = ctx.data_unchecked::>(); let user = loader.load_one(self.user_id).await?; if let Some(actual_user) = user.as_ref() { - cache.validate_user(actual_user); + if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { + if cached_user != actual_user { + *store.is_dirty.write().unwrap() = true; + } + } + store + .users + .write() + .unwrap() + .insert(self.user_id, actual_user.clone()); } Ok(user) } @@ -283,6 +277,7 @@ async fn fetch_with_retry( url: &str, ) -> std::result::Result { let retry_strategy = ExponentialBackoff::from_millis(10).map(jitter).take(3); // Retry up to 3 times + Retry::spawn(retry_strategy, || client.get(url).send()).await } @@ -292,7 +287,6 @@ struct HttpClient { } impl HttpClient { - #[allow(dead_code)] fn new(client: Arc) -> Self { Self { client } } @@ -365,6 +359,7 @@ impl RequestBatcher { fn create_schema() -> Schema { let client = Arc::new(Client::new()); + let http_client = Arc::new(HttpClient::new(client.clone())); let batch_client = Arc::new(RequestBatcher::new(client.clone())); let user_loader = DataLoader::new(UserLoader(batch_client.clone()), tokio::spawn) @@ -373,10 +368,11 @@ fn create_schema() -> Schema { .delay(Duration::from_millis(5)); Schema::build(Query, EmptyMutation, EmptySubscription) + .data(http_client) .data(user_loader) .data(post_loader) .data(batch_client) - .data(Arc::new(Cache::default())) + .data(Arc::new(Store::default())) .finish() } From b5993bb1ae6d8da13fe9f252ca7c2bb4e2cb7154 Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 02:01:25 +0530 Subject: [PATCH 32/39] load users upfront --- projects/dekkku/src/main.rs | 133 +++++++++++++----------------------- 1 file changed, 49 insertions(+), 84 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 5a9e839..63ed76c 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -9,7 +9,7 @@ use rand::seq::SliceRandom; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, sync::{Arc, RwLock}, time::Duration, }; @@ -46,9 +46,10 @@ impl Query { ctx: &Context<'_>, ) -> std::result::Result, async_graphql::Error> { let http_client = ctx.data_unchecked::>(); - let posts: Vec = http_client.request(ALL_POSTS).await?; + let mut posts: Vec = http_client.request(ALL_POSTS).await?; let store = ctx.data_unchecked::>(); + // TODO: clean this up. let are_posts_same = { let is_cache_empty = store.posts.read().unwrap().is_empty(); if is_cache_empty { @@ -80,12 +81,50 @@ impl Query { } }; - if !are_posts_same { - // clean up the users. - store.users.write().unwrap().clear(); + let should_query_user = ctx + .field() + .selection_set() + .any(|field| field.name() == "user"); + + if should_query_user { + if are_posts_same { + // Posts are the same, load the data from store and update the posts' user property. + let store_users = store.users.read().unwrap(); + for post in posts.iter_mut() { + if let Some(cached_user) = store_users.get(&post.user_id) { + post.user = Some(cached_user.clone()); + } + } + } else { + // let loader = ctx.data_unchecked::>(); + // let user_ids = posts.iter().map(|p| p.user_id).collect::>(); + // let users = loader.load_many(user_ids).await?; + + let qp = posts + .iter() + .map(|p| p.user_id) + .collect::>() + .into_iter() + .map(|user_id| format!("id={}", user_id)) + .collect::>() + .join("&"); + let url = format!("{ALL_USERS}?{qp}"); + let users: Vec = http_client.request(&url).await?; + let users = users + .into_iter() + .map(|user| (user.id, user)) + .collect::>(); + let mut store_users = store.users.write().unwrap(); + for post in posts.iter_mut() { + if let Some(user) = users.get(&post.user_id) { + post.user = Some(user.clone()); + store_users.insert(user.id, user.clone()); + } + } + } + *store.is_dirty.write().unwrap() = !are_posts_same; } - *store.is_dirty.write().unwrap() = !are_posts_same; Ok(posts) } @@ -96,21 +135,6 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let post = loader.load_one(id).await?; - if let Some(actual_post) = post.as_ref() { - let store = ctx.data_unchecked::>(); - let mut are_posts_same = true; - if let Some(cached_post) = store.posts.read().unwrap().get(&id) { - if actual_post != cached_post { - *store.is_dirty.write().unwrap() = true; - are_posts_same = false; - } - } - if !are_posts_same { - // clean up the users. - store.users.write().unwrap().clear(); - } - store.posts.write().unwrap().insert(id, actual_post.clone()); - } Ok(post) } @@ -120,24 +144,6 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let http_client = ctx.data_unchecked::>(); let users: Vec = http_client.request(ALL_USERS).await?; - - let store = ctx.data_unchecked::>(); - let mut rng = rand::thread_rng(); - let selected_users: Vec<&User> = users.choose_multiple(&mut rng, 2).collect(); - - if selected_users.len() == 2 { - let users_writer = store.users.read().unwrap(); - let are_users_same = selected_users.iter().all(|user| { - users_writer - .get(&user.id) - .map_or(false, |cached_user| cached_user == *user) - }); - - if !are_users_same { - *store.is_dirty.write().unwrap() = true; - } - } - Ok(users) } @@ -148,61 +154,19 @@ impl Query { ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); let user = loader.load_one(id).await?; - if let Some(actual_user) = &user { - let store = ctx.data_unchecked::>(); - if let Some(cached_user) = store.users.read().unwrap().get(&id) { - if cached_user != actual_user { - *store.is_dirty.write().unwrap() = true; - } - } - store.users.write().unwrap().insert(id, actual_user.clone()); - } Ok(user) } } #[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] -#[graphql(complex)] struct Post { id: i32, #[graphql(name = "userId")] user_id: i32, title: Option, body: Option, -} - -#[ComplexObject] -impl Post { - async fn user( - &self, - ctx: &Context<'_>, - ) -> std::result::Result, async_graphql::Error> { - let store = ctx.data_unchecked::>(); - if !*store.is_dirty.read().unwrap() - && store.users.read().unwrap().contains_key(&self.user_id) - { - let user = store.users.read().unwrap().get(&self.user_id).cloned(); - Ok(user) - } else { - let loader = ctx.data_unchecked::>(); - let user = loader.load_one(self.user_id).await?; - - if let Some(actual_user) = user.as_ref() { - if let Some(cached_user) = store.users.read().unwrap().get(&self.user_id) { - if cached_user != actual_user { - *store.is_dirty.write().unwrap() = true; - } - } - store - .users - .write() - .unwrap() - .insert(self.user_id, actual_user.clone()); - } - Ok(user) - } - } + user: Option, } #[derive(SimpleObject, Serialize, Deserialize, Debug, Clone, PartialEq)] @@ -240,6 +204,7 @@ impl Loader for PostLoader { keys: &[i32], ) -> std::result::Result, Self::Error> { let mut result = std::collections::HashMap::new(); + // too slow, make it parallel. for &id in keys { let url = format!("{}/posts/{}", BASE_URL, id); let post: Post = self.0.request(&url).await?; @@ -277,8 +242,8 @@ async fn fetch_with_retry( url: &str, ) -> std::result::Result { let retry_strategy = ExponentialBackoff::from_millis(10).map(jitter).take(3); // Retry up to 3 times - - Retry::spawn(retry_strategy, || client.get(url).send()).await + let result = Retry::spawn(retry_strategy, || client.get(url).send()).await; + result } #[allow(dead_code)] From 7a1b85bb7115d70ac6d11b9221b3d17aefce5f1b Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 02:08:28 +0530 Subject: [PATCH 33/39] fix --- projects/dekkku/src/main.rs | 64 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 63ed76c..966c393 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -49,44 +49,44 @@ impl Query { let mut posts: Vec = http_client.request(ALL_POSTS).await?; let store = ctx.data_unchecked::>(); - // TODO: clean this up. - let are_posts_same = { - let is_cache_empty = store.posts.read().unwrap().is_empty(); - if is_cache_empty { - let mut rng = rand::thread_rng(); - let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); - if selected_posts.len() == 2 { - let mut posts_writer = store.posts.write().unwrap(); - for post in selected_posts { - posts_writer.insert(post.id, post.clone()); - } - } - false - } else { - let cached_posts: Vec = - store.posts.read().unwrap().values().cloned().collect(); - let mut posts_writer = store.posts.write().unwrap(); - cached_posts.iter().all(|post| { - if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { - if post != new_post { - posts_writer.insert(new_post.id, new_post.clone()); - false - } else { - true - } - } else { - false - } - }) - } - }; - let should_query_user = ctx .field() .selection_set() .any(|field| field.name() == "user"); if should_query_user { + // TODO: clean this up. + let are_posts_same = { + let is_cache_empty = store.posts.read().unwrap().is_empty(); + if is_cache_empty { + let mut rng = rand::thread_rng(); + let selected_posts: Vec<&Post> = posts.choose_multiple(&mut rng, 2).collect(); + if selected_posts.len() == 2 { + let mut posts_writer = store.posts.write().unwrap(); + for post in selected_posts { + posts_writer.insert(post.id, post.clone()); + } + } + false + } else { + let cached_posts: Vec = + store.posts.read().unwrap().values().cloned().collect(); + let mut posts_writer = store.posts.write().unwrap(); + cached_posts.iter().all(|post| { + if let Some(new_post) = posts.iter().find(|p| p.id == post.id) { + if post != new_post { + posts_writer.insert(new_post.id, new_post.clone()); + false + } else { + true + } + } else { + false + } + }) + } + }; + if are_posts_same { // Posts are the same, load the data from store and update the posts' user property. let store_users = store.users.read().unwrap(); From 891eb028a8ca47de6704394b9e9a8a409b03472d Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 02:43:55 +0530 Subject: [PATCH 34/39] bug fixes --- projects/dekkku/src/main.rs | 53 ++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 966c393..655f566 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -4,6 +4,7 @@ use async_graphql::*; use async_graphql_poem::*; use bytes::Bytes; use dashmap::DashMap; +use futures::future; use poem::{listener::TcpListener, web::Html, *}; use rand::seq::SliceRandom; use reqwest::Client; @@ -14,17 +15,16 @@ use std::{ time::Duration, }; use tokio::sync::oneshot; +use tokio::task; use tokio_retry::strategy::{jitter, ExponentialBackoff}; use tokio_retry::Retry; -const BASE_URL: &str = "http://localhost:3000"; const ALL_USERS: &str = "http://localhost:3000/users"; const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { posts: RwLock>, users: RwLock>, - is_dirty: RwLock, } impl Default for Store { @@ -32,7 +32,6 @@ impl Default for Store { Self { posts: RwLock::new(HashMap::new()), users: RwLock::new(HashMap::new()), - is_dirty: RwLock::new(false), } } } @@ -55,7 +54,6 @@ impl Query { .any(|field| field.name() == "user"); if should_query_user { - // TODO: clean this up. let are_posts_same = { let is_cache_empty = store.posts.read().unwrap().is_empty(); if is_cache_empty { @@ -122,7 +120,6 @@ impl Query { } } } - *store.is_dirty.write().unwrap() = !are_posts_same; } Ok(posts) @@ -134,7 +131,16 @@ impl Query { id: i32, ) -> std::result::Result, async_graphql::Error> { let loader = ctx.data_unchecked::>(); - let post = loader.load_one(id).await?; + let mut post = loader.load_one(id).await?; + + // fetch the user as well. + // check if post is changed or not, if posts aren't changed then + if let Some(post_ref) = post.as_mut() { + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(post_ref.user_id).await?; + post_ref.user = user; + } + Ok(post) } @@ -203,13 +209,36 @@ impl Loader for PostLoader { &self, keys: &[i32], ) -> std::result::Result, Self::Error> { + let client = &self.0; + + // Create a vector to hold all the futures + let futures: Vec<_> = keys + .iter() + .map(|&id| { + let url = format!("{}/{}", ALL_POSTS, id); + let client = client.clone(); + task::spawn(async move { + let post: Post = client.request(&url).await?; + Ok::<_, async_graphql::Error>((id, post)) + }) + }) + .collect(); + + // Wait for all futures to complete + let results = future::join_all(futures).await; + + // Collect results into a HashMap let mut result = std::collections::HashMap::new(); - // too slow, make it parallel. - for &id in keys { - let url = format!("{}/posts/{}", BASE_URL, id); - let post: Post = self.0.request(&url).await?; - result.insert(id, post); + for task_result in results { + match task_result { + Ok(Ok((id, post))) => { + result.insert(id, post); + } + Ok(Err(e)) => return Err(e), + Err(e) => return Err(async_graphql::Error::new(format!("Task join error: {}", e))), + } } + Ok(result) } } @@ -228,7 +257,7 @@ impl Loader for UserLoader { .map(|id| format!("id={}", id)) .collect::>() .join("&"); - let url = format!("{}/users?{}", BASE_URL, qp); + let url = format!("{}?{}", ALL_USERS, qp); let users: Vec = self.0.request(&url).await?; for user in users { result.insert(user.id, user); From 9ed11a94cdf3d7648f289a33826ab03b8b47f405 Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 03:09:27 +0530 Subject: [PATCH 35/39] handle error in request batcher --- projects/dekkku/src/main.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 655f566..3b560e4 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -328,16 +328,31 @@ impl RequestBatcher { tokio::spawn(async move { let result = fetch_with_retry(&client, &url).await; - - if let Ok(response) = result { - if let Ok(body) = response.bytes().await { - // let's say in queue, we've got around 10k requests and only one is executing right now. - // and if it fails then we won't reach here to remove the all requests from queue - // handle that scenario. - let senders = { on_going_req.remove(&url) }; + let senders = on_going_req.remove(&url); + + match result { + Ok(response) => match response.bytes().await { + Ok(body) => { + if let Some((_, senders)) = senders { + for sender in senders { + let _ = sender.send(Ok(body.clone())); + } + } + } + Err(e) => { + if let Some((_, senders)) = senders { + for sender in senders { + let err = anyhow::anyhow!(e.to_string()); + let _ = sender.send(Err(err)); + } + } + } + }, + Err(e) => { if let Some((_, senders)) = senders { for sender in senders { - let _ = sender.send(Ok(body.clone())); + let err = anyhow::anyhow!(e.to_string()); + let _ = sender.send(Err(err)); } } } From 8e6fe33542fd41fa4489de7165bba1b674696384 Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 03:18:21 +0530 Subject: [PATCH 36/39] query use when asked --- projects/dekkku/src/main.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 3b560e4..171b186 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -133,14 +133,18 @@ impl Query { let loader = ctx.data_unchecked::>(); let mut post = loader.load_one(id).await?; - // fetch the user as well. - // check if post is changed or not, if posts aren't changed then - if let Some(post_ref) = post.as_mut() { - let loader = ctx.data_unchecked::>(); - let user = loader.load_one(post_ref.user_id).await?; - post_ref.user = user; - } + let should_query_user = ctx + .field() + .selection_set() + .any(|field| field.name() == "user"); + if should_query_user { + if let Some(post_ref) = post.as_mut() { + let loader = ctx.data_unchecked::>(); + let user = loader.load_one(post_ref.user_id).await?; + post_ref.user = user; + } + } Ok(post) } From c3888dd86142ded24170d0779d695ec78bc65884 Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 03:20:43 +0530 Subject: [PATCH 37/39] test dl --- projects/dekkku/src/main.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 171b186..2018742 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -94,24 +94,24 @@ impl Query { } } } else { - // let loader = ctx.data_unchecked::>(); - // let user_ids = posts.iter().map(|p| p.user_id).collect::>(); - // let users = loader.load_many(user_ids).await?; - - let qp = posts - .iter() - .map(|p| p.user_id) - .collect::>() - .into_iter() - .map(|user_id| format!("id={}", user_id)) - .collect::>() - .join("&"); - let url = format!("{ALL_USERS}?{qp}"); - let users: Vec = http_client.request(&url).await?; - let users = users - .into_iter() - .map(|user| (user.id, user)) - .collect::>(); + let loader = ctx.data_unchecked::>(); + let user_ids = posts.iter().map(|p| p.user_id).collect::>(); + let users = loader.load_many(user_ids).await?; + + // let qp = posts + // .iter() + // .map(|p| p.user_id) + // .collect::>() + // .into_iter() + // .map(|user_id| format!("id={}", user_id)) + // .collect::>() + // .join("&"); + // let url = format!("{ALL_USERS}?{qp}"); + // let users: Vec = http_client.request(&url).await?; + // let users = users + // .into_iter() + // .map(|user| (user.id, user)) + // .collect::>(); let mut store_users = store.users.write().unwrap(); for post in posts.iter_mut() { if let Some(user) = users.get(&post.user_id) { From 4edf8d5eec1eb978db013f89a8d8a5379c155dc9 Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 03:30:07 +0530 Subject: [PATCH 38/39] clean --- projects/dekkku/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 2018742..72cce4c 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -10,7 +10,7 @@ use rand::seq::SliceRandom; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, sync::{Arc, RwLock}, time::Duration, }; From b37ef20d13f807aff2065ff5fcf73e3c88d28078 Mon Sep 17 00:00:00 2001 From: dekkku Date: Fri, 13 Sep 2024 11:58:31 +0530 Subject: [PATCH 39/39] remove locks --- projects/dekkku/src/main.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/projects/dekkku/src/main.rs b/projects/dekkku/src/main.rs index 72cce4c..4757f0b 100644 --- a/projects/dekkku/src/main.rs +++ b/projects/dekkku/src/main.rs @@ -24,14 +24,14 @@ const ALL_POSTS: &str = "http://localhost:3000/posts"; struct Store { posts: RwLock>, - users: RwLock>, + users: DashMap, } impl Default for Store { fn default() -> Self { Self { posts: RwLock::new(HashMap::new()), - users: RwLock::new(HashMap::new()), + users: DashMap::default(), } } } @@ -87,9 +87,8 @@ impl Query { if are_posts_same { // Posts are the same, load the data from store and update the posts' user property. - let store_users = store.users.read().unwrap(); for post in posts.iter_mut() { - if let Some(cached_user) = store_users.get(&post.user_id) { + if let Some(cached_user) = store.users.get(&post.user_id) { post.user = Some(cached_user.clone()); } } @@ -112,11 +111,10 @@ impl Query { // .into_iter() // .map(|user| (user.id, user)) // .collect::>(); - let mut store_users = store.users.write().unwrap(); for post in posts.iter_mut() { if let Some(user) = users.get(&post.user_id) { post.user = Some(user.clone()); - store_users.insert(user.id, user.clone()); + store.users.insert(user.id, user.clone()); } } }