text.anonymize.legal-de 0.1.0: WASM bridge to the judge-ner host service
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 1m20s
Some checks failed
CI / Linux x86_64 (Forgejo) (push) Failing after 1m20s
NER anonymization of German legal texts (plain text + DOCX) via the JuraNER model by Harshil Darji (MIT). The transformer model runs in the judge-ner host service; this module forwards documents over loopback HTTP and maps responses onto the capability contract.
This commit is contained in:
commit
3e018f528a
15 changed files with 1781 additions and 0 deletions
66
.forgejo/workflows/ci.yml
Normal file
66
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTFLAGS: "-D warnings"
|
||||||
|
RUST_TOOLCHAIN: "1.86"
|
||||||
|
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
name: Linux x86_64 (Forgejo)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout text-anonymize-legal-de via external URL
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
mkdir -p "$GITHUB_WORKSPACE"
|
||||||
|
cd "$GITHUB_WORKSPACE"
|
||||||
|
git init -q
|
||||||
|
git remote add origin \
|
||||||
|
"https://x-access-token:${GITHUB_TOKEN}@git.flemming.ai/${GITHUB_REPOSITORY}.git"
|
||||||
|
git fetch --depth=1 origin "$GITHUB_SHA"
|
||||||
|
git checkout -q FETCH_HEAD
|
||||||
|
|
||||||
|
- name: Configure git URL rewrite for SDK fetch
|
||||||
|
env:
|
||||||
|
SDK_PAT: ${{ secrets.MODULE_SDK_PAT }}
|
||||||
|
run: |
|
||||||
|
git config --global \
|
||||||
|
"url.https://x-access-token:${SDK_PAT}@git.flemming.ai/.insteadOf" \
|
||||||
|
"https://git.flemming.ai/"
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
curl ca-certificates build-essential pkg-config libssl-dev git
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||||
|
| sh -s -- -y --profile minimal \
|
||||||
|
--default-toolchain "$RUST_TOOLCHAIN" \
|
||||||
|
--target wasm32-wasip2 --component rustfmt --component clippy
|
||||||
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
|
- name: Cargo fmt --check
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
- name: Cargo clippy
|
||||||
|
run: cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
- name: Cargo build (host)
|
||||||
|
run: cargo build --all-targets
|
||||||
|
|
||||||
|
- name: Cargo test (host)
|
||||||
|
run: cargo test --all-targets
|
||||||
|
|
||||||
|
- name: Cargo build (wasm32-wasip2)
|
||||||
|
run: cargo build --release --target wasm32-wasip2
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
target/
|
||||||
|
Cargo.lock.bak
|
||||||
|
.DS_Store
|
||||||
645
Cargo.lock
generated
Normal file
645
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,645 @@
|
||||||
|
# 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",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chain-module-sdk"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "git+https://git.flemming.ai/fai/chain-module-sdk-rust.git?branch=main#98e98e9371f7409560a1ef08bc0923d9a2506449"
|
||||||
|
dependencies = [
|
||||||
|
"chain-module-sdk-macros",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"wit-bindgen 0.36.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chain-module-sdk-macros"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "git+https://git.flemming.ai/fai/chain-module-sdk-rust.git?branch=main#98e98e9371f7409560a1ef08bc0923d9a2506449"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[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 = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
|
[[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 = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[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 = "quote"
|
||||||
|
version = "1.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spdx"
|
||||||
|
version = "0.10.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e17e880bafaeb362a7b751ec46bdc5b61445a188f80e0606e68167cd540fa3"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "text_anonymize_legal_de"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"chain-module-sdk",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"waki",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "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 = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "waki"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e2db2daf1dfbadf228fd8b3c22b96a359135fd673b3d2c203274ee6a0df9c77"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"form_urlencoded",
|
||||||
|
"http",
|
||||||
|
"serde",
|
||||||
|
"waki-macros",
|
||||||
|
"wit-bindgen 0.34.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "waki-macros"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a061143f321cc5eeb523f60bdbcd45cfc3ee8851f8cf24f7a4b963bddc5642eb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.219.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8aa79bcd666a043b58f5fa62b221b0b914dd901e6f620e8ab7371057a797f3e1"
|
||||||
|
dependencies = [
|
||||||
|
"leb128",
|
||||||
|
"wasmparser 0.219.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.220.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e913f9242315ca39eff82aee0e19ee7a372155717ff0eb082c741e435ce25ed1"
|
||||||
|
dependencies = [
|
||||||
|
"leb128",
|
||||||
|
"wasmparser 0.220.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.219.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1ef51bd442042a2a7b562dddb6016ead52c4abab254c376dcffc83add2c9c34"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"spdx",
|
||||||
|
"wasm-encoder 0.219.2",
|
||||||
|
"wasmparser 0.219.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.220.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "185dfcd27fa5db2e6a23906b54c28199935f71d9a27a1a27b3a88d6fee2afae7"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"spdx",
|
||||||
|
"wasm-encoder 0.220.1",
|
||||||
|
"wasmparser 0.220.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.219.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5220ee4c6ffcc0cb9d7c47398052203bc902c8ef3985b0c8134118440c0b2921"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bitflags",
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.220.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d07b6a3b550fefa1a914b6d54fc175dd11c3392da11eee604e6ffc759805d25"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bitflags",
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e11ad55616555605a60a8b2d1d89e006c2076f46c465c892cc2c153b20d4b30"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt 0.34.0",
|
||||||
|
"wit-bindgen-rust-macro 0.34.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a2b3e15cd6068f233926e7d8c7c588b2ec4fb7cc7bf3824115e7c7e2a8485a3"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt 0.36.0",
|
||||||
|
"wit-bindgen-rust-macro 0.36.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "163cee59d3d5ceec0b256735f3ab0dccac434afb0ec38c406276de9c5a11e906"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser 0.219.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b632a5a0fa2409489bd49c9e6d99fcc61bb3d4ce9d1907d44662e75a28c71172"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser 0.220.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "744845cde309b8fa32408d6fb67456449278c66ea4dcd96de29797b302721f02"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7947d0131c7c9da3f01dfde0ab8bd4c4cf3c5bd49b6dba0ae640f1fa752572ea"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6919521fc7807f927a739181db93100ca7ed03c29509b84d5f96b27b2e49a9a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn",
|
||||||
|
"wasm-metadata 0.219.2",
|
||||||
|
"wit-bindgen-core 0.34.0",
|
||||||
|
"wit-component 0.219.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4329de4186ee30e2ef30a0533f9b3c123c019a237a7c82d692807bf1b3ee2697"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn",
|
||||||
|
"wasm-metadata 0.220.1",
|
||||||
|
"wit-bindgen-core 0.36.0",
|
||||||
|
"wit-component 0.220.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c967731fc5d50244d7241ecfc9302a8929db508eea3c601fbc5371b196ba38a5"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wit-bindgen-core 0.34.0",
|
||||||
|
"wit-bindgen-rust 0.34.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "177fb7ee1484d113b4792cc480b1ba57664bbc951b42a4beebe573502135b1fc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wit-bindgen-core 0.36.0",
|
||||||
|
"wit-bindgen-rust 0.36.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.219.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b8479a29d81c063264c3ab89d496787ef78f8345317a2dcf6dece0f129e5fcd"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder 0.219.2",
|
||||||
|
"wasm-metadata 0.219.2",
|
||||||
|
"wasmparser 0.219.2",
|
||||||
|
"wit-parser 0.219.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.220.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b505603761ed400c90ed30261f44a768317348e49f1864e82ecdc3b2744e5627"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder 0.220.1",
|
||||||
|
"wasm-metadata 0.220.1",
|
||||||
|
"wasmparser 0.220.1",
|
||||||
|
"wit-parser 0.220.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.219.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca004bb251010fe956f4a5b9d4bf86b4e415064160dd6669569939e8cbf2504f"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser 0.219.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.220.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae2a7999ed18efe59be8de2db9cb2b7f84d88b27818c79353dfc53131840fe1a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser 0.220.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
53
Cargo.toml
Normal file
53
Cargo.toml
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Standalone Cargo.toml — targets wasm32-wasip2.
|
||||||
|
#
|
||||||
|
# Build:
|
||||||
|
# cargo build --release --target wasm32-wasip2
|
||||||
|
#
|
||||||
|
# Local link-install into the running hub:
|
||||||
|
# fai install --link $(pwd)
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "text_anonymize_legal_de"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "UNLICENSED"
|
||||||
|
publish = false
|
||||||
|
description = "F∆I module providing text.anonymize.legal-de — bridge to the judge-ner host service (JuraNER anonymization of German legal texts)"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# cdylib for the wasm artifact, rlib so unit tests can link the
|
||||||
|
# request/response mapping on the host.
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chain-module-sdk = { git = "https://git.flemming.ai/fai/chain-module-sdk-rust.git", branch = "main" }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
base64 = "0.22"
|
||||||
|
thiserror = "2"
|
||||||
|
|
||||||
|
# WASI-0.2 HTTP client. Only compiled into the wasm32-wasip2 build.
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
waki = "0.5"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
strip = "symbols"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unsafe_op_in_unsafe_fn = "warn"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
unwrap_used = "deny"
|
||||||
|
expect_used = "deny"
|
||||||
|
panic = "deny"
|
||||||
|
print_stdout = "deny"
|
||||||
|
print_stderr = "deny"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
# Empty so this package is NOT picked up by a parent.
|
||||||
73
LICENSE
Normal file
73
LICENSE
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2026 Flemming.AI
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
103
MODULE.de.md
Normal file
103
MODULE.de.md
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
# text.anonymize.legal-de
|
||||||
|
|
||||||
|
NER-basierte Anonymisierung deutscher juristischer Texte (JuraNER).
|
||||||
|
Brückenmodul: das Transformers-Modell läuft im **judge-ner
|
||||||
|
Host-Service** (`fai_judge/host_services/judge-ner/`); dieses
|
||||||
|
WASM-Modul reicht das Dokument per Loopback-HTTP weiter und bildet
|
||||||
|
die Antwort auf den Capability-Contract ab.
|
||||||
|
|
||||||
|
## Capability
|
||||||
|
|
||||||
|
- `text.anonymize.legal-de@0.1.0`
|
||||||
|
|
||||||
|
## Eingaben
|
||||||
|
|
||||||
|
| Name | Typ | Beschreibung |
|
||||||
|
| ---------- | ----- | ------------ |
|
||||||
|
| `request` | json | `{"mode":"text"\|"docx","options":{...}}` — siehe unten. |
|
||||||
|
| `document` | bytes | UTF-8-Klartext (mode=text) oder eine DOCX-Datei (mode=docx). |
|
||||||
|
| `endpoint` | text | Optionale judge-ner-Basis-URL. Default `http://127.0.0.1:8756`. |
|
||||||
|
|
||||||
|
### `request.options` (alle optional)
|
||||||
|
|
||||||
|
| Schlüssel | Default | Bedeutung |
|
||||||
|
| --------- | ------- | --------- |
|
||||||
|
| `importance_levels` | `["High"]` | Zu anonymisierende Entitätsklassen (`High`/`Mid`/`Low`). |
|
||||||
|
| `confidence_threshold` | `0.8` | Minimale NER-Konfidenz (0.0–1.0). |
|
||||||
|
| `manual_phrases` | `[]` | Zusätzlich zu schwärzende Phrasen (Label `RED`). |
|
||||||
|
| `maintain_consistency` | `true` | Gleiche Oberflächenform erhält überall dasselbe ⟦TYPE#⟧-Token. |
|
||||||
|
| `remove_rubrum` | `false` | Rubrum-Block erkennen und durch Platzhalter ersetzen. |
|
||||||
|
|
||||||
|
## Ausgaben
|
||||||
|
|
||||||
|
| Name | Typ | Beschreibung |
|
||||||
|
| ---------- | ----- | ------------ |
|
||||||
|
| `result` | json | `{"replacements": [...], "text": "..."}` — `text` (der anonymisierte Text) nur bei mode=text. `statistics` und `rubrum` werden vom Service durchgereicht, wenn vorhanden. |
|
||||||
|
| `document` | bytes | Das anonymisierte Dokument: UTF-8-Text (`text/plain`) bei mode=text, das umgeschriebene DOCX bei mode=docx. |
|
||||||
|
|
||||||
|
Jeder `replacements`-Eintrag:
|
||||||
|
`{text, label, label_description, start, end, score, line_number,
|
||||||
|
anonymized}`; im DOCX-Modus zusätzlich `run_id_start`,
|
||||||
|
`char_in_start`, `run_id_end`, `char_in_end` zur Adressierung der
|
||||||
|
exakten `w:t`-Knoten des Originaldokuments. Einträge mit
|
||||||
|
`"anonymized": null` wurden erkannt, aber nicht ersetzt
|
||||||
|
(Importance-Level nicht ausgewählt).
|
||||||
|
|
||||||
|
## Berechtigungen & benötigte Services
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
permissions:
|
||||||
|
- "net: localhost"
|
||||||
|
- "net: 127.0.0.1"
|
||||||
|
requires_services:
|
||||||
|
- judge-ner
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Hub führt Steps mit diesem Modul nur aus, wenn der Operator einen
|
||||||
|
`judge-ner`-Service-Endpunkt in `~/.chain/config.yaml` (`services:`)
|
||||||
|
deklariert hat. Beachte: Der erste judge-ner-Start lädt das
|
||||||
|
JuraNER-Modell von HuggingFace — bis dahin antwortet der Service mit
|
||||||
|
503 und dieses Modul schlägt mit einer klaren „NER model not
|
||||||
|
available"-Meldung fehl.
|
||||||
|
|
||||||
|
## Einrichtung des judge-ner-Host-Services
|
||||||
|
|
||||||
|
Das NER-Modell (JuraNER von Harshil Darji, MIT — transformers + torch)
|
||||||
|
kann nicht in der WASM-Sandbox laufen; es läuft als Host-Service.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Service starten (Docker; ~1 GB Modell-Download beim ersten Start,
|
||||||
|
# persistiert im Named Volume — spätere Starts sind offline-fähig)
|
||||||
|
docker run -d --name judge-ner \
|
||||||
|
-p 8756:8756 \
|
||||||
|
-v judge-ner-models:/models \
|
||||||
|
judge-ner
|
||||||
|
|
||||||
|
# 2. Beim Hub registrieren (~/.chain/config.yaml)
|
||||||
|
# services:
|
||||||
|
# judge-ner:
|
||||||
|
# endpoint: http://127.0.0.1:8756
|
||||||
|
# health_check:
|
||||||
|
# url: http://127.0.0.1:8756/health
|
||||||
|
|
||||||
|
# 3. Prüfen
|
||||||
|
curl -s localhost:8756/health # {"status":"ok","model_loaded":true}
|
||||||
|
chain service status judge-ner
|
||||||
|
```
|
||||||
|
|
||||||
|
Quellcode und Image-Build: `fai_judge/host_services/judge-ner/`
|
||||||
|
(`docker build -t judge-ner .`).
|
||||||
|
|
||||||
|
## Fehler
|
||||||
|
|
||||||
|
- Fehlerhafte `request`, Nicht-UTF-8-Textdokument oder 4xx vom
|
||||||
|
Service → `invalid input: ...`
|
||||||
|
- Service nicht erreichbar, 5xx, fehlerhafte Service-Antwort →
|
||||||
|
`internal error: judge-ner at <url> ...`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
cargo build --release --target wasm32-wasip2
|
||||||
|
```
|
||||||
102
MODULE.md
Normal file
102
MODULE.md
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# text.anonymize.legal-de
|
||||||
|
|
||||||
|
NER-based anonymization of German legal texts (JuraNER). Bridge
|
||||||
|
module: the transformers model runs in the **judge-ner host service**
|
||||||
|
(`fai_judge/host_services/judge-ner/`); this WASM module forwards the
|
||||||
|
document over loopback HTTP and maps the response onto the capability
|
||||||
|
contract.
|
||||||
|
|
||||||
|
## Capability
|
||||||
|
|
||||||
|
- `text.anonymize.legal-de@0.1.0`
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---------- | ----- | ----------- |
|
||||||
|
| `request` | json | `{"mode":"text"\|"docx","options":{...}}` — see below. |
|
||||||
|
| `document` | bytes | UTF-8 plain text (mode=text) or a DOCX file (mode=docx). |
|
||||||
|
| `endpoint` | text | Optional judge-ner base URL. Default `http://127.0.0.1:8756`. |
|
||||||
|
|
||||||
|
### `request.options` (all optional)
|
||||||
|
|
||||||
|
| Key | Default | Meaning |
|
||||||
|
| --- | ------- | ------- |
|
||||||
|
| `importance_levels` | `["High"]` | Entity classes to anonymize (`High`/`Mid`/`Low`). |
|
||||||
|
| `confidence_threshold` | `0.8` | Minimum NER confidence (0.0–1.0). |
|
||||||
|
| `manual_phrases` | `[]` | Additional phrases to redact (label `RED`). |
|
||||||
|
| `maintain_consistency` | `true` | Same surface form gets the same ⟦TYPE#⟧ token everywhere. |
|
||||||
|
| `remove_rubrum` | `false` | Detect the rubrum block and replace it with a placeholder. |
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---------- | ----- | ----------- |
|
||||||
|
| `result` | json | `{"replacements": [...], "text": "..."}` — `text` (the anonymized text) only for mode=text. `statistics` and `rubrum` are passed through from the service when present. |
|
||||||
|
| `document` | bytes | The anonymized document: UTF-8 text (`text/plain`) for mode=text, the rewritten DOCX for mode=docx. |
|
||||||
|
|
||||||
|
Each `replacements` entry:
|
||||||
|
`{text, label, label_description, start, end, score, line_number,
|
||||||
|
anonymized}`; DOCX mode additionally carries `run_id_start`,
|
||||||
|
`char_in_start`, `run_id_end`, `char_in_end` addressing the exact
|
||||||
|
`w:t` nodes of the original document. Entries with
|
||||||
|
`"anonymized": null` were detected but not replaced (importance level
|
||||||
|
not selected).
|
||||||
|
|
||||||
|
## Permissions & required services
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
permissions:
|
||||||
|
- "net: localhost"
|
||||||
|
- "net: 127.0.0.1"
|
||||||
|
requires_services:
|
||||||
|
- judge-ner
|
||||||
|
```
|
||||||
|
|
||||||
|
The hub refuses to dispatch a step to this module unless the operator
|
||||||
|
declared a `judge-ner` service endpoint in `~/.chain/config.yaml`
|
||||||
|
(`services:`). Note the first judge-ner start downloads the JuraNER
|
||||||
|
model from HuggingFace — until then the service answers 503 and this
|
||||||
|
module fails with a clear "NER model not available" error.
|
||||||
|
|
||||||
|
## Setting up the judge-ner host service
|
||||||
|
|
||||||
|
The NER model (JuraNER by Harshil Darji, MIT — transformers + torch)
|
||||||
|
cannot run inside the WASM sandbox; it runs as a host service.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Start the service (Docker; ~1 GB model download on first start,
|
||||||
|
# persisted in the named volume — later starts are offline-capable)
|
||||||
|
docker run -d --name judge-ner \
|
||||||
|
-p 8756:8756 \
|
||||||
|
-v judge-ner-models:/models \
|
||||||
|
judge-ner
|
||||||
|
|
||||||
|
# 2. Register it with the hub (~/.chain/config.yaml)
|
||||||
|
# services:
|
||||||
|
# judge-ner:
|
||||||
|
# endpoint: http://127.0.0.1:8756
|
||||||
|
# health_check:
|
||||||
|
# url: http://127.0.0.1:8756/health
|
||||||
|
|
||||||
|
# 3. Verify
|
||||||
|
curl -s localhost:8756/health # {"status":"ok","model_loaded":true}
|
||||||
|
chain service status judge-ner
|
||||||
|
```
|
||||||
|
|
||||||
|
Source and image build: `fai_judge/host_services/judge-ner/`
|
||||||
|
(`docker build -t judge-ner .`).
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
- Malformed `request`, non-UTF-8 text document, or a 4xx from the
|
||||||
|
service → `invalid input: ...`
|
||||||
|
- Service unreachable, 5xx, malformed service response →
|
||||||
|
`internal error: judge-ner at <url> ...`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
cargo build --release --target wasm32-wasip2
|
||||||
|
```
|
||||||
21
NOTICE
Normal file
21
NOTICE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
text-anonymize-legal-de
|
||||||
|
Copyright 2026 Flemming.AI
|
||||||
|
|
||||||
|
This product includes software developed by Dr. Stefan Flemming
|
||||||
|
and contributors (https://flemming.ai/).
|
||||||
|
|
||||||
|
------------------------------------------------------------------
|
||||||
|
Third-party acknowledgments
|
||||||
|
|
||||||
|
This module is a bridge to the judge-ner host service, which builds
|
||||||
|
on the JuraNER named-entity-recognition model and anonymization
|
||||||
|
pipeline by Harshil Darji (MIT License):
|
||||||
|
|
||||||
|
- Model: https://huggingface.co/harshildarji/JuraNER
|
||||||
|
- Related work: https://github.com/harshildarji/German-NER-BERT
|
||||||
|
|
||||||
|
The WASM module itself contains none of that code; it forwards
|
||||||
|
documents to the host service over loopback HTTP.
|
||||||
|
|
||||||
|
JSON (de)serialization uses `serde` + `serde_json`
|
||||||
|
(MIT OR Apache-2.0).
|
||||||
27
STORE.yaml
Normal file
27
STORE.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
schema_version: 1
|
||||||
|
|
||||||
|
author:
|
||||||
|
name: "Flemming.AI"
|
||||||
|
email: chain@flemming.ai
|
||||||
|
url: https://flemming.ai
|
||||||
|
license: Apache-2.0
|
||||||
|
|
||||||
|
tagline:
|
||||||
|
en: "NER anonymization for German legal texts (JuraNER)"
|
||||||
|
de: "NER-Anonymisierung für deutsche Rechtstexte (JuraNER)"
|
||||||
|
|
||||||
|
description:
|
||||||
|
en: "Transformer-based anonymization of German legal documents (plain text and DOCX) using the JuraNER model by Harshil Darji (MIT, HTW Berlin research). Detects persons, addresses, courts, file numbers and more; replaces them with consistent ⟦TYPE#⟧ tokens. Requires the judge-ner host service (Docker) — the NER model runs outside the WASM sandbox."
|
||||||
|
de: "Transformer-basierte Anonymisierung deutscher Rechtsdokumente (Klartext und DOCX) mit dem JuraNER-Modell von Harshil Darji (MIT, HTW-Berlin-Forschung). Erkennt Personen, Adressen, Gerichte, Aktenzeichen u. v. m. und ersetzt sie durch konsistente ⟦TYP#⟧-Token. Benötigt den judge-ner-Host-Service (Docker) — das NER-Modell läuft außerhalb der WASM-Sandbox."
|
||||||
|
|
||||||
|
category: text-processing
|
||||||
|
tags: [legal, german, anonymization, ner, gdpr, requires-host-service]
|
||||||
|
|
||||||
|
assets:
|
||||||
|
icon: assets/icon.svg
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
long_description: MODULE.md
|
||||||
|
|
||||||
|
links:
|
||||||
|
repository: https://git.flemming.ai/chain-modules/text-anonymize-legal-de
|
||||||
8
assets/icon.svg
Normal file
8
assets/icon.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||||
|
<rect width="64" height="64" rx="12" fill="#08090A"/>
|
||||||
|
<!-- document with redacted lines -->
|
||||||
|
<rect x="18" y="12" width="28" height="40" rx="3" fill="none" stroke="#E9E7E2" stroke-width="3"/>
|
||||||
|
<line x1="24" y1="22" x2="40" y2="22" stroke="#E9E7E2" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
<rect x="24" y="28" width="16" height="5" rx="2" fill="#2E8F9E"/>
|
||||||
|
<line x1="24" y1="40" x2="36" y2="40" stroke="#E9E7E2" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 553 B |
BIN
module.wasm
Normal file
BIN
module.wasm
Normal file
Binary file not shown.
112
module.yaml
Normal file
112
module.yaml
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
schema_version: 3
|
||||||
|
provider: judge
|
||||||
|
|
||||||
|
name: text-anonymize-legal-de
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
# Bridge module: forwards the document to the judge-ner host service
|
||||||
|
# (FastAPI, JuraNER transformers model) over loopback HTTP and maps
|
||||||
|
# the response onto the frozen capability contract below.
|
||||||
|
provides:
|
||||||
|
- capability: text.anonymize.legal-de
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
request:
|
||||||
|
type: json
|
||||||
|
description:
|
||||||
|
en: |
|
||||||
|
Anonymization request:
|
||||||
|
{
|
||||||
|
"mode": "text" | "docx",
|
||||||
|
"options": { // all optional
|
||||||
|
"importance_levels": ["High"], // High|Mid|Low
|
||||||
|
"confidence_threshold": 0.8, // 0.0..1.0
|
||||||
|
"manual_phrases": [], // extra phrases to redact
|
||||||
|
"maintain_consistency": true, // same surface -> same token
|
||||||
|
"remove_rubrum": false // drop the rubrum block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
de: |
|
||||||
|
Anonymisierungs-Anfrage:
|
||||||
|
{
|
||||||
|
"mode": "text" | "docx",
|
||||||
|
"options": { // alle optional
|
||||||
|
"importance_levels": ["High"], // High|Mid|Low
|
||||||
|
"confidence_threshold": 0.8, // 0.0..1.0
|
||||||
|
"manual_phrases": [], // zusätzlich zu schwärzende Phrasen
|
||||||
|
"maintain_consistency": true, // gleiche Oberfläche -> gleiches Token
|
||||||
|
"remove_rubrum": false // Rubrum-Block entfernen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document:
|
||||||
|
type: bytes
|
||||||
|
description:
|
||||||
|
en: |
|
||||||
|
The document to anonymize. UTF-8 plain text for mode=text,
|
||||||
|
a DOCX file for mode=docx.
|
||||||
|
de: |
|
||||||
|
Das zu anonymisierende Dokument. UTF-8-Klartext bei mode=text,
|
||||||
|
eine DOCX-Datei bei mode=docx.
|
||||||
|
endpoint:
|
||||||
|
type: text
|
||||||
|
description:
|
||||||
|
en: |
|
||||||
|
Optional base URL of the judge-ner host service.
|
||||||
|
Default: "http://127.0.0.1:8756".
|
||||||
|
de: |
|
||||||
|
Optionale Basis-URL des judge-ner-Host-Services.
|
||||||
|
Default: "http://127.0.0.1:8756".
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
result:
|
||||||
|
type: json
|
||||||
|
description:
|
||||||
|
en: |
|
||||||
|
{
|
||||||
|
"replacements": [ { "text", "label", "label_description",
|
||||||
|
"start", "end", "score", "line_number",
|
||||||
|
"anonymized", ...run-id fields for docx }, ... ],
|
||||||
|
"text": "..." // anonymized text, only for mode=text
|
||||||
|
}
|
||||||
|
`replacements` lists all detected entities; entries with
|
||||||
|
"anonymized": null were detected but not replaced. A
|
||||||
|
"statistics" object (and "rubrum" when remove_rubrum matched)
|
||||||
|
is passed through from the service.
|
||||||
|
de: |
|
||||||
|
{
|
||||||
|
"replacements": [ { "text", "label", "label_description",
|
||||||
|
"start", "end", "score", "line_number",
|
||||||
|
"anonymized", ...Run-ID-Felder bei docx }, ... ],
|
||||||
|
"text": "..." // anonymisierter Text, nur bei mode=text
|
||||||
|
}
|
||||||
|
`replacements` enthält alle erkannten Entitäten; Einträge mit
|
||||||
|
"anonymized": null wurden erkannt, aber nicht ersetzt. Ein
|
||||||
|
"statistics"-Objekt (und "rubrum" bei erkanntem Rubrum) wird
|
||||||
|
vom Service durchgereicht.
|
||||||
|
document:
|
||||||
|
type: bytes
|
||||||
|
description:
|
||||||
|
en: |
|
||||||
|
The anonymized document: the anonymized UTF-8 text for
|
||||||
|
mode=text, the rewritten DOCX for mode=docx.
|
||||||
|
de: |
|
||||||
|
Das anonymisierte Dokument: bei mode=text die anonymisierte
|
||||||
|
UTF-8-Textfassung, bei mode=docx das umgeschriebene DOCX.
|
||||||
|
|
||||||
|
# MIME allow-list for the bytes input (Studio file-picker filter).
|
||||||
|
accepts_mime:
|
||||||
|
- text/plain
|
||||||
|
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||||
|
|
||||||
|
# Outbound HTTP to the local judge-ner host service only.
|
||||||
|
permissions:
|
||||||
|
- "net: localhost"
|
||||||
|
- "net: 127.0.0.1"
|
||||||
|
|
||||||
|
# The hub refuses to dispatch a step to this module unless the
|
||||||
|
# operator declared a `judge-ner` entry in the service registry
|
||||||
|
# (~/.chain/config.yaml `services:`). See
|
||||||
|
# fai_chain/docs/architecture/host-services.md.
|
||||||
|
requires_services:
|
||||||
|
- judge-ner
|
||||||
5
rust-toolchain.toml
Normal file
5
rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "1.86"
|
||||||
|
targets = ["wasm32-wasip2"]
|
||||||
|
profile = "minimal"
|
||||||
|
components = ["rustfmt", "clippy"]
|
||||||
439
src/anonymize.rs
Normal file
439
src/anonymize.rs
Normal file
|
|
@ -0,0 +1,439 @@
|
||||||
|
//! Request/response mapping between the frozen
|
||||||
|
//! `text.anonymize.legal-de` contract and the judge-ner host
|
||||||
|
//! service HTTP API.
|
||||||
|
//!
|
||||||
|
//! All HTTP I/O lives behind the `HttpClient` trait so unit tests
|
||||||
|
//! exercise request building, response parsing and error mapping on
|
||||||
|
//! the host without a network.
|
||||||
|
|
||||||
|
use base64::Engine as _;
|
||||||
|
use base64::engine::general_purpose::STANDARD as B64;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
/// Base URL used when the optional `endpoint` input is absent/empty.
|
||||||
|
pub const DEFAULT_ENDPOINT: &str = "http://127.0.0.1:8756";
|
||||||
|
|
||||||
|
pub const DOCX_MIME: &str =
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
|
pub const TEXT_MIME: &str = "text/plain; charset=utf-8";
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AnonError {
|
||||||
|
/// Caller-side problem (bad request JSON, non-UTF-8 text
|
||||||
|
/// document, service rejected the input as invalid).
|
||||||
|
#[error("invalid input: {0}")]
|
||||||
|
InvalidInput(String),
|
||||||
|
/// Transport failure or unexpected service behaviour.
|
||||||
|
#[error("{0}")]
|
||||||
|
Service(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal HTTP response the mapping layer needs.
|
||||||
|
pub struct HttpResponse {
|
||||||
|
pub status: u16,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HttpClient {
|
||||||
|
fn post_json(&self, url: &str, body: &str) -> Result<HttpResponse, AnonError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Mode {
|
||||||
|
Text,
|
||||||
|
Docx,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Request {
|
||||||
|
pub mode: Mode,
|
||||||
|
#[serde(default)]
|
||||||
|
pub options: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the `request` input JSON.
|
||||||
|
pub fn parse_request(raw: &str) -> Result<Request, AnonError> {
|
||||||
|
let mut req: Request = serde_json::from_str(raw).map_err(|e| {
|
||||||
|
AnonError::InvalidInput(format!(
|
||||||
|
"request must be {{\"mode\":\"text\"|\"docx\",\"options\":{{..}}}}: {e}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if req.options.is_null() {
|
||||||
|
req.options = json!({});
|
||||||
|
} else if !req.options.is_object() {
|
||||||
|
return Err(AnonError::InvalidInput(
|
||||||
|
"request.options must be a JSON object".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalize the endpoint override: empty -> default, strip a
|
||||||
|
/// trailing slash so path joining stays predictable.
|
||||||
|
pub fn resolve_endpoint(endpoint_input: &str) -> String {
|
||||||
|
let trimmed = endpoint_input.trim();
|
||||||
|
let base = if trimmed.is_empty() {
|
||||||
|
DEFAULT_ENDPOINT
|
||||||
|
} else {
|
||||||
|
trimmed
|
||||||
|
};
|
||||||
|
base.trim_end_matches('/').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outcome of one anonymization call, ready to be lifted into
|
||||||
|
/// module `Outputs` by the caller.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AnonOutcome {
|
||||||
|
/// JSON string for the `result` output.
|
||||||
|
pub result_json: String,
|
||||||
|
/// Bytes for the `document` output.
|
||||||
|
pub document: Vec<u8>,
|
||||||
|
/// MIME type of `document`.
|
||||||
|
pub document_mime: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<C: HttpClient>(
|
||||||
|
client: &C,
|
||||||
|
request: &Request,
|
||||||
|
document: &[u8],
|
||||||
|
endpoint_input: &str,
|
||||||
|
) -> Result<AnonOutcome, AnonError> {
|
||||||
|
let base = resolve_endpoint(endpoint_input);
|
||||||
|
match request.mode {
|
||||||
|
Mode::Text => run_text(client, &base, &request.options, document),
|
||||||
|
Mode::Docx => run_docx(client, &base, &request.options, document),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_text<C: HttpClient>(
|
||||||
|
client: &C,
|
||||||
|
base: &str,
|
||||||
|
options: &serde_json::Value,
|
||||||
|
document: &[u8],
|
||||||
|
) -> Result<AnonOutcome, AnonError> {
|
||||||
|
let text = std::str::from_utf8(document).map_err(|_| {
|
||||||
|
AnonError::InvalidInput(
|
||||||
|
"document is not valid UTF-8 text (mode=text expects a UTF-8 text payload)"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let url = format!("{base}/anonymize/text");
|
||||||
|
let body = serde_json::to_string(&json!({ "text": text, "options": options }))
|
||||||
|
.map_err(|e| AnonError::Service(format!("serialize request: {e}")))?;
|
||||||
|
let response = post_expect_json(client, &url, &body)?;
|
||||||
|
|
||||||
|
let anonymized = response
|
||||||
|
.get("text")
|
||||||
|
.and_then(|t| t.as_str())
|
||||||
|
.ok_or_else(|| AnonError::Service(
|
||||||
|
"judge-ner response is missing the 'text' field".to_string(),
|
||||||
|
))?
|
||||||
|
.to_string();
|
||||||
|
let result_json = build_result(&response, Some(&anonymized))?;
|
||||||
|
|
||||||
|
Ok(AnonOutcome {
|
||||||
|
result_json,
|
||||||
|
document: anonymized.into_bytes(),
|
||||||
|
document_mime: TEXT_MIME,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_docx<C: HttpClient>(
|
||||||
|
client: &C,
|
||||||
|
base: &str,
|
||||||
|
options: &serde_json::Value,
|
||||||
|
document: &[u8],
|
||||||
|
) -> Result<AnonOutcome, AnonError> {
|
||||||
|
let url = format!("{base}/anonymize/docx");
|
||||||
|
let body = serde_json::to_string(&json!({
|
||||||
|
"docx_base64": B64.encode(document),
|
||||||
|
"options": options,
|
||||||
|
}))
|
||||||
|
.map_err(|e| AnonError::Service(format!("serialize request: {e}")))?;
|
||||||
|
let response = post_expect_json(client, &url, &body)?;
|
||||||
|
|
||||||
|
let docx_b64 = response
|
||||||
|
.get("docx_base64")
|
||||||
|
.and_then(|d| d.as_str())
|
||||||
|
.ok_or_else(|| AnonError::Service(
|
||||||
|
"judge-ner response is missing the 'docx_base64' field".to_string(),
|
||||||
|
))?;
|
||||||
|
let docx_bytes = B64.decode(docx_b64).map_err(|e| {
|
||||||
|
AnonError::Service(format!("judge-ner returned invalid base64 DOCX: {e}"))
|
||||||
|
})?;
|
||||||
|
let result_json = build_result(&response, None)?;
|
||||||
|
|
||||||
|
Ok(AnonOutcome {
|
||||||
|
result_json,
|
||||||
|
document: docx_bytes,
|
||||||
|
document_mime: DOCX_MIME,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the `result` output JSON from the service response.
|
||||||
|
///
|
||||||
|
/// Frozen contract: `{"replacements": [...]}` plus `"text"` only in
|
||||||
|
/// text mode. `statistics` / `rubrum` are passed through as optional
|
||||||
|
/// extras when the service supplied them.
|
||||||
|
fn build_result(
|
||||||
|
response: &serde_json::Value,
|
||||||
|
text: Option<&str>,
|
||||||
|
) -> Result<String, AnonError> {
|
||||||
|
let replacements = response
|
||||||
|
.get("replacements")
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| AnonError::Service(
|
||||||
|
"judge-ner response is missing the 'replacements' field".to_string(),
|
||||||
|
))?;
|
||||||
|
if !replacements.is_array() {
|
||||||
|
return Err(AnonError::Service(
|
||||||
|
"judge-ner 'replacements' field is not an array".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = serde_json::Map::new();
|
||||||
|
result.insert("replacements".to_string(), replacements);
|
||||||
|
if let Some(t) = text {
|
||||||
|
result.insert("text".to_string(), json!(t));
|
||||||
|
}
|
||||||
|
for passthrough in ["statistics", "rubrum"] {
|
||||||
|
if let Some(v) = response.get(passthrough) {
|
||||||
|
result.insert(passthrough.to_string(), v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::to_string(&serde_json::Value::Object(result))
|
||||||
|
.map_err(|e| AnonError::Service(format!("serialize result: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST and decode, translating HTTP-level failures into contract
|
||||||
|
/// errors. FastAPI signals caller errors as 4xx with a JSON body
|
||||||
|
/// `{"detail": "..."}` — surface those as InvalidInput so the flow
|
||||||
|
/// engine reports them as the caller's problem, not the module's.
|
||||||
|
fn post_expect_json<C: HttpClient>(
|
||||||
|
client: &C,
|
||||||
|
url: &str,
|
||||||
|
body: &str,
|
||||||
|
) -> Result<serde_json::Value, AnonError> {
|
||||||
|
let response = client.post_json(url, body)?;
|
||||||
|
if !(200..300).contains(&response.status) {
|
||||||
|
let detail = serde_json::from_str::<serde_json::Value>(&response.body)
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.get("detail").and_then(|d| d.as_str().map(String::from)))
|
||||||
|
.unwrap_or_else(|| truncate(&response.body, 300));
|
||||||
|
let message = format!(
|
||||||
|
"judge-ner at {url} answered HTTP {status}: {detail}",
|
||||||
|
status = response.status,
|
||||||
|
);
|
||||||
|
return if (400..500).contains(&response.status) && response.status != 404 {
|
||||||
|
Err(AnonError::InvalidInput(message))
|
||||||
|
} else {
|
||||||
|
Err(AnonError::Service(message))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
serde_json::from_str(&response.body).map_err(|e| {
|
||||||
|
AnonError::Service(format!("judge-ner returned non-JSON body from {url}: {e}"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(s: &str, max: usize) -> String {
|
||||||
|
if s.len() <= max {
|
||||||
|
s.to_string()
|
||||||
|
} else {
|
||||||
|
let mut cut = max;
|
||||||
|
while !s.is_char_boundary(cut) {
|
||||||
|
cut -= 1;
|
||||||
|
}
|
||||||
|
format!("{}…", &s[..cut])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||||
|
use super::*;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
struct MockClient {
|
||||||
|
responses: RefCell<Vec<Result<HttpResponse, AnonError>>>,
|
||||||
|
seen: RefCell<Vec<(String, String)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockClient {
|
||||||
|
fn new(responses: Vec<Result<HttpResponse, AnonError>>) -> Self {
|
||||||
|
Self {
|
||||||
|
responses: RefCell::new(responses),
|
||||||
|
seen: RefCell::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpClient for MockClient {
|
||||||
|
fn post_json(&self, url: &str, body: &str) -> Result<HttpResponse, AnonError> {
|
||||||
|
self.seen
|
||||||
|
.borrow_mut()
|
||||||
|
.push((url.to_string(), body.to_string()));
|
||||||
|
self.responses
|
||||||
|
.borrow_mut()
|
||||||
|
.pop()
|
||||||
|
.unwrap_or(Err(AnonError::Service("no more mock responses".into())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ok(body: &str) -> Result<HttpResponse, AnonError> {
|
||||||
|
Ok(HttpResponse {
|
||||||
|
status: 200,
|
||||||
|
body: body.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_request_accepts_text_mode_without_options() {
|
||||||
|
let req = parse_request(r#"{"mode":"text"}"#).unwrap();
|
||||||
|
assert_eq!(req.mode, Mode::Text);
|
||||||
|
assert!(req.options.as_object().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_request_accepts_docx_mode_with_options() {
|
||||||
|
let req =
|
||||||
|
parse_request(r#"{"mode":"docx","options":{"remove_rubrum":true}}"#).unwrap();
|
||||||
|
assert_eq!(req.mode, Mode::Docx);
|
||||||
|
assert_eq!(req.options["remove_rubrum"], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_request_rejects_unknown_mode() {
|
||||||
|
assert!(matches!(
|
||||||
|
parse_request(r#"{"mode":"pdf"}"#),
|
||||||
|
Err(AnonError::InvalidInput(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_request_rejects_non_object_options() {
|
||||||
|
assert!(matches!(
|
||||||
|
parse_request(r#"{"mode":"text","options":[1,2]}"#),
|
||||||
|
Err(AnonError::InvalidInput(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_endpoint_defaults_and_strips_slash() {
|
||||||
|
assert_eq!(resolve_endpoint(""), DEFAULT_ENDPOINT);
|
||||||
|
assert_eq!(resolve_endpoint(" "), DEFAULT_ENDPOINT);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_endpoint("http://10.0.0.5:9000/"),
|
||||||
|
"http://10.0.0.5:9000"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_mode_maps_response_onto_contract() {
|
||||||
|
let service_response = r#"{
|
||||||
|
"text": "Herr ⟦PER1⟧.",
|
||||||
|
"replacements": [{"text":"Müller","label":"PER","start":5,"end":11,
|
||||||
|
"anonymized":"⟦PER1⟧"}],
|
||||||
|
"statistics": {"total_entities": 1},
|
||||||
|
"processing_time": 0.4
|
||||||
|
}"#;
|
||||||
|
let client = MockClient::new(vec![ok(service_response)]);
|
||||||
|
let req = parse_request(r#"{"mode":"text"}"#).unwrap();
|
||||||
|
let outcome = run(&client, &req, "Herr Müller.".as_bytes(), "").unwrap();
|
||||||
|
|
||||||
|
// URL + body
|
||||||
|
let (url, body) = client.seen.borrow()[0].clone();
|
||||||
|
assert_eq!(url, "http://127.0.0.1:8756/anonymize/text");
|
||||||
|
let sent: serde_json::Value = serde_json::from_str(&body).unwrap();
|
||||||
|
assert_eq!(sent["text"], "Herr Müller.");
|
||||||
|
assert!(sent["options"].is_object());
|
||||||
|
|
||||||
|
// result output
|
||||||
|
let result: serde_json::Value = serde_json::from_str(&outcome.result_json).unwrap();
|
||||||
|
assert_eq!(result["text"], "Herr ⟦PER1⟧.");
|
||||||
|
assert_eq!(result["replacements"][0]["anonymized"], "⟦PER1⟧");
|
||||||
|
assert_eq!(result["statistics"]["total_entities"], 1);
|
||||||
|
assert!(result.get("rubrum").is_none());
|
||||||
|
|
||||||
|
// document output = anonymized UTF-8 text
|
||||||
|
assert_eq!(outcome.document, "Herr ⟦PER1⟧.".as_bytes());
|
||||||
|
assert_eq!(outcome.document_mime, TEXT_MIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_mode_rejects_non_utf8_document() {
|
||||||
|
let client = MockClient::new(vec![]);
|
||||||
|
let req = parse_request(r#"{"mode":"text"}"#).unwrap();
|
||||||
|
let err = run(&client, &req, &[0xff, 0xfe, 0x00], "").unwrap_err();
|
||||||
|
assert!(matches!(err, AnonError::InvalidInput(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn docx_mode_encodes_and_decodes_base64() {
|
||||||
|
let input_docx = b"PK\x03\x04fake-docx".to_vec();
|
||||||
|
let output_docx = b"PK\x03\x04anonymized".to_vec();
|
||||||
|
let service_response = serde_json::to_string(&serde_json::json!({
|
||||||
|
"docx_base64": B64.encode(&output_docx),
|
||||||
|
"replacements": [],
|
||||||
|
"statistics": {"total_entities": 0}
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
let client = MockClient::new(vec![ok(&service_response)]);
|
||||||
|
let req = parse_request(r#"{"mode":"docx","options":{"remove_rubrum":true}}"#)
|
||||||
|
.unwrap();
|
||||||
|
let outcome = run(&client, &req, &input_docx, "http://127.0.0.1:9999/").unwrap();
|
||||||
|
|
||||||
|
let (url, body) = client.seen.borrow()[0].clone();
|
||||||
|
assert_eq!(url, "http://127.0.0.1:9999/anonymize/docx");
|
||||||
|
let sent: serde_json::Value = serde_json::from_str(&body).unwrap();
|
||||||
|
assert_eq!(sent["docx_base64"], B64.encode(&input_docx));
|
||||||
|
assert_eq!(sent["options"]["remove_rubrum"], true);
|
||||||
|
|
||||||
|
let result: serde_json::Value = serde_json::from_str(&outcome.result_json).unwrap();
|
||||||
|
assert!(result.get("text").is_none(), "no 'text' key in docx mode");
|
||||||
|
assert_eq!(result["replacements"].as_array().unwrap().len(), 0);
|
||||||
|
|
||||||
|
assert_eq!(outcome.document, output_docx);
|
||||||
|
assert_eq!(outcome.document_mime, DOCX_MIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_4xx_maps_to_invalid_input_with_detail() {
|
||||||
|
let client = MockClient::new(vec![Ok(HttpResponse {
|
||||||
|
status: 422,
|
||||||
|
body: r#"{"detail":"text must not be empty"}"#.to_string(),
|
||||||
|
})]);
|
||||||
|
let req = parse_request(r#"{"mode":"text"}"#).unwrap();
|
||||||
|
let err = run(&client, &req, b"", "").unwrap_err();
|
||||||
|
match err {
|
||||||
|
AnonError::InvalidInput(msg) => {
|
||||||
|
assert!(msg.contains("text must not be empty"), "msg = {msg}");
|
||||||
|
assert!(msg.contains("422"), "msg = {msg}");
|
||||||
|
}
|
||||||
|
other => panic!("expected InvalidInput, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_5xx_maps_to_service_error() {
|
||||||
|
let client = MockClient::new(vec![Ok(HttpResponse {
|
||||||
|
status: 503,
|
||||||
|
body: r#"{"detail":"NER model not available: ..."}"#.to_string(),
|
||||||
|
})]);
|
||||||
|
let req = parse_request(r#"{"mode":"text"}"#).unwrap();
|
||||||
|
let err = run(&client, &req, b"hello", "").unwrap_err();
|
||||||
|
assert!(matches!(err, AnonError::Service(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_replacements_is_a_service_error() {
|
||||||
|
let client = MockClient::new(vec![ok(r#"{"text":"x"}"#)]);
|
||||||
|
let req = parse_request(r#"{"mode":"text"}"#).unwrap();
|
||||||
|
let err = run(&client, &req, b"x", "").unwrap_err();
|
||||||
|
match err {
|
||||||
|
AnonError::Service(msg) => assert!(msg.contains("replacements")),
|
||||||
|
other => panic!("expected Service, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/lib.rs
Normal file
124
src/lib.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
//! `text.anonymize.legal-de` — NER anonymization of German legal
|
||||||
|
//! texts, bridged to the `judge-ner` host service.
|
||||||
|
//!
|
||||||
|
//! The heavy lifting (JuraNER transformers model, DOCX rewriting)
|
||||||
|
//! happens in the judge-ner container — see
|
||||||
|
//! `fai_judge/host_services/judge-ner/`. This module forwards the
|
||||||
|
//! document over loopback HTTP and maps the response onto the frozen
|
||||||
|
//! capability contract:
|
||||||
|
//!
|
||||||
|
//! inputs: `request` (json) {"mode":"text"|"docx","options":{...}}
|
||||||
|
//! `document` (bytes) UTF-8 text or DOCX
|
||||||
|
//! `endpoint` (text, optional) service base URL override
|
||||||
|
//! outputs: `result` (json) {"replacements":[...], "text" only for
|
||||||
|
//! mode=text}
|
||||||
|
//! `document` (bytes) anonymized text/DOCX
|
||||||
|
|
||||||
|
mod anonymize;
|
||||||
|
|
||||||
|
pub use anonymize::DEFAULT_ENDPOINT;
|
||||||
|
|
||||||
|
use chain_module_sdk::prelude::*;
|
||||||
|
|
||||||
|
use crate::anonymize::{AnonError, HttpClient, HttpResponse};
|
||||||
|
|
||||||
|
#[fai_module]
|
||||||
|
pub fn invoke(_ctx: Context, inputs: Inputs) -> Result<Outputs, ModuleError> {
|
||||||
|
let request_raw = read_json_or_text(&inputs, "request")?;
|
||||||
|
let request = anonymize::parse_request(&request_raw).map_err(to_module_error)?;
|
||||||
|
let document = inputs.require_bytes("document")?;
|
||||||
|
let endpoint = inputs
|
||||||
|
.get("endpoint")
|
||||||
|
.and_then(payload_text)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let client = make_client();
|
||||||
|
let outcome = anonymize::run(&client, &request, &document.data, &endpoint)
|
||||||
|
.map_err(to_module_error)?;
|
||||||
|
|
||||||
|
Ok(Outputs::new()
|
||||||
|
.with_json_str("result", outcome.result_json)
|
||||||
|
.with_bytes("document", outcome.document_mime, outcome.document))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_module_error(err: AnonError) -> ModuleError {
|
||||||
|
match err {
|
||||||
|
AnonError::InvalidInput(msg) => ModuleError::invalid_input(msg),
|
||||||
|
AnonError::Service(msg) => ModuleError::internal(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn payload_text(p: &Payload) -> Option<String> {
|
||||||
|
match p {
|
||||||
|
Payload::Text(s) => Some(s.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept the request payload either as `json` (the documented form)
|
||||||
|
/// or `text` (the form gRPC clients without first-class JSON-payload
|
||||||
|
/// support fall through to). Both carry the same JSON string.
|
||||||
|
fn read_json_or_text(inputs: &Inputs, name: &str) -> Result<String, ModuleError> {
|
||||||
|
if let Ok(s) = inputs.require_json_str(name) {
|
||||||
|
return Ok(s.to_string());
|
||||||
|
}
|
||||||
|
if let Ok(s) = inputs.require_text(name) {
|
||||||
|
return Ok(s.to_string());
|
||||||
|
}
|
||||||
|
Err(ModuleError::invalid_input(format!(
|
||||||
|
"input '{name}' must be json or text (carrying a JSON string)"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn make_client() -> WakiClient {
|
||||||
|
WakiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn make_client() -> HostStubClient {
|
||||||
|
HostStubClient
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Host builds (unit tests, `cargo test`) have no outbound HTTP —
|
||||||
|
/// only the wasm32 build talks to the service.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
struct HostStubClient;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl HttpClient for HostStubClient {
|
||||||
|
fn post_json(&self, _url: &str, _body: &str) -> Result<HttpResponse, AnonError> {
|
||||||
|
Err(AnonError::Service(
|
||||||
|
"HTTP path is unavailable on the host build; only wasm32 supports outbound HTTP"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
struct WakiClient;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
impl HttpClient for WakiClient {
|
||||||
|
fn post_json(&self, url: &str, body: &str) -> Result<HttpResponse, AnonError> {
|
||||||
|
let response = waki::Client::new()
|
||||||
|
.post(url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(body.to_string())
|
||||||
|
.send()
|
||||||
|
.map_err(|e| {
|
||||||
|
AnonError::Service(format!(
|
||||||
|
"judge-ner service unreachable at {url}: {e} \
|
||||||
|
(is the judge-ner container running?)"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let status = response.status_code();
|
||||||
|
let bytes = response
|
||||||
|
.body()
|
||||||
|
.map_err(|e| AnonError::Service(format!("read response body: {e}")))?;
|
||||||
|
let body = String::from_utf8(bytes)
|
||||||
|
.map_err(|e| AnonError::Service(format!("response body is not UTF-8: {e}")))?;
|
||||||
|
Ok(HttpResponse { status, body })
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue