An easy-to-host PDS on the ATProtocol, MacOS. Grandma-approved.

feat(MM-69): configuration system — relay.toml parsing #7

Summary#

  • Adds Config struct to the common crate (serde-deserializable from TOML) with v0.1 fields: bind_address, port, data_dir,database_url, public_url, and empty stub sections [blobs], [oauth], [iroh]
  • EZPDS_* env var overrides (prefix: EZPDS_) applied on top of TOML via pure apply_env_overrides — no I/O in the Functional Core
  • Relay binary gains --config/EZPDS_CONFIG CLI arg (clap), loads config on startup, fails fast with clear error message on invalid config; logging via RUST_LOG-aware EnvFilter
  • 13 tests: TOML parsing, env var overrides, missing required field errors, I/O and parse error paths

Architecture notes#

FCIS pattern applied:

  • config.rsFunctional Core: Config, RawConfig, ConfigError, apply_env_overrides (takes explicit env HashMap), validate_and_build
  • config_loader.rsImperative Shell: load_config (reads file + real env), load_config_with_env (pub(crate) for test isolation)
  • relay/src/main.rsImperative Shell: CLI parsing, config loading, structured logging

Test plan#

  • cargo test --workspace — 13 tests pass, 0 failures
  • cargo clippy --workspace -- -D warnings — no warnings
  • cargo fmt --all --check — clean
  • cargo build --package relay — builds successfully
  • Manual smoke test: run ./target/debug/relay --config /nonexistent.toml and confirm error message is error: failed to load config from /nonexistent.toml: failed to read config file: No such file or directory (os error 2)
  • Manual smoke test: create a minimal relay.toml with data_dir and public_url, run ./target/debug/relay and confirm structured log line at startup

