An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(MM-70): error types and shared API envelope

Add ApiError + ErrorCode to the common crate with IntoResponse for Axum.
Serializes to { "error": { "code": "...", "message": "...", "details": {} } }.
Wave 0–2 error codes only; #[non_exhaustive] for future expansion.

authored by

Malpercio and committed by
Tangled
124f71e9 55739f94

+510
+355
Cargo.lock
··· 68 68 checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 69 69 70 70 [[package]] 71 + name = "async-trait" 72 + version = "0.1.89" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 75 + dependencies = [ 76 + "proc-macro2", 77 + "quote", 78 + "syn", 79 + ] 80 + 81 + [[package]] 82 + name = "atomic-waker" 83 + version = "1.1.2" 84 + source = "registry+https://github.com/rust-lang/crates.io-index" 85 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 86 + 87 + [[package]] 88 + name = "axum" 89 + version = "0.7.9" 90 + source = "registry+https://github.com/rust-lang/crates.io-index" 91 + checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" 92 + dependencies = [ 93 + "async-trait", 94 + "axum-core", 95 + "bytes", 96 + "futures-util", 97 + "http", 98 + "http-body", 99 + "http-body-util", 100 + "hyper", 101 + "hyper-util", 102 + "itoa", 103 + "matchit", 104 + "memchr", 105 + "mime", 106 + "percent-encoding", 107 + "pin-project-lite", 108 + "rustversion", 109 + "serde", 110 + "serde_json", 111 + "serde_path_to_error", 112 + "serde_urlencoded", 113 + "sync_wrapper", 114 + "tokio", 115 + "tower", 116 + "tower-layer", 117 + "tower-service", 118 + "tracing", 119 + ] 120 + 121 + [[package]] 122 + name = "axum-core" 123 + version = "0.4.5" 124 + source = "registry+https://github.com/rust-lang/crates.io-index" 125 + checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" 126 + dependencies = [ 127 + "async-trait", 128 + "bytes", 129 + "futures-util", 130 + "http", 131 + "http-body", 132 + "http-body-util", 133 + "mime", 134 + "pin-project-lite", 135 + "rustversion", 136 + "sync_wrapper", 137 + "tower-layer", 138 + "tower-service", 139 + "tracing", 140 + ] 141 + 142 + [[package]] 71 143 name = "bitflags" 72 144 version = "2.11.0" 73 145 source = "registry+https://github.com/rust-lang/crates.io-index" 74 146 checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" 147 + 148 + [[package]] 149 + name = "bytes" 150 + version = "1.11.1" 151 + source = "registry+https://github.com/rust-lang/crates.io-index" 152 + checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 75 153 76 154 [[package]] 77 155 name = "cfg-if" ··· 129 207 name = "common" 130 208 version = "0.1.0" 131 209 dependencies = [ 210 + "axum", 132 211 "serde", 212 + "serde_json", 133 213 "tempfile", 134 214 "thiserror", 135 215 "toml", ··· 168 248 checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 169 249 170 250 [[package]] 251 + name = "form_urlencoded" 252 + version = "1.2.2" 253 + source = "registry+https://github.com/rust-lang/crates.io-index" 254 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 255 + dependencies = [ 256 + "percent-encoding", 257 + ] 258 + 259 + [[package]] 260 + name = "futures-channel" 261 + version = "0.3.32" 262 + source = "registry+https://github.com/rust-lang/crates.io-index" 263 + checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" 264 + dependencies = [ 265 + "futures-core", 266 + ] 267 + 268 + [[package]] 269 + name = "futures-core" 270 + version = "0.3.32" 271 + source = "registry+https://github.com/rust-lang/crates.io-index" 272 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 273 + 274 + [[package]] 275 + name = "futures-task" 276 + version = "0.3.32" 277 + source = "registry+https://github.com/rust-lang/crates.io-index" 278 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 279 + 280 + [[package]] 281 + name = "futures-util" 282 + version = "0.3.32" 283 + source = "registry+https://github.com/rust-lang/crates.io-index" 284 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 285 + dependencies = [ 286 + "futures-core", 287 + "futures-task", 288 + "pin-project-lite", 289 + "slab", 290 + ] 291 + 292 + [[package]] 171 293 name = "getrandom" 172 294 version = "0.4.2" 173 295 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 202 324 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 203 325 204 326 [[package]] 327 + name = "http" 328 + version = "1.4.0" 329 + source = "registry+https://github.com/rust-lang/crates.io-index" 330 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 331 + dependencies = [ 332 + "bytes", 333 + "itoa", 334 + ] 335 + 336 + [[package]] 337 + name = "http-body" 338 + version = "1.0.1" 339 + source = "registry+https://github.com/rust-lang/crates.io-index" 340 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 341 + dependencies = [ 342 + "bytes", 343 + "http", 344 + ] 345 + 346 + [[package]] 347 + name = "http-body-util" 348 + version = "0.1.3" 349 + source = "registry+https://github.com/rust-lang/crates.io-index" 350 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 351 + dependencies = [ 352 + "bytes", 353 + "futures-core", 354 + "http", 355 + "http-body", 356 + "pin-project-lite", 357 + ] 358 + 359 + [[package]] 360 + name = "httparse" 361 + version = "1.10.1" 362 + source = "registry+https://github.com/rust-lang/crates.io-index" 363 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 364 + 365 + [[package]] 366 + name = "httpdate" 367 + version = "1.0.3" 368 + source = "registry+https://github.com/rust-lang/crates.io-index" 369 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 370 + 371 + [[package]] 372 + name = "hyper" 373 + version = "1.8.1" 374 + source = "registry+https://github.com/rust-lang/crates.io-index" 375 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 376 + dependencies = [ 377 + "atomic-waker", 378 + "bytes", 379 + "futures-channel", 380 + "futures-core", 381 + "http", 382 + "http-body", 383 + "httparse", 384 + "httpdate", 385 + "itoa", 386 + "pin-project-lite", 387 + "pin-utils", 388 + "smallvec", 389 + "tokio", 390 + ] 391 + 392 + [[package]] 393 + name = "hyper-util" 394 + version = "0.1.20" 395 + source = "registry+https://github.com/rust-lang/crates.io-index" 396 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 397 + dependencies = [ 398 + "bytes", 399 + "http", 400 + "http-body", 401 + "hyper", 402 + "pin-project-lite", 403 + "tokio", 404 + "tower-service", 405 + ] 406 + 407 + [[package]] 205 408 name = "id-arena" 206 409 version = "2.3.0" 207 410 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 271 474 ] 272 475 273 476 [[package]] 477 + name = "matchit" 478 + version = "0.7.3" 479 + source = "registry+https://github.com/rust-lang/crates.io-index" 480 + checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 481 + 482 + [[package]] 274 483 name = "memchr" 275 484 version = "2.8.0" 276 485 source = "registry+https://github.com/rust-lang/crates.io-index" 277 486 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 278 487 279 488 [[package]] 489 + name = "mime" 490 + version = "0.3.17" 491 + source = "registry+https://github.com/rust-lang/crates.io-index" 492 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 493 + 494 + [[package]] 495 + name = "mio" 496 + version = "1.1.1" 497 + source = "registry+https://github.com/rust-lang/crates.io-index" 498 + checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 499 + dependencies = [ 500 + "libc", 501 + "wasi", 502 + "windows-sys", 503 + ] 504 + 505 + [[package]] 280 506 name = "nu-ansi-term" 281 507 version = "0.50.3" 282 508 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 298 524 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 299 525 300 526 [[package]] 527 + name = "percent-encoding" 528 + version = "2.3.2" 529 + source = "registry+https://github.com/rust-lang/crates.io-index" 530 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 531 + 532 + [[package]] 301 533 name = "pin-project-lite" 302 534 version = "0.2.17" 303 535 source = "registry+https://github.com/rust-lang/crates.io-index" 304 536 checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 537 + 538 + [[package]] 539 + name = "pin-utils" 540 + version = "0.1.0" 541 + source = "registry+https://github.com/rust-lang/crates.io-index" 542 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 305 543 306 544 [[package]] 307 545 name = "prettyplease" ··· 383 621 ] 384 622 385 623 [[package]] 624 + name = "rustversion" 625 + version = "1.0.22" 626 + source = "registry+https://github.com/rust-lang/crates.io-index" 627 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 628 + 629 + [[package]] 630 + name = "ryu" 631 + version = "1.0.23" 632 + source = "registry+https://github.com/rust-lang/crates.io-index" 633 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 634 + 635 + [[package]] 386 636 name = "semver" 387 637 version = "1.0.27" 388 638 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 432 682 ] 433 683 434 684 [[package]] 685 + name = "serde_path_to_error" 686 + version = "0.1.20" 687 + source = "registry+https://github.com/rust-lang/crates.io-index" 688 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 689 + dependencies = [ 690 + "itoa", 691 + "serde", 692 + "serde_core", 693 + ] 694 + 695 + [[package]] 435 696 name = "serde_spanned" 436 697 version = "0.6.9" 437 698 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 441 702 ] 442 703 443 704 [[package]] 705 + name = "serde_urlencoded" 706 + version = "0.7.1" 707 + source = "registry+https://github.com/rust-lang/crates.io-index" 708 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 709 + dependencies = [ 710 + "form_urlencoded", 711 + "itoa", 712 + "ryu", 713 + "serde", 714 + ] 715 + 716 + [[package]] 444 717 name = "sharded-slab" 445 718 version = "0.1.7" 446 719 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 450 723 ] 451 724 452 725 [[package]] 726 + name = "slab" 727 + version = "0.4.12" 728 + source = "registry+https://github.com/rust-lang/crates.io-index" 729 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 730 + 731 + [[package]] 453 732 name = "smallvec" 454 733 version = "1.15.1" 455 734 source = "registry+https://github.com/rust-lang/crates.io-index" 456 735 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 736 + 737 + [[package]] 738 + name = "socket2" 739 + version = "0.6.3" 740 + source = "registry+https://github.com/rust-lang/crates.io-index" 741 + checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" 742 + dependencies = [ 743 + "libc", 744 + "windows-sys", 745 + ] 457 746 458 747 [[package]] 459 748 name = "strsim" ··· 473 762 ] 474 763 475 764 [[package]] 765 + name = "sync_wrapper" 766 + version = "1.0.2" 767 + source = "registry+https://github.com/rust-lang/crates.io-index" 768 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 769 + 770 + [[package]] 476 771 name = "tempfile" 477 772 version = "3.26.0" 478 773 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 515 810 ] 516 811 517 812 [[package]] 813 + name = "tokio" 814 + version = "1.50.0" 815 + source = "registry+https://github.com/rust-lang/crates.io-index" 816 + checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" 817 + dependencies = [ 818 + "libc", 819 + "mio", 820 + "pin-project-lite", 821 + "socket2", 822 + "tokio-macros", 823 + "windows-sys", 824 + ] 825 + 826 + [[package]] 827 + name = "tokio-macros" 828 + version = "2.6.1" 829 + source = "registry+https://github.com/rust-lang/crates.io-index" 830 + checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" 831 + dependencies = [ 832 + "proc-macro2", 833 + "quote", 834 + "syn", 835 + ] 836 + 837 + [[package]] 518 838 name = "toml" 519 839 version = "0.8.23" 520 840 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 556 876 checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 557 877 558 878 [[package]] 879 + name = "tower" 880 + version = "0.5.3" 881 + source = "registry+https://github.com/rust-lang/crates.io-index" 882 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 883 + dependencies = [ 884 + "futures-core", 885 + "futures-util", 886 + "pin-project-lite", 887 + "sync_wrapper", 888 + "tokio", 889 + "tower-layer", 890 + "tower-service", 891 + "tracing", 892 + ] 893 + 894 + [[package]] 895 + name = "tower-layer" 896 + version = "0.3.3" 897 + source = "registry+https://github.com/rust-lang/crates.io-index" 898 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 899 + 900 + [[package]] 901 + name = "tower-service" 902 + version = "0.3.3" 903 + source = "registry+https://github.com/rust-lang/crates.io-index" 904 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 905 + 906 + [[package]] 559 907 name = "tracing" 560 908 version = "0.1.44" 561 909 source = "registry+https://github.com/rust-lang/crates.io-index" 562 910 checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 563 911 dependencies = [ 912 + "log", 564 913 "pin-project-lite", 565 914 "tracing-attributes", 566 915 "tracing-core", ··· 639 988 version = "0.1.1" 640 989 source = "registry+https://github.com/rust-lang/crates.io-index" 641 990 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 991 + 992 + [[package]] 993 + name = "wasi" 994 + version = "0.11.1+wasi-snapshot-preview1" 995 + source = "registry+https://github.com/rust-lang/crates.io-index" 996 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 642 997 643 998 [[package]] 644 999 name = "wasip2"
+2
crates/common/Cargo.toml
··· 7 7 # common: shared types, error envelope, config parsing. 8 8 9 9 [dependencies] 10 + axum = { workspace = true } 10 11 serde = { workspace = true } 12 + serde_json = { workspace = true } 11 13 toml = { workspace = true } 12 14 thiserror = { workspace = true } 13 15
+151
crates/common/src/error.rs
··· 1 + // pattern: Functional Core 2 + 3 + use axum::{ 4 + http::StatusCode, 5 + response::{IntoResponse, Response}, 6 + Json, 7 + }; 8 + use serde::{Deserialize, Serialize}; 9 + use serde_json::Value; 10 + 11 + /// Error codes for the provisioning API. 12 + /// 13 + /// Serialized as SCREAMING_SNAKE_CASE strings in the JSON envelope. 14 + /// `#[non_exhaustive]` prevents external crates from writing exhaustive match 15 + /// arms — new variants can be added in future waves without breaking callers. 16 + #[non_exhaustive] 17 + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 18 + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 19 + pub enum ErrorCode { 20 + // Wave 0–2 initial set 21 + InvalidClaim, 22 + Unauthorized, 23 + TokenExpired, 24 + Forbidden, 25 + NotFound, 26 + WeakPassword, 27 + RateLimited, 28 + ExportInProgress, 29 + } 30 + 31 + impl ErrorCode { 32 + /// Maps each error code to its canonical HTTP status code. 33 + pub fn status_code(&self) -> StatusCode { 34 + match self { 35 + ErrorCode::InvalidClaim => StatusCode::BAD_REQUEST, 36 + ErrorCode::Unauthorized => StatusCode::UNAUTHORIZED, 37 + ErrorCode::TokenExpired => StatusCode::UNAUTHORIZED, 38 + ErrorCode::Forbidden => StatusCode::FORBIDDEN, 39 + ErrorCode::NotFound => StatusCode::NOT_FOUND, 40 + ErrorCode::WeakPassword => StatusCode::UNPROCESSABLE_ENTITY, 41 + ErrorCode::RateLimited => StatusCode::TOO_MANY_REQUESTS, 42 + ErrorCode::ExportInProgress => StatusCode::SERVICE_UNAVAILABLE, 43 + } 44 + } 45 + } 46 + 47 + /// Provisioning API error, serialized as the standard error envelope: 48 + /// 49 + /// ```json 50 + /// { "error": { "code": "NOT_FOUND", "message": "...", "details": {} } } 51 + /// ``` 52 + /// 53 + /// Implements `IntoResponse` so it can be returned directly from Axum handlers. 54 + #[derive(Debug, Serialize)] 55 + pub struct ApiError { 56 + pub code: ErrorCode, 57 + pub message: String, 58 + #[serde(skip_serializing_if = "Option::is_none")] 59 + pub details: Option<Value>, 60 + } 61 + 62 + impl ApiError { 63 + pub fn new(code: ErrorCode, message: impl Into<String>) -> Self { 64 + Self { 65 + code, 66 + message: message.into(), 67 + details: None, 68 + } 69 + } 70 + 71 + pub fn with_details(mut self, details: Value) -> Self { 72 + self.details = Some(details); 73 + self 74 + } 75 + } 76 + 77 + /// Wraps `ApiError` in the `{ "error": ... }` envelope for serialization. 78 + #[derive(Serialize)] 79 + struct ApiErrorEnvelope { 80 + error: ApiError, 81 + } 82 + 83 + impl IntoResponse for ApiError { 84 + fn into_response(self) -> Response { 85 + let status = self.code.status_code(); 86 + (status, Json(ApiErrorEnvelope { error: self })).into_response() 87 + } 88 + } 89 + 90 + #[cfg(test)] 91 + mod tests { 92 + use super::*; 93 + use serde_json::json; 94 + 95 + #[test] 96 + fn serializes_to_error_envelope() { 97 + let err = ApiError::new(ErrorCode::NotFound, "resource not found"); 98 + let actual = serde_json::to_value(ApiErrorEnvelope { error: err }).unwrap(); 99 + assert_eq!( 100 + actual, 101 + json!({ 102 + "error": { 103 + "code": "NOT_FOUND", 104 + "message": "resource not found" 105 + } 106 + }) 107 + ); 108 + } 109 + 110 + #[test] 111 + fn serializes_with_details() { 112 + let err = ApiError::new(ErrorCode::InvalidClaim, "validation failed") 113 + .with_details(json!({ "field": "email" })); 114 + let actual = serde_json::to_value(ApiErrorEnvelope { error: err }).unwrap(); 115 + assert_eq!( 116 + actual, 117 + json!({ 118 + "error": { 119 + "code": "INVALID_CLAIM", 120 + "message": "validation failed", 121 + "details": { "field": "email" } 122 + } 123 + }) 124 + ); 125 + } 126 + 127 + #[test] 128 + fn omits_details_when_absent() { 129 + let err = ApiError::new(ErrorCode::Forbidden, "access denied"); 130 + let actual = serde_json::to_value(ApiErrorEnvelope { error: err }).unwrap(); 131 + // `details` key must not appear at all 132 + assert!(!actual["error"].as_object().unwrap().contains_key("details")); 133 + } 134 + 135 + #[test] 136 + fn status_code_mapping() { 137 + let cases = [ 138 + (ErrorCode::InvalidClaim, StatusCode::BAD_REQUEST), 139 + (ErrorCode::Unauthorized, StatusCode::UNAUTHORIZED), 140 + (ErrorCode::TokenExpired, StatusCode::UNAUTHORIZED), 141 + (ErrorCode::Forbidden, StatusCode::FORBIDDEN), 142 + (ErrorCode::NotFound, StatusCode::NOT_FOUND), 143 + (ErrorCode::WeakPassword, StatusCode::UNPROCESSABLE_ENTITY), 144 + (ErrorCode::RateLimited, StatusCode::TOO_MANY_REQUESTS), 145 + (ErrorCode::ExportInProgress, StatusCode::SERVICE_UNAVAILABLE), 146 + ]; 147 + for (code, expected) in cases { 148 + assert_eq!(code.status_code(), expected, "wrong status for {code:?}"); 149 + } 150 + } 151 + }
+2
crates/common/src/lib.rs
··· 2 2 3 3 mod config; 4 4 mod config_loader; 5 + mod error; 5 6 6 7 pub use config::{BlobsConfig, Config, ConfigError, IrohConfig, OAuthConfig}; 7 8 pub use config_loader::load_config; 9 + pub use error::{ApiError, ErrorCode};