From 8fd9e7d30bb4e80d9c96c2ca475ac53119707d4c Mon Sep 17 00:00:00 2001 From: Enrico Piovesan Date: Sat, 11 Jul 2026 21:13:31 -0600 Subject: [PATCH] Extract doc-approval-core-rs shared HTTP/SSE crate for GTK and CLI linux-gtk and cli-rust now depend on a workspace crate for send_command, session listing, and SSE events, mirroring traverse-core-rs from #59. Co-authored-by: Cursor --- AGENTS.md | 2 +- README.md | 2 +- apps/doc-approval/{linux-gtk => }/Cargo.lock | 497 ++++- apps/doc-approval/Cargo.toml | 3 + apps/doc-approval/cli-rust/Cargo.lock | 1817 ----------------- apps/doc-approval/cli-rust/Cargo.toml | 5 +- apps/doc-approval/cli-rust/README.md | 61 +- apps/doc-approval/cli-rust/src/client.rs | 181 +- .../cli-rust/src/commands/health.rs | 19 +- .../cli-rust/src/commands/submit.rs | 126 +- apps/doc-approval/cli-rust/src/lib.rs | 4 +- .../cli-rust/tests/integration_tests.rs | 38 +- .../doc-approval-core-rs/Cargo.toml | 18 + .../doc-approval-core-rs/src/client.rs | 250 +++ .../doc-approval-core-rs/src/discovery.rs | 53 + .../doc-approval-core-rs/src/lib.rs | 20 + .../doc-approval-core-rs/src/sessions.rs | 72 + .../doc-approval-core-rs/src/sse.rs | 104 + .../doc-approval-core-rs/src/state.rs | 88 + .../tests/client_tests.rs | 119 ++ .../doc-approval-core-rs/tests/sse_tests.rs | 26 + apps/doc-approval/linux-gtk/Cargo.toml | 3 +- apps/doc-approval/linux-gtk/README.md | 58 +- apps/doc-approval/linux-gtk/src/client.rs | 198 +- apps/doc-approval/linux-gtk/src/lib.rs | 4 +- .../linux-gtk/src/ui/main_window.rs | 168 +- .../linux-gtk/tests/client_tests.rs | 50 +- scripts/ci/repository_checks.sh | 2 + 28 files changed, 1509 insertions(+), 2479 deletions(-) rename apps/doc-approval/{linux-gtk => }/Cargo.lock (81%) create mode 100644 apps/doc-approval/Cargo.toml delete mode 100644 apps/doc-approval/cli-rust/Cargo.lock create mode 100644 apps/doc-approval/doc-approval-core-rs/Cargo.toml create mode 100644 apps/doc-approval/doc-approval-core-rs/src/client.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/src/discovery.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/src/lib.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/src/sessions.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/src/sse.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/src/state.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/tests/client_tests.rs create mode 100644 apps/doc-approval/doc-approval-core-rs/tests/sse_tests.rs diff --git a/AGENTS.md b/AGENTS.md index e66cf15..58e59bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ gh project item-list 2 --owner traverse-framework --format json --limit 300 \ - **Phase 3 embedded runtime** ([#109](https://github.com/traverse-framework/reference-apps/issues/109)–[#118](https://github.com/traverse-framework/reference-apps/issues/118)) — see `docs/embedded-runtime-plan.md`; blocked on a **consumable platform embedder SDK** (Traverse [#553](https://github.com/traverse-framework/Traverse/issues/553) closed via [#578](https://github.com/traverse-framework/Traverse/pull/578) with manifest `execution_mode` only — no web/Swift/etc. embedder package yet). Traverse [#615](https://github.com/traverse-framework/Traverse/pull/615) added wasm32 core build only — not a platform embedder package. - **doc-approval multi-capability showcase** ([#111](https://github.com/traverse-framework/reference-apps/issues/111), [#112](https://github.com/traverse-framework/reference-apps/issues/112)) — blocked on Traverse [#538](https://github.com/traverse-framework/Traverse/issues/538) / [#555](https://github.com/traverse-framework/Traverse/issues/555) (`extract` / `recommend` agents) -Ready: [#110](https://github.com/traverse-framework/reference-apps/issues/110) traverse-starter.pipeline (Traverse [#620](https://github.com/traverse-framework/Traverse/issues/620) closed); [#73](https://github.com/traverse-framework/reference-apps/issues/73) shared Rust client package ([#58](https://github.com/traverse-framework/reference-apps/issues/58)/[#59](https://github.com/traverse-framework/reference-apps/issues/59) Done; [#72](https://github.com/traverse-framework/reference-apps/issues/72) in this PR). +Ready: [#110](https://github.com/traverse-framework/reference-apps/issues/110) traverse-starter.pipeline; [#73](https://github.com/traverse-framework/reference-apps/issues/73) doc-approval-core-rs ([#58](https://github.com/traverse-framework/reference-apps/issues/58)/[#59](https://github.com/traverse-framework/reference-apps/issues/59)/[#72](https://github.com/traverse-framework/reference-apps/issues/72) Done). Update this section when a PR changes platform status (see PR template checklist). diff --git a/README.md b/README.md index 1f4cf78..ead4d5d 100644 --- a/README.md +++ b/README.md @@ -175,4 +175,4 @@ Active blockers on [Project 2](https://github.com/orgs/traverse-framework/projec - **doc-approval multi-capability showcase** ([#111](https://github.com/traverse-framework/reference-apps/issues/111), [#112](https://github.com/traverse-framework/reference-apps/issues/112)) — blocked on Traverse [#538](https://github.com/traverse-framework/Traverse/issues/538) / [#555](https://github.com/traverse-framework/Traverse/issues/555) (`extract` / `recommend` agents). - **traverse-starter.pipeline showcase** ([#110](https://github.com/traverse-framework/reference-apps/issues/110)) — Ready (Traverse [#620](https://github.com/traverse-framework/Traverse/issues/620) closed; pipeline on Traverse `main`). -Ready on Project 2: [#73](https://github.com/traverse-framework/reference-apps/issues/73)/[#110](https://github.com/traverse-framework/reference-apps/issues/110) — shared Rust client + pipeline showcase ([#58](https://github.com/traverse-framework/reference-apps/issues/58)/[#59](https://github.com/traverse-framework/reference-apps/issues/59) Done; [#72](https://github.com/traverse-framework/reference-apps/issues/72) in progress). +Ready on Project 2: [#110](https://github.com/traverse-framework/reference-apps/issues/110) pipeline showcase ([#58](https://github.com/traverse-framework/reference-apps/issues/58)/[#59](https://github.com/traverse-framework/reference-apps/issues/59)/[#72](https://github.com/traverse-framework/reference-apps/issues/72) Done; [#73](https://github.com/traverse-framework/reference-apps/issues/73) in progress). diff --git a/apps/doc-approval/linux-gtk/Cargo.lock b/apps/doc-approval/Cargo.lock similarity index 81% rename from apps/doc-approval/linux-gtk/Cargo.lock rename to apps/doc-approval/Cargo.lock index abe30a8..d62c2ed 100644 --- a/apps/doc-approval/linux-gtk/Cargo.lock +++ b/apps/doc-approval/Cargo.lock @@ -11,6 +11,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -21,6 +71,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "assert_cmd" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -45,6 +110,17 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "bstr" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -53,9 +129,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cairo-rs" @@ -82,9 +158,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.66" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", "shlex", @@ -120,7 +196,72 @@ checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures", - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", ] [[package]] @@ -150,6 +291,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "directories" version = "5.0.1" @@ -182,14 +329,43 @@ dependencies = [ "syn", ] +[[package]] +name = "doc-approval-cli" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "clap", + "colored 2.2.0", + "doc-approval-core-rs", + "futures-util", + "mockito", + "predicates", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "doc-approval-core-rs" +version = "0.1.0" +dependencies = [ + "futures-util", + "reqwest", + "serde", + "serde_json", + "tokio", + "wiremock", +] + [[package]] name = "doc-approval-gtk" version = "0.1.0" dependencies = [ "directories", + "doc-approval-core-rs", + "futures-util", "gtk4", "libadwaita", - "reqwest", "serde", "serde_json", "tokio", @@ -218,6 +394,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -391,6 +576,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -400,8 +597,8 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "rand_core", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasm-bindgen", ] @@ -858,6 +1055,12 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.18" @@ -933,6 +1136,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.33" @@ -947,9 +1159,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" @@ -971,6 +1183,46 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mockito" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0" +dependencies = [ + "assert-json-diff", + "bytes", + "colored 3.1.1", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "log", + "pin-project-lite", + "rand 0.9.5", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.17.0" @@ -987,6 +1239,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "option-ext" version = "0.2.0" @@ -1017,6 +1275,29 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1044,6 +1325,45 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1091,7 +1411,7 @@ dependencies = [ "bytes", "getrandom 0.4.3", "lru-slab", - "rand", + "rand 0.10.2", "rand_pcg", "ring", "rustc-hash", @@ -1127,12 +1447,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.2" @@ -1141,7 +1477,26 @@ checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", "getrandom 0.4.3", - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -1156,7 +1511,16 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", ] [[package]] @@ -1172,9 +1536,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -1184,9 +1548,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -1208,6 +1572,7 @@ dependencies = [ "base64", "bytes", "futures-core", + "futures-util", "http", "http-body", "http-body-util", @@ -1227,12 +1592,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", ] @@ -1303,9 +1670,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -1313,6 +1680,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.28" @@ -1389,6 +1762,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "slab" version = "0.4.12" @@ -1417,6 +1796,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -1473,6 +1858,12 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "1.0.69" @@ -1525,9 +1916,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -1547,6 +1938,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "socket2", "tokio-macros", @@ -1738,12 +2130,27 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version-compare" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.1" @@ -1759,6 +2166,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.126" @@ -1814,6 +2230,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.103" @@ -2038,6 +2467,12 @@ dependencies = [ "url", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.3" @@ -2067,6 +2502,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.8" diff --git a/apps/doc-approval/Cargo.toml b/apps/doc-approval/Cargo.toml new file mode 100644 index 0000000..5ec1e73 --- /dev/null +++ b/apps/doc-approval/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["doc-approval-core-rs", "linux-gtk", "cli-rust"] diff --git a/apps/doc-approval/cli-rust/Cargo.lock b/apps/doc-approval/cli-rust/Cargo.lock deleted file mode 100644 index 6fa8bad..0000000 --- a/apps/doc-approval/cli-rust/Cargo.lock +++ /dev/null @@ -1,1817 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "assert-json-diff" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "assert_cmd" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" - -[[package]] -name = "bstr" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" -dependencies = [ - "memchr", - "regex-automata", - "serde_core", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytes" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" - -[[package]] -name = "cc" -version = "1.2.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "clap" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "doc-approval-cli" -version = "0.1.0" -dependencies = [ - "assert_cmd", - "clap", - "colored", - "mockito", - "predicates", - "reqwest", - "serde", - "serde_json", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "memchr" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" - -[[package]] -name = "mio" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "mockito" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0" -dependencies = [ - "assert-json-diff", - "bytes", - "colored", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "log", - "pin-project-lite", - "rand 0.9.4", - "regex", - "serde_json", - "serde_urlencoded", - "similar", - "tokio", -] - -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" -dependencies = [ - "bytes", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core 0.10.1", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustls" -version = "0.23.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "similar" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/apps/doc-approval/cli-rust/Cargo.toml b/apps/doc-approval/cli-rust/Cargo.toml index 0eacf76..4820b94 100644 --- a/apps/doc-approval/cli-rust/Cargo.toml +++ b/apps/doc-approval/cli-rust/Cargo.toml @@ -10,9 +10,11 @@ name = "doc-approval" path = "src/main.rs" [dependencies] +doc-approval-core-rs = { path = "../doc-approval-core-rs" } clap = { version = "4", features = ["derive", "env"] } colored = "2" -reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls"] } +futures-util = "0.3" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -20,3 +22,4 @@ serde_json = "1" mockito = "1" assert_cmd = "2" predicates = "3" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } diff --git a/apps/doc-approval/cli-rust/README.md b/apps/doc-approval/cli-rust/README.md index 4a716f7..c8248f1 100644 --- a/apps/doc-approval/cli-rust/README.md +++ b/apps/doc-approval/cli-rust/README.md @@ -1,61 +1,10 @@ # doc-approval (Rust CLI) -Terminal client for the `doc-approval` reference app. Phase 1 uses HTTP polling against the public Traverse HTTP/JSON API (spec 033) — same flow as `web-react` and the native GUI clients. - -## Prerequisites - -- **Rust 1.78+** via [rustup](https://rustup.rs/) -- **Traverse runtime** running locally: - -```bash -git clone https://github.com/traverse-framework/Traverse.git /tmp/traverse -cd /tmp/traverse && git checkout v0.6.0 -cargo run -p traverse-cli -- serve -``` - -## Build and install +Terminal client for the `doc-approval` reference app. Uses shared `doc-approval-core-rs` for HTTP command dispatch and SSE app-state events. ```bash -cd apps/doc-approval/cli-rust -cargo build --release -cargo test -cargo install --path . # installs `doc-approval` binary to ~/.cargo/bin +cd apps/doc-approval +cargo build -p doc-approval-cli --release +cargo test -p doc-approval-cli -p doc-approval-core-rs +cargo install --path cli-rust ``` - -## Usage - -```bash -# Human-readable output -doc-approval submit --file contract.txt -doc-approval submit --text "Agreement between..." - -# Machine-readable JSON -doc-approval submit --file contract.txt --json - -# Health check -doc-approval health -doc-approval health --json - -# Override runtime URL and workspace -doc-approval submit --base-url http://192.168.1.42:8787 --workspace my-workspace --text "..." -``` - -Environment variables: - -| Variable | Default | -|---|---| -| `TRAVERSE_BASE_URL` | `http://127.0.0.1:8787` | -| `TRAVERSE_WORKSPACE` | `local-default` | - -## Architecture - -| File | Role | -|---|---| -| `client.rs` | Blocking `reqwest` HTTP client | -| `commands/submit.rs` | Execute + poll loop | -| `commands/health.rs` | `/healthz` check | -| `output.rs` | Human (colored) and JSON formatters | - -## Phase 2 (not implemented) - -SSE streaming and `doc-approval queue` when Traverse ships [#525](https://github.com/traverse-framework/Traverse/issues/525)–[#527](https://github.com/traverse-framework/Traverse/issues/527). diff --git a/apps/doc-approval/cli-rust/src/client.rs b/apps/doc-approval/cli-rust/src/client.rs index 8978eee..97f3704 100644 --- a/apps/doc-approval/cli-rust/src/client.rs +++ b/apps/doc-approval/cli-rust/src/client.rs @@ -1,176 +1,5 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct DocApprovalOutput { - #[serde(rename = "docType")] - pub doc_type: String, - pub parties: Vec, - pub amounts: Vec, - pub confidence: f64, - pub recommendation: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct TraceEvent { - pub event_type: String, - pub timestamp: String, - pub data: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ExecutionPollResult { - pub execution_id: String, - pub status: String, - pub output: Option, - pub error: Option, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum TraverseClientError { - Http(u16), - Decode, - Request(String), -} - -impl std::fmt::Display for TraverseClientError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Http(code) => write!(f, "HTTP {code}"), - Self::Decode => write!(f, "decode failed"), - Self::Request(msg) => write!(f, "request failed: {msg}"), - } - } -} - -impl std::error::Error for TraverseClientError {} - -pub struct TraverseClient { - http: reqwest::blocking::Client, -} - -impl Default for TraverseClient { - fn default() -> Self { - Self::new() - } -} - -impl TraverseClient { - pub fn new() -> Self { - Self { - http: reqwest::blocking::Client::new(), - } - } - - pub fn with_client(http: reqwest::blocking::Client) -> Self { - Self { http } - } - - pub fn check_health(&self, base_url: &str) -> Result { - let url = format!("{}/healthz", normalize_base_url(base_url)); - let response = self - .http - .get(url) - .send() - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - Ok(response.status().is_success()) - } - - pub fn execute( - &self, - base_url: &str, - workspace_id: &str, - capability: &str, - input: &Value, - ) -> Result { - let url = format!( - "{}/v1/workspaces/{}/execute", - normalize_base_url(base_url), - workspace_id - ); - let body = serde_json::json!({ "capability": capability, "input": input }); - let response = self - .http - .post(url) - .json(&body) - .send() - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - if !response.status().is_success() { - return Err(TraverseClientError::Http(response.status().as_u16())); - } - let payload: ExecuteResponse = response - .json() - .map_err(|_| TraverseClientError::Decode)?; - Ok(payload.execution_id) - } - - pub fn poll_execution( - &self, - base_url: &str, - workspace_id: &str, - execution_id: &str, - ) -> Result { - let url = format!( - "{}/v1/workspaces/{}/executions/{}", - normalize_base_url(base_url), - workspace_id, - execution_id - ); - let response = self - .http - .get(url) - .send() - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - if !response.status().is_success() { - return Err(TraverseClientError::Http(response.status().as_u16())); - } - let payload: ExecutionPollResponse = response - .json() - .map_err(|_| TraverseClientError::Decode)?; - Ok(ExecutionPollResult { - execution_id: execution_id.to_string(), - status: payload.status, - output: payload.output, - error: payload.error, - }) - } - - pub fn fetch_trace( - &self, - base_url: &str, - workspace_id: &str, - execution_id: &str, - ) -> Result, TraverseClientError> { - let url = format!( - "{}/v1/workspaces/{}/traces/{}", - normalize_base_url(base_url), - workspace_id, - execution_id - ); - let response = self - .http - .get(url) - .send() - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - if !response.status().is_success() { - return Err(TraverseClientError::Http(response.status().as_u16())); - } - response.json().map_err(|_| TraverseClientError::Decode) - } -} - -fn normalize_base_url(base_url: &str) -> String { - base_url.trim().trim_end_matches('/').to_string() -} - -#[derive(Debug, Deserialize)] -struct ExecuteResponse { - execution_id: String, -} - -#[derive(Debug, Deserialize)] -struct ExecutionPollResponse { - status: String, - output: Option, - error: Option, -} +//! Re-exports the shared doc-approval-core-rs client for the CLI shell. +pub use doc_approval_core_rs::{ + DocApprovalClient as TraverseClient, DocApprovalClientError as TraverseClientError, + DocApprovalOutput, TraceEvent, DEFAULT_APP_ID, +}; diff --git a/apps/doc-approval/cli-rust/src/commands/health.rs b/apps/doc-approval/cli-rust/src/commands/health.rs index ede1ae1..0f369bd 100644 --- a/apps/doc-approval/cli-rust/src/commands/health.rs +++ b/apps/doc-approval/cli-rust/src/commands/health.rs @@ -2,8 +2,21 @@ use crate::client::TraverseClient; use crate::output::print_health; pub fn execute(base_url: &str, json: bool) -> i32 { - let client = TraverseClient::new(); - let online = client.check_health(base_url).unwrap_or(false); + let runtime = match tokio::runtime::Runtime::new() { + Ok(rt) => rt, + Err(err) => { + eprintln!("runtime failed: {err}"); + return 1; + } + }; + let online = runtime.block_on(async { + let client = TraverseClient::new(); + client.check_health(base_url).await.unwrap_or(false) + }); print_health(base_url, online, json); - if online { 0 } else { 1 } + if online { + 0 + } else { + 1 + } } diff --git a/apps/doc-approval/cli-rust/src/commands/submit.rs b/apps/doc-approval/cli-rust/src/commands/submit.rs index 73fed61..c7e931c 100644 --- a/apps/doc-approval/cli-rust/src/commands/submit.rs +++ b/apps/doc-approval/cli-rust/src/commands/submit.rs @@ -1,59 +1,101 @@ -use std::{thread, time::Duration}; +use futures_util::StreamExt; +use doc_approval_core_rs::{AppState, DEFAULT_APP_ID}; use crate::client::{DocApprovalOutput, TraverseClient}; use crate::output::{print_submit_result, SubmitResultJson}; -use crate::CAPABILITY_ID; pub fn execute(base_url: &str, workspace: &str, document: &str, json: bool) -> i32 { - let client = TraverseClient::new(); - let execution_id = match client.execute( - base_url, - workspace, - CAPABILITY_ID, - &serde_json::json!({ "document": document }), - ) { - Ok(id) => id, + let runtime = match tokio::runtime::Runtime::new() { + Ok(rt) => rt, Err(err) => { - eprintln!("execute failed: {err}"); + eprintln!("runtime failed: {err}"); return 1; } }; - loop { - match client.poll_execution(base_url, workspace, &execution_id) { - Ok(result) if result.status == "succeeded" => { - let trace = client - .fetch_trace(base_url, workspace, &execution_id) - .unwrap_or_default(); - let output = result.output.unwrap_or(DocApprovalOutput { - doc_type: String::new(), - parties: vec![], - amounts: vec![], - confidence: 0.0, - recommendation: String::new(), - }); - print_submit_result( - &SubmitResultJson { - execution_id, - output, - trace, - }, - json, - ); - return 0; - } - Ok(result) if result.status == "failed" => { - eprintln!( - "execution failed: {}", - result.error.unwrap_or_else(|| "unknown error".to_string()) - ); + runtime.block_on(async { + let client = TraverseClient::new(); + let accepted = match client.submit_document(base_url, workspace, document).await { + Ok(accepted) => accepted, + Err(err) => { + eprintln!("submit failed: {err}"); return 1; } - Ok(_) => thread::sleep(Duration::from_secs(1)), + }; + + let mut stream = match client + .subscribe_events(base_url, workspace, DEFAULT_APP_ID) + .await + { + Ok(stream) => stream, Err(err) => { - eprintln!("poll failed: {err}"); + eprintln!("subscribe failed: {err}"); return 1; } + }; + + while let Some(item) = stream.next().await { + match item { + Ok(event) if event.event_type == "heartbeat" => continue, + Ok(event) => { + if let Some(sid) = event.session_id.as_deref() { + if sid != accepted.session_id { + continue; + } + } + if event.event_type == "error" + || matches!(event.state.as_ref(), Some(AppState::Error)) + { + eprintln!( + "execution failed: {}", + event + .error_message + .unwrap_or_else(|| "unknown error".to_string()) + ); + return 1; + } + let terminal = matches!(event.state.as_ref(), Some(AppState::Results)) + || event.event_type == "capability_result"; + if !terminal { + continue; + } + let execution_id = event + .execution_id + .or(accepted.execution_id.clone()) + .unwrap_or_default(); + let output = event.output.unwrap_or(DocApprovalOutput { + doc_type: String::new(), + parties: vec![], + amounts: vec![], + confidence: 0.0, + recommendation: String::new(), + }); + let trace = if execution_id.is_empty() { + Vec::new() + } else { + client + .fetch_trace(base_url, workspace, &execution_id) + .await + .unwrap_or_default() + }; + print_submit_result( + &SubmitResultJson { + execution_id, + output, + trace, + }, + json, + ); + return 0; + } + Err(err) => { + eprintln!("event stream failed: {err}"); + return 1; + } + } } - } + + eprintln!("event stream ended before result"); + 1 + }) } diff --git a/apps/doc-approval/cli-rust/src/lib.rs b/apps/doc-approval/cli-rust/src/lib.rs index 9227c29..fb013c5 100644 --- a/apps/doc-approval/cli-rust/src/lib.rs +++ b/apps/doc-approval/cli-rust/src/lib.rs @@ -2,6 +2,4 @@ pub mod client; pub mod commands; pub mod output; -pub const CAPABILITY_ID: &str = "doc-approval.analyze"; -pub const DEFAULT_BASE_URL: &str = "http://127.0.0.1:8787"; -pub const DEFAULT_WORKSPACE: &str = "local-default"; +pub use doc_approval_core_rs::{DEFAULT_APP_ID, DEFAULT_BASE_URL, DEFAULT_WORKSPACE}; diff --git a/apps/doc-approval/cli-rust/tests/integration_tests.rs b/apps/doc-approval/cli-rust/tests/integration_tests.rs index 05f7486..20f80af 100644 --- a/apps/doc-approval/cli-rust/tests/integration_tests.rs +++ b/apps/doc-approval/cli-rust/tests/integration_tests.rs @@ -1,34 +1,44 @@ use doc_approval_cli::client::TraverseClient; use doc_approval_cli::commands; -#[test] -fn check_health_returns_true_on_200() { - let mut server = mockito::Server::new(); +#[tokio::test] +async fn check_health_returns_true_on_200() { + let mut server = mockito::Server::new_async().await; let _mock = server .mock("GET", "/healthz") .with_status(200) - .create(); + .create_async() + .await; let client = TraverseClient::new(); - assert!(client.check_health(&server.url()).unwrap()); + assert!(client.check_health(&server.url()).await.unwrap()); } #[test] fn submit_flow_succeeds_against_mock_server() { let mut server = mockito::Server::new(); - let _execute = server - .mock("POST", "/v1/workspaces/local-default/execute") - .with_status(200) - .with_body(r#"{"execution_id":"exec_abc"}"#) + let _submit = server + .mock( + "POST", + "/v1/workspaces/local-default/apps/doc-approval/commands", + ) + .with_status(202) + .with_header("content-type", "application/json") + .with_body( + r#"{"api_version":"v1","status":"accepted","workspace_id":"local-default","app_id":"doc-approval","session_id":"sess-1","command":"submit","state":"processing","execution_id":"exec_abc"}"#, + ) .create(); - let _poll = server - .mock("GET", "/v1/workspaces/local-default/executions/exec_abc") - .with_status(200) - .with_body( - r#"{"status":"succeeded","output":{"docType":"invoice","parties":["A"],"amounts":["$100"],"confidence":0.9,"recommendation":"approve"}}"#, + let sse_body = "event: capability_result\ndata: {\"state\":\"results\",\"session_id\":\"sess-1\",\"execution_id\":\"exec_abc\",\"output\":{\"docType\":\"nda\",\"parties\":[\"A\"],\"amounts\":[],\"confidence\":0.9,\"recommendation\":\"approve\"}}\n\n"; + let _events = server + .mock( + "GET", + "/v1/workspaces/local-default/apps/doc-approval/events", ) + .with_status(200) + .with_header("content-type", "text/event-stream") + .with_body(sse_body) .create(); let _trace = server diff --git a/apps/doc-approval/doc-approval-core-rs/Cargo.toml b/apps/doc-approval/doc-approval-core-rs/Cargo.toml new file mode 100644 index 0000000..f4dc683 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "doc-approval-core-rs" +version = "0.1.0" +edition = "2021" +description = "Shared Traverse HTTP/SSE client for doc-approval Rust shells" +license = "MIT" + +[dependencies] +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +futures-util = "0.3" +tokio = { version = "1", features = ["rt", "macros", "time"] } + +[dev-dependencies] +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +wiremock = "0.6" +serde_json = "1" diff --git a/apps/doc-approval/doc-approval-core-rs/src/client.rs b/apps/doc-approval/doc-approval-core-rs/src/client.rs new file mode 100644 index 0000000..f384524 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/src/client.rs @@ -0,0 +1,250 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use crate::sse; +use crate::state::StateEvent; +use crate::{list_sessions, SessionSummary, DEFAULT_APP_ID}; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct DocApprovalOutput { + #[serde(rename = "docType")] + pub doc_type: String, + pub parties: Vec, + pub amounts: Vec, + pub confidence: f64, + pub recommendation: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct TraceEvent { + pub event_type: String, + pub timestamp: String, + pub data: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CommandAccepted { + pub api_version: String, + pub status: String, + pub workspace_id: String, + pub app_id: String, + pub session_id: String, + pub command: String, + pub state: String, + pub execution_id: Option, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum DocApprovalClientError { + Http(u16), + Decode, + Request(String), +} + +impl std::fmt::Display for DocApprovalClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Http(code) => write!(f, "HTTP {code}"), + Self::Decode => write!(f, "decode failed"), + Self::Request(msg) => write!(f, "request failed: {msg}"), + } + } +} + +impl std::error::Error for DocApprovalClientError {} + +#[derive(Clone)] +pub struct DocApprovalClient { + http: reqwest::Client, +} + +impl Default for DocApprovalClient { + fn default() -> Self { + Self::new() + } +} + +impl DocApprovalClient { + pub fn new() -> Self { + Self { + http: reqwest::Client::new(), + } + } + + pub fn with_client(http: reqwest::Client) -> Self { + Self { http } + } + + pub async fn health_check(&self, base_url: &str) -> Result { + let url = format!("{}/healthz", normalize_base_url(base_url)); + let response = self + .http + .get(url) + .send() + .await + .map_err(|e| DocApprovalClientError::Request(e.to_string()))?; + Ok(response.status().is_success()) + } + + pub async fn check_health(&self, base_url: &str) -> Result { + self.health_check(base_url).await + } + + pub async fn send_command( + &self, + base_url: &str, + workspace_id: &str, + app_id: &str, + command: &str, + payload: &Value, + session_id: Option<&str>, + ) -> Result { + let url = format!( + "{}/v1/workspaces/{}/apps/{}/commands", + normalize_base_url(base_url), + workspace_id, + app_id + ); + let mut body = json!({ + "command": command, + "payload": payload, + }); + if let Some(session_id) = session_id { + body["session_id"] = Value::String(session_id.to_string()); + } + let response = self + .http + .post(url) + .json(&body) + .send() + .await + .map_err(|e| DocApprovalClientError::Request(e.to_string()))?; + if !(200..300).contains(&response.status().as_u16()) { + return Err(DocApprovalClientError::Http(response.status().as_u16())); + } + let payload: CommandAcceptedResponse = response + .json() + .await + .map_err(|_| DocApprovalClientError::Decode)?; + Ok(CommandAccepted { + api_version: payload.api_version, + status: payload.status, + workspace_id: payload.workspace_id, + app_id: payload.app_id, + session_id: payload.session_id, + command: payload.command, + state: payload.state, + execution_id: payload.execution_id, + }) + } + + /// Session-scoped command dispatch (issue DoD). + pub async fn send_command_for_session( + &self, + base_url: &str, + workspace_id: &str, + session_id: &str, + command: &str, + payload: &Value, + ) -> Result { + self.send_command( + base_url, + workspace_id, + DEFAULT_APP_ID, + command, + payload, + Some(session_id), + ) + .await + } + + pub async fn submit_document( + &self, + base_url: &str, + workspace_id: &str, + document: &str, + ) -> Result { + self.send_command( + base_url, + workspace_id, + DEFAULT_APP_ID, + "submit", + &json!({ "document": document }), + None, + ) + .await + } + + pub async fn list_sessions( + &self, + base_url: &str, + workspace_id: &str, + state: Option<&str>, + ) -> Result, DocApprovalClientError> { + list_sessions(&self.http, base_url, workspace_id, DEFAULT_APP_ID, state).await + } + + pub async fn fetch_trace( + &self, + base_url: &str, + workspace_id: &str, + execution_id: &str, + ) -> Result, DocApprovalClientError> { + let url = format!( + "{}/v1/workspaces/{}/traces/{}", + normalize_base_url(base_url), + workspace_id, + execution_id + ); + let response = self + .http + .get(url) + .send() + .await + .map_err(|e| DocApprovalClientError::Request(e.to_string()))?; + if !response.status().is_success() { + return Err(DocApprovalClientError::Http(response.status().as_u16())); + } + response + .json() + .await + .map_err(|_| DocApprovalClientError::Decode) + } + + pub fn app_events_url(base_url: &str, workspace_id: &str, app_id: &str) -> String { + format!( + "{}/v1/workspaces/{}/apps/{}/events", + normalize_base_url(base_url), + workspace_id, + app_id + ) + } + + pub async fn subscribe_events( + &self, + base_url: &str, + workspace_id: &str, + app_id: &str, + ) -> Result< + impl futures_util::Stream>, + DocApprovalClientError, + > { + sse::subscribe_events(&self.http, base_url, workspace_id, app_id).await + } +} + +pub fn normalize_base_url(base_url: &str) -> String { + base_url.trim().trim_end_matches('/').to_string() +} + +#[derive(Debug, Deserialize)] +struct CommandAcceptedResponse { + api_version: String, + status: String, + workspace_id: String, + app_id: String, + session_id: String, + command: String, + state: String, + execution_id: Option, +} diff --git a/apps/doc-approval/doc-approval-core-rs/src/discovery.rs b/apps/doc-approval/doc-approval-core-rs/src/discovery.rs new file mode 100644 index 0000000..41f0fd0 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/src/discovery.rs @@ -0,0 +1,53 @@ +use serde::Deserialize; +use std::fs; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ServerInfo { + pub base_url: String, + pub workspace_default: String, +} + +#[derive(Debug, Deserialize)] +struct ServerJson { + base_url: String, + #[serde(default = "default_workspace")] + workspace_default: String, +} + +fn default_workspace() -> String { + "local-default".to_string() +} + +pub struct ServerDiscovery; + +impl ServerDiscovery { + pub fn from_file(path: impl AsRef) -> Option { + let data = fs::read_to_string(path).ok()?; + let parsed: ServerJson = serde_json::from_str(&data).ok()?; + Some(ServerInfo { + base_url: parsed.base_url, + workspace_default: parsed.workspace_default, + }) + } + + /// Walk parents from `start` looking for `.traverse/server.json`. + pub fn discover(start: impl AsRef) -> Option { + let mut current = start.as_ref().to_path_buf(); + for _ in 0..8 { + let candidate = current.join(".traverse").join("server.json"); + if let Some(info) = Self::from_file(&candidate) { + return Some(info); + } + if !current.pop() { + break; + } + } + None + } + + pub fn discover_cwd() -> Option { + let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + Self::discover(cwd) + } +} diff --git a/apps/doc-approval/doc-approval-core-rs/src/lib.rs b/apps/doc-approval/doc-approval-core-rs/src/lib.rs new file mode 100644 index 0000000..0ffcb97 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/src/lib.rs @@ -0,0 +1,20 @@ +//! Shared Traverse runtime client for doc-approval Rust shells. +//! Platform-neutral: no GTK or terminal UI imports. + +mod client; +mod discovery; +mod sessions; +mod sse; +mod state; + +pub use client::{ + CommandAccepted, DocApprovalClient, DocApprovalClientError, DocApprovalOutput, TraceEvent, +}; +pub use discovery::{ServerDiscovery, ServerInfo}; +pub use sessions::{list_sessions, parse_sessions, SessionSummary}; +pub use sse::subscribe_events; +pub use state::{AppState, StateEvent}; + +pub const DEFAULT_APP_ID: &str = "doc-approval"; +pub const DEFAULT_BASE_URL: &str = "http://127.0.0.1:8787"; +pub const DEFAULT_WORKSPACE: &str = "local-default"; diff --git a/apps/doc-approval/doc-approval-core-rs/src/sessions.rs b/apps/doc-approval/doc-approval-core-rs/src/sessions.rs new file mode 100644 index 0000000..9ffc533 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/src/sessions.rs @@ -0,0 +1,72 @@ +use serde::Deserialize; +use serde_json::Value; + +use crate::client::{normalize_base_url, DocApprovalClientError}; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct SessionSummary { + pub session_id: String, + pub state: String, + pub title: Option, +} + +pub async fn list_sessions( + http: &reqwest::Client, + base_url: &str, + workspace_id: &str, + app_id: &str, + state: Option<&str>, +) -> Result, DocApprovalClientError> { + let mut url = format!( + "{}/v1/workspaces/{}/apps/{}/sessions", + normalize_base_url(base_url), + workspace_id, + app_id + ); + if let Some(state) = state { + url.push_str(&format!("?state={state}")); + } + let response = http + .get(&url) + .send() + .await + .map_err(|e| DocApprovalClientError::Request(e.to_string()))?; + if !response.status().is_success() { + return Err(DocApprovalClientError::Http(response.status().as_u16())); + } + let raw: Value = response + .json() + .await + .map_err(|_| DocApprovalClientError::Decode)?; + Ok(parse_sessions(&raw)) +} + +pub fn parse_sessions(raw: &Value) -> Vec { + let items = if let Some(arr) = raw.as_array() { + arr.clone() + } else if let Some(arr) = raw.get("sessions").and_then(|v| v.as_array()) { + arr.clone() + } else { + return Vec::new(); + }; + items + .into_iter() + .filter_map(|item| { + let session_id = item + .get("session_id") + .or_else(|| item.get("id")) + .and_then(|v| v.as_str())? + .to_string(); + let state = item.get("state")?.as_str()?.to_string(); + let title = item + .get("title") + .and_then(|v| v.as_str()) + .map(str::to_string); + Some(SessionSummary { + session_id, + state, + title, + }) + }) + .collect() +} diff --git a/apps/doc-approval/doc-approval-core-rs/src/sse.rs b/apps/doc-approval/doc-approval-core-rs/src/sse.rs new file mode 100644 index 0000000..5d2b146 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/src/sse.rs @@ -0,0 +1,104 @@ +use futures_util::{stream, StreamExt}; +use futures_util::stream::BoxStream; +use reqwest::Client; + +use crate::client::{DocApprovalClient, DocApprovalClientError}; +use crate::state::StateEvent; + +pub async fn subscribe_events( + http: &Client, + base_url: &str, + workspace_id: &str, + app_id: &str, +) -> Result>, DocApprovalClientError> +{ + let url = DocApprovalClient::app_events_url(base_url, workspace_id, app_id); + let response = http + .get(&url) + .header("Accept", "text/event-stream") + .send() + .await + .map_err(|e| DocApprovalClientError::Request(e.to_string()))?; + if !(200..300).contains(&response.status().as_u16()) { + return Err(DocApprovalClientError::Http(response.status().as_u16())); + } + + let byte_stream = response.bytes_stream(); + let event_stream = stream::unfold( + SseParser { + byte_stream, + buffer: String::new(), + }, + |mut parser| async move { + loop { + if let Some(event) = parser.pop_event() { + return Some((Ok(event), parser)); + } + match parser.byte_stream.next().await { + Some(Ok(chunk)) => { + parser.buffer.push_str(&String::from_utf8_lossy(&chunk)); + } + Some(Err(err)) => { + return Some(( + Err(DocApprovalClientError::Request(err.to_string())), + parser, + )); + } + None => return None, + } + } + }, + ); + + Ok(Box::pin(event_stream)) +} + +struct SseParser { + byte_stream: S, + buffer: String, +} + +impl SseParser { + fn pop_event(&mut self) -> Option { + let idx = self.buffer.find("\n\n")?; + let block = self.buffer[..idx].to_string(); + self.buffer = self.buffer[idx + 2..].to_string(); + parse_block(&block) + } +} + +fn parse_block(block: &str) -> Option { + let mut event_type = String::from("message"); + let mut data_lines: Vec = Vec::new(); + for line in block.lines() { + if line.starts_with(':') { + continue; + } + if let Some(rest) = line.strip_prefix("event:") { + event_type = rest.trim().to_string(); + } else if let Some(rest) = line.strip_prefix("data:") { + data_lines.push(rest.trim().to_string()); + } + } + if data_lines.is_empty() && event_type != "heartbeat" { + return None; + } + let data = data_lines.join("\n"); + StateEvent::from_sse(&event_type, &data) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_capability_result_block() { + let block = "event: capability_result\ndata: {\"state\":\"results\",\"session_id\":\"s1\",\"execution_id\":\"e1\",\"output\":{\"docType\":\"nda\",\"parties\":[],\"amounts\":[],\"confidence\":0.9,\"recommendation\":\"approve\"}}"; + let event = parse_block(block).expect("event"); + assert_eq!(event.event_type, "capability_result"); + assert_eq!( + event.output.as_ref().map(|o| o.doc_type.as_str()), + Some("nda") + ); + } +} diff --git a/apps/doc-approval/doc-approval-core-rs/src/state.rs b/apps/doc-approval/doc-approval-core-rs/src/state.rs new file mode 100644 index 0000000..e11373a --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/src/state.rs @@ -0,0 +1,88 @@ +use serde::Deserialize; +use serde_json::Value; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AppState { + Idle, + Processing, + Results, + Error, + Other(String), +} + +impl AppState { + pub fn from_runtime(name: &str) -> Self { + match name { + "idle" => Self::Idle, + "processing" => Self::Processing, + "results" => Self::Results, + "error" => Self::Error, + other => Self::Other(other.to_string()), + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Idle => "idle", + Self::Processing => "processing", + Self::Results => "results", + Self::Error => "error", + Self::Other(s) => s.as_str(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StateEvent { + pub event_type: String, + pub state: Option, + pub session_id: Option, + pub execution_id: Option, + pub output: Option, + pub error_message: Option, + pub raw: Value, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct EventPayload { + pub state: Option, + pub session_id: Option, + pub execution_id: Option, + pub output: Option, + pub error: Option, +} + +impl StateEvent { + pub fn from_sse(event_type: &str, data: &str) -> Option { + if event_type == "heartbeat" { + return Some(Self { + event_type: event_type.to_string(), + state: None, + session_id: None, + execution_id: None, + output: None, + error_message: None, + raw: Value::Null, + }); + } + let raw: Value = serde_json::from_str(data).ok()?; + let payload: EventPayload = serde_json::from_value(raw.clone()).ok()?; + let error_message = match payload.error { + Some(Value::String(s)) => Some(s), + Some(Value::Object(map)) => map + .get("message") + .and_then(|v| v.as_str()) + .map(str::to_string), + _ => None, + }; + Some(Self { + event_type: event_type.to_string(), + state: payload.state.as_deref().map(AppState::from_runtime), + session_id: payload.session_id, + execution_id: payload.execution_id, + output: payload.output, + error_message, + raw, + }) + } +} diff --git a/apps/doc-approval/doc-approval-core-rs/tests/client_tests.rs b/apps/doc-approval/doc-approval-core-rs/tests/client_tests.rs new file mode 100644 index 0000000..28613b9 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/tests/client_tests.rs @@ -0,0 +1,119 @@ +use doc_approval_core_rs::{DocApprovalClient, ServerDiscovery, StateEvent}; +use serde_json::json; +use wiremock::matchers::{method, path, query_param}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +#[tokio::test] +async fn health_check_ok() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/healthz")) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + let client = DocApprovalClient::new(); + assert!(client.health_check(&server.uri()).await.unwrap()); +} + +#[tokio::test] +async fn send_command_posts_to_apps_commands() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v1/workspaces/local-default/apps/doc-approval/commands")) + .respond_with(ResponseTemplate::new(202).set_body_json(json!({ + "api_version": "v1", + "status": "accepted", + "workspace_id": "local-default", + "app_id": "doc-approval", + "session_id": "sess-1", + "command": "submit", + "state": "processing", + "execution_id": "exec-1" + }))) + .mount(&server) + .await; + + let client = DocApprovalClient::new(); + let accepted = client + .submit_document(&server.uri(), "local-default", "hello") + .await + .unwrap(); + assert_eq!(accepted.session_id, "sess-1"); +} + +#[tokio::test] +async fn list_sessions_parses_pending_review() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/v1/workspaces/local-default/apps/doc-approval/sessions")) + .and(query_param("state", "pending_review")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "sessions": [{"session_id":"s1","state":"pending_review","title":"Contract"}] + }))) + .mount(&server) + .await; + + let client = DocApprovalClient::new(); + let sessions = client + .list_sessions(&server.uri(), "local-default", Some("pending_review")) + .await + .unwrap(); + assert_eq!(sessions.len(), 1); + assert_eq!(sessions[0].title.as_deref(), Some("Contract")); +} + +#[tokio::test] +async fn send_command_for_session_includes_session_id() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v1/workspaces/local-default/apps/doc-approval/commands")) + .respond_with(ResponseTemplate::new(202).set_body_json(json!({ + "api_version": "v1", + "status": "accepted", + "workspace_id": "local-default", + "app_id": "doc-approval", + "session_id": "sess-approve", + "command": "approve", + "state": "approved" + }))) + .mount(&server) + .await; + + let client = DocApprovalClient::new(); + let accepted = client + .send_command_for_session( + &server.uri(), + "local-default", + "sess-approve", + "approve", + &json!({}), + ) + .await + .unwrap(); + assert_eq!(accepted.command, "approve"); +} + +#[test] +fn server_discovery_reads_json() { + let mut path = std::env::temp_dir(); + path.push(format!("doc-approval-core-rs-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&path); + std::fs::create_dir_all(path.join(".traverse")).unwrap(); + std::fs::write( + path.join(".traverse/server.json"), + r#"{"base_url":"http://127.0.0.1:9999","workspace_default":"ws-a"}"#, + ) + .unwrap(); + let info = ServerDiscovery::from_file(path.join(".traverse/server.json")).unwrap(); + assert_eq!(info.base_url, "http://127.0.0.1:9999"); +} + +#[test] +fn state_event_from_sse() { + let event = StateEvent::from_sse( + "capability_result", + r#"{"state":"results","output":{"docType":"nda","parties":[],"amounts":[],"confidence":0.5,"recommendation":"review"}}"#, + ) + .unwrap(); + assert_eq!(event.output.unwrap().doc_type, "nda"); +} diff --git a/apps/doc-approval/doc-approval-core-rs/tests/sse_tests.rs b/apps/doc-approval/doc-approval-core-rs/tests/sse_tests.rs new file mode 100644 index 0000000..1a03c48 --- /dev/null +++ b/apps/doc-approval/doc-approval-core-rs/tests/sse_tests.rs @@ -0,0 +1,26 @@ +use doc_approval_core_rs::{parse_sessions, StateEvent}; +use serde_json::json; + +#[test] +fn parses_heartbeat() { + let event = StateEvent::from_sse("heartbeat", "").unwrap(); + assert_eq!(event.event_type, "heartbeat"); +} + +#[test] +fn parses_capability_result_payload() { + let event = StateEvent::from_sse( + "capability_result", + r#"{"state":"results","session_id":"s1","execution_id":"e1","output":{"docType":"nda","parties":["A"],"amounts":[],"confidence":0.9,"recommendation":"approve"}}"#, + ) + .unwrap(); + assert_eq!(event.output.unwrap().recommendation, "approve"); +} + +#[test] +fn parses_sessions_wrapper() { + let sessions = parse_sessions(&json!({ + "sessions": [{"session_id":"s1","state":"pending_review","title":"T"}] + })); + assert_eq!(sessions[0].session_id, "s1"); +} diff --git a/apps/doc-approval/linux-gtk/Cargo.toml b/apps/doc-approval/linux-gtk/Cargo.toml index 81d34c5..20be173 100644 --- a/apps/doc-approval/linux-gtk/Cargo.toml +++ b/apps/doc-approval/linux-gtk/Cargo.toml @@ -13,10 +13,11 @@ name = "doc-approval-gtk" path = "src/main.rs" [dependencies] +doc-approval-core-rs = { path = "../doc-approval-core-rs" } gtk4 = "0.9" libadwaita = { version = "0.7", features = ["v1_5"] } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } +futures-util = "0.3" serde = { version = "1", features = ["derive"] } serde_json = "1" directories = "5" diff --git a/apps/doc-approval/linux-gtk/README.md b/apps/doc-approval/linux-gtk/README.md index 3387ef6..abbc751 100644 --- a/apps/doc-approval/linux-gtk/README.md +++ b/apps/doc-approval/linux-gtk/README.md @@ -1,58 +1,12 @@ # doc-approval (Linux GTK4) -Native Linux submitter client for the `doc-approval` reference app. Phase 1 uses HTTP polling against the public Traverse HTTP/JSON API (spec 033) — same flow as `web-react` and the other native clients. +Native Linux client for the `doc-approval` reference app. Uses shared `doc-approval-core-rs` for HTTP command dispatch and SSE app-state events. -## Prerequisites - -- **Ubuntu 22.04+** (or equivalent distro with GTK4 + libadwaita) -- **Rust 1.78+** via [rustup](https://rustup.rs/) -- **System dependencies**: - -```bash -sudo apt install libgtk-4-dev libadwaita-1-dev -``` - -- **Traverse runtime** running locally: - -```bash -git clone https://github.com/traverse-framework/Traverse.git /tmp/traverse -cd /tmp/traverse && git checkout v0.6.0 -cargo run -p traverse-cli -- serve -``` - -## Runtime URL configuration - -Open **Preferences** (gear icon in the header bar) and set: - -- **Runtime URL** — default `http://127.0.0.1:8787` -- **Workspace** — default `local-default` - -Values persist to `~/.config/doc-approval/settings.json`. - -## Build and run +Requires GTK4 + libadwaita system packages. ```bash -cd apps/doc-approval/linux-gtk -cargo build -cargo test -cargo run +cd apps/doc-approval +cargo build -p doc-approval-gtk +cargo test -p doc-approval-core-rs +cargo run -p doc-approval-gtk ``` - -## Architecture - -| File | Role | -|---|---| -| `client.rs` | `reqwest` HTTP client (execute / poll / trace / healthz) | -| `execution_state.rs` | `Arc>` polling state machine | -| `ui/main_window.rs` | `AdwApplicationWindow` — document input, analysis output, header health strip | -| `ui/preferences.rs` | Runtime URL + workspace dialog | - -The UI renders runtime-provided output fields only (`docType`, `parties`, `amounts`, `confidence`, `recommendation`). - -## Phase 2 (not implemented) - -SSE subscription when Traverse ships [#525](https://github.com/traverse-framework/Traverse/issues/525)–[#527](https://github.com/traverse-framework/Traverse/issues/527). - -## Design language - -Follow [docs/design-language.md](../../../docs/design-language.md). diff --git a/apps/doc-approval/linux-gtk/src/client.rs b/apps/doc-approval/linux-gtk/src/client.rs index 3713c15..dbbc016 100644 --- a/apps/doc-approval/linux-gtk/src/client.rs +++ b/apps/doc-approval/linux-gtk/src/client.rs @@ -1,193 +1,5 @@ -use serde::Deserialize; -use serde_json::Value; - -#[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct DocApprovalOutput { - #[serde(rename = "docType")] - pub doc_type: String, - pub parties: Vec, - pub amounts: Vec, - pub confidence: f64, - pub recommendation: String, -} - -#[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct TraceEvent { - pub event_type: String, - pub timestamp: String, - pub data: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ExecutionPollResult { - pub execution_id: String, - pub status: String, - pub output: Option, - pub error: Option, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum TraverseClientError { - Http(u16), - Decode, - Request(String), -} - -impl std::fmt::Display for TraverseClientError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Http(code) => write!(f, "HTTP {code}"), - Self::Decode => write!(f, "decode failed"), - Self::Request(msg) => write!(f, "request failed: {msg}"), - } - } -} - -impl std::error::Error for TraverseClientError {} - -pub struct TraverseClient { - http: reqwest::Client, -} - -impl Clone for TraverseClient { - fn clone(&self) -> Self { - Self { - http: self.http.clone(), - } - } -} - -impl Default for TraverseClient { - fn default() -> Self { - Self::new() - } -} - -impl TraverseClient { - pub fn new() -> Self { - Self { - http: reqwest::Client::new(), - } - } - - pub fn with_client(http: reqwest::Client) -> Self { - Self { http } - } - - pub async fn check_health(&self, base_url: &str) -> Result { - let url = format!("{}/healthz", normalize_base_url(base_url)); - let response = self - .http - .get(url) - .send() - .await - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - Ok(response.status().is_success()) - } - - pub async fn execute( - &self, - base_url: &str, - workspace_id: &str, - capability: &str, - input: &serde_json::Value, - ) -> Result { - let url = format!( - "{}/v1/workspaces/{}/execute", - normalize_base_url(base_url), - workspace_id - ); - let body = serde_json::json!({ "capability": capability, "input": input }); - let response = self - .http - .post(url) - .json(&body) - .send() - .await - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - if !response.status().is_success() { - return Err(TraverseClientError::Http(response.status().as_u16())); - } - let payload: ExecuteResponse = response - .json() - .await - .map_err(|_| TraverseClientError::Decode)?; - Ok(payload.execution_id) - } - - pub async fn poll_execution( - &self, - base_url: &str, - workspace_id: &str, - execution_id: &str, - ) -> Result { - let url = format!( - "{}/v1/workspaces/{}/executions/{}", - normalize_base_url(base_url), - workspace_id, - execution_id - ); - let response = self - .http - .get(url) - .send() - .await - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - if !response.status().is_success() { - return Err(TraverseClientError::Http(response.status().as_u16())); - } - let payload: ExecutionPollResponse = response - .json() - .await - .map_err(|_| TraverseClientError::Decode)?; - Ok(ExecutionPollResult { - execution_id: execution_id.to_string(), - status: payload.status, - output: payload.output, - error: payload.error, - }) - } - - pub async fn fetch_trace( - &self, - base_url: &str, - workspace_id: &str, - execution_id: &str, - ) -> Result, TraverseClientError> { - let url = format!( - "{}/v1/workspaces/{}/traces/{}", - normalize_base_url(base_url), - workspace_id, - execution_id - ); - let response = self - .http - .get(url) - .send() - .await - .map_err(|e| TraverseClientError::Request(e.to_string()))?; - if !response.status().is_success() { - return Err(TraverseClientError::Http(response.status().as_u16())); - } - response - .json() - .await - .map_err(|_| TraverseClientError::Decode) - } -} - -fn normalize_base_url(base_url: &str) -> String { - base_url.trim().trim_end_matches('/').to_string() -} - -#[derive(Debug, Deserialize)] -struct ExecuteResponse { - execution_id: String, -} - -#[derive(Debug, Deserialize)] -struct ExecutionPollResponse { - status: String, - output: Option, - error: Option, -} +//! Re-exports the shared doc-approval-core-rs client for the GTK shell. +pub use doc_approval_core_rs::{ + DocApprovalClient as TraverseClient, DocApprovalClientError as TraverseClientError, + DocApprovalOutput, TraceEvent, DEFAULT_APP_ID, +}; diff --git a/apps/doc-approval/linux-gtk/src/lib.rs b/apps/doc-approval/linux-gtk/src/lib.rs index 5ff6964..db90c2f 100644 --- a/apps/doc-approval/linux-gtk/src/lib.rs +++ b/apps/doc-approval/linux-gtk/src/lib.rs @@ -3,6 +3,4 @@ pub mod execution_state; pub mod settings; pub mod ui; -pub const CAPABILITY_ID: &str = "doc-approval.analyze"; -pub const DEFAULT_BASE_URL: &str = "http://127.0.0.1:8787"; -pub const DEFAULT_WORKSPACE: &str = "local-default"; +pub use doc_approval_core_rs::{DEFAULT_APP_ID, DEFAULT_BASE_URL, DEFAULT_WORKSPACE}; diff --git a/apps/doc-approval/linux-gtk/src/ui/main_window.rs b/apps/doc-approval/linux-gtk/src/ui/main_window.rs index 9d1d083..ac6b905 100644 --- a/apps/doc-approval/linux-gtk/src/ui/main_window.rs +++ b/apps/doc-approval/linux-gtk/src/ui/main_window.rs @@ -1,17 +1,20 @@ +use futures_util::StreamExt; use gtk4::prelude::*; use gtk4::{ - Box as GtkBox, Button, Label, Orientation, ScrolledWindow, TextView, ToggleButton, + Box as GtkBox, Button, Label, Orientation, ScrolledWindow, TextView, TextViewBuffer, + ToggleButton, }; use libadwaita as adw; use std::cell::RefCell; use std::rc::Rc; use std::sync::{Arc, Mutex}; +use doc_approval_core_rs::AppState; -use crate::client::TraverseClient; +use crate::client::{DocApprovalOutput, TraverseClient, DEFAULT_APP_ID}; use crate::execution_state::{ExecutionPhase, ExecutionState, RuntimeStatus}; use crate::settings::{load_settings, save_settings, AppSettings}; use crate::ui::preferences::PreferencesDialog; -use crate::CAPABILITY_ID; + pub struct MainWindow { pub window: adw::ApplicationWindow, @@ -25,7 +28,7 @@ impl MainWindow { let window = adw::ApplicationWindow::builder() .application(app) - .title("doc-approval") + .title("Doc Approval") .default_width(900) .default_height(700) .build(); @@ -60,8 +63,13 @@ impl MainWindow { .build(); content.append(&document_scroll); + let document_count = Label::new(Some(&format!("0/{10_000}"))); + document_count.add_css_class("dim-label"); + document_count.set_halign(gtk4::Align::End); + content.append(&document_count); + let button_row = GtkBox::new(Orientation::Horizontal, 8); - let submit_button = Button::with_label("Analyze"); + let submit_button = Button::with_label("Analyze Document"); let reset_button = Button::with_label("Reset"); button_row.append(&submit_button); button_row.append(&reset_button); @@ -76,7 +84,7 @@ impl MainWindow { content.append(&Label::new(Some("Analysis Result"))); - let output_label = Label::new(Some("Submit a document above to start analysis.")); + let output_label = Label::new(Some("Submit a document above to start a workflow.")); output_label.set_wrap(true); output_label.set_xalign(0.0); output_label.add_css_class("dim-label"); @@ -119,18 +127,18 @@ impl MainWindow { match &state.phase { ExecutionPhase::Idle => { output_label.set_text(if online { - "Submit a document above to start analysis." + "Submit a document above to start a workflow." } else { "Connect to the Traverse runtime to see analysis output here." }); output_label.add_css_class("dim-label"); } ExecutionPhase::Loading => { - output_label.set_text("Starting analysis…"); + output_label.set_text("Starting execution…"); output_label.remove_css_class("dim-label"); } ExecutionPhase::Polling { execution_id } => { - output_label.set_text(&format!("Polling execution {execution_id}…")); + output_label.set_text(&format!("Waiting for analysis events ({execution_id})…")); output_label.remove_css_class("dim-label"); } ExecutionPhase::Failed { error } => { @@ -170,10 +178,16 @@ impl MainWindow { document_buffer.connect_changed({ let state = state.clone(); + let document_count = document_count.clone(); let refresh_ui = refresh_ui.clone(); move |buffer| { - let text = buffer.text(&buffer.start_iter(), &buffer.end_iter(), true); - state.lock().unwrap().document = text.to_string(); + let mut text = buffer.text(&buffer.start_iter(), &buffer.end_iter(), true); + if text.len() > 10_000 { + text.truncate(10_000); + buffer.set_text(&text); + } + state.lock().unwrap().document = text.clone(); + document_count.set_text(&format!("{}/{10_000}", text.len())); refresh_ui(); } }); @@ -207,25 +221,21 @@ impl MainWindow { let state = state.clone(); let refresh_ui = refresh_ui.clone(); async move { - match client - .execute( - &base_url, - &workspace, - CAPABILITY_ID, - &serde_json::json!({ "document": document }), - ) - .await - { - Ok(execution_id) => { + match client.submit_document(&base_url, &workspace, &document).await { + Ok(accepted) => { + let label = accepted + .execution_id + .clone() + .unwrap_or_else(|| accepted.session_id.clone()); state.lock().unwrap().phase = - ExecutionPhase::Polling { execution_id: execution_id.clone() }; + ExecutionPhase::Polling { execution_id: label }; refresh_ui(); - poll_until_terminal( + wait_for_result( &client, &state, &base_url, &workspace, - &execution_id, + &accepted.session_id, &refresh_ui, ) .await; @@ -243,11 +253,9 @@ impl MainWindow { reset_button.connect_clicked({ let state = state.clone(); - let document_buffer = document_buffer.clone(); let refresh_ui = refresh_ui.clone(); move |_| { state.lock().unwrap().reset(); - document_buffer.set_text(""); refresh_ui(); } }); @@ -331,50 +339,94 @@ async fn refresh_health( }); } -async fn poll_until_terminal( +async fn wait_for_result( client: &TraverseClient, state: &Arc>, base_url: &str, workspace: &str, - execution_id: &str, + session_id: &str, refresh_ui: &impl Fn(), ) { - loop { - match client - .poll_execution(base_url, workspace, execution_id) - .await - { - Ok(result) if result.status == "succeeded" => { - let trace = client - .fetch_trace(base_url, workspace, execution_id) - .await - .unwrap_or_default(); - let output = result.output.unwrap_or(crate::client::DocApprovalOutput { - doc_type: String::new(), - parties: vec![], - amounts: vec![], - confidence: 0.0, - recommendation: String::new(), - }); - state.lock().unwrap().phase = ExecutionPhase::Succeeded { output, trace }; - refresh_ui(); - return; + let mut stream = match client + .subscribe_events(base_url, workspace, DEFAULT_APP_ID) + .await + { + Ok(stream) => stream, + Err(err) => { + state.lock().unwrap().phase = ExecutionPhase::Failed { + error: err.to_string(), + }; + refresh_ui(); + return; + } + }; + + while let Some(item) = stream.next().await { + match item { + Ok(event) if event.event_type == "heartbeat" => continue, + Ok(event) => { + if let Some(sid) = event.session_id.as_deref() { + if sid != session_id { + continue; + } + } + if event.event_type == "error" + || matches!(event.state.as_ref(), Some(AppState::Error)) + { + state.lock().unwrap().phase = ExecutionPhase::Failed { + error: event + .error_message + .unwrap_or_else(|| "execution failed".to_string()), + }; + refresh_ui(); + return; + } + let terminal = matches!(event.state.as_ref(), Some(AppState::Results)) + || event.event_type == "capability_result"; + if terminal { + if let Some(output) = event.output { + let execution_id = event.execution_id.clone().unwrap_or_default(); + let trace = if execution_id.is_empty() { + Vec::new() + } else { + client + .fetch_trace(base_url, workspace, &execution_id) + .await + .unwrap_or_default() + }; + state.lock().unwrap().phase = + ExecutionPhase::Succeeded { output, trace }; + refresh_ui(); + return; + } + if matches!(event.state.as_ref(), Some(AppState::Results)) { + state.lock().unwrap().phase = ExecutionPhase::Succeeded { + output: DocApprovalOutput { + doc_type: String::new(), + parties: vec![], + amounts: vec![], + confidence: 0.0, + recommendation: String::new(), + }, + trace: Vec::new(), + }; + refresh_ui(); + return; + } + } } - Ok(result) if result.status == "failed" => { + Err(err) => { state.lock().unwrap().phase = ExecutionPhase::Failed { - error: result.error.unwrap_or_else(|| "execution failed".to_string()), + error: err.to_string(), }; refresh_ui(); return; } - Ok(_) => { - glib::timeout_future_seconds(1).await; - } - Err(err) => { - state.lock().unwrap().phase = ExecutionPhase::Failed { error: err.to_string() }; - refresh_ui(); - return; - } } } + + state.lock().unwrap().phase = ExecutionPhase::Failed { + error: "event stream ended before result".to_string(), + }; + refresh_ui(); } diff --git a/apps/doc-approval/linux-gtk/tests/client_tests.rs b/apps/doc-approval/linux-gtk/tests/client_tests.rs index 9e3965e..be86042 100644 --- a/apps/doc-approval/linux-gtk/tests/client_tests.rs +++ b/apps/doc-approval/linux-gtk/tests/client_tests.rs @@ -1,5 +1,4 @@ use doc_approval_gtk::client::TraverseClient; -use doc_approval_gtk::CAPABILITY_ID; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -17,50 +16,27 @@ async fn check_health_returns_true_on_200() { } #[tokio::test] -async fn execute_returns_execution_id() { +async fn submit_document_posts_command() { let server = MockServer::start().await; Mock::given(method("POST")) - .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + .and(path("/v1/workspaces/local-default/apps/doc-approval/commands")) + .respond_with(ResponseTemplate::new(202).set_body_json(serde_json::json!({ + "api_version": "v1", + "status": "accepted", + "workspace_id": "local-default", + "app_id": "doc-approval", + "session_id": "sess-1", + "command": "submit", + "state": "processing", "execution_id": "exec_abc" }))) .mount(&server) .await; let client = TraverseClient::new(); - let id = client - .execute( - &server.uri(), - "local-default", - CAPABILITY_ID, - &serde_json::json!({ "document": "contract" }), - ) + let accepted = client + .submit_document(&server.uri(), "local-default", "contract") .await .unwrap(); - assert_eq!(id, "exec_abc"); -} - -#[tokio::test] -async fn poll_execution_parses_output() { - let server = MockServer::start().await; - Mock::given(method("GET")) - .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ - "status": "succeeded", - "output": { - "docType": "invoice", - "parties": ["A", "B"], - "amounts": ["$100"], - "confidence": 0.9, - "recommendation": "approve" - } - }))) - .mount(&server) - .await; - - let client = TraverseClient::new(); - let result = client - .poll_execution(&server.uri(), "local-default", "exec_abc") - .await - .unwrap(); - assert_eq!(result.status, "succeeded"); - assert_eq!(result.output.unwrap().doc_type, "invoice"); + assert_eq!(accepted.execution_id.as_deref(), Some("exec_abc")); } diff --git a/scripts/ci/repository_checks.sh b/scripts/ci/repository_checks.sh index 0d6e010..d0a16f2 100644 --- a/scripts/ci/repository_checks.sh +++ b/scripts/ci/repository_checks.sh @@ -61,6 +61,8 @@ check "apps/doc-approval/linux-gtk/README.md" "doc-a check "apps/doc-approval/cli-rust/Cargo.toml" "doc-approval cli-rust Cargo project" check "apps/doc-approval/cli-rust/README.md" "doc-approval cli-rust README" check "apps/doc-approval/DocApprovalCore/Package.swift" "doc-approval DocApprovalCore package" +check "apps/doc-approval/doc-approval-core-rs/Cargo.toml" "doc-approval-core-rs Cargo crate" +check "apps/doc-approval/Cargo.toml" "doc-approval Cargo workspace" # meeting-notes clients check "apps/meeting-notes/web-react/package.json" "meeting-notes web-react package"