From 48c7bef90f1e102c750aa687ac6ad9b8cd5a092f Mon Sep 17 00:00:00 2001
From: kristiankarl <{ID}+{username}@users.noreply.github.com>
Date: Sun, 24 May 2026 18:23:15 +0200
Subject: [PATCH] Moved graphwalker-rs folder to it's own repo
---
graphwalker-rs/.gitignore | 1 -
graphwalker-rs/Cargo.lock | 2627 ---------------
graphwalker-rs/Cargo.toml | 11 -
graphwalker-rs/README.md | 68 -
graphwalker-rs/doc/.gitignore | 6 -
graphwalker-rs/doc/Gemfile | 5 -
graphwalker-rs/doc/_config.yml | 22 -
graphwalker-rs/doc/_includes/head_custom.html | 3 -
.../doc/_sass/color_schemes/graphwalker.scss | 33 -
graphwalker-rs/doc/cli.md | 312 --
graphwalker-rs/doc/generators.md | 246 --
graphwalker-rs/doc/getting-started.md | 232 --
graphwalker-rs/doc/index.md | 41 -
graphwalker-rs/doc/json-format.md | 242 --
graphwalker-rs/doc/model-based-testing.md | 79 -
graphwalker-rs/doc/rest-api.md | 216 --
graphwalker-rs/doc/stop-conditions.md | 263 --
graphwalker-rs/doc/studio.md | 164 -
graphwalker-rs/doc/websocket-api.md | 560 ----
graphwalker-rs/graphwalker-cli/Cargo.toml | 30 -
.../graphwalker-cli/src/commands/check.rs | 92 -
.../graphwalker-cli/src/commands/convert.rs | 33 -
.../graphwalker-cli/src/commands/methods.rs | 40 -
.../graphwalker-cli/src/commands/mod.rs | 91 -
.../graphwalker-cli/src/commands/offline.rs | 132 -
.../graphwalker-cli/src/commands/online.rs | 42 -
.../src/commands/requirements.rs | 42 -
.../graphwalker-cli/src/commands/source.rs | 82 -
graphwalker-rs/graphwalker-cli/src/main.rs | 60 -
.../graphwalker-cli/tests/cli_tests.rs | 790 -----
.../tests/fixtures/graphml/Login.graphml | 201 --
.../tests/fixtures/json/Login.json | 158 -
.../fixtures/json/MultiModelSharedState.json | 62 -
.../tests/fixtures/json/NoStartElement.json | 32 -
.../tests/fixtures/json/SimplestModel.json | 22 -
.../tests/fixtures/json/SmallModel.json | 44 -
.../tests/fixtures/json/WithRequirements.json | 42 -
.../tests/fixtures/json/example.json | 57 -
.../graphwalker-cli/tests/online_tests.rs | 832 -----
.../graphwalker-cli/tests/test.template | 9 -
graphwalker-rs/graphwalker-core/Cargo.toml | 13 -
.../src/algorithm/chinese_postman.rs | 457 ---
.../graphwalker-core/src/algorithm/mod.rs | 309 --
.../graphwalker-core/src/algorithm/tests.rs | 493 ---
.../graphwalker-core/src/condition/mod.rs | 279 --
.../graphwalker-core/src/condition/tests.rs | 497 ---
.../graphwalker-core/src/generator/mod.rs | 473 ---
.../graphwalker-core/src/generator/tests.rs | 564 ----
graphwalker-rs/graphwalker-core/src/lib.rs | 5 -
.../graphwalker-core/src/machine/mod.rs | 892 -----
.../graphwalker-core/src/machine/tests.rs | 1363 --------
.../graphwalker-core/src/model/builder.rs | 454 ---
.../graphwalker-core/src/model/mod.rs | 120 -
.../graphwalker-core/src/model/runtime.rs | 528 ---
.../graphwalker-core/src/model/tests.rs | 826 -----
graphwalker-rs/graphwalker-dsl/Cargo.toml | 9 -
.../graphwalker-dsl/src/generator.rs | 306 --
.../graphwalker-dsl/src/generator/tests.rs | 269 --
graphwalker-rs/graphwalker-dsl/src/lib.rs | 2 -
graphwalker-rs/graphwalker-dsl/src/yed.rs | 199 --
.../graphwalker-dsl/src/yed/tests.rs | 219 --
graphwalker-rs/graphwalker-io/Cargo.toml | 13 -
graphwalker-rs/graphwalker-io/src/graphml.rs | 378 ---
.../graphwalker-io/src/graphml/tests.rs | 163 -
graphwalker-rs/graphwalker-io/src/json.rs | 488 ---
.../graphwalker-io/src/json/tests.rs | 203 --
graphwalker-rs/graphwalker-io/src/lib.rs | 63 -
.../tests/fixtures/graphml/Guards.graphml | 124 -
.../tests/fixtures/graphml/Login.graphml | 201 --
.../fixtures/graphml/SharedStateLogin.graphml | 201 --
.../tests/fixtures/json/DependencyModel.json | 48 -
.../tests/fixtures/json/Login.json | 158 -
.../json/ModelWithPredefinedPath.json | 46 -
.../tests/fixtures/json/SharedState.json | 45 -
.../tests/fixtures/json/SmallModel.json | 44 -
.../tests/fixtures/json/example.json | 57 -
.../graphwalker-model-checker/Cargo.toml | 12 -
.../graphwalker-model-checker/src/lib.rs | 241 --
.../graphwalker-model-checker/src/tests.rs | 421 ---
.../tests/fixtures/json/petClinic.json | 280 --
graphwalker-rs/graphwalker-restful/Cargo.toml | 18 -
.../graphwalker-restful/src/actor.rs | 493 ---
graphwalker-rs/graphwalker-restful/src/lib.rs | 71 -
.../graphwalker-restful/src/rest.rs | 77 -
.../graphwalker-restful/src/session.rs | 182 --
.../graphwalker-restful/src/websocket.rs | 593 ----
graphwalker-rs/graphwalker-studio/Cargo.toml | 17 -
graphwalker-rs/graphwalker-studio/build.rs | 30 -
.../graphwalker-studio/frontend/.gitignore | 24 -
.../graphwalker-studio/frontend/README.md | 73 -
.../frontend/eslint.config.js | 22 -
.../graphwalker-studio/frontend/index.html | 14 -
.../frontend/package-lock.json | 2880 -----------------
.../graphwalker-studio/frontend/package.json | 37 -
.../frontend/public/favicon.svg | 1 -
.../frontend/public/icons.svg | 24 -
.../graphwalker-studio/frontend/src/App.tsx | 500 ---
.../frontend/src/client/websocket.ts | 145 -
.../frontend/src/components/EditorTabs.tsx | 58 -
.../frontend/src/components/GraphEditor.tsx | 696 ----
.../src/components/PropertiesPanel.tsx | 254 --
.../frontend/src/components/Sidebar.tsx | 115 -
.../frontend/src/components/StatusBar.tsx | 215 --
.../graphwalker-studio/frontend/src/index.css | 38 -
.../graphwalker-studio/frontend/src/main.tsx | 10 -
.../frontend/src/store/editor-store.ts | 29 -
.../frontend/src/store/execution-store.ts | 113 -
.../frontend/src/store/model-store.ts | 262 --
.../frontend/src/store/session-store.ts | 39 -
.../frontend/src/store/types.ts | 35 -
.../frontend/src/types.d.ts | 1 -
.../frontend/tsconfig.app.json | 26 -
.../graphwalker-studio/frontend/tsconfig.json | 7 -
.../frontend/tsconfig.node.json | 24 -
.../frontend/vite.config.ts | 17 -
graphwalker-rs/graphwalker-studio/src/main.rs | 141 -
.../static/assets/index-C5tFCUfF.js | 383 ---
.../static/assets/index-CWpSWhR4.css | 2 -
.../graphwalker-studio/static/favicon.svg | 1 -
.../graphwalker-studio/static/icons.svg | 24 -
.../graphwalker-studio/static/index.html | 15 -
121 files changed, 27193 deletions(-)
delete mode 100644 graphwalker-rs/.gitignore
delete mode 100644 graphwalker-rs/Cargo.lock
delete mode 100644 graphwalker-rs/Cargo.toml
delete mode 100644 graphwalker-rs/README.md
delete mode 100644 graphwalker-rs/doc/.gitignore
delete mode 100644 graphwalker-rs/doc/Gemfile
delete mode 100644 graphwalker-rs/doc/_config.yml
delete mode 100644 graphwalker-rs/doc/_includes/head_custom.html
delete mode 100644 graphwalker-rs/doc/_sass/color_schemes/graphwalker.scss
delete mode 100644 graphwalker-rs/doc/cli.md
delete mode 100644 graphwalker-rs/doc/generators.md
delete mode 100644 graphwalker-rs/doc/getting-started.md
delete mode 100644 graphwalker-rs/doc/index.md
delete mode 100644 graphwalker-rs/doc/json-format.md
delete mode 100644 graphwalker-rs/doc/model-based-testing.md
delete mode 100644 graphwalker-rs/doc/rest-api.md
delete mode 100644 graphwalker-rs/doc/stop-conditions.md
delete mode 100644 graphwalker-rs/doc/studio.md
delete mode 100644 graphwalker-rs/doc/websocket-api.md
delete mode 100644 graphwalker-rs/graphwalker-cli/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/check.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/convert.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/methods.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/mod.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/offline.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/online.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/requirements.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/commands/source.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/src/main.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/cli_tests.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/graphml/Login.graphml
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/Login.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/MultiModelSharedState.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/NoStartElement.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/SimplestModel.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/SmallModel.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/WithRequirements.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/fixtures/json/example.json
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/online_tests.rs
delete mode 100644 graphwalker-rs/graphwalker-cli/tests/test.template
delete mode 100644 graphwalker-rs/graphwalker-core/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-core/src/algorithm/chinese_postman.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/algorithm/mod.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/algorithm/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/condition/mod.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/condition/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/generator/mod.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/generator/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/lib.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/machine/mod.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/machine/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/model/builder.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/model/mod.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/model/runtime.rs
delete mode 100644 graphwalker-rs/graphwalker-core/src/model/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-dsl/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-dsl/src/generator.rs
delete mode 100644 graphwalker-rs/graphwalker-dsl/src/generator/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-dsl/src/lib.rs
delete mode 100644 graphwalker-rs/graphwalker-dsl/src/yed.rs
delete mode 100644 graphwalker-rs/graphwalker-dsl/src/yed/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-io/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-io/src/graphml.rs
delete mode 100644 graphwalker-rs/graphwalker-io/src/graphml/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-io/src/json.rs
delete mode 100644 graphwalker-rs/graphwalker-io/src/json/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-io/src/lib.rs
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/graphml/Guards.graphml
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/graphml/Login.graphml
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/graphml/SharedStateLogin.graphml
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/json/DependencyModel.json
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/json/Login.json
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/json/ModelWithPredefinedPath.json
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/json/SharedState.json
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/json/SmallModel.json
delete mode 100644 graphwalker-rs/graphwalker-io/tests/fixtures/json/example.json
delete mode 100644 graphwalker-rs/graphwalker-model-checker/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-model-checker/src/lib.rs
delete mode 100644 graphwalker-rs/graphwalker-model-checker/src/tests.rs
delete mode 100644 graphwalker-rs/graphwalker-model-checker/tests/fixtures/json/petClinic.json
delete mode 100644 graphwalker-rs/graphwalker-restful/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-restful/src/actor.rs
delete mode 100644 graphwalker-rs/graphwalker-restful/src/lib.rs
delete mode 100644 graphwalker-rs/graphwalker-restful/src/rest.rs
delete mode 100644 graphwalker-rs/graphwalker-restful/src/session.rs
delete mode 100644 graphwalker-rs/graphwalker-restful/src/websocket.rs
delete mode 100644 graphwalker-rs/graphwalker-studio/Cargo.toml
delete mode 100644 graphwalker-rs/graphwalker-studio/build.rs
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/.gitignore
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/README.md
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/eslint.config.js
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/index.html
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/package-lock.json
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/package.json
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/public/favicon.svg
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/public/icons.svg
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/App.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/client/websocket.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/components/EditorTabs.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/components/GraphEditor.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/components/PropertiesPanel.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/components/Sidebar.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/components/StatusBar.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/index.css
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/main.tsx
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/store/editor-store.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/store/execution-store.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/store/model-store.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/store/session-store.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/store/types.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/src/types.d.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/tsconfig.app.json
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/tsconfig.json
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/tsconfig.node.json
delete mode 100644 graphwalker-rs/graphwalker-studio/frontend/vite.config.ts
delete mode 100644 graphwalker-rs/graphwalker-studio/src/main.rs
delete mode 100644 graphwalker-rs/graphwalker-studio/static/assets/index-C5tFCUfF.js
delete mode 100644 graphwalker-rs/graphwalker-studio/static/assets/index-CWpSWhR4.css
delete mode 100644 graphwalker-rs/graphwalker-studio/static/favicon.svg
delete mode 100644 graphwalker-rs/graphwalker-studio/static/icons.svg
delete mode 100644 graphwalker-rs/graphwalker-studio/static/index.html
diff --git a/graphwalker-rs/.gitignore b/graphwalker-rs/.gitignore
deleted file mode 100644
index b83d22266..000000000
--- a/graphwalker-rs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target/
diff --git a/graphwalker-rs/Cargo.lock b/graphwalker-rs/Cargo.lock
deleted file mode 100644
index e83ea2026..000000000
--- a/graphwalker-rs/Cargo.lock
+++ /dev/null
@@ -1,2627 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "const-random",
- "getrandom 0.3.4",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[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 = "anyhow"
-version = "1.0.102"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
-
-[[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.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-
-[[package]]
-name = "axum"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
-dependencies = [
- "axum-core",
- "base64",
- "bytes",
- "form_urlencoded",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-util",
- "itoa",
- "matchit",
- "memchr",
- "mime",
- "percent-encoding",
- "pin-project-lite",
- "serde_core",
- "serde_json",
- "serde_path_to_error",
- "serde_urlencoded",
- "sha1",
- "sync_wrapper",
- "tokio",
- "tokio-tungstenite",
- "tower",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "axum-core"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
-dependencies = [
- "bytes",
- "futures-core",
- "http",
- "http-body",
- "http-body-util",
- "mime",
- "pin-project-lite",
- "sync_wrapper",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "base64"
-version = "0.22.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
-
-[[package]]
-name = "bitflags"
-version = "2.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
-
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "bstr"
-version = "1.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
-dependencies = [
- "memchr",
- "regex-automata",
- "serde",
-]
-
-[[package]]
-name = "bumpalo"
-version = "3.20.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
-
-[[package]]
-name = "bytes"
-version = "1.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
-
-[[package]]
-name = "cc"
-version = "1.2.62"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
-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 = "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 = "const-random"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
-dependencies = [
- "const-random-macro",
-]
-
-[[package]]
-name = "const-random-macro"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
-dependencies = [
- "getrandom 0.2.17",
- "once_cell",
- "tiny-keccak",
-]
-
-[[package]]
-name = "core-foundation"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "core-foundation"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
-[[package]]
-name = "cpufeatures"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crunchy"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
-
-[[package]]
-name = "crypto-common"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
-dependencies = [
- "generic-array",
- "typenum",
-]
-
-[[package]]
-name = "data-encoding"
-version = "2.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
-
-[[package]]
-name = "difflib"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
-
-[[package]]
-name = "digest"
-version = "0.10.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
-dependencies = [
- "block-buffer",
- "crypto-common",
-]
-
-[[package]]
-name = "displaydoc"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "encoding_rs"
-version = "0.8.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "errno"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
-dependencies = [
- "libc",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "fastrand"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
-
-[[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 = "foldhash"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
-
-[[package]]
-name = "foreign-types"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-dependencies = [
- "foreign-types-shared",
-]
-
-[[package]]
-name = "foreign-types-shared"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-
-[[package]]
-name = "form_urlencoded"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
-dependencies = [
- "percent-encoding",
-]
-
-[[package]]
-name = "futures"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[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-executor"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
-dependencies = [
- "futures-core",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-io"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
-
-[[package]]
-name = "futures-macro"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[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-channel",
- "futures-core",
- "futures-io",
- "futures-macro",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-project-lite",
- "slab",
-]
-
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[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.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi 6.0.0",
- "wasip2",
- "wasip3",
-]
-
-[[package]]
-name = "graphwalker-cli"
-version = "0.1.0"
-dependencies = [
- "assert_cmd",
- "clap",
- "futures-util",
- "graphwalker-core",
- "graphwalker-dsl",
- "graphwalker-io",
- "graphwalker-model-checker",
- "graphwalker-restful",
- "predicates",
- "rand 0.8.6",
- "reqwest",
- "serde",
- "serde_json",
- "tokio",
- "tokio-tungstenite",
- "tracing-subscriber",
-]
-
-[[package]]
-name = "graphwalker-core"
-version = "0.1.0"
-dependencies = [
- "rand 0.8.6",
- "rhai",
- "serde",
- "serde_json",
- "uuid",
-]
-
-[[package]]
-name = "graphwalker-dsl"
-version = "0.1.0"
-dependencies = [
- "graphwalker-core",
-]
-
-[[package]]
-name = "graphwalker-io"
-version = "0.1.0"
-dependencies = [
- "graphwalker-core",
- "graphwalker-dsl",
- "quick-xml",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "graphwalker-model-checker"
-version = "0.1.0"
-dependencies = [
- "graphwalker-core",
- "graphwalker-dsl",
- "graphwalker-io",
- "serde_json",
-]
-
-[[package]]
-name = "graphwalker-restful"
-version = "0.1.0"
-dependencies = [
- "axum",
- "futures",
- "graphwalker-core",
- "graphwalker-dsl",
- "graphwalker-io",
- "graphwalker-model-checker",
- "rand 0.8.6",
- "serde",
- "serde_json",
- "tokio",
- "tower-http",
- "tracing",
-]
-
-[[package]]
-name = "graphwalker-studio"
-version = "0.1.0"
-dependencies = [
- "axum",
- "clap",
- "graphwalker-restful",
- "tokio",
- "tower-http",
- "tracing",
- "tracing-subscriber",
-]
-
-[[package]]
-name = "h2"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
-dependencies = [
- "atomic-waker",
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "http",
- "indexmap",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.15.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
-dependencies = [
- "foldhash",
-]
-
-[[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.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
-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 = "http-range-header"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
-
-[[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.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
-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",
-]
-
-[[package]]
-name = "hyper-tls"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
-dependencies = [
- "bytes",
- "http-body-util",
- "hyper",
- "hyper-util",
- "native-tls",
- "tokio",
- "tokio-native-tls",
- "tower-service",
-]
-
-[[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",
- "system-configuration",
- "tokio",
- "tower-service",
- "tracing",
- "windows-registry",
-]
-
-[[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 = "id-arena"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
-
-[[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 0.17.1",
- "serde",
- "serde_core",
-]
-
-[[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.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
-dependencies = [
- "cfg-if",
- "futures-util",
- "once_cell",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-
-[[package]]
-name = "leb128fmt"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
-
-[[package]]
-name = "libc"
-version = "0.2.186"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
-
-[[package]]
-name = "litemap"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
-
-[[package]]
-name = "log"
-version = "0.4.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
-
-[[package]]
-name = "matchers"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
-dependencies = [
- "regex-automata",
-]
-
-[[package]]
-name = "matchit"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
-
-[[package]]
-name = "memchr"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
-
-[[package]]
-name = "mime"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
-
-[[package]]
-name = "mime_guess"
-version = "2.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
-dependencies = [
- "mime",
- "unicase",
-]
-
-[[package]]
-name = "mio"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
-dependencies = [
- "libc",
- "wasi",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "native-tls"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
-dependencies = [
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework",
- "security-framework-sys",
- "tempfile",
-]
-
-[[package]]
-name = "normalize-line-endings"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
-
-[[package]]
-name = "nu-ansi-term"
-version = "0.50.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[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"
-dependencies = [
- "portable-atomic",
-]
-
-[[package]]
-name = "once_cell_polyfill"
-version = "1.70.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
-
-[[package]]
-name = "openssl"
-version = "0.10.80"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
-dependencies = [
- "bitflags",
- "cfg-if",
- "foreign-types",
- "libc",
- "openssl-macros",
- "openssl-sys",
-]
-
-[[package]]
-name = "openssl-macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "openssl-probe"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
-
-[[package]]
-name = "openssl-sys"
-version = "0.9.116"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
-
-[[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 = "pkg-config"
-version = "0.3.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
-
-[[package]]
-name = "portable-atomic"
-version = "1.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
-
-[[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 = "prettyplease"
-version = "0.2.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
-dependencies = [
- "proc-macro2",
- "syn",
-]
-
-[[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 = "quick-xml"
-version = "0.37.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
-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.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
-dependencies = [
- "libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
-dependencies = [
- "rand_chacha 0.9.0",
- "rand_core 0.9.5",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.6.4",
-]
-
-[[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.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.17",
-]
-
-[[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 = "regex"
-version = "1.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
-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.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
-
-[[package]]
-name = "reqwest"
-version = "0.12.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
-dependencies = [
- "base64",
- "bytes",
- "encoding_rs",
- "futures-core",
- "h2",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-tls",
- "hyper-util",
- "js-sys",
- "log",
- "mime",
- "native-tls",
- "percent-encoding",
- "pin-project-lite",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper",
- "tokio",
- "tokio-native-tls",
- "tower",
- "tower-http",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
-[[package]]
-name = "rhai"
-version = "1.24.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f9ef5dabe4c0b43d8f1187dc6beb67b53fe607fff7e30c5eb7f71b814b8c2c1"
-dependencies = [
- "ahash",
- "bitflags",
- "num-traits",
- "once_cell",
- "rhai_codegen",
- "smallvec",
- "smartstring",
- "thin-vec",
- "web-time",
-]
-
-[[package]]
-name = "rhai_codegen"
-version = "3.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4322a2a4e8cf30771dd9f27f7f37ca9ac8fe812dddd811096a98483080dabe6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[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 = "rustix"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
-dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "rustls"
-version = "0.23.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
-dependencies = [
- "once_cell",
- "rustls-pki-types",
- "rustls-webpki",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "rustls-pki-types"
-version = "1.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
-dependencies = [
- "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 = "schannel"
-version = "0.1.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "security-framework"
-version = "3.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
-dependencies = [
- "bitflags",
- "core-foundation 0.10.1",
- "core-foundation-sys",
- "libc",
- "security-framework-sys",
-]
-
-[[package]]
-name = "security-framework-sys"
-version = "2.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "semver"
-version = "1.0.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
-
-[[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.149"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
-dependencies = [
- "itoa",
- "memchr",
- "serde",
- "serde_core",
- "zmij",
-]
-
-[[package]]
-name = "serde_path_to_error"
-version = "0.1.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
-dependencies = [
- "itoa",
- "serde",
- "serde_core",
-]
-
-[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "sha1"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "sharded-slab"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
-dependencies = [
- "lazy_static",
-]
-
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
-[[package]]
-name = "signal-hook-registry"
-version = "1.4.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
-dependencies = [
- "errno",
- "libc",
-]
-
-[[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.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "smartstring"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
-dependencies = [
- "autocfg",
- "static_assertions",
- "version_check",
-]
-
-[[package]]
-name = "socket2"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
-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 = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
-[[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.117"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
-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 = "system-configuration"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
-dependencies = [
- "bitflags",
- "core-foundation 0.9.4",
- "system-configuration-sys",
-]
-
-[[package]]
-name = "system-configuration-sys"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "tempfile"
-version = "3.27.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
-dependencies = [
- "fastrand",
- "getrandom 0.4.2",
- "once_cell",
- "rustix",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "termtree"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
-
-[[package]]
-name = "thin-vec"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482"
-
-[[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 = "thread_local"
-version = "1.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "tiny-keccak"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
-dependencies = [
- "crunchy",
-]
-
-[[package]]
-name = "tinystr"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
-dependencies = [
- "displaydoc",
- "zerovec",
-]
-
-[[package]]
-name = "tokio"
-version = "1.52.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
-dependencies = [
- "bytes",
- "libc",
- "mio",
- "pin-project-lite",
- "signal-hook-registry",
- "socket2",
- "tokio-macros",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "2.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "tokio-native-tls"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
-dependencies = [
- "native-tls",
- "tokio",
-]
-
-[[package]]
-name = "tokio-rustls"
-version = "0.26.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
-dependencies = [
- "rustls",
- "tokio",
-]
-
-[[package]]
-name = "tokio-tungstenite"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
-dependencies = [
- "futures-util",
- "log",
- "tokio",
- "tungstenite",
-]
-
-[[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",
- "tracing",
-]
-
-[[package]]
-name = "tower-http"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
-dependencies = [
- "bitflags",
- "bytes",
- "futures-core",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "http-range-header",
- "httpdate",
- "mime",
- "mime_guess",
- "percent-encoding",
- "pin-project-lite",
- "tokio",
- "tokio-util",
- "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 = [
- "log",
- "pin-project-lite",
- "tracing-attributes",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-attributes"
-version = "0.1.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
-dependencies = [
- "once_cell",
- "valuable",
-]
-
-[[package]]
-name = "tracing-log"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
-dependencies = [
- "log",
- "once_cell",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-subscriber"
-version = "0.3.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
-dependencies = [
- "matchers",
- "nu-ansi-term",
- "once_cell",
- "regex-automata",
- "sharded-slab",
- "smallvec",
- "thread_local",
- "tracing",
- "tracing-core",
- "tracing-log",
-]
-
-[[package]]
-name = "try-lock"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
-
-[[package]]
-name = "tungstenite"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8"
-dependencies = [
- "bytes",
- "data-encoding",
- "http",
- "httparse",
- "log",
- "rand 0.9.4",
- "sha1",
- "thiserror",
-]
-
-[[package]]
-name = "typenum"
-version = "1.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
-
-[[package]]
-name = "unicase"
-version = "2.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-
-[[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 = "uuid"
-version = "1.23.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
-dependencies = [
- "getrandom 0.4.2",
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "valuable"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
-
-[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
-[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
-[[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.3+wasi-0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
-dependencies = [
- "wit-bindgen 0.57.1",
-]
-
-[[package]]
-name = "wasip3"
-version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
-dependencies = [
- "wit-bindgen 0.51.0",
-]
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.121"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-futures"
-version = "0.4.71"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.121"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.121"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
-dependencies = [
- "bumpalo",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.121"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "wasm-encoder"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
-dependencies = [
- "leb128fmt",
- "wasmparser",
-]
-
-[[package]]
-name = "wasm-metadata"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
-dependencies = [
- "anyhow",
- "indexmap",
- "wasm-encoder",
- "wasmparser",
-]
-
-[[package]]
-name = "wasmparser"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
-dependencies = [
- "bitflags",
- "hashbrown 0.15.5",
- "indexmap",
- "semver",
-]
-
-[[package]]
-name = "web-sys"
-version = "0.3.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
-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 = "windows-link"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-
-[[package]]
-name = "windows-registry"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
-dependencies = [
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-result"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
-dependencies = [
- "windows-link",
-]
-
-[[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.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.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
-dependencies = [
- "wit-bindgen-rust-macro",
-]
-
-[[package]]
-name = "wit-bindgen"
-version = "0.57.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
-
-[[package]]
-name = "wit-bindgen-core"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
-dependencies = [
- "anyhow",
- "heck",
- "wit-parser",
-]
-
-[[package]]
-name = "wit-bindgen-rust"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
-dependencies = [
- "anyhow",
- "heck",
- "indexmap",
- "prettyplease",
- "syn",
- "wasm-metadata",
- "wit-bindgen-core",
- "wit-component",
-]
-
-[[package]]
-name = "wit-bindgen-rust-macro"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
-dependencies = [
- "anyhow",
- "prettyplease",
- "proc-macro2",
- "quote",
- "syn",
- "wit-bindgen-core",
- "wit-bindgen-rust",
-]
-
-[[package]]
-name = "wit-component"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
-dependencies = [
- "anyhow",
- "bitflags",
- "indexmap",
- "log",
- "serde",
- "serde_derive",
- "serde_json",
- "wasm-encoder",
- "wasm-metadata",
- "wasmparser",
- "wit-parser",
-]
-
-[[package]]
-name = "wit-parser"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
-dependencies = [
- "anyhow",
- "id-arena",
- "indexmap",
- "log",
- "semver",
- "serde",
- "serde_derive",
- "serde_json",
- "unicode-xid",
- "wasmparser",
-]
-
-[[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.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
-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.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
-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.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
-
-[[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/graphwalker-rs/Cargo.toml b/graphwalker-rs/Cargo.toml
deleted file mode 100644
index 58a3385cf..000000000
--- a/graphwalker-rs/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[workspace]
-resolver = "2"
-members = [
- "graphwalker-core",
- "graphwalker-dsl",
- "graphwalker-io",
- "graphwalker-model-checker",
- "graphwalker-cli",
- "graphwalker-restful",
- "graphwalker-studio",
-]
diff --git a/graphwalker-rs/README.md b/graphwalker-rs/README.md
deleted file mode 100644
index 4f03888a3..000000000
--- a/graphwalker-rs/README.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# GraphWalker
-
-GraphWalker is a model-based testing tool. It reads models in the shape of directed graphs and generates test paths from them. Tests are modeled as graphs where **vertices** represent states and **edges** represent transitions between states. GraphWalker traverses these graphs using configurable path generators and stop conditions, producing sequences of steps that can drive automated test execution.
-
-GraphWalker supports guards (boolean conditions on edges), actions (scripts that run on traversal), requirements tracking, weighted edges, shared states across multiple models, and several path generation algorithms ranging from pure random walks to optimal Chinese Postman routes.
-
-## Prerequisites
-
-- **Rust** (1.70 or later) — install via [rustup](https://rustup.rs/)
-- **Git**
-- **Node.js and npm** (only needed if building the Studio frontend from source)
-
-## Building
-
-```bash
-# Clone the repository
-git clone https://github.com/GraphWalker/graphwalker-project.git
-cd graphwalker-project/graphwalker-rs
-
-# Build all crates (debug)
-cargo build
-
-# Build optimized release binaries
-cargo build --release
-
-# Run tests
-cargo test
-```
-
-The main binaries are:
-
-| Binary | Location | Description |
-|--------|----------|-------------|
-| `graphwalker` | `target/release/graphwalker` | CLI tool for offline/online test generation |
-| `graphwalker-studio` | `target/release/graphwalker-studio` | Web-based visual model editor and test runner |
-
-## Quick start
-
-```bash
-# Check a model for issues
-graphwalker check -g model.json
-
-# Generate a test path offline
-graphwalker offline -m model.json "random(edge_coverage(100))"
-
-# Start a REST API server
-graphwalker online -s RESTFUL -p 8080 -m model.json "random(edge_coverage(100))"
-
-# Start the visual Studio
-graphwalker-studio
-```
-
-## Documentation
-
-Full documentation is available at [graphwalker.github.io/graphwalker-rs](https://graphwalker.github.io/graphwalker-rs/).
-
-- [What is Model-Based Testing](https://graphwalker.github.io/graphwalker-rs/model-based-testing)
-- [Getting Started](https://graphwalker.github.io/graphwalker-rs/getting-started)
-- [CLI Reference](https://graphwalker.github.io/graphwalker-rs/cli)
-- [Generators and Stop Conditions](https://graphwalker.github.io/graphwalker-rs/generators)
-- [JSON Model Format](https://graphwalker.github.io/graphwalker-rs/json-format)
-- [REST API](https://graphwalker.github.io/graphwalker-rs/rest-api)
-- [WebSocket API](https://graphwalker.github.io/graphwalker-rs/websocket-api)
-- [GraphWalker Studio](https://graphwalker.github.io/graphwalker-rs/studio)
-
-## License
-
-MIT
diff --git a/graphwalker-rs/doc/.gitignore b/graphwalker-rs/doc/.gitignore
deleted file mode 100644
index c912f251b..000000000
--- a/graphwalker-rs/doc/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-_site/
-.jekyll-cache/
-.jekyll-metadata
-Gemfile.lock
-vendor/
-.bundle/
diff --git a/graphwalker-rs/doc/Gemfile b/graphwalker-rs/doc/Gemfile
deleted file mode 100644
index 8285b35e9..000000000
--- a/graphwalker-rs/doc/Gemfile
+++ /dev/null
@@ -1,5 +0,0 @@
-source "https://rubygems.org"
-
-gem "jekyll", "~> 4.3"
-gem "just-the-docs", "~> 0.10"
-gem "jekyll-remote-theme"
diff --git a/graphwalker-rs/doc/_config.yml b/graphwalker-rs/doc/_config.yml
deleted file mode 100644
index 7511aca8f..000000000
--- a/graphwalker-rs/doc/_config.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-title: GraphWalker
-description: Model-based testing tool — generate test paths from directed graphs
-baseurl: /graphwalker-rs
-url: https://graphwalker.github.io
-
-remote_theme: just-the-docs/just-the-docs
-
-color_scheme: graphwalker
-
-aux_links:
- "GitHub":
- - "https://github.com/GraphWalker/graphwalker-rs"
-
-heading_anchors: true
-
-back_to_top: true
-back_to_top_text: "Back to top"
-
-footer_content: "GraphWalker — Model-Based Testing"
-
-plugins:
- - jekyll-remote-theme
diff --git a/graphwalker-rs/doc/_includes/head_custom.html b/graphwalker-rs/doc/_includes/head_custom.html
deleted file mode 100644
index d0ee726ae..000000000
--- a/graphwalker-rs/doc/_includes/head_custom.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/graphwalker-rs/doc/_sass/color_schemes/graphwalker.scss b/graphwalker-rs/doc/_sass/color_schemes/graphwalker.scss
deleted file mode 100644
index 5b56d8ae1..000000000
--- a/graphwalker-rs/doc/_sass/color_schemes/graphwalker.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-// GraphWalker color scheme for just-the-docs
-// Colors sourced from graphwalker.github.io
-
-// Primary blue from the GraphWalker site
-$link-color: #49a7e9;
-
-// Sidebar / navigation
-$nav-width: 16.5rem;
-$sidebar-color: #27262b;
-$nav-child-link-color: #49a7e9;
-
-// Body
-$body-background-color: #f8f8f8;
-$body-text-color: #111111;
-$body-heading-color: #111111;
-
-// Code blocks
-$code-background-color: #eef0f2;
-
-// Search
-$search-background-color: #f8f8f8;
-
-// Buttons and accents
-$btn-primary-color: #49a7e9;
-$base-button-color: #49a7e9;
-
-// Footer
-$footer-background-color: #27262b;
-$footer-text-color: #f8f8f8;
-
-// Typography — match the Raleway font from the GraphWalker site
-$body-font-family: "Raleway", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
-$mono-font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
diff --git a/graphwalker-rs/doc/cli.md b/graphwalker-rs/doc/cli.md
deleted file mode 100644
index 6dfba73f8..000000000
--- a/graphwalker-rs/doc/cli.md
+++ /dev/null
@@ -1,312 +0,0 @@
----
-layout: default
-title: CLI Reference
-nav_order: 4
----
-
-# CLI Reference
-
-```
-graphwalker [--debug]
-```
-
-## Global flags
-
-| Flag | Description |
-|------|-------------|
-| `--debug` | Enable debug logging |
-| `--help` | Show help |
-| `--version` | Show version |
-
----
-
-## offline
-
-Generate a test path from a model and print it to stdout.
-
-```
-graphwalker offline [OPTIONS]
-```
-
-### Options
-
-| Flag | Short | Long | Description | Default |
-|------|-------|------|-------------|---------|
-| Model + generator | `-m` | `--model` | Model file and generator as a pair: `-m `. Can be repeated. | |
-| Model with embedded generator | `-g` | `--gw` | JSON model file with generator embedded in the `"generator"` field. | |
-| Verbose | `-o` | `--verbose` | Include execution data (variables) in each output line. | off |
-| Unvisited | | `--unvisited` | Include unvisited element counts and names in each output line. | off |
-| Start element | `-e` | `--start-element` | Override the start element by name. | from model |
-| Seed | `-s` | `--seed` | Random seed. `0` means use a random seed. | `0` |
-
-Either `--model` or `--gw` is required. They are mutually exclusive.
-
-### Output format
-
-One JSON object per line:
-
-```json
-{"currentElementName":"e_SomeEdge"}
-{"currentElementName":"v_SomeVertex"}
-```
-
-With `--verbose`:
-
-```json
-{"currentElementName":"e_SomeEdge","data":"x=1; loggedIn=true"}
-```
-
-With `--unvisited`:
-
-```json
-{"currentElementName":"e_SomeEdge","numberOfElements":10,"numberOfUnvisitedElements":7,"unvisitedElements":[{"elementName":"e_Other"},{"elementName":"v_Other"}]}
-```
-
-With both `--verbose` and `--unvisited`, each unvisited element also includes its `elementId`.
-
-### Examples
-
-```bash
-# Use embedded generator from JSON file
-graphwalker offline -g model.json
-
-# Specify generator on command line
-graphwalker offline -m model.json "random(edge_coverage(100))"
-
-# Multiple models with different generators
-graphwalker offline -m model1.json "random(edge_coverage(100))" \
- -m model2.json "a_star(reached_vertex(v_End))"
-
-# Deterministic with seed
-graphwalker offline -g model.json -s 42
-
-# Custom start element
-graphwalker offline -g model.json -e v_LoginPage
-```
-
----
-
-## online
-
-Start a REST or WebSocket service for interactive test generation.
-
-```
-graphwalker online [OPTIONS]
-```
-
-### Options
-
-| Flag | Short | Long | Description | Default |
-|------|-------|------|-------------|---------|
-| Service type | `-s` | `--service` | `RESTFUL` or `WEBSOCKET` | `WEBSOCKET` |
-| Port | `-p` | `--port` | Port to listen on | `8887` |
-| Model + generator | `-m` | `--model` | Model file and generator pair. Can be repeated. | |
-| Start element | `-e` | `--start-element` | Override start element by name. | from model |
-| Seed | | `--seed` | Random seed. `0` means random. | `0` |
-
-### Examples
-
-```bash
-# REST API on port 8080
-graphwalker online -s RESTFUL -p 8080 -m model.json "random(edge_coverage(100))"
-
-# WebSocket on default port
-graphwalker online -m model.json "random(edge_coverage(100))"
-
-# With fixed seed
-graphwalker online -s RESTFUL -p 8080 --seed 42 -m model.json "random(edge_coverage(100))"
-```
-
----
-
-## check
-
-Validate model files and print statistics.
-
-```
-graphwalker check [OPTIONS]
-```
-
-### Options
-
-| Flag | Short | Long | Description |
-|------|-------|------|-------------|
-| Model + generator | `-m` | `--model` | Model file and generator pair. Can be repeated. |
-| Model with embedded generator | `-g` | `--gw` | JSON file with embedded generator. |
-
-Either `--model` or `--gw` is required.
-
-### Output
-
-If no issues are found:
-
-```
-No issues found with the model(s).
-
-Statistics:
- Model: LoginTest
- Unique edges: 4
- Unique vertices: 3
- Edge instances: 4
- Vertex instances: 3
-```
-
-If issues are found, they are printed one per line and the command exits with a non-zero status:
-
-```
-Name of vertex cannot be null or empty
-Vertex 'v_Orphan' has no edges
-2 issue(s) found
-```
-
-For multiple models, a grand total is printed after per-model statistics:
-
-```
-Statistics:
- Model: ModelA
- Unique edges: 2
- Unique vertices: 2
- Edge instances: 2
- Vertex instances: 2
- Model: ModelB
- Unique edges: 3
- Unique vertices: 2
- Edge instances: 3
- Vertex instances: 2
- Total:
- Unique edges: 5
- Unique vertices: 4
- Edge instances: 5
- Vertex instances: 4
-```
-
-"Unique" means distinct element names within each model. Edges with the same name in different models are counted separately.
-
----
-
-## methods
-
-List all unique method names (vertex and edge names) from the model.
-
-```
-graphwalker methods -m [...]
-```
-
-### Options
-
-| Flag | Short | Long | Description |
-|------|-------|------|-------------|
-| Model files | `-m` | `--model` | One or more model files. Required. |
-
-### Output
-
-Sorted, deduplicated list of names, one per line:
-
-```
-e_AnotherAction
-e_FirstAction
-e_SomeOtherAction
-v_VerifySomeAction
-v_VerifySomeOtherAction
-```
-
----
-
-## requirements
-
-List all unique requirement keys from the model.
-
-```
-graphwalker requirements -m [...]
-```
-
-### Options
-
-| Flag | Short | Long | Description |
-|------|-------|------|-------------|
-| Model files | `-m` | `--model` | One or more model files. Required. |
-
-### Output
-
-Sorted, deduplicated list of requirement keys, one per line:
-
-```
-REQ001
-REQ002
-REQ003
-```
-
----
-
-## convert
-
-Convert a model file to another format.
-
-```
-graphwalker convert -i [-f ]
-```
-
-### Options
-
-| Flag | Short | Long | Description | Default |
-|------|-------|------|-------------|---------|
-| Input file | `-i` | `--input` | Path to the input model file. Required. | |
-| Output format | `-f` | `--format` | Output format. Currently only `json` is supported. | `json` |
-
-Outputs the converted model to stdout. Supports input formats: JSON, GraphML.
-
-### Examples
-
-```bash
-# Convert GraphML to JSON
-graphwalker convert -i model.graphml -f json > model.json
-
-# Default format is JSON
-graphwalker convert -i model.graphml > model.json
-```
-
----
-
-## source
-
-Generate source code from a model using a template.
-
-```
-graphwalker source -i
-```
-
-### Options
-
-| Flag | Short | Long | Description |
-|------|-------|------|-------------|
-| Input files | `-i` | `--input` | Two arguments: model file path and template file path. Required. |
-
-### Template format
-
-The template file uses three sections delimited by special markers:
-
-{% raw %}
-```
-HEADER<{{
-// This code appears once at the top
-public class MyTest {
-}}>HEADER
-BODY
- public void {LABEL}() {
- // test step
- }
-FOOTER<{{
-}
-}}>FOOTER
-```
-{% endraw %}
-
-- {% raw %}`HEADER<{{ ... }}>HEADER`{% endraw %} — printed once at the beginning
-- {% raw %}`BODY` — the `{LABEL}` placeholder{% endraw %} is replaced with each method name from the model
-- {% raw %}`FOOTER<{{ ... }}>FOOTER`{% endraw %} — printed once at the end
-
-### Example
-
-```bash
-graphwalker source -i model.json template.txt
-```
diff --git a/graphwalker-rs/doc/generators.md b/graphwalker-rs/doc/generators.md
deleted file mode 100644
index 3591f7ac6..000000000
--- a/graphwalker-rs/doc/generators.md
+++ /dev/null
@@ -1,246 +0,0 @@
----
-layout: default
-title: Generators
-nav_order: 5
----
-
-# Path Generators
-
-A generator is the algorithm that decides which edge to take at each step of the graph traversal. Each generator (except `new_york_street_sweeper`) is paired with a [stop condition](stop-conditions) that determines when to stop.
-
-## DSL syntax
-
-Generators are specified as strings in the format:
-
-```
-generator_name(stop_condition)
-```
-
-Generator names are case-insensitive. Both `snake_case` and `camelCase` forms are accepted. Multiple generators can be chained by separating them with whitespace.
-
----
-
-## random
-
-Randomly walks the graph by selecting uniformly from available outgoing edges at each vertex.
-
-**Syntax:** `random(condition)` or `random_path(condition)` or `randompath(condition)`
-
-**Algorithm:**
-1. At a vertex: collect all outgoing edges that pass their guards. Pick one uniformly at random.
-2. At an edge: move to the target vertex.
-
-**Characteristics:**
-- Simple and fast
-- No bias toward unvisited elements
-- May revisit the same areas many times before reaching full coverage
-- Good for smoke testing and exploring common paths
-
-**Example:**
-```
-random(edge_coverage(100))
-random(length(200))
-random(vertex_coverage(80) or time_duration(60))
-```
-
----
-
-## quick_random
-
-A smarter random walk that steers toward unvisited areas using shortest-path computation.
-
-**Syntax:** `quick_random(condition)` or `quick_random_path(condition)` or `quickrandompath(condition)`
-
-**Algorithm:**
-1. On first step: collect all elements in the model, shuffle them.
-2. Maintain a target element. If the target is reached or not yet set, pick the least-visited element from the shuffled list.
-3. Use Floyd-Warshall shortest-path distances and A* navigation to move toward the target.
-4. Remove visited elements from the target list.
-
-**Characteristics:**
-- Significantly faster at achieving full coverage than pure random
-- Biased toward unvisited elements
-- Uses pre-computed shortest paths (Floyd-Warshall) for efficient navigation
-- Good default choice for coverage-based testing
-
-**Example:**
-```
-quick_random(edge_coverage(100))
-quick_random(vertex_coverage(100))
-```
-
----
-
-## weighted_random
-
-Random walk where edges have weights that control selection probability.
-
-**Syntax:** `weighted_random(condition)` or `weighted_random_path(condition)` or `weightedrandompath(condition)`
-
-**Algorithm:**
-1. At a vertex: collect available outgoing edges with their weights.
-2. Edges with weight > 0 are selected with probability proportional to their weight.
-3. Edges with weight 0 share the remaining probability equally.
-4. At an edge: select uniformly from available next elements.
-
-**Weight rules:**
-- Weights are defined on edges in the model (the `weight` field, values 0.0 to 1.0)
-- The sum of all outgoing edge weights from a vertex must not exceed 1.0
-- Edges with weight 0 get equal shares of `(1.0 - sum_of_weights)`
-- If all weights are 0, all edges are equally likely
-
-**Example:**
-If a vertex has three outgoing edges with weights 0.7, 0.2, and 0:
-- Edge 1 is selected 70% of the time
-- Edge 2 is selected 20% of the time
-- Edge 3 is selected 10% of the time (the remaining probability)
-
-```
-weighted_random(edge_coverage(100))
-weighted_random(length(500))
-```
-
----
-
-## a_star
-
-Directed pathfinding that navigates toward a specific target element using the A* algorithm.
-
-**Syntax:** `a_star(condition)` or `astar(condition)` or `astarpath(condition)`
-
-**Algorithm:**
-1. Extract target elements from the stop condition (works with `reached_vertex`, `reached_edge`, `reached_shared_state`).
-2. Use Floyd-Warshall pre-computed distances to find the nearest target.
-3. At each step, take the next step on the shortest path toward that target.
-
-**Characteristics:**
-- Deterministic shortest path to the target
-- Requires a stop condition that defines target elements
-- Ideal for "navigate to a specific state" scenarios
-- Often used in combined generators: random exploration followed by directed navigation
-
-**Example:**
-```
-a_star(reached_vertex(v_Checkout))
-a_star(reached_edge(e_SubmitOrder))
-a_star(reached_shared_state(LOGGED_IN))
-```
-
----
-
-## shortest_all_paths
-
-Computes an Eulerian path that traverses every edge exactly once (if the graph permits), then follows it deterministically.
-
-**Syntax:** `shortest_all_paths(condition)` or `shortestallpaths(condition)`
-
-**Algorithm:**
-1. On first step: compute an Eulerian trail from the current position.
-2. Follow the pre-computed trail step by step.
-
-**Characteristics:**
-- Deterministic: same graph always produces the same path
-- Visits every edge exactly once (if the graph is Eulerian or semi-Eulerian)
-- Fails if the graph does not have an Eulerian path
-- Efficient: computes the path once, then just follows it
-
-**Requirements:**
-- The graph must be connected
-- The graph must be Eulerian (all vertices have equal in-degree and out-degree) or semi-Eulerian (exactly two vertices have unequal degrees)
-
-**Example:**
-```
-shortest_all_paths(edge_coverage(100))
-```
-
----
-
-## predefined_path
-
-Follows a predetermined sequence of edges defined in the model.
-
-**Syntax:** `predefined_path(condition)` or `predefinedpath(condition)`
-
-**Algorithm:**
-1. Read the `predefinedPathEdgeIds` array from the model.
-2. At each vertex: take the next edge from the predefined sequence.
-3. At each edge: move to the target vertex.
-4. Stop when the sequence is exhausted.
-
-**Characteristics:**
-- Fully deterministic: the path is defined in the model
-- No randomness at all
-- Useful for regression testing specific known paths
-- Fails if the next predefined edge is not available from the current vertex
-
-**Example:**
-```
-predefined_path(predefined_path)
-predefined_path(length(20))
-```
-
----
-
-## new_york_street_sweeper
-
-Computes the optimal route that visits every edge at least once (the Directed Chinese Postman Problem) and follows it.
-
-**Syntax:** `new_york_street_sweeper()` or `newyorkstreetsweeper()`
-
-This generator takes **no stop condition** and **no parameters**. It terminates when the computed path is exhausted.
-
-**Algorithm:**
-1. Check that the graph is strongly connected.
-2. Compute vertex polarities (out-degree minus in-degree).
-3. If all polarities are zero, the graph is Eulerian — compute the Euler circuit directly.
-4. Otherwise, find the minimum-cost set of edges to duplicate using BFS shortest paths and the Hungarian algorithm (min-cost bipartite matching).
-5. Augment the graph with the duplicate edges.
-6. Compute an Euler circuit on the augmented graph using Hierholzer's algorithm.
-
-**Characteristics:**
-- **Optimal**: minimizes the number of duplicate edge traversals
-- **Deterministic**: same graph always produces the same path
-- **Covers every edge** at least once (some may be traversed more than once if the graph is not Eulerian)
-- **Ignores guards and actions**: this generator skips action execution and ignores edge guards, treating the graph as a pure structure
-- Requires the graph to be **strongly connected**
-- Emits warnings to stderr for edges with guards, actions, non-unit weights, or missing source vertices
-
-**Example:**
-```
-new_york_street_sweeper()
-```
-
----
-
-## Combined generators
-
-Multiple generators can be chained in a single string, separated by whitespace. They run sequentially: the first generator runs until its stop condition is fulfilled, then the second takes over from wherever the first stopped, and so on.
-
-**Syntax:**
-```
-generator1(condition1) generator2(condition2) [generator3(condition3) ...]
-```
-
-**Example:**
-```
-random(length(20)) quick_random(edge_coverage(100)) a_star(reached_vertex(v_End))
-```
-
-This runs three phases:
-1. Random walk for 20 steps (warm-up)
-2. Quick random until all edges are covered
-3. A* navigation to `v_End`
-
----
-
-## Summary table
-
-| Generator | Deterministic | Needs stop condition | Respects guards | Special behavior |
-|-----------|:---:|:---:|:---:|---|
-| `random` | No | Yes | Yes | |
-| `quick_random` | No | Yes | Yes | Steers toward unvisited elements |
-| `weighted_random` | No | Yes | Yes | Uses edge weights for selection |
-| `a_star` | Yes | Yes (with target) | Yes | Shortest path to target |
-| `shortest_all_paths` | Yes | Yes | Yes | Requires Eulerian graph |
-| `predefined_path` | Yes | Yes | Yes | Path defined in model |
-| `new_york_street_sweeper` | Yes | No | No | Skips actions, optimal edge coverage |
diff --git a/graphwalker-rs/doc/getting-started.md b/graphwalker-rs/doc/getting-started.md
deleted file mode 100644
index dfef74d89..000000000
--- a/graphwalker-rs/doc/getting-started.md
+++ /dev/null
@@ -1,232 +0,0 @@
----
-layout: default
-title: Getting Started
-nav_order: 3
----
-
-# Getting Started
-
-This guide walks you through installing GraphWalker, creating a model, and generating your first test path.
-
-## Installation
-
-### Prerequisites
-
-- [Rust](https://rustup.rs/) 1.70 or later
-- Git
-
-### Build from source
-
-```bash
-git clone https://github.com/GraphWalker/graphwalker-rs.git
-cd graphwalker-rs
-cargo build --release
-```
-
-The binaries are in `target/release/`:
-
-```bash
-# Add to your PATH (optional)
-export PATH="$PWD/target/release:$PATH"
-```
-
-## Create a model
-
-Create a file called `login.json`:
-
-```json
-{
- "models": [
- {
- "name": "LoginTest",
- "id": "login-model",
- "generator": "random(edge_coverage(100))",
- "startElementId": "e_init",
- "vertices": [
- { "id": "v_start", "name": "v_Start" },
- { "id": "v_login", "name": "v_LoginPage" },
- { "id": "v_app", "name": "v_Application" }
- ],
- "edges": [
- {
- "id": "e_init",
- "name": "e_OpenBrowser",
- "targetVertexId": "v_start"
- },
- {
- "id": "e_navigate",
- "name": "e_NavigateToLogin",
- "sourceVertexId": "v_start",
- "targetVertexId": "v_login"
- },
- {
- "id": "e_login",
- "name": "e_SubmitCredentials",
- "sourceVertexId": "v_login",
- "targetVertexId": "v_app"
- },
- {
- "id": "e_logout",
- "name": "e_Logout",
- "sourceVertexId": "v_app",
- "targetVertexId": "v_login"
- }
- ]
- }
- ]
-}
-```
-
-This model has three states (Start, LoginPage, Application) and four transitions.
-
-## Validate the model
-
-```bash
-graphwalker check -g login.json
-```
-
-Expected output:
-
-```
-No issues found with the model(s).
-
-Statistics:
- Model: LoginTest
- Unique edges: 4
- Unique vertices: 3
- Edge instances: 4
- Vertex instances: 3
-```
-
-## Generate a test path
-
-### Offline mode
-
-Generate a complete test path and print it:
-
-```bash
-graphwalker offline -g login.json
-```
-
-Output (one JSON object per line):
-
-```json
-{"currentElementName":"e_OpenBrowser"}
-{"currentElementName":"v_Start"}
-{"currentElementName":"e_NavigateToLogin"}
-{"currentElementName":"v_LoginPage"}
-{"currentElementName":"e_SubmitCredentials"}
-{"currentElementName":"v_Application"}
-{"currentElementName":"e_Logout"}
-{"currentElementName":"v_LoginPage"}
-```
-
-The generator (`random(edge_coverage(100))`) is embedded in the JSON file. Every edge is visited at least once.
-
-### Override the generator on the command line
-
-```bash
-graphwalker offline -m login.json "random(vertex_coverage(100))"
-```
-
-This uses the `-m` flag which takes a model file and a generator string as a pair.
-
-### Deterministic output with a seed
-
-```bash
-graphwalker offline -g login.json -s 42
-```
-
-The same seed always produces the same path.
-
-### Verbose output
-
-```bash
-graphwalker offline -g login.json -o
-```
-
-Includes execution data (variable values) in each step.
-
-### Track unvisited elements
-
-```bash
-graphwalker offline -g login.json --unvisited
-```
-
-Each step includes counts of remaining unvisited elements.
-
-## Use guards and actions
-
-Update the model to add conditional behavior. Here the `e_SubmitCredentials` edge sets `loggedIn = true`, and a new `e_DirectAccess` edge has a guard that checks this variable:
-
-```json
-{
- "id": "e_login",
- "name": "e_SubmitCredentials",
- "sourceVertexId": "v_login",
- "targetVertexId": "v_app",
- "actions": ["loggedIn = true;"]
-},
-{
- "id": "e_direct",
- "name": "e_DirectAccess",
- "sourceVertexId": "v_start",
- "targetVertexId": "v_app",
- "guard": "loggedIn"
-}
-```
-
-The `e_DirectAccess` edge is only available after `loggedIn` has been set to `true` by a previous traversal of `e_SubmitCredentials`.
-
-## Start an online service
-
-### REST API
-
-```bash
-graphwalker online -s RESTFUL -p 8080 -m login.json "random(edge_coverage(100))"
-```
-
-Then interact via HTTP:
-
-```bash
-curl http://localhost:8080/graphwalker/hasNext
-# {"result":"ok","hasNext":"true"}
-
-curl http://localhost:8080/graphwalker/getNext
-# {"result":"ok","currentElementName":"e_OpenBrowser","currentElementID":"e_init","modelId":"login-model"}
-```
-
-See [REST API](rest-api) for the full reference.
-
-### WebSocket API
-
-```bash
-graphwalker online -s WEBSOCKET -p 8887 -m login.json "random(edge_coverage(100))"
-```
-
-Connect with any WebSocket client and send JSON commands. See [WebSocket API](websocket-api) for details.
-
-## Use GraphWalker Studio
-
-Launch the visual editor:
-
-```bash
-graphwalker-studio
-```
-
-Open [http://localhost:9090](http://localhost:9090) in your browser. You can:
-
-- Create and edit models visually
-- Import JSON and GraphML files
-- Run test generation with play/pause/step controls
-- Set breakpoints on elements
-- Watch execution in real time
-
-See [GraphWalker Studio](studio) for the full guide.
-
-## Next steps
-
-- [Generators](generators) — learn about all the path generation algorithms
-- [Stop Conditions](stop-conditions) — understand when and how traversal stops
-- [JSON Model Format](json-format) — full specification for model files
-- [CLI Reference](cli) — all commands, flags, and options
diff --git a/graphwalker-rs/doc/index.md b/graphwalker-rs/doc/index.md
deleted file mode 100644
index 6d31146a2..000000000
--- a/graphwalker-rs/doc/index.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-layout: default
-title: Home
-nav_order: 1
----
-
-# GraphWalker
-
-GraphWalker is a model-based testing tool. It reads models in the shape of directed graphs and generates test paths from them.
-
-Tests are modeled as graphs where **vertices** represent states and **edges** represent transitions. GraphWalker traverses these graphs using configurable path generators and stop conditions, producing sequences of steps that drive automated test execution.
-
----
-
-## Features
-
-- **Multiple path generators** — random, quick random, weighted random, A*, shortest all paths, predefined path, and New York Street Sweeper (Chinese Postman)
-- **Flexible stop conditions** — edge/vertex coverage, reached element, time duration, length, requirement coverage, and combinations with AND/OR
-- **Guards and actions** — edges can have boolean guards and script actions that modify execution state
-- **Multi-model support** — coordinate across models using shared states
-- **Requirements tracking** — tag elements with requirement IDs and track coverage
-- **Multiple interfaces** — CLI (offline batch), REST API, WebSocket API, and a visual Studio editor
-- **Deterministic replay** — seed-based random generation for reproducible test paths
-
-## Getting started
-
-See the [Getting Started](getting-started) guide for a walkthrough of creating a model and generating your first test path.
-
-## Contents
-
-| Page | Description |
-|------|-------------|
-| [Model-Based Testing](model-based-testing) | What MBT is and how GraphWalker implements it |
-| [Getting Started](getting-started) | Tutorial: install, create a model, run it |
-| [CLI Reference](cli) | All CLI subcommands, flags, and options |
-| [Generators](generators) | Path generation algorithms |
-| [Stop Conditions](stop-conditions) | When to stop generating |
-| [JSON Model Format](json-format) | Complete specification of the model file format |
-| [REST API](rest-api) | HTTP API for online test execution |
-| [WebSocket API](websocket-api) | WebSocket protocol for real-time interaction |
-| [GraphWalker Studio](studio) | Visual model editor and test runner |
diff --git a/graphwalker-rs/doc/json-format.md b/graphwalker-rs/doc/json-format.md
deleted file mode 100644
index 98001d684..000000000
--- a/graphwalker-rs/doc/json-format.md
+++ /dev/null
@@ -1,242 +0,0 @@
----
-layout: default
-title: JSON Model Format
-nav_order: 7
----
-
-# JSON Model Format
-
-GraphWalker models are defined in JSON files. A file contains one or more models, each with vertices (states) and edges (transitions).
-
-## Root structure
-
-```json
-{
- "models": [ ... ],
- "name": "optional name",
- "seed": 12345
-}
-```
-
-| Field | Type | Required | Description |
-|-------|------|:--------:|-------------|
-| `models` | array | Yes | Array of model objects |
-| `name` | string | No | Name for the model collection |
-| `seed` | integer | No | Random seed for reproducible execution |
-
----
-
-## Model
-
-Each model is a directed graph with its own generator, start element, and set of vertices and edges.
-
-```json
-{
- "name": "LoginTest",
- "id": "853429e2-0528-48b9-97b3-7725eafbb8b5",
- "generator": "random(edge_coverage(100))",
- "startElementId": "e0",
- "actions": ["x = 0;"],
- "requirements": ["REQ001"],
- "properties": { "key": "value" },
- "vertices": [ ... ],
- "edges": [ ... ],
- "predefinedPathEdgeIds": ["e0", "e1", "e2"]
-}
-```
-
-| Field | Type | Required | Description |
-|-------|------|:--------:|-------------|
-| `name` | string | No | Human-readable model name |
-| `id` | string | No | Unique identifier |
-| `generator` | string | No | Generator and stop condition DSL string (e.g., `"random(edge_coverage(100))"`) |
-| `startElementId` | string | No | ID of the element to start execution from |
-| `actions` | array of strings | No | Model-level initialization scripts, executed before traversal begins |
-| `requirements` | array of strings | No | Requirement IDs associated with the model |
-| `properties` | object | No | Arbitrary key-value metadata |
-| `vertices` | array | No | Vertex (state) definitions |
-| `edges` | array | No | Edge (transition) definitions |
-| `predefinedPathEdgeIds` | array of strings | No | Ordered edge IDs for the `predefined_path` generator |
-
----
-
-## Vertex
-
-A vertex represents a state in the model.
-
-```json
-{
- "id": "n0",
- "name": "v_LoginPage",
- "sharedState": "LOGGED_OUT",
- "actions": ["validatePage();"],
- "requirements": ["REQ_UI_001"],
- "properties": { "x": 100, "y": 200 }
-}
-```
-
-| Field | Type | Required | Description |
-|-------|------|:--------:|-------------|
-| `id` | string | Yes | Unique identifier within the model |
-| `name` | string | No | Human-readable name. Convention: prefix with `v_` |
-| `sharedState` | string | No | Shared state identifier for multi-model coordination. Vertices in different models with the same `sharedState` act as portals. |
-| `actions` | array of strings | No | Scripts executed when this vertex is visited |
-| `requirements` | array of strings | No | Requirement IDs satisfied by visiting this vertex |
-| `properties` | object | No | Custom metadata (often used for UI layout coordinates) |
-
----
-
-## Edge
-
-An edge represents a transition between two vertices.
-
-```json
-{
- "id": "e1",
- "name": "e_SubmitLogin",
- "sourceVertexId": "n0",
- "targetVertexId": "n1",
- "guard": "!loggedIn",
- "actions": ["loggedIn = true;"],
- "requirements": ["REQ_AUTH_001"],
- "properties": {},
- "weight": 0.8,
- "dependency": 100
-}
-```
-
-| Field | Type | Required | Description |
-|-------|------|:--------:|-------------|
-| `id` | string | Yes | Unique identifier within the model |
-| `name` | string | No | Human-readable name. Convention: prefix with `e_` |
-| `sourceVertexId` | string | No | ID of the source vertex. If omitted, the edge has no source (start edge). |
-| `targetVertexId` | string | Yes | ID of the target vertex |
-| `guard` | string | No | Boolean expression that must be true for the edge to be traversable |
-| `actions` | array of strings | No | Scripts executed when this edge is traversed |
-| `requirements` | array of strings | No | Requirement IDs satisfied by traversing this edge |
-| `properties` | object | No | Custom metadata |
-| `weight` | number | No | Selection probability for `weighted_random` generator (0.0 to 1.0) |
-| `dependency` | integer | No | Dependency priority for `dependency_edge_coverage` condition (0 to 100) |
-
-### Start edges
-
-An edge without a `sourceVertexId` is a **start edge**. It represents the initial transition into the model. The `startElementId` in the model typically points to a start edge.
-
-### Guards
-
-Guard expressions are evaluated as boolean conditions using the execution context's variables. Variables are set by edge and vertex actions.
-
-Examples:
-- `"loggedIn"` — true when the variable `loggedIn` is truthy
-- `"!loggedIn"` — true when not logged in
-- `"itemCount > 0"` — comparison
-- `"loggedIn && itemCount > 0"` — compound condition
-
-An edge with a guard that evaluates to false is unavailable for traversal. An empty or missing guard means the edge is always available.
-
-### Actions
-
-Actions are scripts that modify the execution context. They run when the element is visited.
-
-Examples:
-- `"loggedIn = true;"` — set a variable
-- `"count = count + 1;"` — increment
-- `"rememberMe = !rememberMe;"` — toggle
-
-Multiple actions on an element are executed in order.
-
-### Weights
-
-Edge weights control selection probability in the `weighted_random` generator. The sum of weights on all outgoing edges from a vertex must not exceed 1.0. Edges with weight 0 share the remaining probability equally.
-
-### Dependencies
-
-Edge dependency values (0–100) are used by the `dependency_edge_coverage` stop condition. Edges with dependency at or above the threshold must be visited for the condition to be fulfilled.
-
----
-
-## Multi-model files
-
-A JSON file can contain multiple models. Models coordinate through **shared states**.
-
-```json
-{
- "models": [
- {
- "name": "LoginModel",
- "generator": "random(edge_coverage(100))",
- "startElementId": "e0",
- "vertices": [
- { "id": "n0", "name": "v_Start" },
- { "id": "n1", "name": "v_LoggedIn", "sharedState": "AUTHENTICATED" }
- ],
- "edges": [
- { "id": "e0", "name": "e_Init", "targetVertexId": "n0" },
- { "id": "e1", "name": "e_Login", "sourceVertexId": "n0", "targetVertexId": "n1" }
- ]
- },
- {
- "name": "DashboardModel",
- "generator": "random(edge_coverage(100))",
- "vertices": [
- { "id": "d0", "name": "v_Dashboard", "sharedState": "AUTHENTICATED" },
- { "id": "d1", "name": "v_Settings" }
- ],
- "edges": [
- { "id": "d_e0", "name": "e_OpenSettings", "sourceVertexId": "d0", "targetVertexId": "d1" },
- { "id": "d_e1", "name": "e_Back", "sourceVertexId": "d1", "targetVertexId": "d0" }
- ]
- }
- ]
-}
-```
-
-When GraphWalker reaches `v_LoggedIn` in `LoginModel`, it can portal to `v_Dashboard` in `DashboardModel` because both have `sharedState: "AUTHENTICATED"`. This allows testing cross-cutting flows that span multiple subsystems.
-
----
-
-## Predefined path
-
-The `predefinedPathEdgeIds` field defines a specific traversal order:
-
-```json
-{
- "name": "RegressionTest",
- "generator": "predefined_path(predefined_path)",
- "startElementId": "n0",
- "vertices": [
- { "id": "n0", "name": "v_A" },
- { "id": "n1", "name": "v_B" }
- ],
- "edges": [
- { "id": "e0", "name": "e_AtoB", "sourceVertexId": "n0", "targetVertexId": "n1" },
- { "id": "e1", "name": "e_BtoA", "sourceVertexId": "n1", "targetVertexId": "n0" }
- ],
- "predefinedPathEdgeIds": ["e0", "e1", "e0"]
-}
-```
-
-The traversal follows exactly: `v_A` → `e_AtoB` → `v_B` → `e_BtoA` → `v_A` → `e_AtoB` → `v_B`.
-
----
-
-## Minimal example
-
-The smallest valid model:
-
-```json
-{
- "models": [
- {
- "generator": "random(vertex_coverage(100))",
- "startElementId": "e0",
- "vertices": [
- { "id": "n0", "name": "v_Start" }
- ],
- "edges": [
- { "id": "e0", "name": "e_Init", "targetVertexId": "n0" }
- ]
- }
- ]
-}
-```
diff --git a/graphwalker-rs/doc/model-based-testing.md b/graphwalker-rs/doc/model-based-testing.md
deleted file mode 100644
index 62d2a92c6..000000000
--- a/graphwalker-rs/doc/model-based-testing.md
+++ /dev/null
@@ -1,79 +0,0 @@
----
-layout: default
-title: Model-Based Testing
-nav_order: 2
----
-
-# Model-Based Testing
-
-## What is Model-Based Testing?
-
-Model-Based Testing (MBT) is a software testing approach where test cases are derived from a model that describes the behavior of the system under test. Instead of writing individual test cases by hand, you build a model of how the system works, and a tool automatically generates test sequences from that model.
-
-The model is typically a **directed graph** (also called a finite state machine) where:
-
-- **Vertices** (nodes) represent **states** of the system — observable conditions like "logged in", "shopping cart empty", or "error page displayed"
-- **Edges** (transitions) represent **actions** that move the system from one state to another — things like "click login", "add item to cart", or "submit form"
-
-A test sequence is a **path through the graph**: a series of alternating edges and vertices that represents a realistic usage scenario of the system.
-
-## Why use MBT?
-
-**Coverage guarantees.** Instead of hoping your manually written tests cover all the important paths, MBT tools can mathematically ensure that every edge, every vertex, or every requirement has been exercised.
-
-**Reduced maintenance.** When the system changes, you update the model — a single graph — rather than dozens or hundreds of individual test scripts.
-
-**Combinatorial exploration.** A model with 10 vertices and 20 edges can produce thousands of distinct test paths. MBT tools explore this space systematically, finding paths that a human tester might never think to try.
-
-**Separation of concerns.** The model captures *what* to test. The generator decides *how* to traverse it. The test implementation maps steps to actual system interactions. Each can evolve independently.
-
-## How GraphWalker implements MBT
-
-GraphWalker takes a model (a directed graph defined in JSON or GraphML) and generates a sequence of steps by walking the graph. The walk is controlled by two things:
-
-1. **A path generator** — the algorithm that decides which edge to take next. Options range from pure random selection to optimal mathematical routes.
-
-2. **A stop condition** — the rule that decides when to stop walking. Options include coverage targets (e.g., "visit every edge"), reaching a specific element, time limits, or step counts.
-
-### The execution loop
-
-```
-1. Start at the designated start element
-2. While the stop condition is not fulfilled:
- a. If at a vertex: choose an outgoing edge (using the generator algorithm)
- b. If at an edge: move to the target vertex
- c. Execute any actions on the current element
- d. Check any guards on candidate edges
- e. Record the visit
-3. Output the sequence of visited elements
-```
-
-### Guards and actions
-
-Edges can have **guards** — boolean expressions that must evaluate to true for the edge to be available. This lets you model conditional behavior: "the logout button is only available when logged in."
-
-Edges and vertices can have **actions** — scripts that execute when the element is visited. Actions modify variables in the execution context: `loggedIn = true` or `itemCount = itemCount + 1`. Guards can reference these variables, creating dynamic behavior that changes which paths are available as the walk progresses.
-
-### Multi-model execution
-
-Complex systems can be modeled as multiple graphs that share state through **shared state vertices**. When GraphWalker reaches a shared state vertex in one model, it can jump to the matching shared state vertex in another model, continuing the walk there. This lets you decompose a large system into manageable sub-models that coordinate through well-defined handoff points.
-
-### Requirements tracking
-
-Vertices and edges can be tagged with **requirement IDs**. As GraphWalker traverses the model, it tracks which requirements have been covered. The `requirement_coverage` stop condition can ensure that all requirements are exercised before the walk ends.
-
-## Example
-
-Consider testing a login flow. The model might look like this:
-
-```
-[Start] --e_OpenApp--> [v_LoginPage] --e_EnterCredentials--> [v_LoginPage]
- | |
- +--e_ValidLogin--> [v_Dashboard] -----+
- | |
- +--e_InvalidLogin-> [v_LoginPage]
- |
- +--e_ForgotPassword--> [v_ResetPage]
-```
-
-Running `random(edge_coverage(100))` on this model produces a path that visits every edge at least once, exercising the valid login, invalid login, and forgot password flows in a single test sequence. The specific order varies with each run (unless you fix the random seed), but coverage is guaranteed.
diff --git a/graphwalker-rs/doc/rest-api.md b/graphwalker-rs/doc/rest-api.md
deleted file mode 100644
index 43df079e9..000000000
--- a/graphwalker-rs/doc/rest-api.md
+++ /dev/null
@@ -1,216 +0,0 @@
----
-layout: default
-title: REST API
-nav_order: 8
----
-
-# REST API
-
-Start the REST server with:
-
-```bash
-graphwalker online -s RESTFUL -p 8080 -m model.json "random(edge_coverage(100))"
-```
-
-All endpoints are under the `/graphwalker` path prefix.
-
-## Response format
-
-Successful responses include `"result": "ok"`. Errors include `"result": "nok"` with an `"error"` message.
-
----
-
-## POST /graphwalker/load
-
-Load a model and initialize the execution engine.
-
-**Request body:** JSON model definition (the full model file content).
-
-**Response:**
-
-```json
-{
- "result": "ok",
- "seed": 7298345612
-}
-```
-
-The returned `seed` can be used later to reproduce the same traversal.
-
-**Error example:**
-
-```json
-{
- "result": "nok",
- "error": "Model has no generator specified"
-}
-```
-
----
-
-## GET /graphwalker/hasNext
-
-Check whether more steps are available.
-
-**Response:**
-
-```json
-{
- "result": "ok",
- "hasNext": "true"
-}
-```
-
-Returns `"false"` when the stop condition is fulfilled.
-
-**Error (no model loaded):**
-
-```json
-{
- "result": "nok",
- "error": "No model(s) are loaded."
-}
-```
-
----
-
-## GET /graphwalker/getNext
-
-Advance to the next element and return it.
-
-**Response:**
-
-```json
-{
- "result": "ok",
- "currentElementName": "e_Login",
- "currentElementID": "e1",
- "modelId": "853429e2-0528-48b9-97b3-7725eafbb8b5"
-}
-```
-
-| Field | Description |
-|-------|-------------|
-| `currentElementName` | Name of the current element (vertex or edge) |
-| `currentElementID` | ID of the current element |
-| `modelId` | ID of the model containing the element |
-
----
-
-## GET /graphwalker/getData
-
-Get the current execution data (all variables).
-
-**Response:**
-
-```json
-{
- "result": "ok",
- "data": "loggedIn=true; itemCount=3"
-}
-```
-
-The `data` field is a string representation of all variables in the execution context.
-
----
-
-## PUT /graphwalker/setData/{script}
-
-Execute a script to modify execution data. The script is passed as a URL path parameter.
-
-**Example request:**
-
-```
-PUT /graphwalker/setData/loggedIn%20%3D%20true%3B
-```
-
-(URL-decoded: `loggedIn = true;`)
-
-**Response:**
-
-```json
-{
- "result": "ok"
-}
-```
-
----
-
-## PUT /graphwalker/restart
-
-Reset execution to the initial state. The model remains loaded but all visit counts, variables, and state are cleared.
-
-**Response:**
-
-```json
-{
- "result": "ok"
-}
-```
-
----
-
-## GET /graphwalker/getStatistics
-
-Get coverage statistics for the current execution.
-
-**Response:**
-
-```json
-{
- "result": "ok",
- "totalNumberOfVertices": 5,
- "totalNumberOfEdges": 8,
- "totalNumberOfVisitedVertices": 3,
- "totalNumberOfVisitedEdges": 6,
- "totalNumberOfUnvisitedVertices": 2,
- "totalNumberOfUnvisitedEdges": 2,
- "vertexCoverage": 60,
- "edgeCoverage": 75
-}
-```
-
-| Field | Description |
-|-------|-------------|
-| `totalNumberOfVertices` | Total vertices in all models |
-| `totalNumberOfEdges` | Total edges in all models |
-| `totalNumberOfVisitedVertices` | Vertices visited at least once |
-| `totalNumberOfVisitedEdges` | Edges visited at least once |
-| `totalNumberOfUnvisitedVertices` | Vertices not yet visited |
-| `totalNumberOfUnvisitedEdges` | Edges not yet visited |
-| `vertexCoverage` | Percentage of vertices visited (0–100) |
-| `edgeCoverage` | Percentage of edges visited (0–100) |
-
----
-
-## Typical usage pattern
-
-```bash
-# 1. Load a model (or start the server with -m flag to pre-load)
-curl -X POST http://localhost:8080/graphwalker/load -d @model.json
-
-# 2. Loop: check and step
-while curl -s http://localhost:8080/graphwalker/hasNext | grep -q '"true"'; do
- STEP=$(curl -s http://localhost:8080/graphwalker/getNext)
- echo "$STEP"
- # Execute the test step indicated by currentElementName
-done
-
-# 3. Check final coverage
-curl -s http://localhost:8080/graphwalker/getStatistics
-```
-
----
-
-## Seed and determinism
-
-When starting with `--seed `, the same seed produces identical traversal paths. The seed is also returned by the `/load` endpoint, so you can capture it and replay later:
-
-```bash
-# Capture the seed
-SEED=$(curl -s -X POST http://localhost:8080/graphwalker/load -d @model.json | jq -r '.seed')
-echo "Seed: $SEED"
-
-# Replay with the same seed later
-graphwalker online -s RESTFUL --seed $SEED -m model.json "random(edge_coverage(100))"
-```
diff --git a/graphwalker-rs/doc/stop-conditions.md b/graphwalker-rs/doc/stop-conditions.md
deleted file mode 100644
index b98b9f615..000000000
--- a/graphwalker-rs/doc/stop-conditions.md
+++ /dev/null
@@ -1,263 +0,0 @@
----
-layout: default
-title: Stop Conditions
-nav_order: 6
----
-
-# Stop Conditions
-
-A stop condition determines when a generator should stop traversing the graph. Most conditions require execution to be at a **vertex** to be considered fulfilled — this prevents stopping mid-transition.
-
-## DSL syntax
-
-Stop conditions are specified inside generator parentheses:
-
-```
-generator(condition)
-```
-
-Conditions can be combined with `and`/`&&` (all must be met) or `or`/`||` (any must be met):
-
-```
-generator(condition1 and condition2)
-generator(condition1 or condition2)
-```
-
-Condition names are case-insensitive. Both `snake_case` and `camelCase` forms are accepted.
-
----
-
-## edge_coverage
-
-Stop when a percentage of all edges have been visited at least once.
-
-**Syntax:** `edge_coverage(percent)` or `edgecoverage(percent)`
-
-**Parameter:** integer 0–100
-
-**Fulfilled when:** `(visited_edges / total_edges) >= (percent / 100)` and execution is at a vertex.
-
-**Example:**
-```
-random(edge_coverage(100)) -- visit every edge
-random(edge_coverage(50)) -- visit at least half
-```
-
----
-
-## vertex_coverage
-
-Stop when a percentage of all vertices have been visited at least once.
-
-**Syntax:** `vertex_coverage(percent)` or `vertexcoverage(percent)`
-
-**Parameter:** integer 0–100
-
-**Fulfilled when:** `(visited_vertices / total_vertices) >= (percent / 100)` and execution is at a vertex.
-
-**Example:**
-```
-quick_random(vertex_coverage(100))
-random(vertex_coverage(80))
-```
-
----
-
-## reached_vertex
-
-Stop when a specific vertex (by name) has been visited.
-
-**Syntax:** `reached_vertex(name)` or `reachedvertex(name)`
-
-**Parameter:** vertex name (string)
-
-**Fulfilled when:** execution is at a vertex matching the given name. Once fulfilled, stays fulfilled even if execution moves away ("sticky").
-
-**Note:** If multiple vertices share the same name, reaching any one of them is sufficient.
-
-**Example:**
-```
-a_star(reached_vertex(v_Checkout))
-random(reached_vertex(v_ErrorPage))
-```
-
----
-
-## reached_edge
-
-Stop when a specific edge (by name) has been traversed.
-
-**Syntax:** `reached_edge(name)` or `reachededge(name)`
-
-**Parameter:** edge name (string)
-
-**Fulfilled when:** execution is at an edge matching the given name. Once fulfilled, stays fulfilled ("sticky").
-
-**Example:**
-```
-a_star(reached_edge(e_SubmitOrder))
-random(reached_edge(e_Login))
-```
-
----
-
-## reached_shared_state
-
-Stop when a vertex belonging to a specific shared state has been reached.
-
-**Syntax:** `reached_shared_state(name)` or `reachedsharedstate(name)`
-
-**Parameter:** shared state name (string)
-
-**Fulfilled when:** execution is at a vertex with a matching `sharedState` value. Sticky.
-
-**Example:**
-```
-a_star(reached_shared_state(LOGGED_IN))
-```
-
----
-
-## length
-
-Stop after a specified number of total element visits.
-
-**Syntax:** `length(count)`
-
-**Parameter:** unsigned integer
-
-**Fulfilled when:** the total number of visited elements (vertices + edges combined) reaches the count, and execution is at a vertex.
-
-**Example:**
-```
-random(length(100)) -- stop after 100 steps
-random(length(1000)) -- stop after 1000 steps
-```
-
----
-
-## time_duration
-
-Stop after a specified duration of wall-clock time.
-
-**Syntax:** `time_duration(seconds)` or `timeduration(seconds)`
-
-**Parameter:** integer (seconds)
-
-**Fulfilled when:** the elapsed time since the condition was created exceeds the specified duration, and execution is at a vertex.
-
-**Example:**
-```
-random(time_duration(60)) -- run for 1 minute
-random(time_duration(300)) -- run for 5 minutes
-```
-
----
-
-## requirement_coverage
-
-Stop when a percentage of all requirements have been tested (passed or failed).
-
-**Syntax:** `requirement_coverage(percent)` or `requirementcoverage(percent)`
-
-**Parameter:** integer 0–100
-
-**Fulfilled when:** `((passed + failed) / total_requirements) >= (percent / 100)` and execution is at a vertex. If no requirements are defined, the condition is always fulfilled.
-
-**Example:**
-```
-random(requirement_coverage(100))
-```
-
----
-
-## dependency_edge_coverage
-
-Stop when all edges with a dependency value at or above a threshold have been visited.
-
-**Syntax:** `dependency_edge_coverage(threshold)` or `dependencyedgecoverage(threshold)`
-
-**Parameter:** integer 0–100 (dependency threshold)
-
-**Fulfilled when:** all edges with `dependency >= threshold/100` have been visited, and execution is at a vertex.
-
-**Example:**
-```
-random(dependency_edge_coverage(80)) -- cover all edges with dependency >= 80%
-```
-
----
-
-## predefined_path
-
-Stop when the predefined path has been fully traversed.
-
-**Syntax:** `predefined_path` or `predefinedpath`
-
-No parameters. Used with the `predefined_path` generator.
-
-**Fulfilled when:** the predefined path index has reached the end of the sequence.
-
-**Example:**
-```
-predefined_path(predefined_path)
-```
-
----
-
-## never
-
-Never stops. Used for generators that manage their own termination (like `new_york_street_sweeper`) or as a component in combined conditions.
-
-**Syntax:** `never`
-
-No parameters.
-
-**Example:**
-```
-random(never) -- walk forever (until interrupted)
-```
-
----
-
-## Combining conditions
-
-### AND (all must be met)
-
-Use `and` or `&&`:
-
-```
-random(edge_coverage(100) and reached_vertex(v_End))
-random(length(100) && vertex_coverage(80))
-```
-
-The condition is fulfilled when **all** sub-conditions are fulfilled simultaneously. Fulfillment progress is the average of all sub-conditions.
-
-### OR (any must be met)
-
-Use `or` or `||`:
-
-```
-random(edge_coverage(100) or time_duration(60))
-random(reached_vertex(v_A) || reached_vertex(v_B))
-```
-
-The condition is fulfilled when **any** sub-condition is fulfilled. Fulfillment progress is the maximum of all sub-conditions.
-
----
-
-## Summary table
-
-| Condition | Parameter | Sticky | Description |
-|-----------|-----------|:------:|-------------|
-| `edge_coverage` | percent | No | Percentage of edges visited |
-| `vertex_coverage` | percent | No | Percentage of vertices visited |
-| `reached_vertex` | name | Yes | Specific vertex reached |
-| `reached_edge` | name | Yes | Specific edge traversed |
-| `reached_shared_state` | name | Yes | Shared state reached |
-| `length` | count | No | Total element visits |
-| `time_duration` | seconds | No | Wall-clock time elapsed |
-| `requirement_coverage` | percent | No | Percentage of requirements tested |
-| `dependency_edge_coverage` | threshold | No | High-dependency edges visited |
-| `predefined_path` | none | No | Predefined path completed |
-| `never` | none | No | Never fulfilled |
diff --git a/graphwalker-rs/doc/studio.md b/graphwalker-rs/doc/studio.md
deleted file mode 100644
index d67e835b4..000000000
--- a/graphwalker-rs/doc/studio.md
+++ /dev/null
@@ -1,164 +0,0 @@
----
-layout: default
-title: GraphWalker Studio
-nav_order: 10
----
-
-# GraphWalker Studio
-
-GraphWalker Studio is a web-based visual editor and test execution environment. It lets you create models graphically, run test generation with real-time visualization, and debug execution with breakpoints and step-through controls.
-
-## Starting Studio
-
-```bash
-graphwalker-studio [OPTIONS]
-```
-
-| Option | Short | Long | Default | Description |
-|--------|-------|------|---------|-------------|
-| Browser port | `-b` | `--browser-port` | `9090` | HTTP port for the web UI |
-| WebSocket port | `-w` | `--websocket-port` | `9999` | WebSocket port for the execution engine |
-| Static directory | | `--static-dir` | `static` | Directory containing the frontend files |
-| Debug logging | | `--debug` | off | Enable debug output |
-
-After starting, open [http://localhost:9090](http://localhost:9090) in your browser.
-
----
-
-## Architecture
-
-Studio consists of two servers and a browser-based frontend:
-
-```
-Browser (React + Cytoscape.js)
- ↕ WebSocket (JSON messages)
-graphwalker-studio backend
- ├── HTTP server (port 9090) — serves the web UI
- └── WebSocket server (port 9999) — handles execution commands
-```
-
-The HTTP server serves the static frontend files. The WebSocket server handles all model operations: validation, execution, session management, and real-time event broadcasting.
-
-The frontend communicates with the backend exclusively through the [WebSocket API](websocket-api). All commands documented in the WebSocket API reference are available through Studio.
-
----
-
-## Features
-
-### Model editing
-
-- **Create models** with vertices and edges using a visual graph editor
-- **Edit properties** for any element: name, guards, actions, requirements, shared state, weight, dependency
-- **Multiple models** in a single file with tabs for each model
-- **Import** JSON and GraphML model files
-- **Export** models as JSON
-
-### Test execution
-
-- **Play** to run the generator continuously
-- **Pause** to stop execution at the current element
-- **Step** to advance exactly one element at a time
-- **Stop** to end the session
-
-During execution, visited elements are highlighted in the graph. The current element is visually distinct from previously visited elements.
-
-### Execution settings
-
-- **Seed** — set a random seed for reproducible execution
-- **Global data** — initialize variables before execution starts
-- **Delay** — set a delay between steps for visual observation
-
-### Breakpoints
-
-Click on any vertex or edge to set a breakpoint. When execution reaches a breakpoint element, the session automatically pauses. This allows you to inspect the current state before continuing.
-
-### Session observation
-
-Multiple Studio instances (or WebSocket clients) can connect to the same backend. One session can be observed by another:
-
-1. Client A starts a session (presses Play)
-2. Client B opens Studio and sees the session in the session list
-3. Client B subscribes to Client A's session
-4. Client B sees real-time updates as Client A's execution progresses
-
-This is useful for demonstrations, collaborative debugging, or monitoring test execution from multiple views.
-
-### Model validation
-
-Studio validates models in real time as you edit. Validation checks include:
-
-- Vertices must have non-empty names
-- Element IDs must be unique within a model
-- Edges must have target vertices
-- Vertex and edge names must not contain spaces
-- Edge weights must be non-negative
-- Self-loops on unnamed edges are flagged
-- Models must have a start element or a shared state
-- Models used with `random(edge_coverage(100))` flag cul-de-sac vertices (vertices with no outgoing edges)
-
----
-
-## Workflow example
-
-### 1. Create a model
-
-Open Studio and create a new model. Add vertices for each state in your system and edges for each transition. Set names, guards, and actions on the elements.
-
-### 2. Set the generator
-
-Configure the generator string in the model properties. For example: `random(edge_coverage(100))`.
-
-### 3. Validate
-
-Studio shows validation issues in real time. Fix any issues before running.
-
-### 4. Run
-
-Press Play. Watch the execution traverse the graph. Visited elements change color. The status bar shows progress toward the stop condition.
-
-### 5. Debug
-
-If something unexpected happens:
-- **Pause** execution
-- **Set breakpoints** on elements of interest
-- **Step** through one element at a time
-- **Inspect data** to see current variable values
-
-### 6. Export
-
-Save the model as JSON for use with the CLI or integration into your test pipeline:
-
-```bash
-graphwalker offline -g exported-model.json
-```
-
----
-
-## Keyboard shortcuts
-
-The Studio frontend supports standard keyboard interactions for graph editing. Refer to the Cytoscape.js documentation for graph manipulation controls (pan, zoom, select).
-
----
-
-## Connecting programmatically
-
-Since Studio uses the standard WebSocket API, you can connect to its WebSocket port from any client:
-
-```javascript
-const ws = new WebSocket("ws://localhost:9999/graphwalker");
-
-ws.onopen = () => {
- ws.send(JSON.stringify({
- command: "start",
- gw: { models: [ /* your model */ ] },
- seed: 42
- }));
-};
-
-ws.onmessage = (event) => {
- const msg = JSON.parse(event.data);
- console.log(msg);
-};
-```
-
-This lets you build custom test runners, dashboards, or integrations that interact with the same execution engine that powers Studio.
diff --git a/graphwalker-rs/doc/websocket-api.md b/graphwalker-rs/doc/websocket-api.md
deleted file mode 100644
index e528d3634..000000000
--- a/graphwalker-rs/doc/websocket-api.md
+++ /dev/null
@@ -1,560 +0,0 @@
----
-layout: default
-title: WebSocket API
-nav_order: 9
----
-
-# WebSocket API
-
-Start the WebSocket server with:
-
-```bash
-graphwalker online -s WEBSOCKET -p 8887 -m model.json "random(edge_coverage(100))"
-```
-
-Connect with any WebSocket client to `ws://localhost:8887/graphwalker`.
-
-All messages are JSON objects. Commands from the client include a `"command"` field. Responses include `"success": true` or `"success": false` with a `"message"` field on failure.
-
----
-
-## Commands
-
-### start
-
-Create a new execution session.
-
-**Request:**
-
-```json
-{
- "command": "start",
- "gw": { "models": [ ... ] },
- "seed": 12345,
- "globalData": "x = 0; y = 0;",
- "name": "My Test Session"
-}
-```
-
-| Field | Required | Description |
-|-------|:--------:|-------------|
-| `gw` | Yes | Model definition (same format as the JSON model file) |
-| `seed` | No | Random seed for deterministic execution |
-| `globalData` | No | Initialization script for global variables |
-| `name` | No | Session name (defaults to the first model's name) |
-
-**Response:**
-
-```json
-{
- "command": "start",
- "success": true,
- "seed": 12345,
- "sessionId": "session-1"
-}
-```
-
----
-
-### getNext
-
-Advance the execution by one step.
-
-**Request:**
-
-```json
-{ "command": "getNext" }
-```
-
-**Response:**
-
-```json
-{
- "command": "visitedElement",
- "success": true,
- "modelId": "model-id",
- "elementId": "e1",
- "name": "e_Login",
- "visitedCount": 1,
- "totalCount": 12,
- "stopConditionFulfillment": 0.45,
- "data": "loggedIn=false"
-}
-```
-
-| Field | Description |
-|-------|-------------|
-| `modelId` | ID of the model containing the element |
-| `elementId` | ID of the visited element |
-| `name` | Name of the visited element |
-| `visitedCount` | How many times this element has been visited |
-| `totalCount` | Total elements in the model |
-| `stopConditionFulfillment` | Progress toward the stop condition (0.0 to 1.0) |
-| `data` | Current variable values |
-
-If the session is paused, `getNext` blocks until the session is resumed or stepped.
-
----
-
-### hasNext
-
-Check whether more steps are available.
-
-**Request:**
-
-```json
-{ "command": "hasNext" }
-```
-
-**Response:**
-
-```json
-{
- "command": "hasNext",
- "success": true,
- "hasNext": true
-}
-```
-
----
-
-### getData
-
-Get current execution variables.
-
-**Request:**
-
-```json
-{ "command": "getData" }
-```
-
-**Response:**
-
-```json
-{
- "command": "getData",
- "success": true,
- "data": "loggedIn=true; count=5"
-}
-```
-
----
-
-### setData
-
-Modify execution variables.
-
-**Request:**
-
-```json
-{
- "command": "setData",
- "action": "loggedIn = true;"
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "setData",
- "success": true
-}
-```
-
----
-
-### getModel
-
-Retrieve the loaded model definition.
-
-**Request:**
-
-```json
-{ "command": "getModel" }
-```
-
-**Response:**
-
-```json
-{
- "command": "getModel",
- "success": true,
- "models": "{ \"models\": [ ... ] }"
-}
-```
-
-The `models` field is a JSON string (not a parsed object).
-
----
-
-### updateAllElements
-
-Get visit counts for all elements.
-
-**Request:**
-
-```json
-{ "command": "updateAllElements" }
-```
-
-**Response:**
-
-```json
-{
- "command": "updateAllElements",
- "success": true,
- "elements": [
- { "modelId": "model-1", "elementId": "n0", "visitedCount": 3 },
- { "modelId": "model-1", "elementId": "e0", "visitedCount": 2 }
- ]
-}
-```
-
----
-
-### check
-
-Validate a model without loading it.
-
-**Request:**
-
-```json
-{
- "command": "check",
- "gw": { "models": [ ... ] }
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "check",
- "success": true,
- "issues": []
-}
-```
-
-If there are issues:
-
-```json
-{
- "command": "check",
- "success": true,
- "issues": ["Name of vertex cannot be null or empty"]
-}
-```
-
----
-
-### convertGraphml
-
-Convert a GraphML model to JSON.
-
-**Request:**
-
-```json
-{
- "command": "convertGraphml",
- "graphml": " ... "
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "convertGraphml",
- "success": true,
- "models": "{ \"models\": [ ... ] }"
-}
-```
-
----
-
-## Session management
-
-The WebSocket API supports multiple concurrent sessions. These commands control session execution and observation.
-
-### listSessions
-
-List all active sessions.
-
-**Request:**
-
-```json
-{ "command": "listSessions" }
-```
-
-**Response:**
-
-```json
-{
- "command": "sessions",
- "success": true,
- "sessions": [
- { "id": "session-1", "name": "Login Test" },
- { "id": "session-2", "name": "Payment Flow" }
- ]
-}
-```
-
----
-
-### subscribeSession
-
-Subscribe to execution events from another session.
-
-**Request:**
-
-```json
-{
- "command": "subscribeSession",
- "sessionId": "session-1"
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "subscribeSession",
- "success": true,
- "sessionId": "session-1",
- "name": "Login Test",
- "models": { "models": [ ... ] },
- "elements": [
- { "modelId": "model-1", "elementId": "n0", "visitedCount": 2 }
- ],
- "seed": 12345,
- "paused": false
-}
-```
-
-After subscribing, you receive broadcast events (see below) whenever the session advances.
-
----
-
-### unsubscribeSession
-
-Stop receiving events from a subscribed session.
-
-**Request:**
-
-```json
-{ "command": "unsubscribeSession" }
-```
-
-**Response:**
-
-```json
-{
- "command": "unsubscribeSession",
- "success": true
-}
-```
-
----
-
-### pauseSession
-
-Pause an active session. Subsequent `getNext` calls block until resumed.
-
-**Request:**
-
-```json
-{
- "command": "pauseSession",
- "sessionId": "session-1"
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "pauseSession",
- "success": true
-}
-```
-
----
-
-### resumeSession
-
-Resume a paused session.
-
-**Request:**
-
-```json
-{
- "command": "resumeSession",
- "sessionId": "session-1"
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "resumeSession",
- "success": true
-}
-```
-
----
-
-### stepSession
-
-Execute exactly one step, then pause again.
-
-**Request:**
-
-```json
-{
- "command": "stepSession",
- "sessionId": "session-1"
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "stepSession",
- "success": true
-}
-```
-
----
-
-### setDelay
-
-Set a delay (in milliseconds) between execution steps. Useful for watching execution in real time.
-
-**Request:**
-
-```json
-{
- "command": "setDelay",
- "sessionId": "session-1",
- "value": 500
-}
-```
-
-**Response:**
-
-```json
-{
- "command": "setDelay",
- "success": true
-}
-```
-
----
-
-### setBreakpoints
-
-Set breakpoints on specific elements. When execution reaches a breakpoint, the session automatically pauses.
-
-**Request:**
-
-```json
-{
- "command": "setBreakpoints",
- "sessionId": "session-1",
- "breakpoints": ["model-1,n0", "model-1,e2"]
-}
-```
-
-Breakpoint format: `"modelId,elementId"`.
-
-**Response:**
-
-```json
-{
- "command": "setBreakpoints",
- "success": true
-}
-```
-
----
-
-## Broadcast events
-
-These are unsolicited messages sent to subscribed clients.
-
-### visitedElement
-
-Sent when a subscribed session traverses an element.
-
-```json
-{
- "command": "visitedElement",
- "modelId": "model-1",
- "elementId": "e1",
- "name": "e_Login",
- "visitedCount": 1,
- "totalCount": 12,
- "stopConditionFulfillment": 0.45,
- "data": "loggedIn=false"
-}
-```
-
-### sessionCreated
-
-Sent when a new session is created (to clients that called `listSessions`).
-
-```json
-{
- "command": "sessionCreated",
- "sessionId": "session-2",
- "name": "New Session"
-}
-```
-
-### sessionEnded
-
-Sent when a session terminates.
-
-```json
-{
- "command": "sessionEnded",
- "sessionId": "session-1"
-}
-```
-
-### sessionPaused
-
-Sent when a session is paused (manually or by breakpoint).
-
-```json
-{
- "command": "sessionPaused",
- "sessionId": "session-1"
-}
-```
-
-When triggered by a breakpoint:
-
-```json
-{
- "command": "sessionPaused",
- "sessionId": "session-1",
- "reason": "breakpoint",
- "modelId": "model-1",
- "elementId": "n0"
-}
-```
-
-### sessionResumed
-
-Sent when a session resumes.
-
-```json
-{
- "command": "sessionResumed",
- "sessionId": "session-1"
-}
-```
diff --git a/graphwalker-rs/graphwalker-cli/Cargo.toml b/graphwalker-rs/graphwalker-cli/Cargo.toml
deleted file mode 100644
index 313962ab8..000000000
--- a/graphwalker-rs/graphwalker-cli/Cargo.toml
+++ /dev/null
@@ -1,30 +0,0 @@
-[package]
-name = "graphwalker-cli"
-version = "0.1.0"
-edition = "2021"
-
-[[bin]]
-name = "graphwalker"
-path = "src/main.rs"
-
-[dependencies]
-graphwalker-core = { path = "../graphwalker-core" }
-graphwalker-dsl = { path = "../graphwalker-dsl" }
-graphwalker-io = { path = "../graphwalker-io" }
-graphwalker-model-checker = { path = "../graphwalker-model-checker" }
-graphwalker-restful = { path = "../graphwalker-restful" }
-rand = "0.8"
-clap = { version = "4", features = ["derive"] }
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
-tracing-subscriber = { version = "0.3", features = ["env-filter"] }
-
-[dev-dependencies]
-assert_cmd = "2"
-predicates = "3"
-serde_json = "1"
-reqwest = { version = "0.12", features = ["json"] }
-tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "net"] }
-tokio-tungstenite = "0.29"
-futures-util = "0.3"
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/check.rs b/graphwalker-rs/graphwalker-cli/src/commands/check.rs
deleted file mode 100644
index c399509f7..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/check.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use std::collections::HashSet;
-
-use clap::Args as ClapArgs;
-
-use super::{load_models, load_models_plain, CliResult};
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Model file and generator pairs: -m
- #[arg(short, long = "model", num_args = 2, action = clap::ArgAction::Append)]
- pub model: Vec,
-
- /// Model file with embedded generator (JSON)
- #[arg(short = 'g', long = "gw")]
- pub gw: Option,
-}
-
-pub fn run(args: Args) -> CliResult {
- if args.model.is_empty() && args.gw.is_none() {
- return Err("Either --model (-m) or --gw (-g) is required for check command".into());
- }
-
- let contexts = if let Some(ref gw_file) = args.gw {
- load_models_plain(std::slice::from_ref(gw_file))?
- } else {
- load_models(&args.model)?
- };
- let issues = graphwalker_model_checker::check_contexts(&contexts);
-
- if issues.is_empty() {
- println!("No issues found with the model(s).");
- } else {
- for issue in &issues {
- println!("{}", issue);
- }
- return Err(format!("{} issue(s) found", issues.len()).into());
- }
-
- print_statistics(&contexts);
-
- Ok(())
-}
-
-fn print_statistics(contexts: &[graphwalker_io::ModelContext]) {
- println!();
- println!("Statistics:");
-
- let mut total_edges = 0usize;
- let mut total_vertices = 0usize;
- let mut total_unique_edges = 0usize;
- let mut total_unique_vertices = 0usize;
-
- for ctx in contexts {
- let model = &ctx.model;
- let name = model.name().unwrap_or(model.id());
-
- let num_edges = model.edges().len();
- let num_vertices = model.vertices().len();
-
- let unique_edge_names: HashSet<&str> = model
- .edges()
- .iter()
- .filter_map(|e| e.name())
- .filter(|n| !n.is_empty())
- .collect();
- let unique_vertex_names: HashSet<&str> = model
- .vertices()
- .iter()
- .filter_map(|v| v.name())
- .filter(|n| !n.is_empty())
- .collect();
-
- println!(" Model: {}", name);
- println!(" Unique edges: {}", unique_edge_names.len());
- println!(" Unique vertices: {}", unique_vertex_names.len());
- println!(" Edge instances: {}", num_edges);
- println!(" Vertex instances: {}", num_vertices);
-
- total_edges += num_edges;
- total_vertices += num_vertices;
- total_unique_edges += unique_edge_names.len();
- total_unique_vertices += unique_vertex_names.len();
- }
-
- if contexts.len() > 1 {
- println!(" Total:");
- println!(" Unique edges: {}", total_unique_edges);
- println!(" Unique vertices: {}", total_unique_vertices);
- println!(" Edge instances: {}", total_edges);
- println!(" Vertex instances: {}", total_vertices);
- }
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/convert.rs b/graphwalker-rs/graphwalker-cli/src/commands/convert.rs
deleted file mode 100644
index e14e5d035..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/convert.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use std::path::Path;
-
-use clap::Args as ClapArgs;
-
-use super::CliResult;
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Input model file
- #[arg(short, long)]
- pub input: String,
-
- /// Output format: json
- #[arg(short, long, default_value = "json")]
- pub format: String,
-}
-
-pub fn run(args: Args) -> CliResult {
- let path = Path::new(&args.input);
- let contexts = graphwalker_io::read_model(path)?;
-
- match args.format.to_ascii_lowercase().as_str() {
- "json" => {
- let json = graphwalker_io::json::write_json_string(&contexts)?;
- println!("{}", json);
- }
- other => {
- return Err(format!("Unsupported output format: {}", other).into());
- }
- }
-
- Ok(())
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/methods.rs b/graphwalker-rs/graphwalker-cli/src/commands/methods.rs
deleted file mode 100644
index 653596e4f..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/methods.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use std::collections::BTreeSet;
-
-use clap::Args as ClapArgs;
-
-use super::{load_models_plain, CliResult};
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Model file(s)
- #[arg(short, long = "model", required = true)]
- pub model: Vec,
-}
-
-pub fn run(args: Args) -> CliResult {
- let contexts = load_models_plain(&args.model)?;
- let mut names = BTreeSet::new();
-
- for ctx in &contexts {
- for vertex in ctx.model.vertices() {
- if let Some(name) = vertex.name() {
- if !name.is_empty() {
- names.insert(name.to_string());
- }
- }
- }
- for edge in ctx.model.edges() {
- if let Some(name) = edge.name() {
- if !name.is_empty() {
- names.insert(name.to_string());
- }
- }
- }
- }
-
- for name in &names {
- println!("{}", name);
- }
-
- Ok(())
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/mod.rs b/graphwalker-rs/graphwalker-cli/src/commands/mod.rs
deleted file mode 100644
index 9546c69e8..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/mod.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-pub mod check;
-pub mod convert;
-pub mod methods;
-pub mod offline;
-pub mod online;
-pub mod requirements;
-pub mod source;
-
-use std::path::Path;
-
-use graphwalker_core::machine::ExecutionContext;
-use graphwalker_core::model::ElementIndex;
-use graphwalker_dsl::generator::parse_generator;
-use graphwalker_io::ModelContext;
-
-pub type CliResult = Result<(), Box>;
-
-fn load_models(model_args: &[String]) -> Result, Box> {
- if !model_args.len().is_multiple_of(2) {
- return Err("--model requires pairs of ".into());
- }
-
- let mut all_contexts = Vec::new();
-
- for pair in model_args.chunks(2) {
- let file = &pair[0];
- let generator = &pair[1];
- let path = Path::new(file);
-
- let mut contexts = graphwalker_io::read_model(path)?;
- for ctx in &mut contexts {
- ctx.generator = Some(generator.clone());
- }
- all_contexts.extend(contexts);
- }
-
- Ok(all_contexts)
-}
-
-fn load_models_plain(
- model_args: &[String],
-) -> Result, Box> {
- let mut all_contexts = Vec::new();
- for file in model_args {
- let path = Path::new(file);
- let contexts = graphwalker_io::read_model(path)?;
- all_contexts.extend(contexts);
- }
- Ok(all_contexts)
-}
-
-fn prepare_entries_with_seed(
- contexts: Vec,
- seed: Option,
-) -> Result<
- Vec<(ExecutionContext, graphwalker_core::generator::PathGenerator)>,
- Box,
-> {
- let mut entries = Vec::new();
-
- for ctx in contexts {
- let gen_str = ctx
- .generator
- .as_deref()
- .ok_or("Model has no generator specified")?;
- let generator = parse_generator(gen_str)?;
-
- let mut exec_ctx = if let Some(s) = seed {
- ExecutionContext::new_with_seed(ctx.model, s)
- } else {
- ExecutionContext::new(ctx.model)
- };
-
- if let Some(ref start_id) = ctx.start_element_id {
- if let Some(element) = exec_ctx.model().element_by_id(start_id) {
- exec_ctx.set_next_element(Some(element));
- }
- }
-
- entries.push((exec_ctx, generator));
- }
-
- Ok(entries)
-}
-
-fn element_name(ctx: &ExecutionContext, element: ElementIndex) -> String {
- match element {
- ElementIndex::Vertex(vi) => ctx.model().vertex(vi).name().unwrap_or("").to_string(),
- ElementIndex::Edge(ei) => ctx.model().edge(ei).name().unwrap_or("").to_string(),
- }
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/offline.rs b/graphwalker-rs/graphwalker-cli/src/commands/offline.rs
deleted file mode 100644
index 9f9d79960..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/offline.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-use std::io::Write;
-
-use clap::Args as ClapArgs;
-use rand::prelude::*;
-
-use graphwalker_core::machine::Machine;
-use graphwalker_core::model::ElementIndex;
-
-use super::{element_name, load_models, load_models_plain, prepare_entries_with_seed, CliResult};
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Model file and generator pairs: -m
- #[arg(short, long = "model", num_args = 2, action = clap::ArgAction::Append)]
- pub model: Vec,
-
- /// Model file with embedded generator (JSON)
- #[arg(short = 'g', long = "gw")]
- pub gw: Option,
-
- /// Print verbose output (JSON with data)
- #[arg(short = 'o', long)]
- pub verbose: bool,
-
- /// Print unvisited elements
- #[arg(short, long)]
- pub unvisited: bool,
-
- /// Start element name
- #[arg(short = 'e', long = "start-element")]
- pub start_element: Option,
-
- /// Seed for random generator (0 = no seed)
- #[arg(short = 's', long, default_value_t = 0)]
- pub seed: u64,
-}
-
-pub fn run(args: Args) -> CliResult {
- if args.model.is_empty() && args.gw.is_none() {
- return Err("Either --model (-m) or --gw (-g) is required for offline mode".into());
- }
-
- let mut contexts = if let Some(ref gw_file) = args.gw {
- load_models_plain(std::slice::from_ref(gw_file))?
- } else {
- load_models(&args.model)?
- };
-
- if let Some(ref start_name) = args.start_element {
- set_start_by_name(&mut contexts, start_name)?;
- }
-
- let seed = if args.seed != 0 {
- args.seed
- } else {
- rand::thread_rng().gen()
- };
- let entries = prepare_entries_with_seed(contexts, Some(seed))?;
- let mut machine = Machine::new_with_seed(entries, seed)?;
- machine.set_record_path(false);
-
- let stdout = std::io::stdout();
- let mut out = std::io::BufWriter::new(stdout.lock());
-
- while machine.has_next_step() {
- machine.get_next_step()?;
-
- let ctx_idx = machine.current_context_index();
- let ctx = machine.context(ctx_idx);
- let element = ctx.current_element().unwrap();
- let name = element_name(ctx, element);
-
- let mut json = serde_json::json!({
- "currentElementName": name,
- });
-
- if args.verbose {
- json["data"] = serde_json::json!(ctx.data());
- }
-
- if args.unvisited {
- let model = ctx.model();
- let all = model.all_elements();
- let unvisited: Vec<_> = all.iter().filter(|e| !ctx.is_visited(**e)).collect();
-
- json["numberOfElements"] = serde_json::json!(all.len());
- json["numberOfUnvisitedElements"] = serde_json::json!(unvisited.len());
-
- let unvisited_elements: Vec<_> = unvisited
- .iter()
- .map(|e| {
- let elem_name = element_name(ctx, **e);
- let mut obj = serde_json::json!({"elementName": elem_name});
- if args.verbose {
- let elem_id = match **e {
- ElementIndex::Vertex(vi) => model.vertex(vi).id(),
- ElementIndex::Edge(ei) => model.edge(ei).id(),
- };
- obj["elementId"] = serde_json::json!(elem_id);
- }
- obj
- })
- .collect();
- json["unvisitedElements"] = serde_json::json!(unvisited_elements);
- }
-
- writeln!(out, "{}", json)?;
- }
-
- Ok(())
-}
-
-fn set_start_by_name(
- contexts: &mut [graphwalker_io::ModelContext],
- name: &str,
-) -> Result<(), Box> {
- for ctx in contexts.iter_mut() {
- let elements = ctx.model.find_elements(name);
- if !elements.is_empty() {
- ctx.start_element_id = Some(match elements[0] {
- graphwalker_core::model::ElementIndex::Vertex(vi) => {
- ctx.model.vertex(vi).id().to_string()
- }
- graphwalker_core::model::ElementIndex::Edge(ei) => {
- ctx.model.edge(ei).id().to_string()
- }
- });
- return Ok(());
- }
- }
- Err(format!("Start element '{}' not found", name).into())
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/online.rs b/graphwalker-rs/graphwalker-cli/src/commands/online.rs
deleted file mode 100644
index 0d9d34b88..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/online.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use clap::Args as ClapArgs;
-
-use super::CliResult;
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Service type: RESTFUL or WEBSOCKET
- #[arg(short, long, default_value = "WEBSOCKET")]
- pub service: String,
-
- /// Port to listen on
- #[arg(short, long, default_value_t = 8887)]
- pub port: u16,
-
- /// Model file and generator pairs: -m
- #[arg(short, long = "model", num_args = 2, action = clap::ArgAction::Append)]
- pub model: Vec,
-
- /// Start element name
- #[arg(short = 'e', long = "start-element")]
- pub start_element: Option,
-
- /// Seed for random generator (0 = no seed)
- #[arg(long, default_value_t = 0)]
- pub seed: u64,
-}
-
-pub fn run(args: Args) -> CliResult {
- let seed = if args.seed == 0 {
- None
- } else {
- Some(args.seed)
- };
-
- let rt = tokio::runtime::Runtime::new()?;
- rt.block_on(async {
- match args.service.to_uppercase().as_str() {
- "RESTFUL" => graphwalker_restful::start_rest_server(args.port, seed).await,
- _ => graphwalker_restful::start_websocket_server(args.port).await,
- }
- })
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/requirements.rs b/graphwalker-rs/graphwalker-cli/src/commands/requirements.rs
deleted file mode 100644
index 831addad2..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/requirements.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use std::collections::BTreeSet;
-
-use clap::Args as ClapArgs;
-
-use super::{load_models_plain, CliResult};
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Model file(s)
- #[arg(short, long = "model", required = true)]
- pub model: Vec,
-}
-
-pub fn run(args: Args) -> CliResult {
- let contexts = load_models_plain(&args.model)?;
- let mut reqs = BTreeSet::new();
-
- for ctx in &contexts {
- for vertex in ctx.model.vertices() {
- for req in vertex.requirements() {
- let key = req.key();
- if !key.is_empty() {
- reqs.insert(key.to_string());
- }
- }
- }
- for edge in ctx.model.edges() {
- for req in edge.requirements() {
- let key = req.key();
- if !key.is_empty() {
- reqs.insert(key.to_string());
- }
- }
- }
- }
-
- for req in &reqs {
- println!("{}", req);
- }
-
- Ok(())
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/commands/source.rs b/graphwalker-rs/graphwalker-cli/src/commands/source.rs
deleted file mode 100644
index 434a3c2e0..000000000
--- a/graphwalker-rs/graphwalker-cli/src/commands/source.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-use std::collections::BTreeSet;
-use std::path::Path;
-
-use clap::Args as ClapArgs;
-
-use super::CliResult;
-
-#[derive(ClapArgs)]
-pub struct Args {
- /// Input model file and template file
- #[arg(short, long, num_args = 2)]
- pub input: Vec,
-}
-
-pub fn run(args: Args) -> CliResult {
- if args.input.len() != 2 {
- return Err("--input requires exactly 2 arguments: ".into());
- }
-
- let model_path = Path::new(&args.input[0]);
- let template_path = Path::new(&args.input[1]);
-
- let contexts = graphwalker_io::read_model(model_path)?;
- let template = std::fs::read_to_string(template_path)?;
-
- let mut names = BTreeSet::new();
- for ctx in &contexts {
- for vertex in ctx.model.vertices() {
- if let Some(name) = vertex.name() {
- if !name.is_empty() {
- names.insert(name.to_string());
- }
- }
- }
- for edge in ctx.model.edges() {
- if let Some(name) = edge.name() {
- if !name.is_empty() {
- names.insert(name.to_string());
- }
- }
- }
- }
-
- let (header, body, footer) = parse_template(&template)?;
-
- print!("{}", header);
- for name in &names {
- print!("{}", body.replace("{LABEL}", name));
- }
- print!("{}", footer);
-
- Ok(())
-}
-
-fn parse_template(template: &str) -> Result<(&str, &str, &str), Box> {
- let header_start = template
- .find("HEADER<{{")
- .ok_or("Template missing HEADER<{{")?;
- let header_content_start = header_start + "HEADER<{{".len();
- let header_end = template[header_content_start..]
- .find("}}>HEADER")
- .ok_or("Template missing }}>HEADER")?
- + header_content_start;
- let header = &template[header_content_start..header_end];
-
- let body_start = header_end + "}}>HEADER".len();
-
- let footer_marker = template[body_start..]
- .find("FOOTER<{{")
- .ok_or("Template missing FOOTER<{{")?
- + body_start;
- let body = &template[body_start..footer_marker];
-
- let footer_content_start = footer_marker + "FOOTER<{{".len();
- let footer_end = template[footer_content_start..]
- .find("}}>FOOTER")
- .ok_or("Template missing }}>FOOTER")?
- + footer_content_start;
- let footer = &template[footer_content_start..footer_end];
-
- Ok((header, body, footer))
-}
diff --git a/graphwalker-rs/graphwalker-cli/src/main.rs b/graphwalker-rs/graphwalker-cli/src/main.rs
deleted file mode 100644
index 8dca82bf2..000000000
--- a/graphwalker-rs/graphwalker-cli/src/main.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-mod commands;
-
-use std::process;
-
-use clap::{Parser, Subcommand};
-
-#[derive(Parser)]
-#[command(name = "graphwalker", version, about = "Model-based testing tool")]
-struct Cli {
- /// Enable debug logging
- #[arg(long, global = true)]
- debug: bool,
-
- #[command(subcommand)]
- command: Command,
-}
-
-#[derive(Subcommand)]
-enum Command {
- /// Generate a test sequence offline
- Offline(commands::offline::Args),
- /// Start an online service (REST or WebSocket)
- Online(commands::online::Args),
- /// List all method names in the model(s)
- Methods(commands::methods::Args),
- /// List all requirements in the model(s)
- Requirements(commands::requirements::Args),
- /// Convert a model to another format
- Convert(commands::convert::Args),
- /// Generate source code from a model using a template
- Source(commands::source::Args),
- /// Check model(s) for issues
- Check(commands::check::Args),
-}
-
-fn main() {
- let cli = Cli::parse();
-
- if cli.debug {
- tracing_subscriber::fmt()
- .with_env_filter("graphwalker_restful=debug,graphwalker=debug")
- .with_target(true)
- .init();
- }
-
- let result = match cli.command {
- Command::Offline(args) => commands::offline::run(args),
- Command::Online(args) => commands::online::run(args),
- Command::Methods(args) => commands::methods::run(args),
- Command::Requirements(args) => commands::requirements::run(args),
- Command::Convert(args) => commands::convert::run(args),
- Command::Source(args) => commands::source::run(args),
- Command::Check(args) => commands::check::run(args),
- };
-
- if let Err(e) = result {
- eprintln!("{}", e);
- process::exit(1);
- }
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/cli_tests.rs b/graphwalker-rs/graphwalker-cli/tests/cli_tests.rs
deleted file mode 100644
index 1985c7a99..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/cli_tests.rs
+++ /dev/null
@@ -1,790 +0,0 @@
-use assert_cmd::Command;
-use predicates::prelude::*;
-
-fn gw() -> Command {
- Command::cargo_bin("graphwalker").unwrap()
-}
-
-fn fixture(path: &str) -> String {
- format!("tests/fixtures/{}", path)
-}
-
-// ---------------------------------------------------------------------------
-// Help & version
-// ---------------------------------------------------------------------------
-
-#[test]
-fn help_shows_usage() {
- gw().arg("--help")
- .assert()
- .success()
- .stdout(predicate::str::contains("Model-based testing tool"));
-}
-
-#[test]
-fn version_shows_version() {
- gw().arg("--version")
- .assert()
- .success()
- .stdout(predicate::str::contains("graphwalker"));
-}
-
-// ---------------------------------------------------------------------------
-// Offline — correct models
-// ---------------------------------------------------------------------------
-
-#[test]
-fn offline_simplest_model() {
- let out = gw()
- .args(["offline", "-m", &fixture("json/SimplestModel.json"), "random(vertex_coverage(100))"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.contains("\"currentElementName\""));
- assert!(stdout.contains("e_Init"));
- assert!(stdout.contains("v_Start"));
-}
-
-#[test]
-fn offline_small_model_edge_coverage() {
- let out = gw()
- .args(["offline", "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let lines: Vec<&str> = stdout.lines().collect();
- assert!(lines.len() >= 4, "Expected at least 4 steps for 4 edges, got {}", lines.len());
-
- let names: Vec = lines
- .iter()
- .filter_map(|l| {
- serde_json::from_str::(l)
- .ok()
- .and_then(|v| v["currentElementName"].as_str().map(String::from))
- })
- .collect();
- assert!(names.contains(&"e_FirstAction".to_string()));
- assert!(names.contains(&"v_VerifySomeAction".to_string()));
- assert!(names.contains(&"v_VerifySomeOtherAction".to_string()));
-}
-
-#[test]
-fn offline_with_seed_is_deterministic() {
- let run = |seed: u64| -> String {
- let out = gw()
- .args([
- "offline", "-s", &seed.to_string(),
- "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .success();
- String::from_utf8(out.get_output().stdout.clone()).unwrap()
- };
-
- let first = run(42);
- let second = run(42);
- assert_eq!(first, second, "Same seed should produce identical output");
-
- let different = run(99);
- assert_ne!(first, different, "Different seeds should produce different output");
-}
-
-#[test]
-fn offline_verbose_includes_data() {
- let out = gw()
- .args([
- "offline", "-o",
- "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- for line in stdout.lines() {
- let v: serde_json::Value = serde_json::from_str(line).expect("Each line should be valid JSON");
- assert!(v.get("data").is_some(), "Verbose mode should include 'data' field: {}", line);
- }
-}
-
-#[test]
-fn offline_unvisited_shows_counts() {
- let out = gw()
- .args([
- "offline", "-u",
- "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let first_line: serde_json::Value = serde_json::from_str(stdout.lines().next().unwrap()).unwrap();
- assert!(first_line.get("numberOfElements").is_some());
- assert!(first_line.get("numberOfUnvisitedElements").is_some());
- assert!(first_line.get("unvisitedElements").is_some());
-
- let last_line: serde_json::Value = serde_json::from_str(stdout.lines().last().unwrap()).unwrap();
- let unvisited = last_line["numberOfUnvisitedElements"].as_u64().unwrap();
- assert_eq!(unvisited, 0, "Last step should have 0 unvisited elements");
-}
-
-#[test]
-fn offline_with_gw_flag() {
- gw().args(["offline", "-g", &fixture("json/SmallModel.json")])
- .assert()
- .success()
- .stdout(predicate::str::contains("currentElementName"));
-}
-
-#[test]
-fn offline_custom_start_element() {
- let out = gw()
- .args([
- "offline", "-e", "v_VerifySomeOtherAction",
- "-m", &fixture("json/SmallModel.json"), "random(vertex_coverage(100))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let first: serde_json::Value = serde_json::from_str(stdout.lines().next().unwrap()).unwrap();
- assert_eq!(
- first["currentElementName"].as_str().unwrap(),
- "v_VerifySomeOtherAction",
- "First element should be the custom start element"
- );
-}
-
-#[test]
-fn offline_login_model_with_guards_and_actions() {
- let out = gw()
- .args(["offline", "-g", &fixture("json/Login.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec = stdout
- .lines()
- .filter_map(|l| {
- serde_json::from_str::(l)
- .ok()
- .and_then(|v| v["currentElementName"].as_str().map(String::from))
- })
- .collect();
-
- assert!(names.contains(&"e_Init".to_string()));
- assert!(names.contains(&"v_ClientNotRunning".to_string()));
- assert!(names.contains(&"e_StartClient".to_string()));
- assert!(names.contains(&"v_LoginPrompted".to_string()));
- assert!(names.contains(&"e_ValidPremiumCredentials".to_string()));
- assert!(names.contains(&"v_Browse".to_string()));
-}
-
-#[test]
-fn offline_multi_model_shared_state() {
- let out = gw()
- .args(["offline", "-g", &fixture("json/MultiModelSharedState.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec = stdout
- .lines()
- .filter_map(|l| {
- serde_json::from_str::(l)
- .ok()
- .and_then(|v| v["currentElementName"].as_str().map(String::from))
- })
- .collect();
-
- assert!(names.contains(&"e_StartA".to_string()), "Should visit ModelA's start edge");
- assert!(names.contains(&"v_A1".to_string()), "Should visit ModelA vertex");
- assert!(names.contains(&"e_Explore".to_string()), "Should cross into ModelB via shared state");
- assert!(names.contains(&"v_B1".to_string()), "Should visit ModelB vertex");
-}
-
-#[test]
-fn offline_a_star_reached_vertex() {
- let out = gw()
- .args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "a_star(reached_vertex(v_VerifySomeOtherAction))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec = stdout
- .lines()
- .filter_map(|l| {
- serde_json::from_str::(l)
- .ok()
- .and_then(|v| v["currentElementName"].as_str().map(String::from))
- })
- .collect();
-
- assert_eq!(
- names.last().unwrap(),
- "v_VerifySomeOtherAction",
- "A* should end at the target vertex"
- );
-}
-
-#[test]
-fn offline_a_star_reached_edge() {
- let out = gw()
- .args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "a_star(reached_edge(e_AnotherAction))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec = stdout
- .lines()
- .filter_map(|l| {
- serde_json::from_str::(l)
- .ok()
- .and_then(|v| v["currentElementName"].as_str().map(String::from))
- })
- .collect();
-
- assert!(names.contains(&"e_AnotherAction".to_string()));
-}
-
-#[test]
-fn offline_vertex_coverage() {
- let out = gw()
- .args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "random(vertex_coverage(100))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec = stdout
- .lines()
- .filter_map(|l| {
- serde_json::from_str::(l)
- .ok()
- .and_then(|v| v["currentElementName"].as_str().map(String::from))
- })
- .collect();
-
- assert!(names.contains(&"v_VerifySomeAction".to_string()));
- assert!(names.contains(&"v_VerifySomeOtherAction".to_string()));
-}
-
-// ---------------------------------------------------------------------------
-// Offline — error cases
-// ---------------------------------------------------------------------------
-
-#[test]
-fn offline_nonexistent_file() {
- gw().args(["offline", "-m", "nonexistent.json", "random(edge_coverage(100))"])
- .assert()
- .failure();
-}
-
-#[test]
-fn offline_no_model_args() {
- gw().args(["offline"])
- .assert()
- .failure();
-}
-
-#[test]
-fn offline_invalid_generator() {
- gw().args(["offline", "-m", &fixture("json/SmallModel.json"), "not_a_generator(100)"])
- .assert()
- .failure();
-}
-
-#[test]
-fn offline_start_element_not_found() {
- gw().args([
- "offline", "-e", "v_DoesNotExist",
- "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .failure()
- .stderr(predicate::str::contains("not found"));
-}
-
-#[test]
-fn offline_invalid_json() {
- std::fs::write("tests/fixtures/json/_invalid.json", "{ not valid json }").unwrap();
- gw().args(["offline", "-g", "tests/fixtures/json/_invalid.json"])
- .assert()
- .failure();
- let _ = std::fs::remove_file("tests/fixtures/json/_invalid.json");
-}
-
-// ---------------------------------------------------------------------------
-// Check
-// ---------------------------------------------------------------------------
-
-#[test]
-fn check_valid_model() {
- gw().args(["check", "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))"])
- .assert()
- .success()
- .stdout(predicate::str::contains("No issues found"));
-}
-
-#[test]
-fn check_valid_model_with_gw_flag() {
- gw().args(["check", "-g", &fixture("json/SmallModel.json")])
- .assert()
- .success()
- .stdout(predicate::str::contains("No issues found"));
-}
-
-#[test]
-fn check_login_model_reports_nameless_vertex() {
- gw().args(["check", "-g", &fixture("json/Login.json")])
- .assert()
- .failure()
- .stdout(predicate::str::contains("Name of vertex cannot be null"));
-}
-
-#[test]
-fn check_multi_model() {
- gw().args(["check", "-g", &fixture("json/MultiModelSharedState.json")])
- .assert()
- .success()
- .stdout(predicate::str::contains("No issues found"));
-}
-
-#[test]
-fn check_statistics_single_model() {
- let out = gw()
- .args(["check", "-g", &fixture("json/SmallModel.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.contains("Statistics:"));
- assert!(stdout.contains("Model: Small model"));
- assert!(stdout.contains("Unique edges: 3"));
- assert!(stdout.contains("Unique vertices: 2"));
- assert!(stdout.contains("Edge instances: 4"));
- assert!(stdout.contains("Vertex instances: 2"));
- assert!(!stdout.contains("Total:"));
-}
-
-#[test]
-fn check_statistics_multi_model() {
- let out = gw()
- .args(["check", "-g", &fixture("json/MultiModelSharedState.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.contains("Model: ModelA"));
- assert!(stdout.contains("Model: ModelB"));
- assert!(stdout.contains("Total:"));
-}
-
-#[test]
-fn check_no_model_args() {
- gw().args(["check"])
- .assert()
- .failure();
-}
-
-#[test]
-fn check_model_no_start_element() {
- gw().args(["check", "-g", &fixture("json/NoStartElement.json")])
- .assert()
- .failure();
-}
-
-// ---------------------------------------------------------------------------
-// Methods
-// ---------------------------------------------------------------------------
-
-#[test]
-fn methods_small_model() {
- let out = gw()
- .args(["methods", "-m", &fixture("json/SmallModel.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec<&str> = stdout.lines().collect();
-
- assert!(names.contains(&"e_FirstAction"));
- assert!(names.contains(&"e_AnotherAction"));
- assert!(names.contains(&"e_SomeOtherAction"));
- assert!(names.contains(&"v_VerifySomeAction"));
- assert!(names.contains(&"v_VerifySomeOtherAction"));
-}
-
-#[test]
-fn methods_login_model() {
- let out = gw()
- .args(["methods", "-m", &fixture("json/Login.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec<&str> = stdout.lines().collect();
-
- assert!(names.contains(&"e_Init"));
- assert!(names.contains(&"e_StartClient"));
- assert!(names.contains(&"e_ValidPremiumCredentials"));
- assert!(names.contains(&"e_Logout"));
- assert!(names.contains(&"e_Exit"));
- assert!(names.contains(&"e_ToggleRememberMe"));
- assert!(names.contains(&"e_Close"));
- assert!(names.contains(&"e_InvalidCredentials"));
- assert!(names.contains(&"v_ClientNotRunning"));
- assert!(names.contains(&"v_LoginPrompted"));
- assert!(names.contains(&"v_Browse"));
-}
-
-#[test]
-fn methods_sorted_alphabetically() {
- let out = gw()
- .args(["methods", "-m", &fixture("json/SmallModel.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec<&str> = stdout.lines().collect();
- let mut sorted = names.clone();
- sorted.sort();
- assert_eq!(names, sorted, "Methods output should be sorted alphabetically");
-}
-
-#[test]
-fn methods_deduplicates() {
- let out = gw()
- .args(["methods", "-m", &fixture("json/SmallModel.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec<&str> = stdout.lines().collect();
- let unique_count = names.len();
- let mut deduped = names.clone();
- deduped.sort();
- deduped.dedup();
- assert_eq!(
- unique_count,
- deduped.len(),
- "e_SomeOtherAction appears on two edges but should be listed once"
- );
-}
-
-#[test]
-fn methods_multi_model() {
- let out = gw()
- .args(["methods", "-m", &fixture("json/MultiModelSharedState.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let names: Vec<&str> = stdout.lines().collect();
-
- assert!(names.contains(&"e_StartA"));
- assert!(names.contains(&"e_GoToPortal"));
- assert!(names.contains(&"e_Explore"));
- assert!(names.contains(&"e_Return"));
- assert!(names.contains(&"v_A1"));
- assert!(names.contains(&"v_Portal"));
- assert!(names.contains(&"v_B1"));
-}
-
-// ---------------------------------------------------------------------------
-// Requirements
-// ---------------------------------------------------------------------------
-
-#[test]
-fn requirements_model_with_requirements() {
- let out = gw()
- .args(["requirements", "-m", &fixture("json/WithRequirements.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let reqs: Vec<&str> = stdout.lines().collect();
-
- assert!(reqs.contains(&"REQ001"));
- assert!(reqs.contains(&"REQ002"));
- assert!(reqs.contains(&"REQ003"));
- assert!(reqs.contains(&"REQ004"));
- assert!(reqs.contains(&"REQ005"));
- assert_eq!(reqs.len(), 5);
-}
-
-#[test]
-fn requirements_model_without_requirements() {
- let out = gw()
- .args(["requirements", "-m", &fixture("json/SmallModel.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.trim().is_empty(), "Model without requirements should produce empty output");
-}
-
-#[test]
-fn requirements_sorted() {
- let out = gw()
- .args(["requirements", "-m", &fixture("json/WithRequirements.json")])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let reqs: Vec<&str> = stdout.lines().collect();
- let mut sorted = reqs.clone();
- sorted.sort();
- assert_eq!(reqs, sorted, "Requirements should be sorted");
-}
-
-// ---------------------------------------------------------------------------
-// Convert
-// ---------------------------------------------------------------------------
-
-#[test]
-fn convert_graphml_to_json() {
- let out = gw()
- .args(["convert", "--input", &fixture("graphml/Login.graphml"), "--format", "json"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("Output should be valid JSON");
- assert!(parsed.get("models").is_some(), "JSON output should have 'models' key");
-
- let models = parsed["models"].as_array().unwrap();
- assert!(!models.is_empty(), "Should have at least one model");
- assert!(models[0].get("vertices").is_some());
- assert!(models[0].get("edges").is_some());
-}
-
-#[test]
-fn convert_json_to_json() {
- let out = gw()
- .args(["convert", "--input", &fixture("json/SmallModel.json"), "--format", "json"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("Output should be valid JSON");
- let models = parsed["models"].as_array().unwrap();
- assert_eq!(models.len(), 1);
-
- let verts = models[0]["vertices"].as_array().unwrap();
- assert_eq!(verts.len(), 2);
- let edges = models[0]["edges"].as_array().unwrap();
- assert_eq!(edges.len(), 4);
-}
-
-#[test]
-fn convert_preserves_element_names() {
- let out = gw()
- .args(["convert", "--input", &fixture("json/SmallModel.json"), "--format", "json"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.contains("v_VerifySomeAction"));
- assert!(stdout.contains("v_VerifySomeOtherAction"));
- assert!(stdout.contains("e_FirstAction"));
- assert!(stdout.contains("e_AnotherAction"));
- assert!(stdout.contains("e_SomeOtherAction"));
-}
-
-#[test]
-fn convert_unsupported_format() {
- gw().args(["convert", "--input", &fixture("json/SmallModel.json"), "--format", "xml"])
- .assert()
- .failure()
- .stderr(predicate::str::contains("Unsupported"));
-}
-
-#[test]
-fn convert_nonexistent_file() {
- gw().args(["convert", "--input", "nonexistent.json", "--format", "json"])
- .assert()
- .failure();
-}
-
-// ---------------------------------------------------------------------------
-// Source
-// ---------------------------------------------------------------------------
-
-#[test]
-fn source_generates_from_template() {
- let out = gw()
- .args(["source", "--input", &fixture("json/SmallModel.json"), "tests/test.template"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.contains("# Generated methods"), "Should contain header");
- assert!(stdout.contains("# End of file"), "Should contain footer");
- assert!(stdout.contains("def v_VerifySomeAction():"));
- assert!(stdout.contains("def v_VerifySomeOtherAction():"));
- assert!(stdout.contains("def e_FirstAction():"));
- assert!(stdout.contains("def e_AnotherAction():"));
- assert!(stdout.contains("def e_SomeOtherAction():"));
-}
-
-#[test]
-fn source_login_model() {
- let out = gw()
- .args(["source", "--input", &fixture("json/Login.json"), "tests/test.template"])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- assert!(stdout.contains("def e_Init():"));
- assert!(stdout.contains("def e_StartClient():"));
- assert!(stdout.contains("def v_ClientNotRunning():"));
- assert!(stdout.contains("def v_Browse():"));
-}
-
-#[test]
-fn source_missing_template() {
- gw().args(["source", "--input", &fixture("json/SmallModel.json"), "nonexistent.template"])
- .assert()
- .failure();
-}
-
-// ---------------------------------------------------------------------------
-// Offline — generator & stop condition combinations
-// ---------------------------------------------------------------------------
-
-#[test]
-fn offline_random_length() {
- let out = gw()
- .args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "random(length(10))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let count = stdout.lines().count();
- assert_eq!(count, 10, "length(10) should produce exactly 10 steps, got {}", count);
-}
-
-#[test]
-fn offline_quick_random() {
- gw().args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "quick_random(edge_coverage(100))",
- ])
- .assert()
- .success()
- .stdout(predicate::str::contains("currentElementName"));
-}
-
-#[test]
-fn offline_weighted_random() {
- gw().args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "weighted_random(edge_coverage(100))",
- ])
- .assert()
- .success()
- .stdout(predicate::str::contains("currentElementName"));
-}
-
-// ---------------------------------------------------------------------------
-// Offline — NewYorkStreetSweeper
-// ---------------------------------------------------------------------------
-
-#[test]
-fn offline_new_york_street_sweeper() {
- let out = gw()
- .args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "new_york_street_sweeper()",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let lines: Vec<&str> = stdout.lines().collect();
- assert!(lines.len() >= 8, "Should visit all 4 edges (at least 8 steps), got {}", lines.len());
-
- let names: Vec = lines
- .iter()
- .filter_map(|l| serde_json::from_str::(l).ok())
- .filter_map(|v| v["currentElementName"].as_str().map(String::from))
- .collect();
-
- assert!(names.contains(&"e_FirstAction".to_string()), "Should visit e_FirstAction");
- assert!(names.contains(&"e_AnotherAction".to_string()), "Should visit e_AnotherAction");
- assert!(names.contains(&"e_SomeOtherAction".to_string()), "Should visit e_SomeOtherAction");
- assert!(names.contains(&"v_VerifySomeAction".to_string()), "Should visit v_VerifySomeAction");
- assert!(names.contains(&"v_VerifySomeOtherAction".to_string()), "Should visit v_VerifySomeOtherAction");
-}
-
-#[test]
-fn offline_new_york_street_sweeper_camel_case() {
- gw().args([
- "offline",
- "-m", &fixture("json/SmallModel.json"), "newyorkstreetsweeper()",
- ])
- .assert()
- .success()
- .stdout(predicate::str::contains("currentElementName"));
-}
-
-// ---------------------------------------------------------------------------
-// Edge cases
-// ---------------------------------------------------------------------------
-
-#[test]
-fn offline_model_with_no_start_element_fails() {
- gw().args([
- "offline",
- "-m", &fixture("json/NoStartElement.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .failure();
-}
-
-#[test]
-fn offline_model_no_start_with_custom_start_succeeds() {
- gw().args([
- "offline", "-e", "v_A",
- "-m", &fixture("json/NoStartElement.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .success()
- .stdout(predicate::str::contains("v_A"));
-}
-
-#[test]
-fn no_subcommand_shows_help() {
- gw().assert()
- .failure()
- .stderr(predicate::str::contains("Usage"));
-}
-
-#[test]
-fn offline_verbose_and_unvisited_combined() {
- let out = gw()
- .args([
- "offline", "-o", "-u",
- "-m", &fixture("json/SmallModel.json"), "random(edge_coverage(100))",
- ])
- .assert()
- .success();
-
- let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap();
- let first: serde_json::Value = serde_json::from_str(stdout.lines().next().unwrap()).unwrap();
- assert!(first.get("data").is_some(), "Should have data from -o");
- assert!(first.get("numberOfElements").is_some(), "Should have unvisited info from -u");
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/graphml/Login.graphml b/graphwalker-rs/graphwalker-cli/tests/fixtures/graphml/Login.graphml
deleted file mode 100644
index 2bfaf846d..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/graphml/Login.graphml
+++ /dev/null
@@ -1,201 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Start
-
-
-
-
-
-
-
-
-
-
- v_ClientNotRunning
-SHARED:CLIENT_NOT_RUNNNG
-
-
-
-
-
-
-
-
-
-
- v_LoginPrompted
-
-
-
-
-
-
-
-
-
-
- v_Browse
-SHARED:LOGGED_IN
-
-
-
-
-
-
-
-
-
-
- e_Init/validLogin=false;rememberMe=false;
-
-
-
-
-
-
-
-
-
-
-
-
- e_StartClient[!rememberMe||!validLogin]
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_ValidPremiumCredentials/validLogin=true;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_Logout
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_Exit
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_ToggleRememberMe/rememberMe=!rememberMe;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_Close
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_StartClient[rememberMe&&validLogin]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e_InvalidCredentials/validLogin=false;
-
-
-
-
-
-
-
-
-
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/Login.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/Login.json
deleted file mode 100644
index 9aba0897f..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/Login.json
+++ /dev/null
@@ -1,158 +0,0 @@
-{
- "models": [
- {
- "name": "Login",
- "id": "853429e2-0528-48b9-97b3-7725eafbb8b5",
- "generator": "random(edge_coverage(100))",
- "actions": [],
- "vertices": [
- {
- "id": "n1",
- "name": "v_ClientNotRunning",
- "sharedState": "CLIENT_NOT_RUNNNG",
- "actions": [],
- "requirements": [],
- "properties": {
- "x": 232,
- "description": "Start the client process",
- "y": 165
- }
- },
- {
- "id": "n2",
- "name": "v_LoginPrompted",
- "actions": [],
- "requirements": [],
- "properties": {
- "x": -64.33185840707965,
- "description": "Thus shla be prompted for user credentilas",
- "y": 311
- }
- },
- {
- "id": "n3",
- "name": "v_Browse",
- "sharedState": "LOGGED_IN",
- "actions": [],
- "requirements": [],
- "properties": {
- "x": 236,
- "description": "A successful login is expected.\nThe user is presented with the initial view of the client.",
- "y": 457
- }
- },
- {
- "id": "Start",
- "actions": [],
- "requirements": [],
- "properties": {
- "x": -226.30088495575222,
- "y": 6.150442477876107
- }
- }
- ],
- "edges": [
- {
- "id": "e0",
- "name": "e_Init",
- "actions": [
- "validLogin=false;rememberMe=false;"
- ],
- "requirements": [],
- "properties": {
- "description": "Remove all cache and user settings from file system."
- },
- "sourceVertexId": "Start",
- "targetVertexId": "n1"
- },
- {
- "id": "e1",
- "name": "e_StartClient",
- "guard": "!rememberMe||!validLogin",
- "actions": [],
- "requirements": [],
- "properties": [],
- "sourceVertexId": "n1",
- "targetVertexId": "n2"
- },
- {
- "id": "e2",
- "name": "e_ValidPremiumCredentials",
- "actions": [
- "validLogin=true;"
- ],
- "requirements": [],
- "properties": {
- "description": "Log in a s Premium user, using valid credentials"
- },
- "sourceVertexId": "n2",
- "targetVertexId": "n3"
- },
- {
- "id": "e3",
- "name": "e_Logout",
- "actions": [],
- "requirements": [],
- "properties": {
- "description": "Logout current user from Spotify"
- },
- "sourceVertexId": "n3",
- "targetVertexId": "n2"
- },
- {
- "id": "e4",
- "name": "e_Exit",
- "actions": [],
- "requirements": [],
- "properties": {
- "description": "Exit and shutdown the client process"
- },
- "sourceVertexId": "n3",
- "targetVertexId": "n1"
- },
- {
- "id": "e5",
- "name": "e_ToggleRememberMe",
- "actions": [
- "rememberMe=!rememberMe;"
- ],
- "requirements": [],
- "properties": [],
- "sourceVertexId": "n2",
- "targetVertexId": "n2"
- },
- {
- "id": "e6",
- "name": "e_Close",
- "actions": [],
- "requirements": [],
- "properties": [],
- "sourceVertexId": "n2",
- "targetVertexId": "n1"
- },
- {
- "id": "e7",
- "name": "e_StartClient",
- "guard": "rememberMe&&validLogin",
- "actions": [],
- "requirements": [],
- "properties": [],
- "sourceVertexId": "n1",
- "targetVertexId": "n3"
- },
- {
- "id": "e8",
- "name": "e_InvalidCredentials",
- "actions": [
- "validLogin=false;"
- ],
- "requirements": [],
- "properties": [],
- "sourceVertexId": "n2",
- "targetVertexId": "n2"
- }
- ],
- "startElementId": "e0"
- }
- ]
-}
\ No newline at end of file
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/MultiModelSharedState.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/MultiModelSharedState.json
deleted file mode 100644
index cd4645852..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/MultiModelSharedState.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "models": [
- {
- "name": "ModelA",
- "generator": "random(edge_coverage(100))",
- "startElementId": "e0",
- "vertices": [
- {
- "name": "v_A1",
- "id": "a_n0"
- },
- {
- "name": "v_Portal",
- "id": "a_n1",
- "sharedState": "SHARED_PORTAL"
- }
- ],
- "edges": [
- {
- "name": "e_StartA",
- "id": "e0",
- "targetVertexId": "a_n0"
- },
- {
- "name": "e_GoToPortal",
- "id": "a_e1",
- "sourceVertexId": "a_n0",
- "targetVertexId": "a_n1"
- }
- ]
- },
- {
- "name": "ModelB",
- "generator": "random(edge_coverage(100))",
- "vertices": [
- {
- "name": "v_Portal",
- "id": "b_n0",
- "sharedState": "SHARED_PORTAL"
- },
- {
- "name": "v_B1",
- "id": "b_n1"
- }
- ],
- "edges": [
- {
- "name": "e_Explore",
- "id": "b_e0",
- "sourceVertexId": "b_n0",
- "targetVertexId": "b_n1"
- },
- {
- "name": "e_Return",
- "id": "b_e1",
- "sourceVertexId": "b_n1",
- "targetVertexId": "b_n0"
- }
- ]
- }
- ]
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/NoStartElement.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/NoStartElement.json
deleted file mode 100644
index 51a800965..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/NoStartElement.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "models": [
- {
- "name": "NoStartModel",
- "generator": "random(edge_coverage(100))",
- "vertices": [
- {
- "name": "v_A",
- "id": "n0"
- },
- {
- "name": "v_B",
- "id": "n1"
- }
- ],
- "edges": [
- {
- "name": "e_Go",
- "id": "e0",
- "sourceVertexId": "n0",
- "targetVertexId": "n1"
- },
- {
- "name": "e_Back",
- "id": "e1",
- "sourceVertexId": "n1",
- "targetVertexId": "n0"
- }
- ]
- }
- ]
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/SimplestModel.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/SimplestModel.json
deleted file mode 100644
index 3a2fc9f0f..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/SimplestModel.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "models": [
- {
- "name": "SimplestModel",
- "generator": "random(vertex_coverage(100))",
- "startElementId": "e0",
- "vertices": [
- {
- "name": "v_Start",
- "id": "n0"
- }
- ],
- "edges": [
- {
- "name": "e_Init",
- "id": "e0",
- "targetVertexId": "n0"
- }
- ]
- }
- ]
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/SmallModel.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/SmallModel.json
deleted file mode 100644
index c725f2e69..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/SmallModel.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "models": [
- {
- "name": "Small model",
- "generator": "random(edge_coverage(100))",
- "startElementId": "e0",
- "vertices": [
- {
- "name": "v_VerifySomeAction",
- "id": "n0"
- },
- {
- "name": "v_VerifySomeOtherAction",
- "id": "n1"
- }
- ],
- "edges": [
- {
- "name": "e_FirstAction",
- "id": "e0",
- "targetVertexId": "n0"
- },
- {
- "name": "e_AnotherAction",
- "id": "e1",
- "sourceVertexId": "n0",
- "targetVertexId": "n1"
- },
- {
- "name": "e_SomeOtherAction",
- "id": "e2",
- "sourceVertexId": "n1",
- "targetVertexId": "n1"
- },
- {
- "name": "e_SomeOtherAction",
- "id": "e3",
- "sourceVertexId": "n1",
- "targetVertexId": "n0"
- }
- ]
- }
- ]
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/WithRequirements.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/WithRequirements.json
deleted file mode 100644
index 58ca10849..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/WithRequirements.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "models": [
- {
- "name": "RequirementsModel",
- "generator": "random(edge_coverage(100))",
- "startElementId": "e0",
- "vertices": [
- {
- "name": "v_Start",
- "id": "n0",
- "requirements": ["REQ001", "REQ002"]
- },
- {
- "name": "v_LoggedIn",
- "id": "n1",
- "requirements": ["REQ003"]
- }
- ],
- "edges": [
- {
- "name": "e_Init",
- "id": "e0",
- "targetVertexId": "n0",
- "requirements": ["REQ004"]
- },
- {
- "name": "e_Login",
- "id": "e1",
- "sourceVertexId": "n0",
- "targetVertexId": "n1",
- "requirements": ["REQ005"]
- },
- {
- "name": "e_Logout",
- "id": "e2",
- "sourceVertexId": "n1",
- "targetVertexId": "n0"
- }
- ]
- }
- ]
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/example.json b/graphwalker-rs/graphwalker-cli/tests/fixtures/json/example.json
deleted file mode 100644
index e9b3fbd41..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/fixtures/json/example.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "models": [
- {
- "name": "Small model",
- "generator": "random(edge_coverage(100))",
- "startElementId": "n0",
- "vertices": [
- {
- "name": "v_VerifySomeAction",
- "id": "n0"
- },
- {
- "name": "v_VerifySomeOtherAction",
- "id": "n1"
- }
- ],
- "edges": [
- {
- "name": "e_FirstAction",
- "id": "e0",
- "targetVertexId": "n0",
- "actions": [
- "x=0;",
- "y=0;"
- ]
- },
- {
- "name": "e_AnotherAction",
- "id": "e1",
- "sourceVertexId": "n0",
- "targetVertexId": "n1",
- "actions": [
- "y+=1;"
- ]
- },
- {
- "name": "e_SomeOtherAction",
- "id": "e2",
- "sourceVertexId": "n1",
- "targetVertexId": "n1",
- "actions": [
- "x+=1;"
- ]
- },
- {
- "name": "e_SomeOtherAction",
- "id": "e3",
- "sourceVertexId": "n1",
- "targetVertexId": "n0",
- "actions": [
- "y+=1;"
- ]
- }
- ]
- }
- ]
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/online_tests.rs b/graphwalker-rs/graphwalker-cli/tests/online_tests.rs
deleted file mode 100644
index 83d416958..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/online_tests.rs
+++ /dev/null
@@ -1,832 +0,0 @@
-use std::process::{Child, Command};
-use std::time::Duration;
-
-use futures_util::{SinkExt, StreamExt};
-use serde_json::{json, Value};
-use tokio::time::{sleep, timeout};
-use tokio_tungstenite::tungstenite::Message;
-
-fn fixture(path: &str) -> String {
- format!("tests/fixtures/{}", path)
-}
-
-fn graphwalker_bin() -> String {
- assert_cmd::cargo::cargo_bin("graphwalker")
- .to_str()
- .unwrap()
- .to_string()
-}
-
-fn start_server(service: &str, port: u16) -> Child {
- start_server_with_args(service, port, &[])
-}
-
-fn start_server_with_args(service: &str, port: u16, extra_args: &[&str]) -> Child {
- let mut cmd = Command::new(graphwalker_bin());
- cmd.args(["online", "-s", service, "-p", &port.to_string()]);
- cmd.args(extra_args);
- cmd.stdout(std::process::Stdio::piped())
- .stderr(std::process::Stdio::piped())
- .spawn()
- .expect("Failed to start server")
-}
-
-async fn wait_for_port(port: u16) {
- for _ in 0..50 {
- if tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port))
- .await
- .is_ok()
- {
- return;
- }
- sleep(Duration::from_millis(100)).await;
- }
- panic!("Server on port {} did not start within 5 seconds", port);
-}
-
-struct ServerGuard {
- child: Child,
-}
-
-impl ServerGuard {
- fn new(service: &str, port: u16) -> Self {
- Self {
- child: start_server(service, port),
- }
- }
-
- fn with_args(service: &str, port: u16, extra_args: &[&str]) -> Self {
- Self {
- child: start_server_with_args(service, port, extra_args),
- }
- }
-}
-
-impl Drop for ServerGuard {
- fn drop(&mut self) {
- let _ = self.child.kill();
- let _ = self.child.wait();
- }
-}
-
-fn small_model_json() -> String {
- std::fs::read_to_string(fixture("json/SmallModel.json")).unwrap()
-}
-
-// ---------------------------------------------------------------------------
-// WebSocket helpers
-// ---------------------------------------------------------------------------
-
-async fn ws_connect(
- port: u16,
-) -> tokio_tungstenite::WebSocketStream<
- tokio_tungstenite::MaybeTlsStream,
-> {
- let url = format!("ws://127.0.0.1:{}", port);
- let (ws, _) = tokio_tungstenite::connect_async(&url)
- .await
- .expect("Failed to connect WebSocket");
- ws
-}
-
-async fn ws_send(
- ws: &mut tokio_tungstenite::WebSocketStream<
- tokio_tungstenite::MaybeTlsStream,
- >,
- msg: &Value,
-) -> Value {
- ws.send(Message::Text(msg.to_string().into()))
- .await
- .expect("Failed to send message");
-
- let resp = timeout(Duration::from_secs(5), ws.next())
- .await
- .expect("Timeout waiting for response")
- .expect("Stream ended")
- .expect("Read error");
-
- serde_json::from_str::(&resp.into_text().unwrap()).expect("Invalid JSON response")
-}
-
-// ---------------------------------------------------------------------------
-// REST tests
-// ---------------------------------------------------------------------------
-
-#[tokio::test]
-async fn rest_load_has_next_get_next() {
- let port = 19100;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- let resp: Value = client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
-
- let resp: Value = client
- .get(format!("{}/hasNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
- assert_eq!(resp["hasNext"], "true");
-
- let resp: Value = client
- .get(format!("{}/getNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
- assert!(resp["currentElementName"].as_str().is_some());
-}
-
-#[tokio::test]
-async fn rest_get_next_without_load_fails() {
- let port = 19101;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- let resp: Value = client
- .get(format!("{}/getNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "nok");
-}
-
-#[tokio::test]
-async fn rest_full_traversal() {
- let port = 19102;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- let resp: Value = client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
-
- let mut visited = Vec::new();
- loop {
- let resp: Value = client
- .get(format!("{}/hasNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- if resp["hasNext"] != "true" {
- break;
- }
-
- let resp: Value = client
- .get(format!("{}/getNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- let name = resp["currentElementName"].as_str().unwrap().to_string();
- visited.push(name);
-
- if visited.len() > 500 {
- panic!("Traversal exceeded 500 steps, aborting");
- }
- }
-
- assert!(!visited.is_empty(), "Should have visited at least one element");
- assert!(visited.contains(&"e_FirstAction".to_string()));
- assert!(visited.contains(&"v_VerifySomeAction".to_string()));
- assert!(visited.contains(&"v_VerifySomeOtherAction".to_string()));
- assert!(visited.contains(&"e_AnotherAction".to_string()));
- assert!(visited.contains(&"e_SomeOtherAction".to_string()));
-}
-
-#[tokio::test]
-async fn rest_get_data() {
- let port = 19103;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap();
-
- let resp: Value = client
- .get(format!("{}/getData", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
- assert!(resp.get("data").is_some());
-}
-
-#[tokio::test]
-async fn rest_set_data() {
- let port = 19104;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap();
-
- let resp: Value = client
- .put(format!("{}/setData/let x = 42;", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
-
- let resp: Value = client
- .get(format!("{}/getData", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- let data = resp["data"].as_str().unwrap();
- assert!(data.contains("x"), "Data should contain the variable we set");
-}
-
-#[tokio::test]
-async fn rest_restart() {
- let port = 19105;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap();
-
- // Take a few steps
- for _ in 0..3 {
- client
- .get(format!("{}/getNext", base))
- .send()
- .await
- .unwrap();
- }
-
- let resp: Value = client
- .put(format!("{}/restart", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
-
- // After restart, hasNext should be true again
- let resp: Value = client
- .get(format!("{}/hasNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["hasNext"], "true");
-}
-
-#[tokio::test]
-async fn rest_get_statistics() {
- let port = 19106;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap();
-
- client
- .get(format!("{}/getNext", base))
- .send()
- .await
- .unwrap();
-
- let resp: Value = client
- .get(format!("{}/getStatistics", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
- assert!(resp.get("totalNumberOfEdges").is_some());
- assert!(resp.get("totalNumberOfVisitedEdges").is_some());
-}
-
-// ---------------------------------------------------------------------------
-// WebSocket tests
-// ---------------------------------------------------------------------------
-
-#[tokio::test]
-async fn ws_start_has_next_get_next() {
- let port = 19110;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- let resp = ws_send(
- &mut ws,
- &json!({
- "command": "start",
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
- assert_eq!(resp["success"], true);
- assert!(resp.get("seed").is_some());
-
- let resp = ws_send(&mut ws, &json!({"command": "hasNext"})).await;
- assert_eq!(resp["success"], true);
- assert_eq!(resp["hasNext"], true);
-
- let resp = ws_send(&mut ws, &json!({"command": "getNext"})).await;
- assert_eq!(resp["success"], true);
- let name = resp["name"]
- .as_str()
- .or_else(|| resp["currentElementName"].as_str());
- assert!(name.is_some(), "Response should contain element name: {resp}");
-}
-
-#[tokio::test]
-async fn ws_full_traversal() {
- let port = 19111;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- let resp = ws_send(
- &mut ws,
- &json!({
- "command": "start",
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
- assert_eq!(resp["success"], true);
-
- let mut visited = Vec::new();
- loop {
- let resp = ws_send(&mut ws, &json!({"command": "hasNext"})).await;
- if resp["hasNext"] != true {
- break;
- }
-
- let resp = ws_send(&mut ws, &json!({"command": "getNext"})).await;
- let name = resp["name"]
- .as_str()
- .or_else(|| resp["currentElementName"].as_str())
- .unwrap()
- .to_string();
- visited.push(name);
-
- if visited.len() > 500 {
- panic!("Traversal exceeded 500 steps");
- }
- }
-
- assert!(!visited.is_empty());
- assert!(visited.contains(&"e_FirstAction".to_string()));
- assert!(visited.contains(&"v_VerifySomeAction".to_string()));
-}
-
-async fn ws_run_with_seed(port: u16, seed: u64) -> Vec {
- let mut ws = ws_connect(port).await;
-
- let resp = ws_send(
- &mut ws,
- &json!({
- "command": "start",
- "seed": seed,
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
- assert_eq!(resp["success"], true);
-
- let mut names = Vec::new();
- for _ in 0..10 {
- let resp = ws_send(&mut ws, &json!({"command": "hasNext"})).await;
- if resp["hasNext"] != true {
- break;
- }
- let resp = ws_send(&mut ws, &json!({"command": "getNext"})).await;
- let name = resp["name"]
- .as_str()
- .or_else(|| resp["currentElementName"].as_str())
- .unwrap()
- .to_string();
- names.push(name);
- }
- names
-}
-
-#[tokio::test]
-async fn ws_start_with_seed_is_deterministic() {
- let port = 19112;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let run1 = ws_run_with_seed(port, 42).await;
- let run2 = ws_run_with_seed(port, 42).await;
-
- assert_eq!(run1, run2, "Same seed should produce identical sequence");
-}
-
-#[tokio::test]
-async fn ws_get_next_without_start_fails() {
- let port = 19113;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- let resp = ws_send(&mut ws, &json!({"command": "getNext"})).await;
- assert_eq!(resp["success"], false);
-}
-
-#[tokio::test]
-async fn ws_get_data() {
- let port = 19114;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- ws_send(
- &mut ws,
- &json!({
- "command": "start",
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
-
- let resp = ws_send(&mut ws, &json!({"command": "getData"})).await;
- assert_eq!(resp["success"], true);
- assert!(resp.get("data").is_some());
-}
-
-#[tokio::test]
-async fn ws_set_data() {
- let port = 19115;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- ws_send(
- &mut ws,
- &json!({
- "command": "start",
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
-
- let resp = ws_send(
- &mut ws,
- &json!({"command": "setData", "action": "let myVar = 99;"}),
- )
- .await;
- assert_eq!(resp["success"], true);
-
- let resp = ws_send(&mut ws, &json!({"command": "getData"})).await;
- let data = resp["data"].as_str().unwrap();
- assert!(
- data.contains("myVar"),
- "getData should reflect the variable we set: {data}"
- );
-}
-
-#[tokio::test]
-async fn ws_list_sessions_initially_empty() {
- let port = 19116;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- let resp = ws_send(&mut ws, &json!({"command": "listSessions"})).await;
- assert_eq!(resp["success"], true);
- let sessions = resp["sessions"].as_array().unwrap();
- assert!(sessions.is_empty(), "No sessions should exist initially");
-}
-
-#[tokio::test]
-async fn ws_start_creates_session() {
- let port = 19117;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- // Start a session
- let mut runner = ws_connect(port).await;
- let resp = ws_send(
- &mut runner,
- &json!({
- "command": "start",
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
- assert_eq!(resp["success"], true);
- let session_id = resp["sessionId"].as_str().unwrap().to_string();
- assert!(!session_id.is_empty());
-
- // A fresh connection should see the session via listSessions
- let mut observer = ws_connect(port).await;
- let resp = ws_send(&mut observer, &json!({"command": "listSessions"})).await;
- assert_eq!(resp["success"], true);
- let sessions = resp["sessions"].as_array().unwrap();
- assert_eq!(sessions.len(), 1);
- assert_eq!(sessions[0]["id"].as_str().unwrap(), session_id);
-}
-
-#[tokio::test]
-async fn ws_subscribe_session() {
- let port = 19118;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- // Start a session
- let mut runner = ws_connect(port).await;
- let resp = ws_send(
- &mut runner,
- &json!({
- "command": "start",
- "gw": serde_json::from_str::(&small_model_json()).unwrap(),
- }),
- )
- .await;
- let session_id = resp["sessionId"].as_str().unwrap().to_string();
-
- // Take a step so there's visited state
- ws_send(&mut runner, &json!({"command": "hasNext"})).await;
- ws_send(&mut runner, &json!({"command": "getNext"})).await;
-
- // Subscribe from observer
- let mut observer = ws_connect(port).await;
- let resp = ws_send(
- &mut observer,
- &json!({"command": "subscribeSession", "sessionId": session_id}),
- )
- .await;
- assert_eq!(resp["success"], true);
- assert!(resp.get("models").is_some(), "Subscribe should return models");
- assert!(resp.get("elements").is_some(), "Subscribe should return elements snapshot");
- assert!(resp.get("seed").is_some(), "Subscribe should return seed");
-}
-
-#[tokio::test]
-async fn ws_unknown_command() {
- let port = 19119;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- let resp = ws_send(&mut ws, &json!({"command": "nonExistentCommand"})).await;
- assert_eq!(resp["success"], false);
-}
-
-#[tokio::test]
-async fn ws_login_model_with_guards() {
- let port = 19120;
- let _server = ServerGuard::new("WEBSOCKET", port);
- wait_for_port(port).await;
-
- let mut ws = ws_connect(port).await;
-
- let login_json: Value =
- serde_json::from_str(&std::fs::read_to_string(fixture("json/Login.json")).unwrap())
- .unwrap();
-
- let resp = ws_send(
- &mut ws,
- &json!({
- "command": "start",
- "gw": login_json,
- }),
- )
- .await;
- assert_eq!(resp["success"], true);
-
- let mut visited = Vec::new();
- loop {
- let resp = ws_send(&mut ws, &json!({"command": "hasNext"})).await;
- if resp["hasNext"] != true {
- break;
- }
-
- let resp = ws_send(&mut ws, &json!({"command": "getNext"})).await;
- assert_eq!(resp["success"], true);
- let name = resp["name"]
- .as_str()
- .or_else(|| resp["currentElementName"].as_str())
- .unwrap()
- .to_string();
- visited.push(name);
-
- if visited.len() > 500 {
- panic!("Traversal exceeded 500 steps");
- }
- }
-
- assert!(visited.contains(&"e_Init".to_string()));
- assert!(visited.contains(&"v_ClientNotRunning".to_string()));
- assert!(visited.contains(&"v_Browse".to_string()));
-}
-
-// ---------------------------------------------------------------------------
-// REST seed tests
-// ---------------------------------------------------------------------------
-
-async fn rest_run_traversal(port: u16) -> Vec {
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- let resp: Value = client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
-
- let mut names = Vec::new();
- loop {
- let resp: Value = client
- .get(format!("{}/hasNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- if resp["hasNext"] != "true" {
- break;
- }
- let resp: Value = client
- .get(format!("{}/getNext", base))
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- names.push(resp["currentElementName"].as_str().unwrap().to_string());
- if names.len() > 500 {
- panic!("Traversal exceeded 500 steps");
- }
- }
- names
-}
-
-#[tokio::test]
-async fn rest_seed_deterministic() {
- let port_a = 19130;
- let port_b = 19131;
- let _server_a = ServerGuard::with_args("RESTFUL", port_a, &["--seed", "42"]);
- let _server_b = ServerGuard::with_args("RESTFUL", port_b, &["--seed", "42"]);
- wait_for_port(port_a).await;
- wait_for_port(port_b).await;
-
- let run_a = rest_run_traversal(port_a).await;
- let run_b = rest_run_traversal(port_b).await;
-
- assert_eq!(run_a, run_b, "Same seed should produce identical traversal");
-}
-
-#[tokio::test]
-async fn rest_seed_different_seeds_diverge() {
- let port_a = 19132;
- let port_b = 19133;
- let _server_a = ServerGuard::with_args("RESTFUL", port_a, &["--seed", "42"]);
- let _server_b = ServerGuard::with_args("RESTFUL", port_b, &["--seed", "99"]);
- wait_for_port(port_a).await;
- wait_for_port(port_b).await;
-
- let run_a = rest_run_traversal(port_a).await;
- let run_b = rest_run_traversal(port_b).await;
-
- assert_ne!(run_a, run_b, "Different seeds should produce different traversals");
-}
-
-#[tokio::test]
-async fn rest_seed_load_returns_seed() {
- let port = 19134;
- let _server = ServerGuard::with_args("RESTFUL", port, &["--seed", "12345"]);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- let resp: Value = client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
- assert_eq!(resp["seed"], 12345, "Load response should echo back the seed");
-}
-
-#[tokio::test]
-async fn rest_no_seed_returns_generated_seed() {
- let port = 19135;
- let _server = ServerGuard::new("RESTFUL", port);
- wait_for_port(port).await;
-
- let client = reqwest::Client::new();
- let base = format!("http://127.0.0.1:{}/graphwalker", port);
-
- let resp: Value = client
- .post(format!("{}/load", base))
- .body(small_model_json())
- .send()
- .await
- .unwrap()
- .json()
- .await
- .unwrap();
- assert_eq!(resp["result"], "ok");
- assert!(
- resp.get("seed").and_then(|v| v.as_u64()).is_some(),
- "Load response should contain a generated seed"
- );
-}
diff --git a/graphwalker-rs/graphwalker-cli/tests/test.template b/graphwalker-rs/graphwalker-cli/tests/test.template
deleted file mode 100644
index 6e0372e27..000000000
--- a/graphwalker-rs/graphwalker-cli/tests/test.template
+++ /dev/null
@@ -1,9 +0,0 @@
-HEADER<{{
-# Generated methods
-}}>HEADER
-def {LABEL}():
- pass
-
-FOOTER<{{
-# End of file
-}}>FOOTER
\ No newline at end of file
diff --git a/graphwalker-rs/graphwalker-core/Cargo.toml b/graphwalker-rs/graphwalker-core/Cargo.toml
deleted file mode 100644
index 830d7dfd5..000000000
--- a/graphwalker-rs/graphwalker-core/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "graphwalker-core"
-version = "0.1.0"
-edition = "2021"
-description = "Core domain model and execution engine for GraphWalker model-based testing"
-license = "MIT"
-
-[dependencies]
-uuid = { version = "1", features = ["v4"] }
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-rand = "0.8"
-rhai = "1"
diff --git a/graphwalker-rs/graphwalker-core/src/algorithm/chinese_postman.rs b/graphwalker-rs/graphwalker-core/src/algorithm/chinese_postman.rs
deleted file mode 100644
index e16b8786b..000000000
--- a/graphwalker-rs/graphwalker-core/src/algorithm/chinese_postman.rs
+++ /dev/null
@@ -1,457 +0,0 @@
-use std::collections::{HashMap, VecDeque};
-
-use crate::model::{EdgeIndex, ElementIndex, RuntimeModel, VertexIndex};
-
-use super::AlgorithmError;
-
-pub struct ChinesePostmanResult {
- pub path: Vec,
- pub warnings: Vec,
-}
-
-pub fn chinese_postman_path(
- model: &RuntimeModel,
- start: VertexIndex,
-) -> Result {
- let num_vertices = model.vertices().len();
- let num_edges = model.edges().len();
-
- if num_edges == 0 {
- return Ok(ChinesePostmanResult {
- path: Vec::new(),
- warnings: Vec::new(),
- });
- }
-
- let mut warnings = collect_warnings(model);
- let mut adj = build_adjacency_list(model, &mut warnings);
-
- let has_edges = !adj[start.0].is_empty()
- || adj
- .iter()
- .any(|edges| edges.iter().any(|&(_, t)| t == start));
- if !has_edges {
- return Err(AlgorithmError::NotStronglyConnected(format!(
- "Start vertex '{}' is not connected to any edges",
- model
- .vertex(start)
- .name()
- .unwrap_or(model.vertex(start).id())
- )));
- }
-
- check_strong_connectivity(model, &adj, num_vertices, start)?;
-
- let mut in_degree = vec![0usize; num_vertices];
- let mut out_degree = vec![0usize; num_vertices];
- for (v, edges) in adj.iter().enumerate() {
- out_degree[v] = edges.len();
- for &(_, target) in edges {
- in_degree[target.0] += 1;
- }
- }
-
- let excess: Vec = (0..num_vertices)
- .map(|v| in_degree[v] as i32 - out_degree[v] as i32)
- .collect();
-
- let is_eulerian = excess.iter().all(|&e| e == 0);
-
- if !is_eulerian {
- augment_graph(&mut adj, &excess, num_vertices)?;
- }
-
- let path = hierholzer(&mut adj, start.0);
-
- Ok(ChinesePostmanResult { path, warnings })
-}
-
-fn collect_warnings(model: &RuntimeModel) -> Vec {
- let mut warnings = Vec::new();
-
- for edge in model.edges() {
- let name = edge.name().unwrap_or(edge.id());
-
- if edge.has_guard() {
- warnings.push(format!(
- "Edge '{}' has a guard which will be ignored",
- name
- ));
- }
- if edge.has_actions() {
- warnings.push(format!(
- "Edge '{}' has actions which will be ignored",
- name
- ));
- }
- if edge.weight() != 0.0 && edge.weight() != 1.0 {
- warnings.push(format!(
- "Edge '{}' has weight {} which will be treated as 1",
- name,
- edge.weight()
- ));
- }
- }
-
- for vertex in model.vertices() {
- if vertex.has_actions() {
- let name = vertex.name().unwrap_or(vertex.id());
- warnings.push(format!(
- "Vertex '{}' has actions which will be ignored",
- name
- ));
- }
- }
-
- warnings
-}
-
-fn build_adjacency_list(
- model: &RuntimeModel,
- warnings: &mut Vec,
-) -> Vec> {
- let num_vertices = model.vertices().len();
- let mut adj: Vec> = vec![Vec::new(); num_vertices];
-
- for (i, edge) in model.edges().iter().enumerate() {
- let ei = EdgeIndex(i);
-
- let target = match edge.target_vertex() {
- Some(t) => t,
- None => {
- warnings.push(format!(
- "Edge '{}' has no target vertex and will be skipped",
- edge.name().unwrap_or(edge.id())
- ));
- continue;
- }
- };
-
- let source = match edge.source_vertex() {
- Some(s) => s,
- None => {
- let target_name = model
- .vertex(target)
- .name()
- .unwrap_or(model.vertex(target).id());
- warnings.push(format!(
- "Edge '{}' has no source vertex; using target vertex '{}' as source",
- edge.name().unwrap_or(edge.id()),
- target_name
- ));
- target
- }
- };
-
- adj[source.0].push((ei, target));
- }
-
- adj
-}
-
-fn check_strong_connectivity(
- model: &RuntimeModel,
- adj: &[Vec<(EdgeIndex, VertexIndex)>],
- num_vertices: usize,
- start: VertexIndex,
-) -> Result<(), AlgorithmError> {
- let mut participating = vec![false; num_vertices];
- for (v, edges) in adj.iter().enumerate() {
- if !edges.is_empty() {
- participating[v] = true;
- }
- for &(_, target) in edges {
- participating[target.0] = true;
- }
- }
-
- let mut forward: Vec> = vec![Vec::new(); num_vertices];
- let mut reverse: Vec> = vec![Vec::new(); num_vertices];
- for (v, edges) in adj.iter().enumerate() {
- for &(_, target) in edges {
- forward[v].push(target.0);
- reverse[target.0].push(v);
- }
- }
-
- let forward_reachable = dfs_reachable(&forward, start.0, num_vertices);
- let reverse_reachable = dfs_reachable(&reverse, start.0, num_vertices);
-
- let mut unreachable = Vec::new();
- for v in 0..num_vertices {
- if participating[v] && (!forward_reachable[v] || !reverse_reachable[v]) {
- unreachable.push(v);
- }
- }
-
- if !unreachable.is_empty() {
- let start_name = model
- .vertex(start)
- .name()
- .unwrap_or(model.vertex(start).id());
- let unreachable_names: Vec = unreachable
- .iter()
- .map(|&v| {
- let vi = VertexIndex(v);
- model
- .vertex(vi)
- .name()
- .unwrap_or(model.vertex(vi).id())
- .to_string()
- })
- .collect();
-
- let msg = format!(
- "Graph is not strongly connected. The following vertices are not \
- bidirectionally reachable from '{}': {}. \
- Consider adding edges to connect these vertices.",
- start_name,
- unreachable_names.join(", ")
- );
-
- return Err(AlgorithmError::NotStronglyConnected(msg));
- }
-
- Ok(())
-}
-
-fn dfs_reachable(neighbors: &[Vec], start: usize, num_vertices: usize) -> Vec {
- let mut visited = vec![false; num_vertices];
- let mut stack = vec![start];
- while let Some(v) = stack.pop() {
- if visited[v] {
- continue;
- }
- visited[v] = true;
- for &u in &neighbors[v] {
- if !visited[u] {
- stack.push(u);
- }
- }
- }
- visited
-}
-
-fn augment_graph(
- adj: &mut [Vec<(EdgeIndex, VertexIndex)>],
- excess: &[i32],
- num_vertices: usize,
-) -> Result<(), AlgorithmError> {
- let mut supply_units: Vec = Vec::new();
- let mut demand_units: Vec = Vec::new();
-
- for (v, &e) in excess.iter().enumerate() {
- if e > 0 {
- for _ in 0..e {
- supply_units.push(v);
- }
- } else if e < 0 {
- for _ in 0..(-e) {
- demand_units.push(v);
- }
- }
- }
-
- debug_assert_eq!(supply_units.len(), demand_units.len());
-
- let n = supply_units.len();
- if n == 0 {
- return Ok(());
- }
-
- let unique_supply: Vec = {
- let mut v = supply_units.clone();
- v.sort();
- v.dedup();
- v
- };
-
- let mut bfs_cache: HashMap = HashMap::new();
- for &v in &unique_supply {
- bfs_cache.insert(v, vertex_bfs(adj, v, num_vertices));
- }
-
- let mut cost = vec![vec![0i32; n]; n];
- for (i, &sv) in supply_units.iter().enumerate() {
- let bfs_result = &bfs_cache[&sv];
- for (j, &dv) in demand_units.iter().enumerate() {
- let dist = bfs_result.dist[dv];
- cost[i][j] = if dist < 0 { i32::MAX / 2 } else { dist };
- }
- }
-
- let assignment = hungarian(&cost);
-
- for (i, &j) in assignment.iter().enumerate() {
- let sv = supply_units[i];
- let dv = demand_units[j];
- if sv == dv {
- continue;
- }
-
- let bfs_result = &bfs_cache[&sv];
- let path = reconstruct_path(bfs_result, sv, dv);
-
- for (src, ei, tgt) in path {
- adj[src].push((ei, VertexIndex(tgt)));
- }
- }
-
- Ok(())
-}
-
-struct BfsResult {
- dist: Vec,
- pred: Vec