From 1251447c2e705c39dd64721c2f10faf09f4b974c Mon Sep 17 00:00:00 2001 From: Eden Zhang Date: Mon, 24 Nov 2025 19:12:11 -0500 Subject: [PATCH 1/4] Setup `clp-credential-manager` skeleton --- Cargo.lock | 415 +++++++++++++++++- Cargo.toml | 3 +- components/clp-credential-manager/Cargo.toml | 28 ++ .../clp-credential-manager/src/config.rs | 116 +++++ .../clp-credential-manager/src/error.rs | 128 ++++++ components/clp-credential-manager/src/main.rs | 60 +++ .../src/routes/health.rs | 13 + .../clp-credential-manager/src/routes/mod.rs | 16 + .../clp-credential-manager/src/service.rs | 109 +++++ 9 files changed, 877 insertions(+), 11 deletions(-) create mode 100644 components/clp-credential-manager/Cargo.toml create mode 100644 components/clp-credential-manager/src/config.rs create mode 100644 components/clp-credential-manager/src/error.rs create mode 100644 components/clp-credential-manager/src/main.rs create mode 100644 components/clp-credential-manager/src/routes/health.rs create mode 100644 components/clp-credential-manager/src/routes/mod.rs create mode 100644 components/clp-credential-manager/src/service.rs diff --git a/Cargo.lock b/Cargo.lock index 0c14062a02..1cc9826280 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -872,9 +872,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-link", + "wasm-bindgen", + "windows-link 0.2.1", ] [[package]] @@ -928,6 +930,32 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "clp-credential-manager" +version = "0.1.0" +dependencies = [ + "anyhow", + "aws-config", + "aws-sdk-sts", + "axum", + "chrono", + "clap", + "jsonwebtoken", + "reqwest", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "sqlx", + "thiserror 2.0.17", + "tokio", + "tower", + "tower-http 0.5.2", + "tracing", + "tracing-subscriber", + "uuid", +] + [[package]] name = "clp-rust-utils" version = "0.1.0" @@ -1326,6 +1354,15 @@ dependencies = [ "zeroize", ] +[[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 = "enum-as-inner" version = "0.6.1" @@ -1344,6 +1381,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "etcetera" version = "0.8.0" @@ -1411,6 +1458,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[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.2" @@ -1888,6 +1950,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.7.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.17" @@ -1907,9 +1985,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2085,6 +2165,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2126,6 +2216,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2148,7 +2253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2184,6 +2289,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.8.1" @@ -2423,6 +2534,23 @@ dependencies = [ "syn", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -2442,6 +2570,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.5" @@ -2528,12 +2666,50 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "outref" version = "0.5.2" @@ -2577,7 +2753,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2595,6 +2771,16 @@ dependencies = [ "digest", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2855,6 +3041,46 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2 0.4.12", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.7.0", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http 0.6.6", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "resolv-conf" version = "0.7.5" @@ -2953,6 +3179,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.21.12" @@ -3399,6 +3638,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.17", + "time", +] + [[package]] name = "slab" version = "0.4.11" @@ -3484,6 +3735,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64 0.22.1", "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -3559,6 +3811,7 @@ dependencies = [ "bitflags 2.10.0", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -3600,6 +3853,7 @@ dependencies = [ "base64 0.22.1", "bitflags 2.10.0", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -3634,6 +3888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", @@ -3702,6 +3957,9 @@ 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" @@ -3714,6 +3972,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "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 = "take_mut" version = "0.2.2" @@ -3726,6 +4005,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3868,6 +4160,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -3959,6 +4261,41 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4238,6 +4575,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.105" @@ -4270,6 +4620,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -4312,9 +4672,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -4339,19 +4699,54 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -4360,7 +4755,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4405,7 +4800,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4445,7 +4840,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", diff --git a/Cargo.toml b/Cargo.toml index a3a8bef443..8e776b1428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] members = [ "components/api-server", + "components/clp-credential-manager", "components/clp-rust-utils", - "components/log-ingestor" + "components/log-ingestor", ] resolver = "3" diff --git a/components/clp-credential-manager/Cargo.toml b/components/clp-credential-manager/Cargo.toml new file mode 100644 index 0000000000..a6aba97906 --- /dev/null +++ b/components/clp-credential-manager/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "clp-credential-manager" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +axum = { version = "0.8.6", features = ["json"] } +chrono = { version = "0.4.38", features = ["serde"] } +clap = { version = "4.5.51", features = ["derive"] } +jsonwebtoken = "9.3.0" +secrecy = { version = "0.10.3", features = ["serde"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +serde_yaml = "0.9.34" +sqlx = { version = "0.8.6", features = ["runtime-tokio", "mysql", "macros", "chrono"] } +thiserror = "2.0.17" +tokio = { version = "1.48.0", features = ["full"] } +tower = "0.5.2" +tower-http = { version = "0.5.2", features = ["trace", "cors"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.20", features = ["env-filter", "fmt", "json", "std"] } +uuid = { version = "1.11.1", features = ["v4", "serde"] } +aws-config = "1.5.1" +aws-sdk-sts = "1.48.0" + +[dev-dependencies] +reqwest = { version = "0.12.9", features = ["json"] } diff --git a/components/clp-credential-manager/src/config.rs b/components/clp-credential-manager/src/config.rs new file mode 100644 index 0000000000..68c1e81681 --- /dev/null +++ b/components/clp-credential-manager/src/config.rs @@ -0,0 +1,116 @@ +#![allow(dead_code)] + +use std::{ + fs, + net::SocketAddr, + path::{Path, PathBuf}, +}; + +use secrecy::SecretString; +use serde::Deserialize; + +use crate::error::{ServiceError, ServiceResult}; + +const DEFAULT_MAX_CONNECTIONS: u32 = 5; +const DEFAULT_SERVER_BIND_ADDRESS: &str = "0.0.0.0"; +const DEFAULT_SERVER_PORT: u16 = 8080; +const DEFAULT_JWT_TTL_SECONDS: u64 = 3600; + +/// Root configuration deserialized from `credential-manager-config.yml`. +#[derive(Debug, Clone, Deserialize)] +pub struct AppConfig { + #[serde(default)] + pub server: ServerConfig, + pub database: DatabaseConfig, + pub jwt: JwtConfig, + #[serde(default)] + pub credentials_file: Option, + #[serde(default)] + pub default_credential: Option, +} + +impl AppConfig { + /// Loads configuration from disk and validates YAML syntax. + /// + /// # Errors: + /// + /// * Returns [`ServiceError::Io`] if the file cannot be read. + /// * Returns [`ServiceError::Yaml`] if parsing fails. + pub fn from_file(path: &Path) -> ServiceResult { + let contents = fs::read_to_string(path)?; + let config: Self = serde_yaml::from_str(&contents)?; + Ok(config) + } +} + +/// Network settings for the Axum server. +#[derive(Debug, Clone, Deserialize)] +pub struct ServerConfig { + #[serde(default = "default_bind_address")] + pub bind_address: String, + #[serde(default = "default_bind_port")] + pub port: u16, +} + +impl ServerConfig { + /// Renders the configured address pair into a [`SocketAddr`]. + pub fn socket_addr(&self) -> ServiceResult { + let addr = format!("{}:{}", self.bind_address, self.port); + addr.parse().map_err(|err| { + ServiceError::Config(format!( + "invalid bind address `{}`:{} ({err})", + self.bind_address, self.port + )) + }) + } +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + bind_address: default_bind_address(), + port: default_bind_port(), + } + } +} + +fn default_bind_address() -> String { + DEFAULT_SERVER_BIND_ADDRESS.to_owned() +} + +const fn default_bind_port() -> u16 { + DEFAULT_SERVER_PORT +} + +/// Connection options for the `MySQL` backend that stores credentials. +#[derive(Debug, Clone, Deserialize)] +pub struct DatabaseConfig { + pub host: String, + #[serde(default = "default_mysql_port")] + pub port: u16, + pub name: String, + pub user: String, + pub password: SecretString, + #[serde(default = "default_max_connections")] + pub max_connections: u32, +} + +const fn default_mysql_port() -> u16 { + 3306 +} + +const fn default_max_connections() -> u32 { + DEFAULT_MAX_CONNECTIONS +} + +/// JWT-related settings shared across token issuance endpoints. +#[derive(Debug, Clone, Deserialize)] +pub struct JwtConfig { + pub secret: SecretString, + #[serde(default = "default_jwt_ttl")] + pub token_ttl_seconds: u64, +} + +const fn default_jwt_ttl() -> u64 { + DEFAULT_JWT_TTL_SECONDS +} diff --git a/components/clp-credential-manager/src/error.rs b/components/clp-credential-manager/src/error.rs new file mode 100644 index 0000000000..f76022e436 --- /dev/null +++ b/components/clp-credential-manager/src/error.rs @@ -0,0 +1,128 @@ +#![allow(dead_code)] + +use std::fmt; + +use axum::{ + Json, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use jsonwebtoken::errors::Error as JwtError; +use serde::Serialize; +use thiserror::Error; + +/// Convenience alias for functions that return a [`ServiceError`]. +pub type ServiceResult = Result; + +/// Canonical error enumeration for the credential manager. +#[derive(Debug, Error)] +pub enum ServiceError { + #[error("configuration error: {0}")] + Config(String), + + #[error("validation error: {0}")] + Validation(String), + + #[error("database error: {0}")] + Database(#[source] sqlx::Error), + + #[error("resource conflict: {0}")] + Conflict(String), + + #[error("resource not found: {0}")] + NotFound(String), + + #[error("jwt error: {0}")] + Jwt(#[source] JwtError), + + #[error("i/o error: {0}")] + Io(#[from] std::io::Error), + + #[error("serialization error: {0}")] + Yaml(#[from] serde_yaml::Error), +} + +impl From for ServiceError { + fn from(err: sqlx::Error) -> Self { + if matches!(err, sqlx::Error::RowNotFound) { + return Self::NotFound("requested record was not found".to_owned()); + } + + if let Some(db_err) = err.as_database_error() + && let Some(mysql_err) = db_err.try_downcast_ref::() + && mysql_err.number() == 1062 + { + return Self::Conflict(mysql_err.message().to_owned()); + } + + Self::Database(err) + } +} + +/// HTTP-friendly representation of service failures. +#[derive(Debug)] +pub struct ApiError { + status: StatusCode, + message: String, +} + +impl ApiError { + /// Creates a new API error with the supplied HTTP status code. + pub fn new(status: StatusCode, message: impl Into) -> Self { + Self { + status, + message: message.into(), + } + } + + /// Convenience constructor for 500-class responses. + pub fn internal(message: impl Into) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, message) + } +} + +impl From for ApiError { + fn from(err: ServiceError) -> Self { + match err { + ServiceError::Validation(msg) => Self::new(StatusCode::BAD_REQUEST, msg), + ServiceError::Conflict(msg) => Self::new(StatusCode::CONFLICT, msg), + ServiceError::NotFound(msg) => Self::new(StatusCode::NOT_FOUND, msg), + ServiceError::Config(msg) => Self::new(StatusCode::INTERNAL_SERVER_ERROR, msg), + ServiceError::Database(source) => { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, source.to_string()) + } + ServiceError::Jwt(source) => { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, source.to_string()) + } + ServiceError::Io(source) => { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, source.to_string()) + } + ServiceError::Yaml(source) => { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, source.to_string()) + } + } + } +} + +#[derive(Debug, Serialize)] +struct ErrorBody { + error: String, +} + +/// Converts API errors into JSON bodies that align with other CLP services. +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + let status = self.status; + let body = Json(ErrorBody { + error: self.message, + }); + + (status, body).into_response() + } +} + +impl fmt::Display for ApiError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} diff --git a/components/clp-credential-manager/src/main.rs b/components/clp-credential-manager/src/main.rs new file mode 100644 index 0000000000..0ca45a3664 --- /dev/null +++ b/components/clp-credential-manager/src/main.rs @@ -0,0 +1,60 @@ +mod config; +mod error; +mod routes; +mod service; + +use std::path::PathBuf; + +use clap::Parser; +use tokio::net::TcpListener; +use tracing::info; +use tracing_subscriber::EnvFilter; + +use crate::{config::AppConfig, routes::build_router, service::CredentialManagerService}; + +/// CLI arguments accepted by the credential manager binary. +#[derive(Debug, Parser)] +#[command(author, version, about = "CLP Credential Manager service", long_about = None)] +struct Args { + #[arg( + short = 'c', + long = "config", + value_name = "FILE", + default_value = "credential-manager-config.yml" + )] + config: PathBuf, +} + +/// Binary entry point that configures logging, loads config, and starts Axum. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + init_tracing(); + + let config = AppConfig::from_file(&args.config)?; + let server_config = config.server.clone(); + let service = CredentialManagerService::new(&config).await?; + let shared_service = service.clone_shared(); + + let router = build_router().with_state(shared_service); + + let addr = server_config.socket_addr()?; + info!(address = %addr, "starting credential manager service"); + + let listener = TcpListener::bind(addr).await?; + axum::serve(listener, router).await?; + + Ok(()) +} + +/// Sets up JSON-formatted tracing using environment filters. +fn init_tracing() { + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + + let _ = tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_target(false) + .json() + .try_init(); +} diff --git a/components/clp-credential-manager/src/routes/health.rs b/components/clp-credential-manager/src/routes/health.rs new file mode 100644 index 0000000000..21af83bc7b --- /dev/null +++ b/components/clp-credential-manager/src/routes/health.rs @@ -0,0 +1,13 @@ +use axum::{Router, http::StatusCode, routing::get}; + +use super::AppState; + +/// Provides the `/health` endpoint used by readiness checks. +pub fn router() -> Router { + Router::new().route("/health", get(health_check)) +} + +/// Responds with `200 OK` to signal that the service is alive. +async fn health_check() -> StatusCode { + StatusCode::OK +} diff --git a/components/clp-credential-manager/src/routes/mod.rs b/components/clp-credential-manager/src/routes/mod.rs new file mode 100644 index 0000000000..9c6181bb2f --- /dev/null +++ b/components/clp-credential-manager/src/routes/mod.rs @@ -0,0 +1,16 @@ +mod health; + +use axum::Router; +use tower_http::trace::TraceLayer; + +use crate::service::SharedService; + +/// Shared Axum state for every route module. +pub type AppState = SharedService; + +/// Composes the service's HTTP surface, layering tracing instrumentation globally. +pub fn build_router() -> Router { + Router::new() + .merge(health::router()) + .layer(TraceLayer::new_for_http()) +} diff --git a/components/clp-credential-manager/src/service.rs b/components/clp-credential-manager/src/service.rs new file mode 100644 index 0000000000..b825ab97c3 --- /dev/null +++ b/components/clp-credential-manager/src/service.rs @@ -0,0 +1,109 @@ +#![allow(dead_code)] + +use std::{sync::Arc, time::Duration}; + +use jsonwebtoken::{DecodingKey, EncodingKey}; +use secrecy::ExposeSecret; +use sqlx::{MySqlPool, mysql::MySqlPoolOptions}; + +use crate::{ + config::{AppConfig, JwtConfig}, + error::{ServiceError, ServiceResult}, +}; + +/// Reference-counted handle that Axum stores as application state. +pub type SharedService = Arc; + +/// High-level façade that wires together persistence, auditing, and JWT handling. +pub struct CredentialManagerService { + db_pool: MySqlPool, + jwt: JwtManager, +} + +impl CredentialManagerService { + /// Establishes the database connection pool and prepares JWT helpers. + /// + /// # Returns: + /// + /// A fully initialized [`CredentialManagerService`] ready to be shared with Axum routes. + /// + /// # Errors: + /// + /// * Propagates errors from [`sqlx::mysql::MySqlPoolOptions::connect_with`]. + /// * Propagates errors from [`JwtManager::new`]. + pub async fn new(config: &AppConfig) -> ServiceResult { + let db_config = &config.database; + let options = sqlx::mysql::MySqlConnectOptions::new() + .host(&db_config.host) + .port(db_config.port) + .database(&db_config.name) + .username(&db_config.user) + .password(db_config.password.expose_secret()); + + let pool = MySqlPoolOptions::new() + .max_connections(db_config.max_connections) + .connect_with(options) + .await?; + + let jwt = JwtManager::new(&config.jwt)?; + + Ok(Self { db_pool: pool, jwt }) + } + + /// Wraps `self` in an [`Arc`] so it can be cloned into Axum handlers. + pub fn clone_shared(self) -> SharedService { + Arc::new(self) + } + + /// Exposes the underlying pool for callers that need direct `SQLx` access. + pub const fn db_pool(&self) -> &MySqlPool { + &self.db_pool + } + + /// Returns the JWT helper for issuing or validating service tokens. + pub const fn jwt(&self) -> &JwtManager { + &self.jwt + } +} + +/// Lightweight wrapper around jsonwebtoken keys plus default TTL metadata. +pub struct JwtManager { + encoding_key: EncodingKey, + decoding_key: DecodingKey, + default_ttl: Duration, +} + +impl JwtManager { + /// Builds signing and verification keys from configuration while enforcing required fields. + fn new(config: &JwtConfig) -> ServiceResult { + let secret = config.secret.expose_secret(); + if secret.is_empty() { + return Err(ServiceError::Config( + "jwt secret must not be empty".to_owned(), + )); + } + + let default_ttl = Duration::from_secs(config.token_ttl_seconds); + + Ok(Self { + encoding_key: EncodingKey::from_secret(secret.as_bytes()), + decoding_key: DecodingKey::from_secret(secret.as_bytes()), + default_ttl, + }) + } + + /// Returns the reusable encoding key when constructing JWT headers. + pub const fn encoding_key(&self) -> &EncodingKey { + &self.encoding_key + } + + /// Returns the reusable decoding key for JWT validation. + pub const fn decoding_key(&self) -> &DecodingKey { + &self.decoding_key + } + + /// Provides the configured token lifetime so callers can align expirations. + pub const fn default_ttl(&self) -> Duration { + self.default_ttl + } +} From 56b163fed5034e87f2a8788f86dd74b3cb68aad1 Mon Sep 17 00:00:00 2001 From: Eden Zhang Date: Mon, 24 Nov 2025 21:16:19 -0500 Subject: [PATCH 2/4] Remove unused code --- .../clp-credential-manager/src/config.rs | 22 +----- .../clp-credential-manager/src/service.rs | 67 ++----------------- 2 files changed, 6 insertions(+), 83 deletions(-) diff --git a/components/clp-credential-manager/src/config.rs b/components/clp-credential-manager/src/config.rs index 68c1e81681..c8fe93db13 100644 --- a/components/clp-credential-manager/src/config.rs +++ b/components/clp-credential-manager/src/config.rs @@ -1,9 +1,7 @@ -#![allow(dead_code)] - use std::{ fs, net::SocketAddr, - path::{Path, PathBuf}, + path::{Path}, }; use secrecy::SecretString; @@ -14,7 +12,6 @@ use crate::error::{ServiceError, ServiceResult}; const DEFAULT_MAX_CONNECTIONS: u32 = 5; const DEFAULT_SERVER_BIND_ADDRESS: &str = "0.0.0.0"; const DEFAULT_SERVER_PORT: u16 = 8080; -const DEFAULT_JWT_TTL_SECONDS: u64 = 3600; /// Root configuration deserialized from `credential-manager-config.yml`. #[derive(Debug, Clone, Deserialize)] @@ -22,11 +19,6 @@ pub struct AppConfig { #[serde(default)] pub server: ServerConfig, pub database: DatabaseConfig, - pub jwt: JwtConfig, - #[serde(default)] - pub credentials_file: Option, - #[serde(default)] - pub default_credential: Option, } impl AppConfig { @@ -102,15 +94,3 @@ const fn default_mysql_port() -> u16 { const fn default_max_connections() -> u32 { DEFAULT_MAX_CONNECTIONS } - -/// JWT-related settings shared across token issuance endpoints. -#[derive(Debug, Clone, Deserialize)] -pub struct JwtConfig { - pub secret: SecretString, - #[serde(default = "default_jwt_ttl")] - pub token_ttl_seconds: u64, -} - -const fn default_jwt_ttl() -> u64 { - DEFAULT_JWT_TTL_SECONDS -} diff --git a/components/clp-credential-manager/src/service.rs b/components/clp-credential-manager/src/service.rs index b825ab97c3..5ef5d58ae7 100644 --- a/components/clp-credential-manager/src/service.rs +++ b/components/clp-credential-manager/src/service.rs @@ -1,23 +1,20 @@ -#![allow(dead_code)] +use std::{sync::Arc}; -use std::{sync::Arc, time::Duration}; - -use jsonwebtoken::{DecodingKey, EncodingKey}; use secrecy::ExposeSecret; use sqlx::{MySqlPool, mysql::MySqlPoolOptions}; use crate::{ - config::{AppConfig, JwtConfig}, - error::{ServiceError, ServiceResult}, + config::{AppConfig}, + error::{ServiceResult}, }; /// Reference-counted handle that Axum stores as application state. pub type SharedService = Arc; /// High-level façade that wires together persistence, auditing, and JWT handling. +#[allow(dead_code)] pub struct CredentialManagerService { db_pool: MySqlPool, - jwt: JwtManager, } impl CredentialManagerService { @@ -45,65 +42,11 @@ impl CredentialManagerService { .connect_with(options) .await?; - let jwt = JwtManager::new(&config.jwt)?; - - Ok(Self { db_pool: pool, jwt }) + Ok(Self { db_pool: pool}) } /// Wraps `self` in an [`Arc`] so it can be cloned into Axum handlers. pub fn clone_shared(self) -> SharedService { Arc::new(self) } - - /// Exposes the underlying pool for callers that need direct `SQLx` access. - pub const fn db_pool(&self) -> &MySqlPool { - &self.db_pool - } - - /// Returns the JWT helper for issuing or validating service tokens. - pub const fn jwt(&self) -> &JwtManager { - &self.jwt - } -} - -/// Lightweight wrapper around jsonwebtoken keys plus default TTL metadata. -pub struct JwtManager { - encoding_key: EncodingKey, - decoding_key: DecodingKey, - default_ttl: Duration, -} - -impl JwtManager { - /// Builds signing and verification keys from configuration while enforcing required fields. - fn new(config: &JwtConfig) -> ServiceResult { - let secret = config.secret.expose_secret(); - if secret.is_empty() { - return Err(ServiceError::Config( - "jwt secret must not be empty".to_owned(), - )); - } - - let default_ttl = Duration::from_secs(config.token_ttl_seconds); - - Ok(Self { - encoding_key: EncodingKey::from_secret(secret.as_bytes()), - decoding_key: DecodingKey::from_secret(secret.as_bytes()), - default_ttl, - }) - } - - /// Returns the reusable encoding key when constructing JWT headers. - pub const fn encoding_key(&self) -> &EncodingKey { - &self.encoding_key - } - - /// Returns the reusable decoding key for JWT validation. - pub const fn decoding_key(&self) -> &DecodingKey { - &self.decoding_key - } - - /// Provides the configured token lifetime so callers can align expirations. - pub const fn default_ttl(&self) -> Duration { - self.default_ttl - } } From b8c0177c50760d3d90749961eb70a593d9a4b45f Mon Sep 17 00:00:00 2001 From: Eden Zhang Date: Mon, 24 Nov 2025 22:38:42 -0500 Subject: [PATCH 3/4] Update docstrings --- .../clp-credential-manager/src/config.rs | 21 ++++++++++++ .../clp-credential-manager/src/error.rs | 34 ++++++++++++++++++- components/clp-credential-manager/src/main.rs | 8 +++++ .../src/routes/health.rs | 8 +++++ .../clp-credential-manager/src/routes/mod.rs | 4 +++ .../clp-credential-manager/src/service.rs | 15 +++++--- 6 files changed, 85 insertions(+), 5 deletions(-) diff --git a/components/clp-credential-manager/src/config.rs b/components/clp-credential-manager/src/config.rs index c8fe93db13..400a893883 100644 --- a/components/clp-credential-manager/src/config.rs +++ b/components/clp-credential-manager/src/config.rs @@ -24,6 +24,14 @@ pub struct AppConfig { impl AppConfig { /// Loads configuration from disk and validates YAML syntax. /// + /// # Parameters: + /// + /// * `path`: Filesystem location of the YAML configuration file. + /// + /// # Returns: + /// + /// A parsed [`AppConfig`] instance when the file can be read and deserialized successfully. + /// /// # Errors: /// /// * Returns [`ServiceError::Io`] if the file cannot be read. @@ -46,6 +54,14 @@ pub struct ServerConfig { impl ServerConfig { /// Renders the configured address pair into a [`SocketAddr`]. + /// + /// # Returns: + /// + /// A [`SocketAddr`] that Axum can bind to when starting the HTTP server. + /// + /// # Errors: + /// + /// Returns [`ServiceError::Config`] if the address string cannot be parsed. pub fn socket_addr(&self) -> ServiceResult { let addr = format!("{}:{}", self.bind_address, self.port); addr.parse().map_err(|err| { @@ -58,6 +74,7 @@ impl ServerConfig { } impl Default for ServerConfig { + /// Provides sensible defaults that bind the HTTP server to all interfaces on port 8080. fn default() -> Self { Self { bind_address: default_bind_address(), @@ -66,10 +83,12 @@ impl Default for ServerConfig { } } +/// Supplies the default bind address of `0.0.0.0` so the service listens on every interface. fn default_bind_address() -> String { DEFAULT_SERVER_BIND_ADDRESS.to_owned() } +/// Supplies the default bind port of `8080` that matches other CLP services. const fn default_bind_port() -> u16 { DEFAULT_SERVER_PORT } @@ -87,10 +106,12 @@ pub struct DatabaseConfig { pub max_connections: u32, } +/// Supplies the default MySQL server port (`3306`). const fn default_mysql_port() -> u16 { 3306 } +/// Supplies the default maximum connection count for the MySQL pool. const fn default_max_connections() -> u32 { DEFAULT_MAX_CONNECTIONS } diff --git a/components/clp-credential-manager/src/error.rs b/components/clp-credential-manager/src/error.rs index f76022e436..73694cd1de 100644 --- a/components/clp-credential-manager/src/error.rs +++ b/components/clp-credential-manager/src/error.rs @@ -43,6 +43,11 @@ pub enum ServiceError { } impl From for ServiceError { + /// Maps raw [`sqlx::Error`] values into domain-specific variants so callers can react precisely. + /// + /// # Parameters: + /// + /// * `err`: The database error surfaced by `sqlx`. fn from(err: sqlx::Error) -> Self { if matches!(err, sqlx::Error::RowNotFound) { return Self::NotFound("requested record was not found".to_owned()); @@ -68,6 +73,15 @@ pub struct ApiError { impl ApiError { /// Creates a new API error with the supplied HTTP status code. + /// + /// # Parameters: + /// + /// * `status`: HTTP status code that best represents the failure. + /// * `message`: Human-readable diagnostic message. + /// + /// # Returns: + /// + /// A new [`ApiError`] ready to be converted into an HTTP response. pub fn new(status: StatusCode, message: impl Into) -> Self { Self { status, @@ -76,6 +90,14 @@ impl ApiError { } /// Convenience constructor for 500-class responses. + /// + /// # Parameters: + /// + /// * `message`: Human-readable error string for the response body. + /// + /// # Returns: + /// + /// A new [`ApiError`] that always maps to [`StatusCode::INTERNAL_SERVER_ERROR`]. pub fn internal(message: impl Into) -> Self { Self::new(StatusCode::INTERNAL_SERVER_ERROR, message) } @@ -104,13 +126,18 @@ impl From for ApiError { } } +/// JSON payload emitted by error responses. #[derive(Debug, Serialize)] struct ErrorBody { error: String, } -/// Converts API errors into JSON bodies that align with other CLP services. impl IntoResponse for ApiError { + /// Serializes the error payload into JSON schema so clients receive consistent bodies. + /// + /// # Returns: + /// + /// An [`axum::response::Response`] containing the status code plus `{"error": "..."}` body. fn into_response(self) -> Response { let status = self.status; let body = Json(ErrorBody { @@ -122,6 +149,11 @@ impl IntoResponse for ApiError { } impl fmt::Display for ApiError { + /// Formats only the message so higher layers can log without leaking sensitive details. + /// + /// # Returns: + /// + /// [`fmt::Result`] signaling whether writing to the formatter succeeded. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.message) } diff --git a/components/clp-credential-manager/src/main.rs b/components/clp-credential-manager/src/main.rs index 0ca45a3664..13152669e4 100644 --- a/components/clp-credential-manager/src/main.rs +++ b/components/clp-credential-manager/src/main.rs @@ -26,6 +26,11 @@ struct Args { } /// Binary entry point that configures logging, loads config, and starts Axum. +/// +/// # Errors: +/// +/// Returns [`anyhow::Error`] when configuration parsing, database connections, TCP binding, +/// or Axum startup fail. #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); @@ -49,6 +54,9 @@ async fn main() -> anyhow::Result<()> { } /// Sets up JSON-formatted tracing using environment filters. +/// +/// The subscriber honors the `RUST_LOG` environment variable when present and otherwise +/// defaults to the `info` level so local development remains verbose enough. fn init_tracing() { let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); diff --git a/components/clp-credential-manager/src/routes/health.rs b/components/clp-credential-manager/src/routes/health.rs index 21af83bc7b..258f99b242 100644 --- a/components/clp-credential-manager/src/routes/health.rs +++ b/components/clp-credential-manager/src/routes/health.rs @@ -3,11 +3,19 @@ use axum::{Router, http::StatusCode, routing::get}; use super::AppState; /// Provides the `/health` endpoint used by readiness checks. +/// +/// # Returns: +/// +/// A [`Router`] that mounts `/health` and delegates to [`health_check`]. pub fn router() -> Router { Router::new().route("/health", get(health_check)) } /// Responds with `200 OK` to signal that the service is alive. +/// +/// # Returns: +/// +/// [`StatusCode::OK`] regardless of request parameters. async fn health_check() -> StatusCode { StatusCode::OK } diff --git a/components/clp-credential-manager/src/routes/mod.rs b/components/clp-credential-manager/src/routes/mod.rs index 9c6181bb2f..5da447c41e 100644 --- a/components/clp-credential-manager/src/routes/mod.rs +++ b/components/clp-credential-manager/src/routes/mod.rs @@ -9,6 +9,10 @@ use crate::service::SharedService; pub type AppState = SharedService; /// Composes the service's HTTP surface, layering tracing instrumentation globally. +/// +/// # Returns: +/// +/// An [`axum::Router`] wired with all route trees and middleware layers. pub fn build_router() -> Router { Router::new() .merge(health::router()) diff --git a/components/clp-credential-manager/src/service.rs b/components/clp-credential-manager/src/service.rs index 5ef5d58ae7..e3f57684a3 100644 --- a/components/clp-credential-manager/src/service.rs +++ b/components/clp-credential-manager/src/service.rs @@ -11,14 +11,18 @@ use crate::{ /// Reference-counted handle that Axum stores as application state. pub type SharedService = Arc; -/// High-level façade that wires together persistence, auditing, and JWT handling. +/// High-level facade that wires together persistence, auditing, and JWT handling. #[allow(dead_code)] pub struct CredentialManagerService { db_pool: MySqlPool, } impl CredentialManagerService { - /// Establishes the database connection pool and prepares JWT helpers. + /// Establishes the database connection pool used by all route handlers. + /// + /// # Parameters: + /// + /// * `config`: Fully parsed application configuration that contains database settings. /// /// # Returns: /// @@ -26,8 +30,7 @@ impl CredentialManagerService { /// /// # Errors: /// - /// * Propagates errors from [`sqlx::mysql::MySqlPoolOptions::connect_with`]. - /// * Propagates errors from [`JwtManager::new`]. + /// * Propagates errors from [`MySqlPoolOptions::connect_with`] when the pool cannot be created. pub async fn new(config: &AppConfig) -> ServiceResult { let db_config = &config.database; let options = sqlx::mysql::MySqlConnectOptions::new() @@ -46,6 +49,10 @@ impl CredentialManagerService { } /// Wraps `self` in an [`Arc`] so it can be cloned into Axum handlers. + /// + /// # Returns: + /// + /// A [`SharedService`] reference-counted pointer that implements [`Clone`]. pub fn clone_shared(self) -> SharedService { Arc::new(self) } From e0d0e255274b3996a20281c946a79da7678b06c1 Mon Sep 17 00:00:00 2001 From: Eden Zhang Date: Wed, 26 Nov 2025 16:05:53 +0000 Subject: [PATCH 4/4] Lint --- components/clp-credential-manager/src/config.rs | 10 +++------- components/clp-credential-manager/src/error.rs | 3 ++- components/clp-credential-manager/src/service.rs | 9 +++------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/components/clp-credential-manager/src/config.rs b/components/clp-credential-manager/src/config.rs index 400a893883..a4a0d1b3e6 100644 --- a/components/clp-credential-manager/src/config.rs +++ b/components/clp-credential-manager/src/config.rs @@ -1,8 +1,4 @@ -use std::{ - fs, - net::SocketAddr, - path::{Path}, -}; +use std::{fs, net::SocketAddr, path::Path}; use secrecy::SecretString; use serde::Deserialize; @@ -106,12 +102,12 @@ pub struct DatabaseConfig { pub max_connections: u32, } -/// Supplies the default MySQL server port (`3306`). +/// Supplies the default `MySQL` server port (`3306`). const fn default_mysql_port() -> u16 { 3306 } -/// Supplies the default maximum connection count for the MySQL pool. +/// Supplies the default maximum connection count for the `MySQL` pool. const fn default_max_connections() -> u32 { DEFAULT_MAX_CONNECTIONS } diff --git a/components/clp-credential-manager/src/error.rs b/components/clp-credential-manager/src/error.rs index 73694cd1de..51deb996ad 100644 --- a/components/clp-credential-manager/src/error.rs +++ b/components/clp-credential-manager/src/error.rs @@ -43,7 +43,8 @@ pub enum ServiceError { } impl From for ServiceError { - /// Maps raw [`sqlx::Error`] values into domain-specific variants so callers can react precisely. + /// Maps raw [`sqlx::Error`] values into domain-specific variants so callers can react + /// precisely. /// /// # Parameters: /// diff --git a/components/clp-credential-manager/src/service.rs b/components/clp-credential-manager/src/service.rs index e3f57684a3..7d5ca4f60c 100644 --- a/components/clp-credential-manager/src/service.rs +++ b/components/clp-credential-manager/src/service.rs @@ -1,12 +1,9 @@ -use std::{sync::Arc}; +use std::sync::Arc; use secrecy::ExposeSecret; use sqlx::{MySqlPool, mysql::MySqlPoolOptions}; -use crate::{ - config::{AppConfig}, - error::{ServiceResult}, -}; +use crate::{config::AppConfig, error::ServiceResult}; /// Reference-counted handle that Axum stores as application state. pub type SharedService = Arc; @@ -45,7 +42,7 @@ impl CredentialManagerService { .connect_with(options) .await?; - Ok(Self { db_pool: pool}) + Ok(Self { db_pool: pool }) } /// Wraps `self` in an [`Arc`] so it can be cloned into Axum handlers.