Closes MM-69

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:web:malpercio.dev/sh.tangled.repo.pull/3mglmncyxxf22
+1201 -1
Diff #0
+792
Cargo.lock
··· 2 2 # It is not intended for manual editing. 3 3 version = 4 4 4 5 + [[package]] 6 + name = "aho-corasick" 7 + version = "1.1.4" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 10 + dependencies = [ 11 + "memchr", 12 + ] 13 + 14 + [[package]] 15 + name = "anstream" 16 + version = "0.6.21" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 19 + dependencies = [ 20 + "anstyle", 21 + "anstyle-parse", 22 + "anstyle-query", 23 + "anstyle-wincon", 24 + "colorchoice", 25 + "is_terminal_polyfill", 26 + "utf8parse", 27 + ] 28 + 29 + [[package]] 30 + name = "anstyle" 31 + version = "1.0.13" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 34 + 35 + [[package]] 36 + name = "anstyle-parse" 37 + version = "0.2.7" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 40 + dependencies = [ 41 + "utf8parse", 42 + ] 43 + 44 + [[package]] 45 + name = "anstyle-query" 46 + version = "1.1.5" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 49 + dependencies = [ 50 + "windows-sys", 51 + ] 52 + 53 + [[package]] 54 + name = "anstyle-wincon" 55 + version = "3.0.11" 56 + source = "registry+https://github.com/rust-lang/crates.io-index" 57 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 58 + dependencies = [ 59 + "anstyle", 60 + "once_cell_polyfill", 61 + "windows-sys", 62 + ] 63 + 64 + [[package]] 65 + name = "anyhow" 66 + version = "1.0.102" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 69 + 70 + [[package]] 71 + name = "bitflags" 72 + version = "2.11.0" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" 75 + 76 + [[package]] 77 + name = "cfg-if" 78 + version = "1.0.4" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 81 + 82 + [[package]] 83 + name = "clap" 84 + version = "4.5.60" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" 87 + dependencies = [ 88 + "clap_builder", 89 + "clap_derive", 90 + ] 91 + 92 + [[package]] 93 + name = "clap_builder" 94 + version = "4.5.60" 95 + source = "registry+https://github.com/rust-lang/crates.io-index" 96 + checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" 97 + dependencies = [ 98 + "anstream", 99 + "anstyle", 100 + "clap_lex", 101 + "strsim", 102 + ] 103 + 104 + [[package]] 105 + name = "clap_derive" 106 + version = "4.5.55" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" 109 + dependencies = [ 110 + "heck", 111 + "proc-macro2", 112 + "quote", 113 + "syn", 114 + ] 115 + 116 + [[package]] 117 + name = "clap_lex" 118 + version = "1.0.0" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" 121 + 122 + [[package]] 123 + name = "colorchoice" 124 + version = "1.0.4" 125 + source = "registry+https://github.com/rust-lang/crates.io-index" 126 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 127 + 5 128 [[package]] 6 129 name = "common" 7 130 version = "0.1.0" 131 + dependencies = [ 132 + "serde", 133 + "tempfile", 134 + "thiserror", 135 + "toml", 136 + ] 8 137 9 138 [[package]] 10 139 name = "crypto" 11 140 version = "0.1.0" 12 141 142 + [[package]] 143 + name = "equivalent" 144 + version = "1.0.2" 145 + source = "registry+https://github.com/rust-lang/crates.io-index" 146 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 147 + 148 + [[package]] 149 + name = "errno" 150 + version = "0.3.14" 151 + source = "registry+https://github.com/rust-lang/crates.io-index" 152 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 153 + dependencies = [ 154 + "libc", 155 + "windows-sys", 156 + ] 157 + 158 + [[package]] 159 + name = "fastrand" 160 + version = "2.3.0" 161 + source = "registry+https://github.com/rust-lang/crates.io-index" 162 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 163 + 164 + [[package]] 165 + name = "foldhash" 166 + version = "0.1.5" 167 + source = "registry+https://github.com/rust-lang/crates.io-index" 168 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 169 + 170 + [[package]] 171 + name = "getrandom" 172 + version = "0.4.2" 173 + source = "registry+https://github.com/rust-lang/crates.io-index" 174 + checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" 175 + dependencies = [ 176 + "cfg-if", 177 + "libc", 178 + "r-efi", 179 + "wasip2", 180 + "wasip3", 181 + ] 182 + 183 + [[package]] 184 + name = "hashbrown" 185 + version = "0.15.5" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 188 + dependencies = [ 189 + "foldhash", 190 + ] 191 + 192 + [[package]] 193 + name = "hashbrown" 194 + version = "0.16.1" 195 + source = "registry+https://github.com/rust-lang/crates.io-index" 196 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 197 + 198 + [[package]] 199 + name = "heck" 200 + version = "0.5.0" 201 + source = "registry+https://github.com/rust-lang/crates.io-index" 202 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 203 + 204 + [[package]] 205 + name = "id-arena" 206 + version = "2.3.0" 207 + source = "registry+https://github.com/rust-lang/crates.io-index" 208 + checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" 209 + 210 + [[package]] 211 + name = "indexmap" 212 + version = "2.13.0" 213 + source = "registry+https://github.com/rust-lang/crates.io-index" 214 + checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" 215 + dependencies = [ 216 + "equivalent", 217 + "hashbrown 0.16.1", 218 + "serde", 219 + "serde_core", 220 + ] 221 + 222 + [[package]] 223 + name = "is_terminal_polyfill" 224 + version = "1.70.2" 225 + source = "registry+https://github.com/rust-lang/crates.io-index" 226 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 227 + 228 + [[package]] 229 + name = "itoa" 230 + version = "1.0.17" 231 + source = "registry+https://github.com/rust-lang/crates.io-index" 232 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 233 + 234 + [[package]] 235 + name = "lazy_static" 236 + version = "1.5.0" 237 + source = "registry+https://github.com/rust-lang/crates.io-index" 238 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 239 + 240 + [[package]] 241 + name = "leb128fmt" 242 + version = "0.1.0" 243 + source = "registry+https://github.com/rust-lang/crates.io-index" 244 + checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 245 + 246 + [[package]] 247 + name = "libc" 248 + version = "0.2.183" 249 + source = "registry+https://github.com/rust-lang/crates.io-index" 250 + checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" 251 + 252 + [[package]] 253 + name = "linux-raw-sys" 254 + version = "0.12.1" 255 + source = "registry+https://github.com/rust-lang/crates.io-index" 256 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 257 + 258 + [[package]] 259 + name = "log" 260 + version = "0.4.29" 261 + source = "registry+https://github.com/rust-lang/crates.io-index" 262 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 263 + 264 + [[package]] 265 + name = "matchers" 266 + version = "0.2.0" 267 + source = "registry+https://github.com/rust-lang/crates.io-index" 268 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 269 + dependencies = [ 270 + "regex-automata", 271 + ] 272 + 273 + [[package]] 274 + name = "memchr" 275 + version = "2.8.0" 276 + source = "registry+https://github.com/rust-lang/crates.io-index" 277 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 278 + 279 + [[package]] 280 + name = "nu-ansi-term" 281 + version = "0.50.3" 282 + source = "registry+https://github.com/rust-lang/crates.io-index" 283 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 284 + dependencies = [ 285 + "windows-sys", 286 + ] 287 + 288 + [[package]] 289 + name = "once_cell" 290 + version = "1.21.3" 291 + source = "registry+https://github.com/rust-lang/crates.io-index" 292 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 293 + 294 + [[package]] 295 + name = "once_cell_polyfill" 296 + version = "1.70.2" 297 + source = "registry+https://github.com/rust-lang/crates.io-index" 298 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 299 + 300 + [[package]] 301 + name = "pin-project-lite" 302 + version = "0.2.17" 303 + source = "registry+https://github.com/rust-lang/crates.io-index" 304 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 305 + 306 + [[package]] 307 + name = "prettyplease" 308 + version = "0.2.37" 309 + source = "registry+https://github.com/rust-lang/crates.io-index" 310 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 311 + dependencies = [ 312 + "proc-macro2", 313 + "syn", 314 + ] 315 + 316 + [[package]] 317 + name = "proc-macro2" 318 + version = "1.0.106" 319 + source = "registry+https://github.com/rust-lang/crates.io-index" 320 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 321 + dependencies = [ 322 + "unicode-ident", 323 + ] 324 + 325 + [[package]] 326 + name = "quote" 327 + version = "1.0.45" 328 + source = "registry+https://github.com/rust-lang/crates.io-index" 329 + checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 330 + dependencies = [ 331 + "proc-macro2", 332 + ] 333 + 334 + [[package]] 335 + name = "r-efi" 336 + version = "6.0.0" 337 + source = "registry+https://github.com/rust-lang/crates.io-index" 338 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 339 + 340 + [[package]] 341 + name = "regex-automata" 342 + version = "0.4.14" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" 345 + dependencies = [ 346 + "aho-corasick", 347 + "memchr", 348 + "regex-syntax", 349 + ] 350 + 351 + [[package]] 352 + name = "regex-syntax" 353 + version = "0.8.10" 354 + source = "registry+https://github.com/rust-lang/crates.io-index" 355 + checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" 356 + 13 357 [[package]] 14 358 name = "relay" 15 359 version = "0.1.0" 360 + dependencies = [ 361 + "anyhow", 362 + "clap", 363 + "common", 364 + "tracing", 365 + "tracing-subscriber", 366 + ] 16 367 17 368 [[package]] 18 369 name = "repo-engine" 19 370 version = "0.1.0" 371 + 372 + [[package]] 373 + name = "rustix" 374 + version = "1.1.4" 375 + source = "registry+https://github.com/rust-lang/crates.io-index" 376 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 377 + dependencies = [ 378 + "bitflags", 379 + "errno", 380 + "libc", 381 + "linux-raw-sys", 382 + "windows-sys", 383 + ] 384 + 385 + [[package]] 386 + name = "semver" 387 + version = "1.0.27" 388 + source = "registry+https://github.com/rust-lang/crates.io-index" 389 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 390 + 391 + [[package]] 392 + name = "serde" 393 + version = "1.0.228" 394 + source = "registry+https://github.com/rust-lang/crates.io-index" 395 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 396 + dependencies = [ 397 + "serde_core", 398 + "serde_derive", 399 + ] 400 + 401 + [[package]] 402 + name = "serde_core" 403 + version = "1.0.228" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 406 + dependencies = [ 407 + "serde_derive", 408 + ] 409 + 410 + [[package]] 411 + name = "serde_derive" 412 + version = "1.0.228" 413 + source = "registry+https://github.com/rust-lang/crates.io-index" 414 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 415 + dependencies = [ 416 + "proc-macro2", 417 + "quote", 418 + "syn", 419 + ] 420 + 421 + [[package]] 422 + name = "serde_json" 423 + version = "1.0.149" 424 + source = "registry+https://github.com/rust-lang/crates.io-index" 425 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 426 + dependencies = [ 427 + "itoa", 428 + "memchr", 429 + "serde", 430 + "serde_core", 431 + "zmij", 432 + ] 433 + 434 + [[package]] 435 + name = "serde_spanned" 436 + version = "0.6.9" 437 + source = "registry+https://github.com/rust-lang/crates.io-index" 438 + checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 439 + dependencies = [ 440 + "serde", 441 + ] 442 + 443 + [[package]] 444 + name = "sharded-slab" 445 + version = "0.1.7" 446 + source = "registry+https://github.com/rust-lang/crates.io-index" 447 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 448 + dependencies = [ 449 + "lazy_static", 450 + ] 451 + 452 + [[package]] 453 + name = "smallvec" 454 + version = "1.15.1" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 457 + 458 + [[package]] 459 + name = "strsim" 460 + version = "0.11.1" 461 + source = "registry+https://github.com/rust-lang/crates.io-index" 462 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 463 + 464 + [[package]] 465 + name = "syn" 466 + version = "2.0.117" 467 + source = "registry+https://github.com/rust-lang/crates.io-index" 468 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 469 + dependencies = [ 470 + "proc-macro2", 471 + "quote", 472 + "unicode-ident", 473 + ] 474 + 475 + [[package]] 476 + name = "tempfile" 477 + version = "3.26.0" 478 + source = "registry+https://github.com/rust-lang/crates.io-index" 479 + checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" 480 + dependencies = [ 481 + "fastrand", 482 + "getrandom", 483 + "once_cell", 484 + "rustix", 485 + "windows-sys", 486 + ] 487 + 488 + [[package]] 489 + name = "thiserror" 490 + version = "2.0.18" 491 + source = "registry+https://github.com/rust-lang/crates.io-index" 492 + checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 493 + dependencies = [ 494 + "thiserror-impl", 495 + ] 496 + 497 + [[package]] 498 + name = "thiserror-impl" 499 + version = "2.0.18" 500 + source = "registry+https://github.com/rust-lang/crates.io-index" 501 + checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 502 + dependencies = [ 503 + "proc-macro2", 504 + "quote", 505 + "syn", 506 + ] 507 + 508 + [[package]] 509 + name = "thread_local" 510 + version = "1.1.9" 511 + source = "registry+https://github.com/rust-lang/crates.io-index" 512 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 513 + dependencies = [ 514 + "cfg-if", 515 + ] 516 + 517 + [[package]] 518 + name = "toml" 519 + version = "0.8.23" 520 + source = "registry+https://github.com/rust-lang/crates.io-index" 521 + checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 522 + dependencies = [ 523 + "serde", 524 + "serde_spanned", 525 + "toml_datetime", 526 + "toml_edit", 527 + ] 528 + 529 + [[package]] 530 + name = "toml_datetime" 531 + version = "0.6.11" 532 + source = "registry+https://github.com/rust-lang/crates.io-index" 533 + checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 534 + dependencies = [ 535 + "serde", 536 + ] 537 + 538 + [[package]] 539 + name = "toml_edit" 540 + version = "0.22.27" 541 + source = "registry+https://github.com/rust-lang/crates.io-index" 542 + checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 543 + dependencies = [ 544 + "indexmap", 545 + "serde", 546 + "serde_spanned", 547 + "toml_datetime", 548 + "toml_write", 549 + "winnow", 550 + ] 551 + 552 + [[package]] 553 + name = "toml_write" 554 + version = "0.1.2" 555 + source = "registry+https://github.com/rust-lang/crates.io-index" 556 + checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 557 + 558 + [[package]] 559 + name = "tracing" 560 + version = "0.1.44" 561 + source = "registry+https://github.com/rust-lang/crates.io-index" 562 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 563 + dependencies = [ 564 + "pin-project-lite", 565 + "tracing-attributes", 566 + "tracing-core", 567 + ] 568 + 569 + [[package]] 570 + name = "tracing-attributes" 571 + version = "0.1.31" 572 + source = "registry+https://github.com/rust-lang/crates.io-index" 573 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 574 + dependencies = [ 575 + "proc-macro2", 576 + "quote", 577 + "syn", 578 + ] 579 + 580 + [[package]] 581 + name = "tracing-core" 582 + version = "0.1.36" 583 + source = "registry+https://github.com/rust-lang/crates.io-index" 584 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 585 + dependencies = [ 586 + "once_cell", 587 + "valuable", 588 + ] 589 + 590 + [[package]] 591 + name = "tracing-log" 592 + version = "0.2.0" 593 + source = "registry+https://github.com/rust-lang/crates.io-index" 594 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 595 + dependencies = [ 596 + "log", 597 + "once_cell", 598 + "tracing-core", 599 + ] 600 + 601 + [[package]] 602 + name = "tracing-subscriber" 603 + version = "0.3.22" 604 + source = "registry+https://github.com/rust-lang/crates.io-index" 605 + checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 606 + dependencies = [ 607 + "matchers", 608 + "nu-ansi-term", 609 + "once_cell", 610 + "regex-automata", 611 + "sharded-slab", 612 + "smallvec", 613 + "thread_local", 614 + "tracing", 615 + "tracing-core", 616 + "tracing-log", 617 + ] 618 + 619 + [[package]] 620 + name = "unicode-ident" 621 + version = "1.0.24" 622 + source = "registry+https://github.com/rust-lang/crates.io-index" 623 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 624 + 625 + [[package]] 626 + name = "unicode-xid" 627 + version = "0.2.6" 628 + source = "registry+https://github.com/rust-lang/crates.io-index" 629 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 630 + 631 + [[package]] 632 + name = "utf8parse" 633 + version = "0.2.2" 634 + source = "registry+https://github.com/rust-lang/crates.io-index" 635 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 636 + 637 + [[package]] 638 + name = "valuable" 639 + version = "0.1.1" 640 + source = "registry+https://github.com/rust-lang/crates.io-index" 641 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 642 + 643 + [[package]] 644 + name = "wasip2" 645 + version = "1.0.2+wasi-0.2.9" 646 + source = "registry+https://github.com/rust-lang/crates.io-index" 647 + checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" 648 + dependencies = [ 649 + "wit-bindgen", 650 + ] 651 + 652 + [[package]] 653 + name = "wasip3" 654 + version = "0.4.0+wasi-0.3.0-rc-2026-01-06" 655 + source = "registry+https://github.com/rust-lang/crates.io-index" 656 + checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" 657 + dependencies = [ 658 + "wit-bindgen", 659 + ] 660 + 661 + [[package]] 662 + name = "wasm-encoder" 663 + version = "0.244.0" 664 + source = "registry+https://github.com/rust-lang/crates.io-index" 665 + checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" 666 + dependencies = [ 667 + "leb128fmt", 668 + "wasmparser", 669 + ] 670 + 671 + [[package]] 672 + name = "wasm-metadata" 673 + version = "0.244.0" 674 + source = "registry+https://github.com/rust-lang/crates.io-index" 675 + checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" 676 + dependencies = [ 677 + "anyhow", 678 + "indexmap", 679 + "wasm-encoder", 680 + "wasmparser", 681 + ] 682 + 683 + [[package]] 684 + name = "wasmparser" 685 + version = "0.244.0" 686 + source = "registry+https://github.com/rust-lang/crates.io-index" 687 + checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" 688 + dependencies = [ 689 + "bitflags", 690 + "hashbrown 0.15.5", 691 + "indexmap", 692 + "semver", 693 + ] 694 + 695 + [[package]] 696 + name = "windows-link" 697 + version = "0.2.1" 698 + source = "registry+https://github.com/rust-lang/crates.io-index" 699 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 700 + 701 + [[package]] 702 + name = "windows-sys" 703 + version = "0.61.2" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 706 + dependencies = [ 707 + "windows-link", 708 + ] 709 + 710 + [[package]] 711 + name = "winnow" 712 + version = "0.7.15" 713 + source = "registry+https://github.com/rust-lang/crates.io-index" 714 + checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" 715 + dependencies = [ 716 + "memchr", 717 + ] 718 + 719 + [[package]] 720 + name = "wit-bindgen" 721 + version = "0.51.0" 722 + source = "registry+https://github.com/rust-lang/crates.io-index" 723 + checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 724 + dependencies = [ 725 + "wit-bindgen-rust-macro", 726 + ] 727 + 728 + [[package]] 729 + name = "wit-bindgen-core" 730 + version = "0.51.0" 731 + source = "registry+https://github.com/rust-lang/crates.io-index" 732 + checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" 733 + dependencies = [ 734 + "anyhow", 735 + "heck", 736 + "wit-parser", 737 + ] 738 + 739 + [[package]] 740 + name = "wit-bindgen-rust" 741 + version = "0.51.0" 742 + source = "registry+https://github.com/rust-lang/crates.io-index" 743 + checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" 744 + dependencies = [ 745 + "anyhow", 746 + "heck", 747 + "indexmap", 748 + "prettyplease", 749 + "syn", 750 + "wasm-metadata", 751 + "wit-bindgen-core", 752 + "wit-component", 753 + ] 754 + 755 + [[package]] 756 + name = "wit-bindgen-rust-macro" 757 + version = "0.51.0" 758 + source = "registry+https://github.com/rust-lang/crates.io-index" 759 + checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" 760 + dependencies = [ 761 + "anyhow", 762 + "prettyplease", 763 + "proc-macro2", 764 + "quote", 765 + "syn", 766 + "wit-bindgen-core", 767 + "wit-bindgen-rust", 768 + ] 769 + 770 + [[package]] 771 + name = "wit-component" 772 + version = "0.244.0" 773 + source = "registry+https://github.com/rust-lang/crates.io-index" 774 + checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" 775 + dependencies = [ 776 + "anyhow", 777 + "bitflags", 778 + "indexmap", 779 + "log", 780 + "serde", 781 + "serde_derive", 782 + "serde_json", 783 + "wasm-encoder", 784 + "wasm-metadata", 785 + "wasmparser", 786 + "wit-parser", 787 + ] 788 + 789 + [[package]] 790 + name = "wit-parser" 791 + version = "0.244.0" 792 + source = "registry+https://github.com/rust-lang/crates.io-index" 793 + checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" 794 + dependencies = [ 795 + "anyhow", 796 + "id-arena", 797 + "indexmap", 798 + "log", 799 + "semver", 800 + "serde", 801 + "serde_derive", 802 + "serde_json", 803 + "unicode-xid", 804 + "wasmparser", 805 + ] 806 + 807 + [[package]] 808 + name = "zmij" 809 + version = "1.0.21" 810 + source = "registry+https://github.com/rust-lang/crates.io-index" 811 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+4
Cargo.toml
··· 24 24 # Serialization 25 25 serde = { version = "1", features = ["derive"] } 26 26 serde_json = "1" 27 + toml = "0.8" 28 + 29 + # CLI argument parsing (relay) 30 + clap = { version = "4", features = ["derive", "env"] } 27 31 28 32 # Error handling 29 33 anyhow = "1"
+8
crates/common/Cargo.toml
··· 5 5 publish.workspace = true 6 6 7 7 # common: shared types, error envelope, config parsing. 8 + 9 + [dependencies] 10 + serde = { workspace = true } 11 + toml = { workspace = true } 12 + thiserror = { workspace = true } 13 + 14 + [dev-dependencies] 15 + tempfile = "3"
+259
crates/common/src/config.rs
··· 1 + // pattern: Functional Core 2 + 3 + use serde::Deserialize; 4 + use std::collections::HashMap; 5 + use std::path::PathBuf; 6 + 7 + /// Validated, fully-resolved relay configuration. 8 + #[derive(Debug, Clone)] 9 + pub struct Config { 10 + pub bind_address: String, 11 + pub port: u16, 12 + pub data_dir: PathBuf, 13 + pub database_url: String, 14 + pub public_url: String, 15 + pub blobs: BlobsConfig, 16 + pub oauth: OAuthConfig, 17 + pub iroh: IrohConfig, 18 + } 19 + 20 + /// Stub for future blob storage configuration. 21 + #[derive(Debug, Clone, Deserialize, Default)] 22 + pub struct BlobsConfig {} 23 + 24 + /// Stub for future OAuth configuration. 25 + #[derive(Debug, Clone, Deserialize, Default)] 26 + pub struct OAuthConfig {} 27 + 28 + /// Stub for future Iroh networking configuration. 29 + #[derive(Debug, Clone, Deserialize, Default)] 30 + pub struct IrohConfig {} 31 + 32 + /// Raw TOML-deserialized config with all fields optional to support env-var overlays. 33 + #[derive(Debug, Deserialize, Default)] 34 + pub(crate) struct RawConfig { 35 + pub(crate) bind_address: Option<String>, 36 + pub(crate) port: Option<u16>, 37 + pub(crate) data_dir: Option<String>, 38 + pub(crate) database_url: Option<String>, 39 + pub(crate) public_url: Option<String>, 40 + #[serde(default)] 41 + pub(crate) blobs: BlobsConfig, 42 + #[serde(default)] 43 + pub(crate) oauth: OAuthConfig, 44 + #[serde(default)] 45 + pub(crate) iroh: IrohConfig, 46 + } 47 + 48 + #[derive(Debug, thiserror::Error)] 49 + pub enum ConfigError { 50 + #[error("failed to read config file: {0}")] 51 + Io(#[from] std::io::Error), 52 + #[error("failed to parse config file: {0}")] 53 + Parse(#[from] toml::de::Error), 54 + #[error("invalid configuration: missing required field '{field}'")] 55 + MissingField { field: &'static str }, 56 + #[error("invalid configuration: {0}")] 57 + Invalid(String), 58 + } 59 + 60 + /// Apply `EZPDS_*` environment variable overrides to a `RawConfig`. 61 + /// 62 + /// Receives the environment as a map so this function stays pure (no `std::env` access). 63 + pub(crate) fn apply_env_overrides( 64 + raw: &mut RawConfig, 65 + env: &HashMap<String, String>, 66 + ) -> Result<(), ConfigError> { 67 + if let Some(v) = env.get("EZPDS_BIND_ADDRESS") { 68 + raw.bind_address = Some(v.clone()); 69 + } 70 + if let Some(v) = env.get("EZPDS_PORT") { 71 + raw.port = Some(v.parse::<u16>().map_err(|_| { 72 + ConfigError::Invalid(format!("EZPDS_PORT is not a valid port number: '{v}'")) 73 + })?); 74 + } 75 + if let Some(v) = env.get("EZPDS_DATA_DIR") { 76 + raw.data_dir = Some(v.clone()); 77 + } 78 + if let Some(v) = env.get("EZPDS_DATABASE_URL") { 79 + raw.database_url = Some(v.clone()); 80 + } 81 + if let Some(v) = env.get("EZPDS_PUBLIC_URL") { 82 + raw.public_url = Some(v.clone()); 83 + } 84 + Ok(()) 85 + } 86 + 87 + /// Validate a `RawConfig` and build a `Config`, applying defaults for optional fields. 88 + pub(crate) fn validate_and_build(raw: RawConfig) -> Result<Config, ConfigError> { 89 + let bind_address = raw.bind_address.unwrap_or_else(|| "0.0.0.0".to_string()); 90 + let port = raw.port.unwrap_or(8080); 91 + let data_dir: PathBuf = raw 92 + .data_dir 93 + .ok_or(ConfigError::MissingField { field: "data_dir" })? 94 + .into(); 95 + let database_url = raw 96 + .database_url 97 + .unwrap_or_else(|| data_dir.join("relay.db").to_string_lossy().into_owned()); 98 + let public_url = raw.public_url.ok_or(ConfigError::MissingField { 99 + field: "public_url", 100 + })?; 101 + 102 + Ok(Config { 103 + bind_address, 104 + port, 105 + data_dir, 106 + database_url, 107 + public_url, 108 + blobs: raw.blobs, 109 + oauth: raw.oauth, 110 + iroh: raw.iroh, 111 + }) 112 + } 113 + 114 + #[cfg(test)] 115 + mod tests { 116 + use super::*; 117 + 118 + fn minimal_raw() -> RawConfig { 119 + RawConfig { 120 + data_dir: Some("/var/pds".to_string()), 121 + public_url: Some("https://pds.example.com".to_string()), 122 + ..Default::default() 123 + } 124 + } 125 + 126 + #[test] 127 + fn parses_minimal_toml() { 128 + let toml = r#" 129 + data_dir = "/var/pds" 130 + public_url = "https://pds.example.com" 131 + "#; 132 + let raw: RawConfig = toml::from_str(toml).unwrap(); 133 + let config = validate_and_build(raw).unwrap(); 134 + 135 + assert_eq!(config.bind_address, "0.0.0.0"); 136 + assert_eq!(config.port, 8080); 137 + assert_eq!(config.data_dir, PathBuf::from("/var/pds")); 138 + assert_eq!(config.database_url, "/var/pds/relay.db"); 139 + assert_eq!(config.public_url, "https://pds.example.com"); 140 + } 141 + 142 + #[test] 143 + fn parses_full_toml() { 144 + let toml = r#" 145 + bind_address = "127.0.0.1" 146 + port = 3000 147 + data_dir = "/data" 148 + database_url = "sqlite:///data/custom.db" 149 + public_url = "https://pds.example.com" 150 + "#; 151 + let raw: RawConfig = toml::from_str(toml).unwrap(); 152 + let config = validate_and_build(raw).unwrap(); 153 + 154 + assert_eq!(config.bind_address, "127.0.0.1"); 155 + assert_eq!(config.port, 3000); 156 + assert_eq!(config.data_dir, PathBuf::from("/data")); 157 + assert_eq!(config.database_url, "sqlite:///data/custom.db"); 158 + } 159 + 160 + #[test] 161 + fn parses_stub_sections() { 162 + let toml = r#" 163 + data_dir = "/var/pds" 164 + public_url = "https://pds.example.com" 165 + 166 + [blobs] 167 + 168 + [oauth] 169 + 170 + [iroh] 171 + "#; 172 + let raw: RawConfig = toml::from_str(toml).unwrap(); 173 + let config = validate_and_build(raw).unwrap(); 174 + 175 + assert_eq!(config.public_url, "https://pds.example.com"); 176 + } 177 + 178 + #[test] 179 + fn database_url_defaults_to_data_dir() { 180 + let config = validate_and_build(minimal_raw()).unwrap(); 181 + assert_eq!(config.database_url, "/var/pds/relay.db"); 182 + } 183 + 184 + #[test] 185 + fn env_override_port() { 186 + let mut raw = minimal_raw(); 187 + let env = HashMap::from([("EZPDS_PORT".to_string(), "9090".to_string())]); 188 + apply_env_overrides(&mut raw, &env).unwrap(); 189 + let config = validate_and_build(raw).unwrap(); 190 + 191 + assert_eq!(config.port, 9090); 192 + } 193 + 194 + #[test] 195 + fn env_override_all_fields() { 196 + let mut raw = RawConfig::default(); 197 + let env = HashMap::from([ 198 + ("EZPDS_BIND_ADDRESS".to_string(), "127.0.0.1".to_string()), 199 + ("EZPDS_PORT".to_string(), "4000".to_string()), 200 + ("EZPDS_DATA_DIR".to_string(), "/tmp/pds".to_string()), 201 + ( 202 + "EZPDS_DATABASE_URL".to_string(), 203 + "sqlite:///tmp/relay.db".to_string(), 204 + ), 205 + ( 206 + "EZPDS_PUBLIC_URL".to_string(), 207 + "https://pds.test".to_string(), 208 + ), 209 + ]); 210 + apply_env_overrides(&mut raw, &env).unwrap(); 211 + let config = validate_and_build(raw).unwrap(); 212 + 213 + assert_eq!(config.bind_address, "127.0.0.1"); 214 + assert_eq!(config.port, 4000); 215 + assert_eq!(config.data_dir, PathBuf::from("/tmp/pds")); 216 + assert_eq!(config.database_url, "sqlite:///tmp/relay.db"); 217 + assert_eq!(config.public_url, "https://pds.test"); 218 + } 219 + 220 + #[test] 221 + fn env_override_invalid_port_returns_error() { 222 + let mut raw = minimal_raw(); 223 + let env = HashMap::from([("EZPDS_PORT".to_string(), "not_a_port".to_string())]); 224 + let err = apply_env_overrides(&mut raw, &env).unwrap_err(); 225 + 226 + assert!(matches!(err, ConfigError::Invalid(_))); 227 + assert!(err.to_string().contains("EZPDS_PORT")); 228 + } 229 + 230 + #[test] 231 + fn missing_data_dir_returns_error() { 232 + let raw = RawConfig { 233 + public_url: Some("https://pds.example.com".to_string()), 234 + ..Default::default() 235 + }; 236 + let err = validate_and_build(raw).unwrap_err(); 237 + 238 + assert!(matches!( 239 + err, 240 + ConfigError::MissingField { field: "data_dir" } 241 + )); 242 + } 243 + 244 + #[test] 245 + fn missing_public_url_returns_error() { 246 + let raw = RawConfig { 247 + data_dir: Some("/var/pds".to_string()), 248 + ..Default::default() 249 + }; 250 + let err = validate_and_build(raw).unwrap_err(); 251 + 252 + assert!(matches!( 253 + err, 254 + ConfigError::MissingField { 255 + field: "public_url" 256 + } 257 + )); 258 + } 259 + }
+86
crates/common/src/config_loader.rs
··· 1 + // pattern: Imperative Shell 2 + 3 + use std::collections::HashMap; 4 + use std::path::Path; 5 + 6 + use crate::config::{apply_env_overrides, validate_and_build, Config, ConfigError, RawConfig}; 7 + 8 + /// Load [`Config`] from a TOML file with an explicit environment map. 9 + /// 10 + /// Prefer [`load_config`] for production use. This variant is `pub(crate)` so 11 + /// tests can pass a controlled environment without leaking real `EZPDS_*` vars. 12 + pub(crate) fn load_config_with_env( 13 + path: &Path, 14 + env: &HashMap<String, String>, 15 + ) -> Result<Config, ConfigError> { 16 + let contents = std::fs::read_to_string(path)?; 17 + let mut raw: RawConfig = toml::from_str(&contents)?; 18 + apply_env_overrides(&mut raw, env)?; 19 + validate_and_build(raw) 20 + } 21 + 22 + /// Load [`Config`] from a TOML file, applying `EZPDS_*` environment variable overrides. 23 + pub fn load_config(path: &Path) -> Result<Config, ConfigError> { 24 + let env: HashMap<String, String> = std::env::vars().collect(); 25 + load_config_with_env(path, &env) 26 + } 27 + 28 + #[cfg(test)] 29 + mod tests { 30 + use super::*; 31 + use std::io::Write; 32 + 33 + fn empty_env() -> HashMap<String, String> { 34 + HashMap::new() 35 + } 36 + 37 + #[test] 38 + fn loads_config_from_file() { 39 + let mut tmp = tempfile::NamedTempFile::new().unwrap(); 40 + writeln!( 41 + tmp, 42 + r#"data_dir = "/var/pds" 43 + public_url = "https://pds.example.com""# 44 + ) 45 + .unwrap(); 46 + 47 + let config = load_config_with_env(tmp.path(), &empty_env()).unwrap(); 48 + 49 + assert_eq!(config.public_url, "https://pds.example.com"); 50 + assert_eq!(config.bind_address, "0.0.0.0"); 51 + assert_eq!(config.port, 8080); 52 + } 53 + 54 + #[test] 55 + fn env_overrides_applied_from_file() { 56 + let mut tmp = tempfile::NamedTempFile::new().unwrap(); 57 + writeln!( 58 + tmp, 59 + r#"data_dir = "/var/pds" 60 + public_url = "https://pds.example.com""# 61 + ) 62 + .unwrap(); 63 + let env = HashMap::from([("EZPDS_PORT".to_string(), "9999".to_string())]); 64 + 65 + let config = load_config_with_env(tmp.path(), &env).unwrap(); 66 + 67 + assert_eq!(config.port, 9999); 68 + } 69 + 70 + #[test] 71 + fn returns_error_for_missing_file() { 72 + let result = load_config_with_env(Path::new("/nonexistent/relay.toml"), &empty_env()); 73 + 74 + assert!(matches!(result, Err(ConfigError::Io(_)))); 75 + } 76 + 77 + #[test] 78 + fn returns_error_for_invalid_toml() { 79 + let mut tmp = tempfile::NamedTempFile::new().unwrap(); 80 + writeln!(tmp, "not valid toml = [[[").unwrap(); 81 + 82 + let result = load_config_with_env(tmp.path(), &empty_env()); 83 + 84 + assert!(matches!(result, Err(ConfigError::Parse(_)))); 85 + } 86 + }
+6
crates/common/src/lib.rs
··· 1 1 // common: shared types, error envelope, config parsing. 2 + 3 + mod config; 4 + mod config_loader; 5 + 6 + pub use config::{BlobsConfig, Config, ConfigError, IrohConfig, OAuthConfig}; 7 + pub use config_loader::load_config;
+7
crates/relay/Cargo.toml
··· 9 9 [[bin]] 10 10 name = "relay" 11 11 path = "src/main.rs" 12 + 13 + [dependencies] 14 + common = { workspace = true } 15 + clap = { workspace = true } 16 + anyhow = { workspace = true } 17 + tracing = { workspace = true } 18 + tracing-subscriber = { workspace = true }
+39 -1
crates/relay/src/main.rs
··· 1 + // pattern: Imperative Shell 2 + 3 + use anyhow::Context; 4 + use clap::Parser; 5 + use std::path::PathBuf; 6 + 7 + #[derive(Parser)] 8 + #[command(name = "relay", about = "ezpds relay server")] 9 + struct Cli { 10 + /// Path to relay.toml config file (env: EZPDS_CONFIG) 11 + #[arg(long, env = "EZPDS_CONFIG")] 12 + config: Option<PathBuf>, 13 + } 14 + 1 15 fn main() { 2 - println!("relay starting"); 16 + if let Err(err) = run() { 17 + eprintln!("error: {err:#}"); 18 + std::process::exit(1); 19 + } 20 + } 21 + 22 + fn run() -> anyhow::Result<()> { 23 + tracing_subscriber::fmt() 24 + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 25 + .init(); 26 + 27 + let cli = Cli::parse(); 28 + let config_path = cli.config.unwrap_or_else(|| PathBuf::from("relay.toml")); 29 + 30 + let config = common::load_config(&config_path) 31 + .with_context(|| format!("failed to load config from {}", config_path.display()))?; 32 + 33 + tracing::info!( 34 + bind_address = %config.bind_address, 35 + port = config.port, 36 + public_url = %config.public_url, 37 + "relay starting" 38 + ); 39 + 40 + Ok(()) 3 41 }

History

2 rounds 0 comments
sign up or login to add to the discussion
2 commits
expand
feat(MM-69): configuration system — relay.toml parsing
fix(MM-69): address PR review — UTF-8 safety, error context, pure core
expand 0 comments
pull request successfully merged
malpercio.dev submitted #0
1 commit
expand
feat(MM-69): configuration system — relay.toml parsing
expand 0 comments