From 67700a48f488909797b0fc9dcf6031a66b780bee Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 10:12:15 +0000 Subject: [PATCH 01/10] Draft example for field accessor --- Cargo.lock | 1579 +++++++++++++++++++++++++++++++++--- Cargo.toml | 3 +- content/SUMMARY.md | 8 +- content/field-accessors.md | 152 ++++ src/lib.rs | 66 ++ 5 files changed, 1702 insertions(+), 106 deletions(-) create mode 100644 content/field-accessors.md diff --git a/Cargo.lock b/Cargo.lock index a0cf625..a432da7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,66 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "anyhow" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[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", +] + +[[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" @@ -17,6 +71,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -172,6 +247,7 @@ dependencies = [ "cgp", "datetime", "itertools", + "reqwest", "serde", "serde_json", "sha1", @@ -206,6 +282,22 @@ dependencies = [ "cgp-component", ] +[[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.16" @@ -249,12 +341,134 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[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-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -266,211 +480,1374 @@ dependencies = [ ] [[package]] -name = "iso8601" -version = "0.3.0" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "nom", + "cfg-if", + "libc", + "wasi", ] [[package]] -name = "itertools" -version = "0.11.0" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "itoa" -version = "1.0.11" +name = "h2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] -name = "libc" -version = "0.2.169" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "locale" -version = "0.2.2" +name = "http" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ - "libc", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "memchr" -version = "2.7.4" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] [[package]] -name = "nom" -version = "4.2.3" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "memchr", - "version_check 0.1.5", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "pad" -version = "0.1.6" +name = "httparse" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ - "unicode-width", + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", ] [[package]] -name = "prettyplease" -version = "0.2.25" +name = "hyper-rustls" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ - "proc-macro2", - "syn", + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] -name = "proc-macro2" -version = "1.0.89" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "unicode-ident", + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] -name = "quote" -version = "1.0.37" +name = "hyper-util" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "proc-macro2", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] -name = "redox_syscall" -version = "0.1.57" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "ryu" -version = "1.0.18" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "serde" -version = "1.0.214" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "serde_derive", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "serde_derive" -version = "1.0.214" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "proc-macro2", - "quote", - "syn", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "serde_json" -version = "1.0.132" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "sha1" -version = "0.10.6" +name = "icu_properties_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "syn" -version = "2.0.87" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] [[package]] -name = "typenum" -version = "1.17.0" +name = "idna" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] [[package]] -name = "unicode-ident" -version = "1.0.13" +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] [[package]] -name = "unicode-width" -version = "0.1.14" +name = "indexmap" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] [[package]] -name = "version_check" -version = "0.1.5" +name = "ipnet" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] -name = "version_check" -version = "0.9.5" +name = "iso8601" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f" +dependencies = [ + "nom", +] [[package]] -name = "winapi" -version = "0.3.9" +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "either", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "locale" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" +dependencies = [ + "libc", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[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 = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check 0.1.5", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +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 = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "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", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[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.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[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 = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[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.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +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 = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[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.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[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.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[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.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[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", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index fe85093..97cc26e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ itertools = "0.11.0" serde_json = "1" anyhow = "1" datetime = "0.5.2" -sha1 = "0.10.6" \ No newline at end of file +sha1 = "0.10.6" +reqwest = { version = "0.12.12", features = [ "blocking", "json" ] } \ No newline at end of file diff --git a/content/SUMMARY.md b/content/SUMMARY.md index 93b4c4c..a5255c9 100644 --- a/content/SUMMARY.md +++ b/content/SUMMARY.md @@ -25,6 +25,10 @@ - [Delegated Error Raisers](delegated-error-raiser.md) - [Error Reporting](error-reporting.md) - [Error Wrapping](error-wrapping.md) +- [Field Accessors](field-accessors.md) + - [`HasField`]() + - [`HasFieldMut`]() + - [Field Macros]() - [Component Presets]() - [Trait-Generic Providers]() - [`WithProvider`]() @@ -35,10 +39,6 @@ - [Provider Middleware]() - [Detached Provider]() - [Inner]() -- [Field Accessors]() - - [`HasField`]() - - [`HasFieldMut`]() - - [Field Macros]() - [Builder]() - [Dispatcher]() - [Generic Data Types]() diff --git a/content/field-accessors.md b/content/field-accessors.md new file mode 100644 index 0000000..47d28ac --- /dev/null +++ b/content/field-accessors.md @@ -0,0 +1,152 @@ +# Field Accessors + +Using impl-side dependencies, CGP provides a way to inject dependencies into providers +without polluting the public interfaces with additional constraints. A common use of +dependency injection is for the provider to retrieve some values from the context. +More commonly, we call this pattern field _accessor_ or _getter_, since we are getting +or accessing field values from the context. +In this chapter, we will walk through how to effectively define and use field accessors +with CGP. + +## Example: API Call + +Supposed that our application needs to make API calls to an external services to read +messages by message ID. To abstract away the details of the API call, we would define +CGP traits such as follows: + +```rust +# extern crate cgp; +# +use cgp::prelude::*; + +#[cgp_component { + name: MessageIdTypeComponent, + provider: ProvideMessageIdType, +}] +pub trait HasMessageIdType { + type MessageId; +} + +#[cgp_component { + name: MessageTypeComponent, + provider: ProvideMessageType, +}] +pub trait HasMessageType { + type Message; +} + +#[cgp_component { + provider: MessageQuerier, +}] +pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { + fn query_message(&self, message_id: &Self::MessageId) -> Result; +} +``` + +Following the patterns for [associated types](./associated-types.md), we define the +type traits `HasMessageIdType` and `HasMessageType` to abstract away the detailed structures +of the message ID and messages. +Following the patterns for [error handling](./error-handling.md), we define the +`CanQueryMessage` trait to accept an abstract `MessageId` value, and return +either an abstract `Message` or an abstract `Error`. + +With the interfaces defined, we will then try and implement a naive API client provider +that queries the message as HTTP request: + +```rust +# extern crate cgp; +# extern crate reqwest; +# extern crate serde; +# +# use cgp::prelude::*; +use reqwest::blocking::Client; +use reqwest::StatusCode; +use serde::Deserialize; + +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +pub struct ReadMessageFromApi; + +#[derive(Debug)] +pub struct ErrStatusCode { + pub status_code: StatusCode, +} + +#[derive(Deserialize)] +pub struct ApiMessageResponse { + pub message: String, +} + +impl MessageQuerier for ReadMessageFromApi +where + Context: HasMessageIdType + + HasMessageType + + CanRaiseError + + CanRaiseError, +{ + fn query_message(_context: &Context, message_id: &u64) -> Result { + let client = Client::new(); + + let response = client + .get(format!("http://localhost:8000/api/messages/{message_id}")) + .send() + .map_err(Context::raise_error)?; + + let status_code = response.status(); + + if !status_code.is_success() { + return Err(Context::raise_error(ErrStatusCode { status_code })); + } + + let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; + + Ok(message_response.message) + } +} +``` + +For the purpose of the examples here, we will use the [`reqwest`](https://docs.rs/reqwest) +library to make the HTTP calls. We will also use the _blocking_ version of the APIs +in this chapter, as we will only cover about doing asynchronous programming in CGP in +later chapters. + +In the above example, we implement `MessageQuerier` for the provider `ReadMessageFromApi`. +For simplicity, we require the additional constraint that `MessageId` needs to be +`u64`, and the `Message` type is just a simple `String`. +We also make use of the context to raise the `reqwest::Error` returned from calling +`reqwest` methods, and also a custom `ErrStatusCode` error in case if the server +returns error HTTP response. + +Inside the method body, we first build a reqwest `Client`, and then use it to issue +a HTTP GET request to the URL `"http://localhost:8000/api/messages/{message_id}"`. +If the returned HTTP status is not successful, we raise the error `ErrStatusCode`. +Otherwise, we parse the response body as JSON using the `ApiMessageResponse` struct, +which expects the response body to contain a `message` string field. + +We may quickly notice that the naive provider has several things hard coded. +For start, it has the hardcoded API base URL `http://localhost:8000`, which should +be made configurable. We will next walk through how to define _accessor_ traits +to access these configurable values from the context. + +## Getting API URL \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e69de29..f10a067 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1,66 @@ +use cgp::prelude::*; +use reqwest::blocking::Client; +use reqwest::StatusCode; +use serde::Deserialize; + +#[cgp_component { + name: MessageIdTypeComponent, + provider: ProvideMessageIdType, +}] +pub trait HasMessageIdType { + type MessageId; +} + +#[cgp_component { + name: MessageTypeComponent, + provider: ProvideMessageType, +}] +pub trait HasMessageType { + type Message; +} + +#[cgp_component { + provider: MessageQuerier, +}] +pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { + fn query_message(&self, message_id: &Self::MessageId) -> Result; +} + +pub struct ReadMessageFromApi; + +#[derive(Debug)] +pub struct ErrStatusCode { + pub status_code: StatusCode, +} + +#[derive(Deserialize)] +pub struct ApiMessageResponse { + pub message: String, +} + +impl MessageQuerier for ReadMessageFromApi +where + Context: HasMessageIdType + + HasMessageType + + CanRaiseError + + CanRaiseError, +{ + fn query_message(_context: &Context, message_id: &u64) -> Result { + let client = Client::new(); + + let response = client + .get(format!("http://localhost:8000/api/messages/{message_id}")) + .send() + .map_err(Context::raise_error)?; + + let status_code = response.status(); + + if !status_code.is_success() { + return Err(Context::raise_error(ErrStatusCode { status_code })); + } + + let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; + + Ok(message_response.message) + } +} From cac66bff5018922b941b8ac1be3d9caa70cd77b1 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 10:58:20 +0000 Subject: [PATCH 02/10] Add sections for accessor traits --- content/field-accessors.md | 256 ++++++++++++++++++++++++++++++++++++- src/lib.rs | 34 ++++- 2 files changed, 287 insertions(+), 3 deletions(-) diff --git a/content/field-accessors.md b/content/field-accessors.md index 47d28ac..403cb65 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -149,4 +149,258 @@ For start, it has the hardcoded API base URL `http://localhost:8000`, which shou be made configurable. We will next walk through how to define _accessor_ traits to access these configurable values from the context. -## Getting API URL \ No newline at end of file +## Getting the Base API URL + +Using CGP, it is pretty straightforward to define an accessor trait for getting +values from the context. To make the base API URL configurable, we would define +a `HasApiBaseUrl` trait as follows: + +```rust +# extern crate cgp; +# +# use cgp::prelude::*; +# +#[cgp_component { + provider: ApiBaseUrlGetter, +}] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} +``` + +The trait `HasApiBaseUrl` provides a method `api_base_url`, which returns a `&String` +from the context. In production applications, we may want the method to return a +[`Url`](https://docs.rs/url/latest/url/struct.Url.html), or even an abstract `Url` type. +But we will use strings here to keep the example simple. + +We can then include `HasApiBaseUrl` inside `ReadMessageFromApi`, so that we can +construct the HTTP request using the base API URL provided by the context: + +```rust +# extern crate cgp; +# extern crate reqwest; +# extern crate serde; +# +# use cgp::prelude::*; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +impl MessageQuerier for ReadMessageFromApi +where + Context: HasMessageIdType + + HasMessageType + + HasApiBaseUrl + + CanRaiseError + + CanRaiseError, +{ + fn query_message(context: &Context, message_id: &u64) -> Result { + let client = Client::new(); + + let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); + + let response = client.get(url).send().map_err(Context::raise_error)?; + + let status_code = response.status(); + + if !status_code.is_success() { + return Err(Context::raise_error(ErrStatusCode { status_code })); + } + + let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; + + Ok(message_response.message) + } +} +``` + +## Getting the Auth Token + +Aside from the base API URL, it is common for API services to require some kind of authentication +to protect the API resource from being accessed by unauthorized party. +For the purpose of this example, we will make use of simple _bearer tokens_ to access the API. + +Similar to `HasApiBaseUrl`, we will define a `HasAuthToken` getter to get the +auth token as follows: + +```rust +# extern crate cgp; +# +# use cgp::prelude::*; +# +#[cgp_component { + name: AuthTokenTypeComponent, + provider: ProvideAuthTokenType, +}] +pub trait HasAuthTokenType { + type AuthToken; +} + +#[cgp_component { + provider: AuthTokenGettter, +}] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} +``` + +Similar to the [earlier chapter](./associated-types.md), we first define `HasAuthTokenType` +to keep the `AuthToken` type abstract. In fact, the same `HasAuthTokenType` trait +and their respective providers could be reused across the chapters. This also shows +that having minimal CGP traits make it easier to reuse the same interface across +different applications. + +We then define a getter trait `HasAuthToken`, to get an abstract `AuthToken` value from +the context. We can then update `ReadMessageFromApi` to include the auth token +inside the `Authorization` HTTP header: + +```rust +# extern crate cgp; +# extern crate reqwest; +# extern crate serde; +# +# use core::fmt::Display; +# +# use cgp::prelude::*; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# #[cgp_component { +# provider: AuthTokenGettter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +impl MessageQuerier for ReadMessageFromApi +where + Context: HasMessageIdType + + HasMessageType + + HasApiBaseUrl + + HasAuthToken + + CanRaiseError + + CanRaiseError, + Context::AuthToken: Display, +{ + fn query_message(context: &Context, message_id: &u64) -> Result { + let client = Client::new(); + + let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); + + let response = client + .get(url) + .bearer_auth(context.auth_token()) + .send() + .map_err(Context::raise_error)?; + + let status_code = response.status(); + + if !status_code.is_success() { + return Err(Context::raise_error(ErrStatusCode { status_code })); + } + + let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; + + Ok(message_response.message) + } +} +``` + +In the updated code, we make use of reqwest's +[`bearer_auth`](https://docs.rs/reqwest/latest/reqwest/blocking/struct.RequestBuilder.html#method.bearer_auth) +method to include the auth token into the HTTP header. +In this case, the provider only require `Context::AuthToken` to implement `Display`, +making it possible to be used with custom `AuthToken` types other than `String`. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f10a067..a306eb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +use core::fmt::Display; + use cgp::prelude::*; use reqwest::blocking::Client; use reqwest::StatusCode; @@ -26,6 +28,28 @@ pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { fn query_message(&self, message_id: &Self::MessageId) -> Result; } +#[cgp_component { + provider: ApiBaseUrlGetter, +}] +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +#[cgp_component { + name: AuthTokenTypeComponent, + provider: ProvideAuthTokenType, +}] +pub trait HasAuthTokenType { + type AuthToken; +} + +#[cgp_component { + provider: AuthTokenGettter, +}] +pub trait HasAuthToken: HasAuthTokenType { + fn auth_token(&self) -> &Self::AuthToken; +} + pub struct ReadMessageFromApi; #[derive(Debug)] @@ -42,14 +66,20 @@ impl MessageQuerier for ReadMessageFromApi where Context: HasMessageIdType + HasMessageType + + HasApiBaseUrl + + HasAuthToken + CanRaiseError + CanRaiseError, + Context::AuthToken: Display, { - fn query_message(_context: &Context, message_id: &u64) -> Result { + fn query_message(context: &Context, message_id: &u64) -> Result { let client = Client::new(); + let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); + let response = client - .get(format!("http://localhost:8000/api/messages/{message_id}")) + .get(url) + .bearer_auth(context.auth_token()) .send() .map_err(Context::raise_error)?; From 7058379fcf0b752b946d27afbbb78ce807c48f6d Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 11:31:41 +0000 Subject: [PATCH 03/10] Add section for accessor method minimalism --- content/field-accessors.md | 69 +++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/content/field-accessors.md b/content/field-accessors.md index 403cb65..83d1bcf 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -403,4 +403,71 @@ In the updated code, we make use of reqwest's [`bearer_auth`](https://docs.rs/reqwest/latest/reqwest/blocking/struct.RequestBuilder.html#method.bearer_auth) method to include the auth token into the HTTP header. In this case, the provider only require `Context::AuthToken` to implement `Display`, -making it possible to be used with custom `AuthToken` types other than `String`. \ No newline at end of file +making it possible to be used with custom `AuthToken` types other than `String`. + +## Accessor Method Minimalism + +Given that it is common for providers like `ReadMessageFromApi` to use both `HasApiBaseUrl` and +`HasAuthToken` together, it may be tempting to merge the two traits and define a single trait +that contains both accessor methods: + +```rust +# extern crate cgp; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +#[cgp_component { + provider: ApiClientFieldsGetter, +}] +pub trait HasApiClientFields: HasAuthTokenType { + fn api_base_url(&self) -> &String; + + fn auth_token(&self) -> &Self::AuthToken; +} +``` + +Although this approach also works, it introduces unnecessary coupling between +the `api_base_url` field and the `auth_token` field. +If a provider only needs `api_base_url` but not `auth_token`, it would still +have to include the dependencies that it don't need. +Similarly, we can no longer implement separate providers for `ApiClientFieldsGetter` +to separately provide the fields `api_base_url` and `auth_token` in different ways. + +The coupling of unrelated fields also makes it more challenging to evolve the +application in the future. For example, if we switch to a different authentication +method like public key cryptography, we now need to remove the `auth_token` +method and replace it with a different method, which would affect all code +that depend on `HasApiClientFields`. On the other hand, it is much simpler +to add an additional getter trait, and gradually deprecate and transition +providers to use the new trait while still keeping the old trait around. + +As an application grows more complex, it would also be common to require +dozens of accessor methods, which would make a trait like `HasApiClientFields` +quickly become the bottleneck, and making it difficult for the application +to further evolve. In general, it is not possible to know up front which +of the accessor methods are related, and it can be a distraction to +attempt to make up theories of why it "makes sense" to group accessor +methods in certain ways. + +With the experience of using CGP in real world applications, we find that +one accessor method per accessor trait is the most effective way to +quickly iterate on the application implementation. +This makes it easy to add or remove accessor methods, and it removes a lot of +cognitive overload on having to think, decide and debate about which trait +an accessor method should belong or not belong to. +With the passage of time, it is almost inevitable that an accessor trait +that contains multiple accessor methods will need to be broken up, +because some of the accessor methods are no longer applicable to some +part of the application. + +As we will see in later sections and chapters, breaking the accessor methods +down to individual traits also allows us to introduce new design patterns +that can work when the trait contains only one accessor method. \ No newline at end of file From 1466f59240c380b3a35915f226b4e5ac4e191e9b Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 12:39:41 +0000 Subject: [PATCH 04/10] Draft context implementation --- Cargo.lock | 122 +++++++++++++++++++------------------ Cargo.toml | 25 +++++++- content/field-accessors.md | 16 ++++- src/lib.rs | 69 +++++++++++++++++++++ 4 files changed, 170 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a432da7..81a76a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "atomic-waker" @@ -101,8 +101,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cgp" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5adf76ce3579324bc8804a03da7e05a5012e4096e295d7ce16073dd0f55e923e" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-async", "cgp-core", @@ -112,8 +111,7 @@ dependencies = [ [[package]] name = "cgp-async" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0623cc42c39044ad4223a60b58d427fdece736913fa70e0c0b7787b207545886" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-async-macro", "cgp-sync", @@ -122,8 +120,7 @@ dependencies = [ [[package]] name = "cgp-async-macro" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22933d3a1fb401cc0208e4f8ef16f4c80579701a47b81d2f23ee5b0b697a7b0" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "proc-macro2", "quote", @@ -133,27 +130,20 @@ dependencies = [ [[package]] name = "cgp-component" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a567833f055c865fb46258f7c6a511b22a1da6bc22f58b47a5e5e3da7a2dd2" -dependencies = [ - "cgp-component-macro", -] +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" [[package]] name = "cgp-component-macro" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4c375220b569b618d6b4ec8793c61bbdd24925786aeb4ea1eac5010916c262" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-component-macro-lib", - "proc-macro2", ] [[package]] name = "cgp-component-macro-lib" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e9fa127625afac0527a2f1595ba365142d8d875ff04f155cb248aee456d2c0" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "itertools", "prettyplease", @@ -165,53 +155,60 @@ dependencies = [ [[package]] name = "cgp-core" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39951e4828fb216d51bfa42121095654f2adb0e4abbc9d03b011c5b47372a4c1" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-async", "cgp-component", + "cgp-component-macro", "cgp-error", "cgp-field", - "cgp-inner", + "cgp-field-macro", "cgp-type", ] [[package]] name = "cgp-error" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3866da7384b88d3260e781e60bc9da3a68d5552c33eea56e6780fea8c97d000" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-async", "cgp-component", + "cgp-component-macro", "cgp-type", ] +[[package]] +name = "cgp-error-anyhow" +version = "0.2.0" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" +dependencies = [ + "anyhow", + "cgp-core", +] + [[package]] name = "cgp-extra" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f79c15ed0088b1ca06ad58a8f124c1d63f4a554073ed372ff2b488a3fbf6fb9" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ + "cgp-inner", "cgp-run", + "cgp-runtime", ] [[package]] name = "cgp-field" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6956ca07b849e38106ee5b3c71c7e396088619115fe60934fd2598b873a9efac" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-component", - "cgp-field-macro", "cgp-type", ] [[package]] name = "cgp-field-macro" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2286ec0bd3a48571ee3644e80a52ba029f10c2edd6bc3da4f4ef9279583798d5" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-field-macro-lib", "proc-macro2", @@ -220,10 +217,8 @@ dependencies = [ [[package]] name = "cgp-field-macro-lib" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e05fc04f941c797c28db9dd9685d779b76784f29bdbe2eb368329abfca14d9a" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ - "itertools", "prettyplease", "proc-macro2", "quote", @@ -233,10 +228,10 @@ dependencies = [ [[package]] name = "cgp-inner" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd062e3bef5a8353acdcdae3e04c0012de0b19e9bd02acbccfce09e895814cf" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-component", + "cgp-component-macro", ] [[package]] @@ -245,6 +240,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cgp", + "cgp-error-anyhow", "datetime", "itertools", "reqwest", @@ -256,14 +252,22 @@ dependencies = [ [[package]] name = "cgp-run" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf13ddcb395e915d314abf917d86dc7fa461bd73a3da2724b4698d45eaa283e" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-async", "cgp-component", + "cgp-component-macro", "cgp-error", ] +[[package]] +name = "cgp-runtime" +version = "0.2.0" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" +dependencies = [ + "cgp-core", +] + [[package]] name = "cgp-sync" version = "0.2.0" @@ -276,10 +280,10 @@ dependencies = [ [[package]] name = "cgp-type" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3c9a9059625de770e4acddff6d5e56c5defd959b010b9643cfcb81832f5bed" +source = "git+https://github.com/contextgeneric/cgp.git#3dc5190a4d687c0a2792e5b3a1426d40c0254118" dependencies = [ "cgp-component", + "cgp-component-macro", ] [[package]] @@ -799,18 +803,18 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" @@ -1008,9 +1012,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "483f8c21f64f3ea09fe0f30f5d48c3e8eefe5dac9129f0075f76593b4c1da705" dependencies = [ "proc-macro2", "syn", @@ -1018,18 +1022,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1198,18 +1202,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -1218,9 +1222,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -1302,9 +1306,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -1484,9 +1488,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" diff --git a/Cargo.toml b/Cargo.toml index 97cc26e..b724e79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,31 @@ edition = "2021" [dependencies] cgp = { version = "0.2.0" } +cgp-error-anyhow = { version = "0.2.0" } serde = {version = "1", features = ["derive"] } -itertools = "0.11.0" +itertools = "0.14.0" serde_json = "1" anyhow = "1" datetime = "0.5.2" sha1 = "0.10.6" -reqwest = { version = "0.12.12", features = [ "blocking", "json" ] } \ No newline at end of file +reqwest = { version = "0.12.12", features = [ "blocking", "json" ] } + + +[patch.crates-io] +cgp = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-core = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-extra = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-async = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-async-macro = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-component = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-component-macro = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-component-macro-lib = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-type = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-field = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-field-macro = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-field-macro-lib = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-error = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-error-anyhow = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-run = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-runtime = { git = "https://github.com/contextgeneric/cgp.git" } +cgp-inner = { git = "https://github.com/contextgeneric/cgp.git" } diff --git a/content/field-accessors.md b/content/field-accessors.md index 83d1bcf..8d3e55e 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -470,4 +470,18 @@ part of the application. As we will see in later sections and chapters, breaking the accessor methods down to individual traits also allows us to introduce new design patterns -that can work when the trait contains only one accessor method. \ No newline at end of file +that can work when the trait contains only one accessor method. + +Nevertheless, CGP does not prevent developers to define accessor traits that contain +multiple types and accessor methods. +In terms of comfort, it would also make sense for developers who are new to CGP +to want to define non-minimal traits, since it has been in the mainstream +programming practices for decades. +As a result, readers are encourage to feel free to experiment around, and +include as many types and methods in a CGP trait as they prefer. + +On the other hand, for the purpose of this book, we will continue to make use +of minimal traits, since the book serves as reference materials that should +encourage best practices to its readers. + +## Implementing Accessor Providers \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index a306eb9..53e270e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,10 @@ use core::fmt::Display; +use cgp::core::component::UseDelegate; +use cgp::core::error::impls::RaiseFrom; +use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; use cgp::prelude::*; +use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; use reqwest::blocking::Client; use reqwest::StatusCode; use serde::Deserialize; @@ -94,3 +98,68 @@ where Ok(message_response.message) } } + +pub struct UseStringAuthToken; + +impl ProvideAuthTokenType for UseStringAuthToken { + type AuthToken = String; +} + +pub struct UseU64MessageId; + +impl ProvideMessageIdType for UseU64MessageId { + type MessageId = u64; +} + +pub struct UseStringMessage; + +impl ProvideMessageType for UseStringMessage { + type Message = String; +} + +pub struct ApiClient { + pub api_base_url: String, + pub auth_token: String, +} + +pub struct ApiClientComponents; + +pub struct RaiseApiErrors; + +impl HasComponents for ApiClient { + type Components = ApiClientComponents; +} + +delegate_components! { + ApiClientComponents { + ErrorTypeComponent: UseAnyhowError, + ErrorRaiserComponent: UseDelegate, + MessageIdTypeComponent: UseU64MessageId, + MessageTypeComponent: UseStringMessage, + AuthTokenTypeComponent: UseStringAuthToken, + MessageQuerierComponent: ReadMessageFromApi, + } +} + +delegate_components! { + RaiseApiErrors { + reqwest::Error: RaiseFrom, + ErrStatusCode: DebugAnyhowError, + } +} + +impl ApiBaseUrlGetter for ApiClientComponents { + fn api_base_url(api_client: &ApiClient) -> &String { + &api_client.api_base_url + } +} + +impl AuthTokenGettter for ApiClientComponents { + fn auth_token(api_client: &ApiClient) -> &String { + &api_client.auth_token + } +} + +pub trait CanUseApiClient: CanQueryMessage {} + +impl CanUseApiClient for ApiClient {} From 72cf6bd852eda6c102e088816489f87ad1d54691 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 12:59:52 +0000 Subject: [PATCH 05/10] Add implementation section --- content/SUMMARY.md | 4 +- content/field-accessors.md | 237 ++++++++++++++++++++++++++++++++++++- 2 files changed, 237 insertions(+), 4 deletions(-) diff --git a/content/SUMMARY.md b/content/SUMMARY.md index a5255c9..b89215f 100644 --- a/content/SUMMARY.md +++ b/content/SUMMARY.md @@ -26,9 +26,7 @@ - [Error Reporting](error-reporting.md) - [Error Wrapping](error-wrapping.md) - [Field Accessors](field-accessors.md) - - [`HasField`]() - - [`HasFieldMut`]() - - [Field Macros]() + - [Context-Generic Accessor Provider]() - [Component Presets]() - [Trait-Generic Providers]() - [`WithProvider`]() diff --git a/content/field-accessors.md b/content/field-accessors.md index 8d3e55e..69060f7 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -484,4 +484,239 @@ On the other hand, for the purpose of this book, we will continue to make use of minimal traits, since the book serves as reference materials that should encourage best practices to its readers. -## Implementing Accessor Providers \ No newline at end of file +## Implementing Accessor Providers + +Now that we have implemented the provider, we would look at how to implement +a concrete context that uses `ReadMessageFromApi` and implement the accessors. + +First of all, we would implement the type traits by implementing type providers +that fit the constraints of `ReadMessageFromApi`: + +```rust +# extern crate cgp; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +pub struct UseU64MessageId; + +impl ProvideMessageIdType for UseU64MessageId { + type MessageId = u64; +} + +pub struct UseStringMessage; + +impl ProvideMessageType for UseStringMessage { + type Message = String; +} + +pub struct UseStringAuthToken; + +impl ProvideAuthTokenType for UseStringAuthToken { + type AuthToken = String; +} +``` + +We can then implement an `ApiClient` context that makes use of all providers +as follows: + +```rust +# extern crate cgp; +# extern crate cgp_error_anyhow; +# extern crate reqwest; +# extern crate serde; +# +# use core::fmt::Display; +# +# use cgp::core::component::UseDelegate; +# use cgp::core::error::impls::RaiseFrom; +# use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +# use cgp::prelude::*; +# use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# #[cgp_component { +# provider: AuthTokenGettter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +# impl MessageQuerier for ReadMessageFromApi +# where +# Context: HasMessageIdType +# + HasMessageType +# + HasApiBaseUrl +# + HasAuthToken +# + CanRaiseError +# + CanRaiseError, +# Context::AuthToken: Display, +# { +# fn query_message(context: &Context, message_id: &u64) -> Result { +# let client = Client::new(); +# +# let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); +# +# let response = client +# .get(url) +# .bearer_auth(context.auth_token()) +# .send() +# .map_err(Context::raise_error)?; +# +# let status_code = response.status(); +# +# if !status_code.is_success() { +# return Err(Context::raise_error(ErrStatusCode { status_code })); +# } +# +# let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; +# +# Ok(message_response.message) +# } +# } +# +# pub struct UseStringAuthToken; +# +# impl ProvideAuthTokenType for UseStringAuthToken { +# type AuthToken = String; +# } +# +# pub struct UseU64MessageId; +# +# impl ProvideMessageIdType for UseU64MessageId { +# type MessageId = u64; +# } +# +# pub struct UseStringMessage; +# +# impl ProvideMessageType for UseStringMessage { +# type Message = String; +# } +# +pub struct ApiClient { + pub api_base_url: String, + pub auth_token: String, +} + +pub struct ApiClientComponents; + +pub struct RaiseApiErrors; + +impl HasComponents for ApiClient { + type Components = ApiClientComponents; +} + +delegate_components! { + ApiClientComponents { + ErrorTypeComponent: UseAnyhowError, + ErrorRaiserComponent: UseDelegate, + MessageIdTypeComponent: UseU64MessageId, + MessageTypeComponent: UseStringMessage, + AuthTokenTypeComponent: UseStringAuthToken, + MessageQuerierComponent: ReadMessageFromApi, + } +} + +delegate_components! { + RaiseApiErrors { + reqwest::Error: RaiseFrom, + ErrStatusCode: DebugAnyhowError, + } +} + +impl ApiBaseUrlGetter for ApiClientComponents { + fn api_base_url(api_client: &ApiClient) -> &String { + &api_client.api_base_url + } +} + +impl AuthTokenGettter for ApiClientComponents { + fn auth_token(api_client: &ApiClient) -> &String { + &api_client.auth_token + } +} + +pub trait CanUseApiClient: CanQueryMessage {} + +impl CanUseApiClient for ApiClient {} +``` + +The `ApiClient` context is defined with the fields that we need to implement the accessor traits. +We then have context-specific implementation of `ApiBaseUrlGetter` and `AuthTokenGettter` to work +directly with `ApiClient`. With that, our wiring is completed, and we can check that +`ApiClient` implements `CanQueryMessage`. From ca40acd1b83d56cab67ff5aae1aa80af2acb697e Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 13:48:22 +0000 Subject: [PATCH 06/10] Add sections for HasField and Symbol --- content/SUMMARY.md | 1 - content/field-accessors.md | 144 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 51 +++++++++---- 3 files changed, 183 insertions(+), 13 deletions(-) diff --git a/content/SUMMARY.md b/content/SUMMARY.md index b89215f..4f58faa 100644 --- a/content/SUMMARY.md +++ b/content/SUMMARY.md @@ -26,7 +26,6 @@ - [Error Reporting](error-reporting.md) - [Error Wrapping](error-wrapping.md) - [Field Accessors](field-accessors.md) - - [Context-Generic Accessor Provider]() - [Component Presets]() - [Trait-Generic Providers]() - [`WithProvider`]() diff --git a/content/field-accessors.md b/content/field-accessors.md index 69060f7..f86db38 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -720,3 +720,147 @@ The `ApiClient` context is defined with the fields that we need to implement the We then have context-specific implementation of `ApiBaseUrlGetter` and `AuthTokenGettter` to work directly with `ApiClient`. With that, our wiring is completed, and we can check that `ApiClient` implements `CanQueryMessage`. + +## Context-Generic Accessor Provider + +Although the previous accessor implementation for `ApiClient` works, we have to have explicit and +concrete access to the `ApiClient` context in order to implement the accessors. +While this is not too bad with only two accessor methods, it can quickly become tedious once +the application grows, and we need to implement many accessors across many contexts. +It would be more efficient if we can implement _context-generic_ providers for field accessors, +and then use them for any context that contains a given field. + +To make the implementation of context-generic accessors possible, the `cgp` crate offers a derivable +`HasField` trait that can be used as a proxy to access the fields in a concrete context. +The trait is defined as follows: + +```rust +# use core::marker::PhantomData; +# +pub trait HasField { + type Value; + + fn get_field(&self, tag: PhantomData) -> &Self::Value; +} +``` + +For each of the field inside a concrete context, we can implement a `HasField` instance +with the `Tag` type representing the field _name_, and the associated type `Value` +representing the field _type_. +There is also a `get_field` method, which gets a reference of the field value from +the context. The `get_field` method accepts an additional `tag` parameter, +which is just a `PhantomData` with the field name `Tag` as the type. +This phantom parameter is mainly used to help type inference in Rust, +as otherwise Rust would not be able to infer which field `Tag` we are trying to access. + +We can automatically derive `HasField` instances for a context like `ApiClient` +by using the derive macro as follows: + +```rust +# extern crate cgp; +# +# use cgp::prelude::*; +# +#[derive(HasField)] +pub struct ApiClient { + pub api_base_url: String, + pub auth_token: String, +} +``` + +The derive macro would then generate the following `HasField` instances for +`ApiClient`: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# pub struct ApiClient { +# pub api_base_url: String, +# pub auth_token: String, +# } +impl HasField for ApiClient { + type Value = String; + + fn get_field(&self, _tag: PhantomData) -> &String { + &self.api_base_url + } +} + +impl HasField for ApiClient { + type Value = String; + + fn get_field(&self, _tag: PhantomData) -> &String { + &self.auth_token + } +} +``` + +## Symbols + +In the derived `HasField` instances, we can see the use of `symbol!("api_base_url")` +and `symbol!("auth_token")` at the position of the `Tag` generic type. +Recall that a string like `"api_base_url"` is a _value_ of type `&str`, +but we want to use the string as a _type_. +To do that, we use the `symbol!` macro to "lift" a string value into a unique +type, so that we get a _type_ that uniquely identifies the string `"api_base_url"`. +Basically, this means that if the string content in two different uses of `symbol!` +are the same, then they would be treated as the same type. + +Behind the scene, `symbol!` first use the `Char` type to "lift" individual characters +into types. The `Char` type is defined as follows: + +```rust +pub struct Char; +``` + +We make use of the [_const generics_](https://blog.rust-lang.org/2021/02/26/const-generics-mvp-beta.html) +feature in Rust to parameterize `Char` with a constant `CHAR` of type `char`. +The `Char` struct itself has an empty body, because we only want to use it like +a `char` at the type level. + +Note that although we can use const generics to lift individual characters, we can't +yet use a type like `String` or `&str` inside const generics. +So until we can use strings inside const generics, we need a different workaround +to lift strings into types. + +We workaround that by constructing a _type-level list_ of characters. So a type like +`symbol!("abc")` would be desugared to something like: + +```rust,ignore +(Char<'a'>, (Char<'b'>, (Char<'c'>, ()))) +``` + +In `cgp`, instead of using the native Rust tuple, we define the `Cons` and `Nil` +types to help identifying type level lists: + +```rust +pub struct Nil; + +pub struct Cons(pub Head, pub Tail); +``` + +Similar to the linked list concepts in Lisp, the `Nil` type is used to represent +an empty type-level list, and the `Cons` type is used to "add" an element to the +front of the type-level list. + +With that, the actual desugaring of a type like `symbol!("abc")` looks like follows: + +```rust,ignore +Cons, Cons, Cons, Nil>>> +``` + +Although the type make look complicated, it has a pretty compact representation from the +perspective of the Rust compiler. And since we never construct a value out of the symbol +type at runtime, we don't need to worry about any runtime overhead on using symbol types. +Aside from that, since we will mostly only use `HasField` to implement context-generic +accessors, there is negligible compile-time overhead of using `HasField` inside large +codebases. + +It is also worth noting that the current representation of symbols is a temporary +workaround. Once Rust supports the use of strings inside const generics, we can +migrate the desugaring of `symbol!` to make use of that to simplify the type +representation. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 53e270e..0063dc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,10 @@ use core::fmt::Display; +use core::marker::PhantomData; use cgp::core::component::UseDelegate; use cgp::core::error::impls::RaiseFrom; use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +use cgp::core::field::impls::use_field::UseField; use cgp::prelude::*; use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; use reqwest::blocking::Client; @@ -117,11 +119,46 @@ impl ProvideMessageType for UseStringMessage { type Message = String; } +impl ApiBaseUrlGetter for UseField +where + Context: HasField, +{ + fn api_base_url(context: &Context) -> &String { + context.get_field(PhantomData) + } +} + +impl AuthTokenGettter for UseField +where + Context: HasAuthTokenType + HasField, +{ + fn auth_token(context: &Context) -> &Context::AuthToken { + context.get_field(PhantomData) + } +} + +// #[derive(HasField)] pub struct ApiClient { pub api_base_url: String, pub auth_token: String, } +impl HasField for ApiClient { + type Value = String; + + fn get_field(&self, _tag: PhantomData) -> &String { + &self.api_base_url + } +} + +impl HasField for ApiClient { + type Value = String; + + fn get_field(&self, _tag: PhantomData) -> &String { + &self.auth_token + } +} + pub struct ApiClientComponents; pub struct RaiseApiErrors; @@ -137,6 +174,8 @@ delegate_components! { MessageIdTypeComponent: UseU64MessageId, MessageTypeComponent: UseStringMessage, AuthTokenTypeComponent: UseStringAuthToken, + ApiBaseUrlGetterComponent: UseField, + AuthTokenGettterComponent: UseField, MessageQuerierComponent: ReadMessageFromApi, } } @@ -148,18 +187,6 @@ delegate_components! { } } -impl ApiBaseUrlGetter for ApiClientComponents { - fn api_base_url(api_client: &ApiClient) -> &String { - &api_client.api_base_url - } -} - -impl AuthTokenGettter for ApiClientComponents { - fn auth_token(api_client: &ApiClient) -> &String { - &api_client.auth_token - } -} - pub trait CanUseApiClient: CanQueryMessage {} impl CanUseApiClient for ApiClient {} From f31bcd21ae161837bb11f8b498ca678a868dbcea Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 15:11:25 +0000 Subject: [PATCH 07/10] Add section for UseField pattern --- content/field-accessors.md | 370 ++++++++++++++++++++++++++++++++++++- src/lib.rs | 24 +-- 2 files changed, 368 insertions(+), 26 deletions(-) diff --git a/content/field-accessors.md b/content/field-accessors.md index f86db38..b39e80d 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -279,7 +279,7 @@ pub trait HasAuthTokenType { } #[cgp_component { - provider: AuthTokenGettter, + provider: AuthTokenGetter, }] pub trait HasAuthToken: HasAuthTokenType { fn auth_token(&self) -> &Self::AuthToken; @@ -347,7 +347,7 @@ inside the `Authorization` HTTP header: # } # # #[cgp_component { -# provider: AuthTokenGettter, +# provider: AuthTokenGetter, # }] # pub trait HasAuthToken: HasAuthTokenType { # fn auth_token(&self) -> &Self::AuthToken; @@ -599,7 +599,7 @@ as follows: # } # # #[cgp_component { -# provider: AuthTokenGettter, +# provider: AuthTokenGetter, # }] # pub trait HasAuthToken: HasAuthTokenType { # fn auth_token(&self) -> &Self::AuthToken; @@ -705,7 +705,7 @@ impl ApiBaseUrlGetter for ApiClientComponents { } } -impl AuthTokenGettter for ApiClientComponents { +impl AuthTokenGetter for ApiClientComponents { fn auth_token(api_client: &ApiClient) -> &String { &api_client.auth_token } @@ -717,7 +717,7 @@ impl CanUseApiClient for ApiClient {} ``` The `ApiClient` context is defined with the fields that we need to implement the accessor traits. -We then have context-specific implementation of `ApiBaseUrlGetter` and `AuthTokenGettter` to work +We then have context-specific implementation of `ApiBaseUrlGetter` and `AuthTokenGetter` to work directly with `ApiClient`. With that, our wiring is completed, and we can check that `ApiClient` implements `CanQueryMessage`. @@ -863,4 +863,362 @@ codebases. It is also worth noting that the current representation of symbols is a temporary workaround. Once Rust supports the use of strings inside const generics, we can migrate the desugaring of `symbol!` to make use of that to simplify the type -representation. \ No newline at end of file +representation. + +## Using `HasField` in Accessor Providers + +Using `HasField`, we can then implement a context-generic provider for `ApiUrlGetter` +like follows: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +pub struct GetApiUrl; + +impl ApiBaseUrlGetter for GetApiUrl +where + Context: HasField, +{ + fn api_base_url(context: &Context) -> &String { + context.get_field(PhantomData) + } +} +``` + +The provider `GetApiUrl` is implemented for any `Context` type that implements +`HasField`. This means that as long as the +context uses `#[derive(HasField)]` has an `api_url` field with `String` type, +then we can use `GetApiUrl` with it. + +Similarly, we can implement a context-generic provider for `AuthTokenGetter` as follows: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# #[cgp_component { +# provider: AuthTokenGetter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +pub struct GetAuthToken; + +impl AuthTokenGetter for GetAuthToken +where + Context: HasAuthTokenType + HasField, +{ + fn auth_token(context: &Context) -> &Context::AuthToken { + context.get_field(PhantomData) + } +} +``` + +The provider `GetAuthToken` is slightly more complicated, because the `auth_token()` method +returns an abstract `Context::AuthToken` type. +To work with that, we first need `Context` to implement `HasAuthTokenType`, and then +require the `Value` associated type to be the same as `Context::AuthToken`. +This means that `GetAuthToken` can be used with a context, if it uses +`#[derive(HasField)]` and has an `auth_token` field with the same type as +the `AuthToken` type that it implements. + +## The `UseField` Pattern + +In the previous section, we managed to implement the context-generic accessor providers +`GetApiUrl` and `GetAuthToken`, without access to the concrete context. However, the field names +`api_url` and `auth_token` are hardcoded into the provider implementation. This means that +a concrete context cannot choose different _field names_ for the specific fields, unless +they manually re-implement the accessors. + +There may be different reasons why a context may want to use different names to store the +field values. For example, there could be two independent accessor providers that happen +to choose the same field name for different types. A context may also have multiple similar +fields that serve similar purposes but with slightly different names. +Whatever the reason is, it would be nice if we can allow the contexts to customize the +field names, instead of letting the providers to pick fixed field names. + +For this purpose, the `cgp` crate provides the `UseField` type that we can use to +implement accessor providers: + +```rust +# use core::marker::PhantomData; +# +pub struct UseField(pub PhantomData); +``` + +Similar to the [`UseDelegate` pattern](./delegated-error-raiser.md), the `UseField` type +is used as a label for accessor implementations following the `UseField` pattern. +Using `UseField`, we can implement the providers as follows: + + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +use cgp::core::field::impls::use_field::UseField; + +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# #[cgp_component { +# provider: AuthTokenGetter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +impl ApiBaseUrlGetter for UseField +where + Context: HasField, +{ + fn api_base_url(context: &Context) -> &String { + context.get_field(PhantomData) + } +} + +impl AuthTokenGetter for UseField +where + Context: HasAuthTokenType + HasField, +{ + fn auth_token(context: &Context) -> &Context::AuthToken { + context.get_field(PhantomData) + } +} +``` + +Compared to the explicit providers `GetApiUrl` and `GetAuthToken`, we implement +the traits `ApiBaseUrlGetter` and `AuthTokenGetter` directly on the `UseField` +type provided by the `cgp` crate. +The implementation is also parameterized by an additional `Tag` type, to represent +the name of the field we want to use. +We can see that the implementation is almost the same as before, except that +we no longer use `symbol!` to directly refer to the field names. + +Using `UseField`, we get to simplify the implementation of `ApiClient` and +wire up the accessor components directly inside `delegate_components!`: + +```rust +# extern crate cgp; +# extern crate cgp_error_anyhow; +# extern crate reqwest; +# extern crate serde; +# +# use core::fmt::Display; +# use core::marker::PhantomData; +# +# use cgp::core::component::UseDelegate; +# use cgp::core::error::impls::RaiseFrom; +# use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +# use cgp::core::field::impls::use_field::UseField; +# use cgp::prelude::*; +# use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# #[cgp_component { +# provider: AuthTokenGetter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +# impl MessageQuerier for ReadMessageFromApi +# where +# Context: HasMessageIdType +# + HasMessageType +# + HasApiBaseUrl +# + HasAuthToken +# + CanRaiseError +# + CanRaiseError, +# Context::AuthToken: Display, +# { +# fn query_message(context: &Context, message_id: &u64) -> Result { +# let client = Client::new(); +# +# let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); +# +# let response = client +# .get(url) +# .bearer_auth(context.auth_token()) +# .send() +# .map_err(Context::raise_error)?; +# +# let status_code = response.status(); +# +# if !status_code.is_success() { +# return Err(Context::raise_error(ErrStatusCode { status_code })); +# } +# +# let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; +# +# Ok(message_response.message) +# } +# } +# +# pub struct UseStringAuthToken; +# +# impl ProvideAuthTokenType for UseStringAuthToken { +# type AuthToken = String; +# } +# +# pub struct UseU64MessageId; +# +# impl ProvideMessageIdType for UseU64MessageId { +# type MessageId = u64; +# } +# +# pub struct UseStringMessage; +# +# impl ProvideMessageType for UseStringMessage { +# type Message = String; +# } +# +# impl ApiBaseUrlGetter for UseField +# where +# Context: HasField, +# { +# fn api_base_url(context: &Context) -> &String { +# context.get_field(PhantomData) +# } +# } +# +# impl AuthTokenGetter for UseField +# where +# Context: HasAuthTokenType + HasField, +# { +# fn auth_token(context: &Context) -> &Context::AuthToken { +# context.get_field(PhantomData) +# } +# } +# +# #[derive(HasField)] +# pub struct ApiClient { +# pub api_base_url: String, +# pub auth_token: String, +# } +# +# pub struct ApiClientComponents; +# +# pub struct RaiseApiErrors; +# +# impl HasComponents for ApiClient { +# type Components = ApiClientComponents; +# } +# +delegate_components! { + ApiClientComponents { + ErrorTypeComponent: UseAnyhowError, + ErrorRaiserComponent: UseDelegate, + MessageIdTypeComponent: UseU64MessageId, + MessageTypeComponent: UseStringMessage, + AuthTokenTypeComponent: UseStringAuthToken, + ApiBaseUrlGetterComponent: UseField, + AuthTokenGetterComponent: UseField, + MessageQuerierComponent: ReadMessageFromApi, + } +} +# +# delegate_components! { +# RaiseApiErrors { +# reqwest::Error: RaiseFrom, +# ErrStatusCode: DebugAnyhowError, +# } +# } +# +# pub trait CanUseApiClient: CanQueryMessage {} +# +# impl CanUseApiClient for ApiClient {} +``` + +The wiring above uses `UseField` to implement `ApiBaseUrlGetterComponent`, +and `UseField` to implement `AuthTokenGetterComponent`. +With the field names specified explicitly in the wiring, we can easily change the field +names in the `ApiClient` context, and update the wiring accordingly. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 0063dc2..d757f04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ pub trait HasAuthTokenType { } #[cgp_component { - provider: AuthTokenGettter, + provider: AuthTokenGetter, }] pub trait HasAuthToken: HasAuthTokenType { fn auth_token(&self) -> &Self::AuthToken; @@ -128,7 +128,7 @@ where } } -impl AuthTokenGettter for UseField +impl AuthTokenGetter for UseField where Context: HasAuthTokenType + HasField, { @@ -137,28 +137,12 @@ where } } -// #[derive(HasField)] +#[derive(HasField)] pub struct ApiClient { pub api_base_url: String, pub auth_token: String, } -impl HasField for ApiClient { - type Value = String; - - fn get_field(&self, _tag: PhantomData) -> &String { - &self.api_base_url - } -} - -impl HasField for ApiClient { - type Value = String; - - fn get_field(&self, _tag: PhantomData) -> &String { - &self.auth_token - } -} - pub struct ApiClientComponents; pub struct RaiseApiErrors; @@ -175,7 +159,7 @@ delegate_components! { MessageTypeComponent: UseStringMessage, AuthTokenTypeComponent: UseStringAuthToken, ApiBaseUrlGetterComponent: UseField, - AuthTokenGettterComponent: UseField, + AuthTokenGetterComponent: UseField, MessageQuerierComponent: ReadMessageFromApi, } } From 31087e189f6846bd365aa24a7ddeed79b06b5549 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 16:17:43 +0000 Subject: [PATCH 08/10] Add section for static accessors --- content/field-accessors.md | 392 ++++++++++++++++++++++++++++++++++++- src/lib.rs | 23 +-- 2 files changed, 401 insertions(+), 14 deletions(-) diff --git a/content/field-accessors.md b/content/field-accessors.md index b39e80d..489b3ec 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -980,8 +980,8 @@ Using `UseField`, we can implement the providers as follows: # use core::marker::PhantomData; # # use cgp::prelude::*; -use cgp::core::field::impls::use_field::UseField; - +# use cgp::core::field::impls::use_field::UseField; +# # #[cgp_component { # provider: ApiBaseUrlGetter, # }] @@ -1221,4 +1221,390 @@ delegate_components! { The wiring above uses `UseField` to implement `ApiBaseUrlGetterComponent`, and `UseField` to implement `AuthTokenGetterComponent`. With the field names specified explicitly in the wiring, we can easily change the field -names in the `ApiClient` context, and update the wiring accordingly. \ No newline at end of file +names in the `ApiClient` context, and update the wiring accordingly. + +## Using `HasField` Directly Inside Providers + +Since the `HasField` trait can be automatically derived by contexts, some readers may be +tempted to not define any accessor trait, and instead make use of `HasField` directly +inside the providers. For example, we can in principle remove `HasApiBaseUrl` and +`HasAuthToken`, and re-implement `ReadMessageFromApi` as follows: + +```rust +# extern crate cgp; +# extern crate reqwest; +# extern crate serde; +# +# use core::fmt::Display; +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +impl MessageQuerier for ReadMessageFromApi +where + Context: HasMessageIdType + + HasMessageType + + HasAuthTokenType + + HasField + + HasField + + CanRaiseError + + CanRaiseError, + Context::AuthToken: Display, +{ + fn query_message(context: &Context, message_id: &u64) -> Result { + let client = Client::new(); + + let url = format!( + "{}/api/messages/{}", + context.get_field(PhantomData::), + message_id + ); + + let response = client + .get(url) + .bearer_auth(context.get_field(PhantomData::)) + .send() + .map_err(Context::raise_error)?; + + let status_code = response.status(); + + if !status_code.is_success() { + return Err(Context::raise_error(ErrStatusCode { status_code })); + } + + let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; + + Ok(message_response.message) + } +} +``` + +In the implementation above, the provider `ReadMessageFromApi` requires the context to implement +`HasField` and `HasField`. +To preserve the original behavior, we also have additional constraints that the field `api_base_url` +needs to be of `String` type, and the field `auth_token` needs to have the same type as +`Context::AuthToken`. +When using `get_field`, since there are two instances of `HasField` implemented in scope, +we need to fully qualify the call to specify the field name that we want to access, +such as `context.get_field(PhantomData::)`. + +As we can see, the direct use of `HasField` may not necessary make the code simpler, and instead +require more verbose specification of the fields. The direct use of `HasFields` also requires +explicit specification of what the field types should be. +Whereas in accessor traits like `HasAuthToken`, we can better specify that the method always +return the abstract type `Self::AuthToken`, so one cannot accidentally read from different +fields that happen to have the same underlying concrete type. + +By using `HasField` directly, the provider also makes it less flexible for the context to have +custom ways of getting the field value. For example, instead of putting the `api_url` field +directly in the context, we may want to put it inside another `ApiConfig` struct such as follows: + +```rust +pub struct Config { + pub api_base_url: String, + // other fields +} + +pub struct ApiClient { + pub config: Config, + pub auth_token: String, + // other fields +} +``` + +In such cases, with an accessor trait like `HasApiUrl`, the context can easily make use of +custom accessor providers to implement such indirect access. But with direct use of +`HasFields`, it would be more tedious to implement the indirect access. + +## Static Accessors + +One benefit of defining minimal accessor traits is that we get to implement custom +accessor providers that do not necessarily need to read the field values from the context. +For example, we can implement _static accessor_ providers that always return a global +constant value. + +The use of static accessors can be useful when we want to hard code some values for a +specific context. For instance, we may want to define a production `ApiClient` context +that always use a hard-coded API URL: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +use std::sync::OnceLock; + +# use cgp::prelude::*; +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +pub struct UseProductionApiUrl; + +impl ApiBaseUrlGetter for UseProductionApiUrl { + fn api_base_url(_context: &Context) -> &String { + static BASE_URL: OnceLock = OnceLock::new(); + + BASE_URL.get_or_init(|| "https://api.example.com".into()) + } +} +``` + +The provider `UseProductionApiUrl` implements `ApiBaseUrlGetter` for any context type. +Inside the `api_base_url` method, we first define a static `BASE_URL` value with the +type `OnceLock`. The use of [`OnceLock`](https://doc.rust-lang.org/std/sync/struct.OnceLock.html) +allows us to define a global variable in Rust that is initialized exactly once, and +then remain constant throughout the application. +This is mainly useful because constructors like `String::from` are not currently `const fn`, +so we have to make use of `OnceLock::get_or_init` to run the non-const constructor. +By defining the static variable inside the method, we ensure that the variable can only be +accessed and initialized by the provider. + +Using `UseProductionApiUrl`, we can now define a production `ApiClient` context such as follows: + +```rust +# extern crate cgp; +# extern crate cgp_error_anyhow; +# extern crate reqwest; +# extern crate serde; +# +# use core::fmt::Display; +# use core::marker::PhantomData; +# use std::sync::OnceLock; +# +# use cgp::core::component::UseDelegate; +# use cgp::core::error::impls::RaiseFrom; +# use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +# use cgp::core::field::impls::use_field::UseField; +# use cgp::prelude::*; +# use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; +# use reqwest::blocking::Client; +# use reqwest::StatusCode; +# use serde::Deserialize; +# +# #[cgp_component { +# name: MessageIdTypeComponent, +# provider: ProvideMessageIdType, +# }] +# pub trait HasMessageIdType { +# type MessageId; +# } +# +# #[cgp_component { +# name: MessageTypeComponent, +# provider: ProvideMessageType, +# }] +# pub trait HasMessageType { +# type Message; +# } +# +# #[cgp_component { +# provider: MessageQuerier, +# }] +# pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { +# fn query_message(&self, message_id: &Self::MessageId) -> Result; +# } +# +# #[cgp_component { +# provider: ApiBaseUrlGetter, +# }] +# pub trait HasApiBaseUrl { +# fn api_base_url(&self) -> &String; +# } +# +# #[cgp_component { +# name: AuthTokenTypeComponent, +# provider: ProvideAuthTokenType, +# }] +# pub trait HasAuthTokenType { +# type AuthToken; +# } +# +# #[cgp_component { +# provider: AuthTokenGetter, +# }] +# pub trait HasAuthToken: HasAuthTokenType { +# fn auth_token(&self) -> &Self::AuthToken; +# } +# +# pub struct ReadMessageFromApi; +# +# #[derive(Debug)] +# pub struct ErrStatusCode { +# pub status_code: StatusCode, +# } +# +# #[derive(Deserialize)] +# pub struct ApiMessageResponse { +# pub message: String, +# } +# +# impl MessageQuerier for ReadMessageFromApi +# where +# Context: HasMessageIdType +# + HasMessageType +# + HasApiBaseUrl +# + HasAuthToken +# + CanRaiseError +# + CanRaiseError, +# Context::AuthToken: Display, +# { +# fn query_message(context: &Context, message_id: &u64) -> Result { +# let client = Client::new(); +# +# let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); +# +# let response = client +# .get(url) +# .bearer_auth(context.auth_token()) +# .send() +# .map_err(Context::raise_error)?; +# +# let status_code = response.status(); +# +# if !status_code.is_success() { +# return Err(Context::raise_error(ErrStatusCode { status_code })); +# } +# +# let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; +# +# Ok(message_response.message) +# } +# } +# +# pub struct UseStringAuthToken; +# +# impl ProvideAuthTokenType for UseStringAuthToken { +# type AuthToken = String; +# } +# +# pub struct UseU64MessageId; +# +# impl ProvideMessageIdType for UseU64MessageId { +# type MessageId = u64; +# } +# +# pub struct UseStringMessage; +# +# impl ProvideMessageType for UseStringMessage { +# type Message = String; +# } +# +# impl AuthTokenGetter for UseField +# where +# Context: HasAuthTokenType + HasField, +# { +# fn auth_token(context: &Context) -> &Context::AuthToken { +# context.get_field(PhantomData) +# } +# } +# +# pub struct UseProductionApiUrl; +# +# impl ApiBaseUrlGetter for UseProductionApiUrl { +# fn api_base_url(_context: &Context) -> &String { +# static BASE_URL: OnceLock = OnceLock::new(); +# +# BASE_URL.get_or_init(|| "https://api.example.com".into()) +# } +# } +# +#[derive(HasField)] +pub struct ApiClient { + pub auth_token: String, +} + +pub struct ApiClientComponents; + +# pub struct RaiseApiErrors; +# +impl HasComponents for ApiClient { + type Components = ApiClientComponents; +} + +delegate_components! { + ApiClientComponents { + ErrorTypeComponent: UseAnyhowError, + ErrorRaiserComponent: UseDelegate, + MessageIdTypeComponent: UseU64MessageId, + MessageTypeComponent: UseStringMessage, + AuthTokenTypeComponent: UseStringAuthToken, + ApiBaseUrlGetterComponent: UseProductionApiUrl, + AuthTokenGetterComponent: UseField, + MessageQuerierComponent: ReadMessageFromApi, + } +} +# +# delegate_components! { +# RaiseApiErrors { +# reqwest::Error: RaiseFrom, +# ErrStatusCode: DebugAnyhowError, +# } +# } +# +# pub trait CanUseApiClient: CanQueryMessage {} +# +# impl CanUseApiClient for ApiClient {} +``` + +Inside the component wiring, we choose `UseProductionApiUrl` to be the provider +for `ApiBaseUrlGetterComponent`. +Notice that now the `ApiClient` context no longer contain any `api_base_url` field. + +The use of static accessors can be useful to implement specialized contexts +that keep the values constant for certain fields. +With this approach, the constant values no longer needs to be passed around +as part of the context during runtime, and we no longer need to worry +about keeping the field private or preventing the wrong value being assigned +at runtime. +Thanks to the compile-time wiring, we may even get some performance advantage +as compared to passing around dynamic values at runtime. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d757f04..2c4630c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use core::fmt::Display; use core::marker::PhantomData; +use std::sync::OnceLock; use cgp::core::component::UseDelegate; use cgp::core::error::impls::RaiseFrom; @@ -119,15 +120,6 @@ impl ProvideMessageType for UseStringMessage { type Message = String; } -impl ApiBaseUrlGetter for UseField -where - Context: HasField, -{ - fn api_base_url(context: &Context) -> &String { - context.get_field(PhantomData) - } -} - impl AuthTokenGetter for UseField where Context: HasAuthTokenType + HasField, @@ -137,9 +129,18 @@ where } } +pub struct UseProductionApiUrl; + +impl ApiBaseUrlGetter for UseProductionApiUrl { + fn api_base_url(_context: &Context) -> &String { + static BASE_URL: OnceLock = OnceLock::new(); + + BASE_URL.get_or_init(|| "https://api.example.com".into()) + } +} + #[derive(HasField)] pub struct ApiClient { - pub api_base_url: String, pub auth_token: String, } @@ -158,7 +159,7 @@ delegate_components! { MessageIdTypeComponent: UseU64MessageId, MessageTypeComponent: UseStringMessage, AuthTokenTypeComponent: UseStringAuthToken, - ApiBaseUrlGetterComponent: UseField, + ApiBaseUrlGetterComponent: UseProductionApiUrl, AuthTokenGetterComponent: UseField, MessageQuerierComponent: ReadMessageFromApi, } From 5b3197b8ad75c44f7208d236925baf6cb87fa51d Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 16:42:12 +0000 Subject: [PATCH 09/10] Finish chapter --- content/field-accessors.md | 72 ++++++++++++++- src/lib.rs | 176 ------------------------------------- 2 files changed, 71 insertions(+), 177 deletions(-) diff --git a/content/field-accessors.md b/content/field-accessors.md index 489b3ec..b61a7f7 100644 --- a/content/field-accessors.md +++ b/content/field-accessors.md @@ -1362,6 +1362,12 @@ In such cases, with an accessor trait like `HasApiUrl`, the context can easily m custom accessor providers to implement such indirect access. But with direct use of `HasFields`, it would be more tedious to implement the indirect access. +That said, similar to other shortcut methods, the direct use of `HasField` can be convenient +during initial development, as it helps to significantly reduce the number of traits the +developer needs to keep track of. As a result, we encourage readers to feel free to make +use of `HasField` as they see fit, and then slowly migrate to proper accessor traits +when the need arise. + ## Static Accessors One benefit of defining minimal accessor traits is that we get to implement custom @@ -1607,4 +1613,68 @@ as part of the context during runtime, and we no longer need to worry about keeping the field private or preventing the wrong value being assigned at runtime. Thanks to the compile-time wiring, we may even get some performance advantage -as compared to passing around dynamic values at runtime. \ No newline at end of file +as compared to passing around dynamic values at runtime. + +## Auto Accessor Traits + +The need to define and wire up many CGP components may overwhelm a developer who +is new to CGP. +At least during the beginning phase, a project don't usually that much flexibility +in customizing how fields are accessed. +As such, some may consider the full use of field accessors introduced in this chapter +being unnecessarily complicated. + +One intermediate way to simplify use of accessor traits is to define them _not_ +as CGP components, but as regular Rust traits with blanket implementations that +use `HasField`. For example, we can re-define the `HasApiUrl` trait as follows: + +```rust +# extern crate cgp; +# +# use core::marker::PhantomData; +# +# use cgp::prelude::*; +# +pub trait HasApiBaseUrl { + fn api_base_url(&self) -> &String; +} + +impl HasApiBaseUrl for Context +where + Context: HasField, +{ + fn api_base_url(&self) -> &String { + self.get_field(PhantomData) + } +} +``` + +This way, the `HasApiBaseUrl` will always be implemented for any context +that derive `HasField` and have the relevant field, and +there is no need to have explicit wiring of `ApiBaseUrlGetterComponent` +inside the wiring of the context components. + +With this, providers like `ReadMessageFromApi` can still use traits like `HasApiBaseUrl` +to simplify the access of fields. And the context implementors can just use +`#[derive(HasField)]` without having to worry about the wiring. + +The main downside of this approach is that the context cannot easily override the +implementation of `HaswApiBaseUrl`, unless they don't implement `HasField` at all. +Nevertheless, it will be straightforward to refactor the trait in the future +to turn it into a full CGP component. + +As a result, this may be an appealing option for readers who want to have a simpler +experience of using CGP and not use its full power. + +## Conclusion + +In this chapter, we have learned about different ways to define accessor traits, +and to implement the accessor providers. The use of a derivable `HasField` trait +makes it possible to implement context-generic accessor providers without +requiring direct access to the concrete context. The use of the `UseField` pattern +unifies the convention of implementing field accessors, and allows contexts +to choose different field names for the accessors. + +As we will see in later chapters, the use of context-generic accessor providers +make it possible to implement almost everything as context-generic providers, +and leaving almost no code tied to specific concrete contexts. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 2c4630c..8b13789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,177 +1 @@ -use core::fmt::Display; -use core::marker::PhantomData; -use std::sync::OnceLock; -use cgp::core::component::UseDelegate; -use cgp::core::error::impls::RaiseFrom; -use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; -use cgp::core::field::impls::use_field::UseField; -use cgp::prelude::*; -use cgp_error_anyhow::{DebugAnyhowError, UseAnyhowError}; -use reqwest::blocking::Client; -use reqwest::StatusCode; -use serde::Deserialize; - -#[cgp_component { - name: MessageIdTypeComponent, - provider: ProvideMessageIdType, -}] -pub trait HasMessageIdType { - type MessageId; -} - -#[cgp_component { - name: MessageTypeComponent, - provider: ProvideMessageType, -}] -pub trait HasMessageType { - type Message; -} - -#[cgp_component { - provider: MessageQuerier, -}] -pub trait CanQueryMessage: HasMessageIdType + HasMessageType + HasErrorType { - fn query_message(&self, message_id: &Self::MessageId) -> Result; -} - -#[cgp_component { - provider: ApiBaseUrlGetter, -}] -pub trait HasApiBaseUrl { - fn api_base_url(&self) -> &String; -} - -#[cgp_component { - name: AuthTokenTypeComponent, - provider: ProvideAuthTokenType, -}] -pub trait HasAuthTokenType { - type AuthToken; -} - -#[cgp_component { - provider: AuthTokenGetter, -}] -pub trait HasAuthToken: HasAuthTokenType { - fn auth_token(&self) -> &Self::AuthToken; -} - -pub struct ReadMessageFromApi; - -#[derive(Debug)] -pub struct ErrStatusCode { - pub status_code: StatusCode, -} - -#[derive(Deserialize)] -pub struct ApiMessageResponse { - pub message: String, -} - -impl MessageQuerier for ReadMessageFromApi -where - Context: HasMessageIdType - + HasMessageType - + HasApiBaseUrl - + HasAuthToken - + CanRaiseError - + CanRaiseError, - Context::AuthToken: Display, -{ - fn query_message(context: &Context, message_id: &u64) -> Result { - let client = Client::new(); - - let url = format!("{}/api/messages/{}", context.api_base_url(), message_id); - - let response = client - .get(url) - .bearer_auth(context.auth_token()) - .send() - .map_err(Context::raise_error)?; - - let status_code = response.status(); - - if !status_code.is_success() { - return Err(Context::raise_error(ErrStatusCode { status_code })); - } - - let message_response: ApiMessageResponse = response.json().map_err(Context::raise_error)?; - - Ok(message_response.message) - } -} - -pub struct UseStringAuthToken; - -impl ProvideAuthTokenType for UseStringAuthToken { - type AuthToken = String; -} - -pub struct UseU64MessageId; - -impl ProvideMessageIdType for UseU64MessageId { - type MessageId = u64; -} - -pub struct UseStringMessage; - -impl ProvideMessageType for UseStringMessage { - type Message = String; -} - -impl AuthTokenGetter for UseField -where - Context: HasAuthTokenType + HasField, -{ - fn auth_token(context: &Context) -> &Context::AuthToken { - context.get_field(PhantomData) - } -} - -pub struct UseProductionApiUrl; - -impl ApiBaseUrlGetter for UseProductionApiUrl { - fn api_base_url(_context: &Context) -> &String { - static BASE_URL: OnceLock = OnceLock::new(); - - BASE_URL.get_or_init(|| "https://api.example.com".into()) - } -} - -#[derive(HasField)] -pub struct ApiClient { - pub auth_token: String, -} - -pub struct ApiClientComponents; - -pub struct RaiseApiErrors; - -impl HasComponents for ApiClient { - type Components = ApiClientComponents; -} - -delegate_components! { - ApiClientComponents { - ErrorTypeComponent: UseAnyhowError, - ErrorRaiserComponent: UseDelegate, - MessageIdTypeComponent: UseU64MessageId, - MessageTypeComponent: UseStringMessage, - AuthTokenTypeComponent: UseStringAuthToken, - ApiBaseUrlGetterComponent: UseProductionApiUrl, - AuthTokenGetterComponent: UseField, - MessageQuerierComponent: ReadMessageFromApi, - } -} - -delegate_components! { - RaiseApiErrors { - reqwest::Error: RaiseFrom, - ErrStatusCode: DebugAnyhowError, - } -} - -pub trait CanUseApiClient: CanQueryMessage {} - -impl CanUseApiClient for ApiClient {} From 49812f67c0cea2a7a145d144b84ff35c38278f2d Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 6 Jan 2025 16:50:55 +0000 Subject: [PATCH 10/10] Fix error wrapping chapter --- content/error-wrapping.md | 75 ++++++--------------------------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/content/error-wrapping.md b/content/error-wrapping.md index 91f74b4..5db57f5 100644 --- a/content/error-wrapping.md +++ b/content/error-wrapping.md @@ -350,13 +350,6 @@ To see how `CanWrapError` works in practice, we can redefine `LoadJsonConfig` to # fn config_path(&self) -> &PathBuf; # } # -# #[cgp_component { -# provider: ErrorWrapper, -# }] -# pub trait CanWrapError: HasErrorType { -# fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; -# } -# pub struct LoadJsonConfig; impl ConfigLoader for LoadJsonConfig @@ -430,13 +423,7 @@ So we can implement an error wrapper provider for `anyhow::Error` as follows: # use core::fmt::Display; # # use cgp::prelude::*; -# -# #[cgp_component { -# provider: ErrorWrapper, -# }] -# pub trait CanWrapError: HasErrorType { -# fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; -# } +# use cgp::core::error::ErrorWrapper; # pub struct WrapWithAnyhowContext; @@ -524,13 +511,6 @@ type as follows: # fn config_path(&self) -> &PathBuf; # } # -# #[cgp_component { -# provider: ErrorWrapper, -# }] -# pub trait CanWrapError: HasErrorType { -# fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; -# } -# pub struct LoadJsonConfig; pub struct ErrLoadJsonConfig<'a, Context> { @@ -627,13 +607,7 @@ as follows: # use core::fmt::Debug; # # use cgp::prelude::*; -# -# #[cgp_component { -# provider: ErrorWrapper, -# }] -# pub trait CanWrapError: HasErrorType { -# fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; -# } +# use cgp::core::error::ErrorWrapper; # pub struct WrapWithAnyhowDebug; @@ -691,13 +665,6 @@ pub mod traits { pub trait HasConfigPath { fn config_path(&self) -> &PathBuf; } - - #[cgp_component { - provider: ErrorWrapper, - }] - pub trait CanWrapError: HasErrorType { - fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; - } } pub mod impls { @@ -705,7 +672,7 @@ pub mod impls { use std::path::PathBuf; use std::{fs, io}; - use cgp::core::error::{ErrorRaiser, ProvideErrorType}; + use cgp::core::error::{ErrorRaiser, ErrorWrapper,ProvideErrorType}; use cgp::prelude::*; use serde::Deserialize; @@ -819,7 +786,7 @@ pub mod contexts { use std::path::PathBuf; use cgp::core::component::UseDelegate; - use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; + use cgp::core::error::{ErrorRaiserComponent, ErrorWrapperComponent, ErrorTypeComponent}; use cgp::prelude::*; use serde::Deserialize; @@ -888,15 +855,12 @@ we can also make use of the `UseDelegate` pattern to implement delegated error w ```rust # extern crate cgp; # +# use core::marker::PhantomData; +# # use cgp::prelude::*; -# use cgp::core::component::UseDelegate; +# use cgp::core::error::ErrorWrapper; # -# #[cgp_component { -# provider: ErrorWrapper, -# }] -# pub trait CanWrapError: HasErrorType { -# fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; -# } +# pub struct UseDelegate(pub PhantomData); # impl ErrorWrapper for UseDelegate where @@ -925,6 +889,7 @@ to different error wrappers, similar to how we dispatch the error raisers based # use std::path::PathBuf; # # use cgp::core::component::UseDelegate; +# use cgp::core::error::ErrorWrapper; # use cgp::prelude::*; # # #[cgp_component { @@ -948,24 +913,6 @@ to different error wrappers, similar to how we dispatch the error raisers based # pub trait HasConfigPath { # fn config_path(&self) -> &PathBuf; # } -# -# #[cgp_component { -# provider: ErrorWrapper, -# }] -# pub trait CanWrapError: HasErrorType { -# fn wrap_error(error: Self::Error, detail: Detail) -> Self::Error; -# } -# -# impl ErrorWrapper for UseDelegate -# where -# Context: HasErrorType, -# Components: DelegateComponent, -# Components::Delegate: ErrorWrapper, -# { -# fn wrap_error(error: Context::Error, detail: Detail) -> Context::Error { -# Components::Delegate::wrap_error(error, detail) -# } -# } # } # # pub mod impls { @@ -973,7 +920,7 @@ to different error wrappers, similar to how we dispatch the error raisers based # use std::path::PathBuf; # use std::{fs, io}; # -# use cgp::core::error::{ErrorRaiser, ProvideErrorType}; +# use cgp::core::error::{ErrorRaiser, ErrorWrapper, ProvideErrorType}; # use cgp::prelude::*; # use serde::Deserialize; # @@ -1099,7 +1046,7 @@ to different error wrappers, similar to how we dispatch the error raisers based # use std::path::PathBuf; # # use cgp::core::component::UseDelegate; -# use cgp::core::error::{ErrorRaiserComponent, ErrorTypeComponent}; +# use cgp::core::error::{ErrorRaiserComponent, ErrorWrapperComponent, ErrorTypeComponent}; # use cgp::prelude::*; # use serde::Deserialize; #