diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml new file mode 100644 index 0000000..f77943d --- /dev/null +++ b/.github/workflows/browser.yml @@ -0,0 +1,31 @@ +name: Browser CI +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +concurrency: + group: browser-ci-${{ github.ref }} + cancel-in-progress: true +jobs: + browser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: pnpm install --frozen-lockfile + - run: cargo build -p gfa-cli --locked + - run: pnpm test:browser + - uses: actions/upload-artifact@v4 + if: always() + with: + name: browser-results + path: | + playwright-report/ + test-results/ diff --git a/.gitignore b/.gitignore index f5df090..236fa11 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ dist/ __pycache__/ /schemas/openapi.json + +/playwright-report/ +/test-results/ diff --git a/Cargo.lock b/Cargo.lock index 7ac5d74..ec78120 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "atoi" version = "2.0.0" @@ -52,6 +61,24 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[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", +] + [[package]] name = "axum" version = "0.8.9" @@ -87,24 +114,6 @@ dependencies = [ "tower-service", ] -[[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", -] - [[package]] name = "base64" version = "0.22.1" @@ -150,6 +159,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "borrow-or-share" version = "0.2.4" @@ -196,6 +214,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-oid" version = "0.9.6" @@ -211,6 +235,21 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + [[package]] name = "crc" version = "3.4.0" @@ -221,10 +260,13 @@ dependencies = [ ] [[package]] -name = "crc-catalog" -version = "2.5.0" +name = "crc32fast" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] [[package]] name = "crossbeam-queue" @@ -251,6 +293,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "data-encoding" version = "2.11.1" @@ -263,23 +314,45 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -381,6 +454,15 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "zlib-rs", +] + [[package]] name = "fluent-uri" version = "0.3.2" @@ -621,10 +703,11 @@ dependencies = [ "gfa-service", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tokio", "tower", "utoipa", + "utoipa-swagger-ui", ] [[package]] @@ -742,7 +825,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -755,13 +838,16 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.5.0" +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", - "itoa", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] @@ -775,16 +861,13 @@ dependencies = [ ] [[package]] -name = "http-body-util" -version = "0.1.5" +name = "http" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "itoa", ] [[package]] @@ -800,23 +883,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "hyper" -version = "1.11.1" +name = "hybrid-array" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", + "typenum", ] [[package]] @@ -834,6 +906,26 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "icu_collections" version = "2.3.0" @@ -861,6 +953,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + [[package]] name = "icu_normalizer" version = "2.3.0" @@ -876,10 +974,10 @@ dependencies = [ ] [[package]] -name = "icu_normalizer_data" +name = "icu_properties_data" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_properties" @@ -896,12 +994,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - [[package]] name = "icu_provider" version = "2.3.1" @@ -917,6 +1009,16 @@ dependencies = [ "zerovec", ] +[[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 = "idna" version = "1.1.0" @@ -928,16 +1030,6 @@ dependencies = [ "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.2" @@ -1075,7 +1167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -1084,6 +1176,16 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[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 = "mime" version = "0.3.17" @@ -1102,17 +1204,19 @@ dependencies = [ ] [[package]] -name = "num" -version = "0.4.3" +name = "num-bigint-dig" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "num-bigint", - "num-complex", + "lazy_static", + "libm", "num-integer", "num-iter", - "num-rational", "num-traits", + "rand 0.8.8", + "smallvec", + "zeroize", ] [[package]] @@ -1125,22 +1229,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.8", - "smallvec", - "zeroize", -] - [[package]] name = "num-cmp" version = "0.1.0" @@ -1196,6 +1284,20 @@ dependencies = [ "libm", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1209,10 +1311,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] -name = "parking" -version = "2.2.1" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] [[package]] name = "parking_lot" @@ -1225,17 +1334,10 @@ dependencies = [ ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "pem-rfc7468" @@ -1339,27 +1441,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -1398,6 +1479,27 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1417,23 +1519,23 @@ dependencies = [ ] [[package]] -name = "ref-cast" +name = "ref-cast-impl" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ - "ref-cast-impl", + "proc-macro2", + "quote", + "syn 3.0.6", ] [[package]] -name = "ref-cast-impl" +name = "ref-cast" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", + "ref-cast-impl", ] [[package]] @@ -1467,14 +1569,26 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "rsa" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", @@ -1487,6 +1601,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + [[package]] name = "rustix" version = "1.1.5" @@ -1513,15 +1662,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "schemars" -version = "0.8.22" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", + "winapi-util", ] [[package]] @@ -1537,20 +1683,22 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "schemars" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] [[package]] -name = "serde" -version = "1.0.229" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde_core" @@ -1562,25 +1710,25 @@ dependencies = [ ] [[package]] -name = "serde_derive" -version = "1.0.229" +name = "serde_derive_internals" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 2.0.119", ] [[package]] -name = "serde_derive_internals" -version = "0.29.1" +name = "serde_derive" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -1619,6 +1767,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + [[package]] name = "sha1" version = "0.10.7" @@ -1626,8 +1784,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -1637,8 +1795,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1663,10 +1832,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "slab" version = "0.4.12" @@ -1711,19 +1886,6 @@ dependencies = [ "der", ] -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - [[package]] name = "sqlx-core" version = "0.8.6" @@ -1749,7 +1911,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror", "tokio", @@ -1758,19 +1920,6 @@ dependencies = [ "url", ] -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.119", -] - [[package]] name = "sqlx-macros-core" version = "0.8.6" @@ -1786,7 +1935,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-sqlite", "syn 2.0.119", @@ -1794,6 +1943,19 @@ dependencies = [ "url", ] +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.119", +] + [[package]] name = "sqlx-mysql" version = "0.8.6" @@ -1806,7 +1968,7 @@ dependencies = [ "byteorder", "bytes", "crc", - "digest", + "digest 0.10.7", "dotenvy", "either", "futures-channel", @@ -1826,7 +1988,7 @@ dependencies = [ "rand 0.8.8", "rsa", "sha1", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -1863,7 +2025,7 @@ dependencies = [ "rand 0.8.8", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -1896,6 +2058,19 @@ dependencies = [ "url", ] +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1972,23 +2147,23 @@ dependencies = [ ] [[package]] -name = "thiserror" +name = "thiserror-impl" version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 3.0.6", ] [[package]] -name = "thiserror-impl" +name = "thiserror" version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", + "thiserror-impl", ] [[package]] @@ -2007,22 +2182,6 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" -[[package]] -name = "tokio" -version = "1.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" -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.2" @@ -2070,18 +2229,19 @@ dependencies = [ ] [[package]] -name = "tower" -version = "0.5.3" +name = "tokio" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] @@ -2097,15 +2257,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] -name = "tracing" -version = "0.1.44" +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ - "log", + "futures-core", + "futures-util", "pin-project-lite", - "tracing-attributes", - "tracing-core", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", ] [[package]] @@ -2128,6 +2291,18 @@ dependencies = [ "once_cell", ] +[[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 = "tungstenite" version = "0.28.0" @@ -2167,6 +2342,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -2218,6 +2399,41 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa-gen" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "utoipa-swagger-ui-vendored" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" +dependencies = [ + "axum", + "base64", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "utoipa", + "utoipa-swagger-ui-vendored", + "zip", +] + [[package]] name = "utoipa" version = "5.5.0" @@ -2231,14 +2447,14 @@ dependencies = [ ] [[package]] -name = "utoipa-gen" -version = "5.5.0" +name = "uuid-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "outref", + "uuid", + "vsimd", ] [[package]] @@ -2252,17 +2468,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "uuid-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" -dependencies = [ - "outref", - "uuid", - "vsimd", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -2281,6 +2486,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2303,15 +2518,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] -name = "wasm-bindgen" +name = "wasm-bindgen-macro-support" version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", "wasm-bindgen-shared", ] @@ -2326,25 +2541,25 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-macro-support" +name = "wasm-bindgen-shared" version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.6", - "wasm-bindgen-shared", + "unicode-ident", ] [[package]] -name = "wasm-bindgen-shared" +name = "wasm-bindgen" version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ - "unicode-ident", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] @@ -2358,42 +2573,12 @@ dependencies = [ ] [[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -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.48.5" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-sys 0.61.2", ] [[package]] @@ -2438,6 +2623,45 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "wit-bindgen" version = "0.57.1" @@ -2457,17 +2681,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - [[package]] name = "yoke-derive" version = "0.8.3" @@ -2481,12 +2694,14 @@ dependencies = [ ] [[package]] -name = "zerocopy" -version = "0.8.59" +name = "yoke" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "zerocopy-derive", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] @@ -2501,12 +2716,12 @@ dependencies = [ ] [[package]] -name = "zerofrom" -version = "0.1.8" +name = "zerocopy" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ - "zerofrom-derive", + "zerocopy-derive", ] [[package]] @@ -2521,6 +2736,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + [[package]] name = "zeroize" version = "1.9.0" @@ -2538,6 +2762,17 @@ dependencies = [ "zerofrom", ] +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "zerovec" version = "0.11.8" @@ -2550,18 +2785,39 @@ dependencies = [ ] [[package]] -name = "zerovec-derive" -version = "0.11.6" +name = "zip" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", + "arbitrary", + "crc32fast", + "flate2", + "indexmap", + "memchr", + "zopfli", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 6dfc6a6..443b9fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ tokio-tungstenite = { version = "0.28", default-features = false, features = ["c futures-util = { version = "0.3", features = ["sink"] } sha2 = "0.10.9" utoipa = { version = "5.4.0", default-features = false, features = ["macros"] } +utoipa-swagger-ui = { version = "9.0.2", default-features = false, features = ["axum", "vendored"] } [workspace.lints.rust] unsafe_code = "forbid" diff --git a/crates/adapters/gfa-http/Cargo.toml b/crates/adapters/gfa-http/Cargo.toml index c3002ea..e2b56a8 100644 --- a/crates/adapters/gfa-http/Cargo.toml +++ b/crates/adapters/gfa-http/Cargo.toml @@ -14,6 +14,7 @@ serde_json.workspace = true tokio.workspace = true sha2.workspace = true utoipa.workspace = true +utoipa-swagger-ui.workspace = true [dev-dependencies] tower.workspace = true diff --git a/crates/adapters/gfa-http/src/lib.rs b/crates/adapters/gfa-http/src/lib.rs index 8ac8941..6ac0d60 100644 --- a/crates/adapters/gfa-http/src/lib.rs +++ b/crates/adapters/gfa-http/src/lib.rs @@ -74,7 +74,16 @@ fn build_router( "Check schema derives.", ) })?; + let docs = utoipa_swagger_ui::SwaggerUi::new("/docs").config( + utoipa_swagger_ui::Config::from("/v1/openapi.json") + .validator_url("none") + .query_config_enabled(false) + .use_base_layout() + .filter(true) + .try_it_out_enabled(true), + ); let mut router = Router::new() + .merge(docs) .route( "/v1/openapi.json", get(openapi::document).layer(axum::Extension(openapi::Document(document))), diff --git a/crates/adapters/gfa-http/src/tests.rs b/crates/adapters/gfa-http/src/tests.rs index d2eb854..84d6926 100644 --- a/crates/adapters/gfa-http/src/tests.rs +++ b/crates/adapters/gfa-http/src/tests.rs @@ -218,3 +218,48 @@ fn domain_errors_preserve_recovery_context_and_http_status() { assert_eq!(mapped.error.details["turn"], 7); } } + +#[tokio::test] +async fn interactive_docs_are_bundled_and_share_local_access_guards() -> TestResult { + let app = router()?; + let response = app.clone().oneshot(request("GET", "/docs", "")?).await?; + assert_eq!(response.status(), StatusCode::SEE_OTHER); + assert_eq!(response.headers()[header::LOCATION], "/docs/"); + for (path, content_type, content) in [ + ("/docs/", "text/html", "swagger-ui"), + ("/docs/swagger-ui.css", "text/css", ".swagger-ui"), + ( + "/docs/swagger-ui-bundle.js", + "javascript", + "SwaggerUIBundle", + ), + ( + "/docs/swagger-initializer.js", + "javascript", + "/v1/openapi.json", + ), + ] { + let response = app.clone().oneshot(request("GET", path, "")?).await?; + assert_eq!(response.status(), StatusCode::OK, "{path}"); + assert!(response.headers()[header::CONTENT_TYPE] + .to_str()? + .contains(content_type)); + assert_eq!(response.headers()[header::CACHE_CONTROL], "no-store"); + let body = to_bytes(response.into_body(), 8 * 1024 * 1024).await?; + let body = std::str::from_utf8(&body)?; + assert!(body.contains(content), "{path}"); + if path.ends_with("initializer.js") { + assert!(body.contains(r#""validatorUrl": "none""#)); + assert!(body.contains(r#""queryConfigEnabled": false"#)); + } + let mut rejected = request("GET", path, "")?; + rejected + .headers_mut() + .insert(header::ORIGIN, "https://example.org".parse()?); + assert_eq!( + app.clone().oneshot(rejected).await?.status(), + StatusCode::FORBIDDEN + ); + } + Ok(()) +} diff --git a/crates/adapters/gfa-store/src/lib.rs b/crates/adapters/gfa-store/src/lib.rs index 6acf35a..f720a03 100644 --- a/crates/adapters/gfa-store/src/lib.rs +++ b/crates/adapters/gfa-store/src/lib.rs @@ -4,7 +4,7 @@ use gfa_service::{ }; use sqlx::{ sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous}, - Sqlite, SqlitePool, Transaction, + Connection, Sqlite, SqliteConnection, SqlitePool, Transaction, }; use std::{path::Path, time::Duration}; @@ -33,22 +33,35 @@ impl SqliteMatchStore { } async fn connect(options: SqliteConnectOptions, connections: u32) -> Result { + let options = options + .foreign_keys(true) + .busy_timeout(Duration::from_secs(5)); + if connections > 1 { + // Configure WAL and migrate before the pool can open other connections. + // A checkout during migrations otherwise lets pool maintenance race + // PRAGMA journal_mode against SQLite's schema write lock. + let mut connection = SqliteConnection::connect_with(&options) + .await + .map_err(unavailable)?; + let migrated = MIGRATOR.run(&mut connection).await; + connection.close().await.map_err(unavailable)?; + migrated.map_err(unavailable)?; + } let pool = SqlitePoolOptions::new() .max_connections(connections) .min_connections(1) .idle_timeout(None) .max_lifetime(None) .test_before_acquire(false) - .connect_with( - options - .foreign_keys(true) - .busy_timeout(Duration::from_secs(5)), - ) + .connect_with(options) .await .map_err(unavailable)?; - if let Err(error) = MIGRATOR.run(&pool).await { - pool.close().await; - return Err(unavailable(error)); + // In-memory databases must keep their sole connection alive. + if connections == 1 { + if let Err(error) = MIGRATOR.run(&pool).await { + pool.close().await; + return Err(unavailable(error)); + } } Ok(Self { pool }) } diff --git a/crates/adapters/gfa-store/src/tests.rs b/crates/adapters/gfa-store/src/tests.rs index a8d9270..753d81c 100644 --- a/crates/adapters/gfa-store/src/tests.rs +++ b/crates/adapters/gfa-store/src/tests.rs @@ -301,3 +301,23 @@ async fn history_cursor_is_ordered_bounded_and_parameterized() -> TestResult { store.close().await; Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn fresh_database_initialization_precedes_pool_expansion() -> TestResult { + let directory = tempfile::tempdir()?; + for iteration in 0..32 { + let path = directory.path().join(format!("fresh-{iteration}.sqlite")); + let store = SqliteMatchStore::open(&path).await?; + store.create(new_record("startup")).await?; + assert_eq!(store.load("startup").await?, Some(new_record("startup"))); + store.close().await; + let restored = SqliteMatchStore::open(&path).await?; + assert_eq!(restored.load("startup").await?, Some(new_record("startup"))); + restored.close().await; + } + let memory = SqliteMatchStore::in_memory().await?; + memory.create(new_record("memory")).await?; + assert_eq!(memory.load("memory").await?, Some(new_record("memory"))); + memory.close().await; + Ok(()) +} diff --git a/docs/local-play.md b/docs/local-play.md index d24a222..748fa73 100644 --- a/docs/local-play.md +++ b/docs/local-play.md @@ -149,3 +149,11 @@ omit the WebSocket path. Export instructions are in `schemas/README.md`. `GET /v1/matches/{id}` returns current public metadata reconstructed from the event log. `GET /v1/matches?game_id=tictactoe&status=finished&limit=50` scans local history in ascending match-id order. Pass the response's `next` as `after` to continue. The limit bounds records scanned before filtering, so an empty filtered page can still have a continuation. A new scan includes concurrent creations that sort before your current cursor. Metadata omits seeds, starting state, observations and reasoning. This index is for the loopback-restricted single-user mode; public multi-user hosting needs owner/visibility authorization. + +## Interactive API documentation + +Open [the local API explorer](http://127.0.0.1:8080/docs/) after starting the server. The explorer uses the generated OpenAPI contract and bundles its JavaScript and styles with the server, so it works without a CDN. Expand an operation, edit the example, and select **Execute**. Start with `POST /v1/matches`, copy its `match_id`, then submit a move through `POST /v1/matches/{id}/actions` with the current turn and seat. + +Requests run against the same local server. The explorer uses the same Host and Origin checks as the API, and no external schema validator is contacted. Browser tests create a match and make a move through the visible controls. + +To run the browser check locally, build the CLI with `cargo build -p gfa-cli`, install Chromium with `pnpm exec playwright install chromium`, then run `pnpm test:browser`. CI uses the Chrome installation supplied by the GitHub runner image. diff --git a/package.json b/package.json index d260d38..da8aa47 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "typecheck": "tsc --noEmit -p web/tsconfig.json", "lint": "eslint web && depcruise web --config .dependency-cruiser.cjs", "format:check": "prettier --check 'web/**/*.{ts,tsx,css,html,json}'", - "format": "prettier --write 'web/**/*.{ts,tsx,css,html,json}'" + "format": "prettier --write 'web/**/*.{ts,tsx,css,html,json}'", + "test:browser": "playwright test" }, "devDependencies": { "@eslint/js": "^9.22.0", @@ -17,6 +18,7 @@ "globals": "^16.0.0", "prettier": "^3.5.3", "typescript": "^5.8.2", - "typescript-eslint": "^8.26.0" + "typescript-eslint": "^8.26.0", + "@playwright/test": "1.58.2" } } diff --git a/playwright.config.mjs b/playwright.config.mjs new file mode 100644 index 0000000..4975102 --- /dev/null +++ b/playwright.config.mjs @@ -0,0 +1,26 @@ +import { defineConfig } from "@playwright/test"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const directory = mkdtempSync(join(tmpdir(), "gfa-browser-")); + +export default defineConfig({ + testDir: "./tests/browser", + workers: 1, + retries: 0, + reporter: [["list"], ["html", { open: "never" }]], + use: { + channel: process.env.CI ? "chrome" : undefined, + baseURL: "http://127.0.0.1:18080", + trace: "retain-on-failure", + screenshot: "only-on-failure", + }, + webServer: { + command: `target/debug/gfa serve --sqlite "${join(directory, "matches.sqlite")}" --port 18080`, + url: "http://127.0.0.1:18080/healthz", + timeout: 30000, + reuseExistingServer: false, + gracefulShutdown: { signal: "SIGTERM", timeout: 10000 }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d8137b..b5a6b47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@eslint/js': specifier: ^9.22.0 version: 9.39.5 + '@playwright/test': + specifier: 1.58.2 + version: 1.58.2 dependency-cruiser: specifier: ^16.10.2 version: 16.10.4 @@ -410,6 +413,11 @@ packages: cpu: [x64] os: [linux] + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + engines: {node: '>=18'} + hasBin: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -857,6 +865,11 @@ packages: flatted@3.4.4: resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1063,6 +1076,16 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} @@ -1556,6 +1579,10 @@ snapshots: '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.63.5': @@ -2035,6 +2062,9 @@ snapshots: flatted@3.4.4: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -2194,6 +2224,14 @@ snapshots: picomatch@4.0.7: {} + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + postcss@8.5.28: dependencies: nanoid: 3.3.19 diff --git a/tests/browser/docs.spec.mjs b/tests/browser/docs.spec.mjs new file mode 100644 index 0000000..3719fab --- /dev/null +++ b/tests/browser/docs.spec.mjs @@ -0,0 +1,49 @@ +import { expect, test } from "@playwright/test"; + +test("bundled API explorer creates a match and plays a move", async ({ page }) => { + const external = []; + const errors = []; + page.on("pageerror", (error) => errors.push(error.message)); + await page.route("**/*", (route) => { + if (new URL(route.request().url()).origin !== "http://127.0.0.1:18080") { + external.push(route.request().url()); + return route.abort(); + } + return route.continue(); + }); + await page.goto("/docs"); + await expect(page.getByRole("heading", { name: /GamesForAI local API/ })).toBeVisible(); + + const create = page.locator("#operations-Matches-create"); + await create.locator(".opblock-summary").click(); + await create.locator("textarea.body-param__text").fill(JSON.stringify({ + game_id: "tictactoe", + seed: 42, + include_info: false, + })); + const createdResponse = page.waitForResponse((response) => + response.url().endsWith("/v1/matches") && response.request().method() === "POST"); + await create.getByRole("button", { name: "Execute", exact: true }).click(); + const created = await createdResponse; + expect(created.status()).toBe(201); + const initial = await created.json(); + expect(initial.turn).toBe(0); + await expect(create.locator(".responses-inner")).toContainText(initial.match_id); + + const move = page.locator("#operations-Matches-make_move"); + await move.locator(".opblock-summary").click(); + await move.locator('tr[data-param-name="id"] input').fill(initial.match_id); + await move.locator("textarea.body-param__text").fill(JSON.stringify({ + seat: 0, turn: 0, action: "r2c2", + })); + const movedResponse = page.waitForResponse((response) => + response.url().endsWith("/actions") && response.request().method() === "POST"); + await move.getByRole("button", { name: "Execute", exact: true }).click(); + const moved = await movedResponse; + expect(moved.status()).toBe(200); + const result = await moved.json(); + expect(result.state.turn).toBe(1); + await expect(move.locator(".responses-inner")).toContainText("r2c2"); + expect(errors).toEqual([]); + expect(external).toEqual([]); +});