A Rust port of the Solid Community Server (CSS) β a standards-compliant Solid server implementing the LDP, WAC, and WebID-TLS specifications.
- Requirements
- Workspace layout
- Quickstart
- Running the server
- Running integration tests
- Unit tests
- CLI reference
- Environment variables
- Architecture
- Contributing
| Tool | Minimum version |
|---|---|
| Rust | 1.76 (stable) |
| Cargo | ships with Rust |
Install Rust via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shCrates live directly at the repository root β there is no crates/ subdirectory.
solid-data-governance-rs/
βββ Cargo.toml # workspace manifest
β
βββ http-types/ # core domain types
β βββ src/
β βββ error.rs # SolidError hierarchy (4xx / 5xx)
β βββ handler.rs # OperationHandler async trait
β βββ identifier.rs # ResourceIdentifier + helpers
β βββ metadata.rs # RepresentationMetadata (Content-Type, ETag, β¦)
β βββ operation.rs # Operation enum (GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS)
β βββ representation.rs # Representation (body + metadata)
β
βββ solid-storage/ # storage traits and backends
β βββ src/
β βββ error.rs # StorageError (NotFound, AlreadyExists, AlreadyExpired, β¦)
β βββ key_value.rs # KeyValueStorage, ExpiringStorage, PassthroughKeyValueStorage
β βββ resource_store.rs # ResourceStore, BaseResourceStore, PassthroughStore, ReadOnlyStore
β βββ backends/
β βββ expiring.rs # WrappedExpiringStorage (TTL + background sweep)
β βββ file.rs # JsonFileStorage<V> (file-backed KV store)
β βββ memory.rs # MemoryMapStorage<V> (in-memory KV store)
β βββ mod.rs
β
βββ authz/ # authorisation traits
β βββ src/
β βββ authorizer.rs # Authorizer async trait
β βββ credentials.rs # Credentials (agent WebID + issuer)
β βββ permissions.rs # PermissionReader async trait + AclPermission flags
β
βββ identity/ # account and pod management traits
β βββ src/
β βββ account.rs # AccountStore async trait (CRUD + password)
β βββ client_credentials.rs # ClientCredentialsStore trait
β βββ pod.rs # PodManager + PodStore traits
β βββ webid.rs # WebIdStore + WebIdLinkStore traits
β
βββ static-assets/ # static file serving
β βββ src/
β βββ entry.rs # StaticAssetEntry (URL prefix β filesystem path)
β βββ handler.rs # StaticAssetHandler (MIME detection, path traversal guard)
β
βββ server-core/ # HTTP server: router, middleware, pipeline, lifecycle
β βββ src/
β βββ app.rs # App + AppConfig (bind, start, graceful shutdown)
β βββ handler.rs # OperationHandler bridge to Axum extractors
β βββ middleware.rs # CORS + credential extraction + authz tower layers
β βββ pipeline.rs # RequestPipeline (operation dispatch + authz wiring)
β βββ routing.rs # ldp_router() β LDP method Γ path table
β
βββ cli/ # two runnable binaries
β βββ src/
β βββ main.rs # subcommand dispatch (serve | test)
β βββ serve.rs # solid-server: CLI args, hostname resolution, server start
β βββ test_runner.rs # solid-test: CLI args, suite dispatch
β
βββ integration-tests/ # HTTP integration test library (used by solid-test)
βββ src/
βββ client.rs # SolidClient (reqwest wrapper, TAP assertions)
βββ lib.rs # public re-exports
βββ runner.rs # TestRunner (suite registry, TAP printer, exit code)
βββ suites/
βββ containers.rs # LDP container behaviour
βββ content_negotiation.rs # Accept / Content-Type negotiation
βββ error_responses.rs # 4xx error body and header shape
βββ health.rs # root resource liveness checks
βββ mod.rs
βββ resource_crud.rs # PUT / GET / DELETE on documents
βββ wac.rs # WAC access control (401, WAC-Allow)
# 1. Clone
git clone https://github.com/Genefold/solid-data-governance-rs.git
cd solid-data-governance-rs
# 2. Build everything
cargo build --release
# 3. Start the server (defaults: http://localhost:3000/)
cargo run --bin solid-server
# 4. In a second terminal β run the integration tests
cargo run --bin solid-test
# how to Log
RUST_LOG=server_core=debug cargo run --bin solid-server# Development build (fastest compile)
cargo run --bin solid-server
# Release build (optimised)
cargo run --release --bin solid-server
# Custom port, host, and base URL
cargo run --bin solid-server -- \
--port 4000 \
--host 0.0.0.0 \
--base-url http://my-server.example/
# File-backed storage (persists data to disk)
cargo run --bin solid-server -- --root-dir ./data
# Verbose logging
cargo run --bin solid-server -- --log-level debugHostname resolution β
--hostaccepts hostnames (e.g.localhost) as well as IP literals. The server resolves the name via the OS DNS resolver and binds to the resulting IP. If DNS fails it falls back to0.0.0.0:<port>and logs a warning.
The solid-test binary connects to a running Solid server and exercises its HTTP API with a TAP-formatted report. It works against both this Rust server and the original TypeScript CSS.
# 1. Start the server (in one terminal)
cargo run --bin solid-server -- --port 3001
# 2. Run all integration tests (in another terminal)
cargo run --bin solid-test -- --base-url http://localhost:3001/
# Run only a specific suite (substring match, case-insensitive)
cargo run --bin solid-test -- --filter resource-crud
# Verbose: print every request and response line
cargo run --bin solid-test -- --verbose
# Against an external / TypeScript CSS instance
cargo run --bin solid-test -- --base-url https://my-css-instance.example/TAP version 14
# health
ok 1 - GET / returns 200 or 401
ok 2 - OPTIONS / returns 204 or 200 with Allow header
ok 3 - HEAD / does not return a body
# resource-crud
ok 4 - PUT creates a new document and returns 201
ok 5 - GET returns the stored body after PUT
ok 6 - GET on absent resource returns 404
ok 7 - PUT on existing resource overwrites and returns 200 or 204
ok 8 - DELETE returns 204 and removes the resource
ok 9 - DELETE on absent resource returns 404
ok 10 - GET returns matching Content-Type
# containers
ok 11 - GET / responds with 200 or 401 (root is a container)
ok 12 - PUT to container URL creates container (201)
ok 13 - POST to container creates child resource (201)
ok 14 - GET on container includes ldp:Container Link header
ok 15 - DELETE on empty container returns 204
# content-negotiation
ok 16 - GET with Accept: text/turtle returns Turtle
ok 17 - GET with Accept: application/ld+json returns JSON-LD or 406
ok 18 - GET plain-text resource echoes text/plain
ok 19 - GET with unsupported Accept type returns 406 or 200
# error-responses
ok 20 - GET unknown path returns 404
ok 21 - PATCH without supported patch Content-Type returns 415 or 405
ok 22 - 404 response body describes the error
ok 23 - PUT to path with missing parent returns 201 (auto-create) or 404/409
# wac
ok 24 - GET on unprotected resource returns 200
ok 25 - GET on ACL-protected resource without credentials returns 401
ok 26 - WAC-Allow header is present on protected resource
1..26
# passed: 26 failed: 0
The runner exits with code 0 on full pass and 1 if any test fails.
Each crate carries its own #[cfg(test)] modules. Run them with:
# All crates at once
cargo test
# Single crate (use the Cargo package name)
cargo test -p http-types
cargo test -p solid-storage
cargo test -p authz
cargo test -p identity
cargo test -p static-assets
cargo test -p server-core
# Single test by name
cargo test -p http-types not_found_is_client_error
# With captured output (useful for debugging)
cargo test -- --nocapture| Crate | Tests / ported from TypeScript |
|---|---|
http-types |
SolidError hierarchy, ResourceIdentifier helpers Β· ported from HttpError.test.ts, ResourceIdentifier.test.ts |
solid-storage |
MemoryMapStorage, JsonFileStorage, WrappedExpiringStorage, PassthroughKeyValueStorage, BaseResourceStore, PassthroughStore, ReadOnlyStore Β· ported from MemoryMapStorage.test.ts, BaseResourceStore.test.ts, PassthroughStore.test.ts, ReadOnlyStore.test.ts |
authz |
Authorizer + PermissionReader trait contracts |
identity |
AccountStore, PodManager, WebIdStore, ClientCredentialsStore trait contracts |
static-assets |
StaticAssetHandler path-traversal guard, MIME detection |
server-core |
Router construction, middleware layer ordering |
integration-tests |
SolidClient assertion helpers |
Starts the HTTP server.
Usage: solid-server [OPTIONS]
Options:
-b, --base-url <URL> Base URL advertised to clients
[env: CSS_BASE_URL] [default: http://localhost:3000/]
-p, --port <PORT> TCP port to listen on
[env: CSS_PORT] [default: 3000]
--host <HOST> Hostname or IP to bind to (DNS-resolved)
[env: CSS_HOST] [default: localhost]
-l, --log-level <LEVEL> Log level: trace | debug | info | warn | error
[env: CSS_LOG_LEVEL] [default: info]
--root-dir <PATH> Root directory for file-backed storage
[env: CSS_ROOT_DIR] (omit for in-memory storage)
-h, --help Print help
-V, --version Print version
Runs the HTTP integration test suite against any live Solid server.
Usage: solid-test [OPTIONS]
Options:
-b, --base-url <URL> Base URL of the server under test
[env: CSS_BASE_URL] [default: http://localhost:3000/]
--filter <SUBSTR> Only run suites whose name contains SUBSTR
(case-insensitive)
-v, --verbose Print each request/response line for all tests
--timeout-ms <MS> Per-request timeout in milliseconds [default: 10000]
-h, --help Print help
-V, --version Print version
All CLI flags can be set via environment variables. Explicit flags override environment variables; both override built-in defaults.
| Variable | Flag equivalent | Default |
|---|---|---|
CSS_BASE_URL |
--base-url |
http://localhost:3000/ |
CSS_PORT |
--port |
3000 |
CSS_HOST |
--host |
localhost |
CSS_LOG_LEVEL |
--log-level |
info |
CSS_ROOT_DIR |
--root-dir |
(in-memory) |
RUST_LOG |
(overrides --log-level entirely) |
β |
RUST_LOG follows the standard tracing-subscriber filter syntax, e.g.:
RUST_LOG=solid_storage=debug,solid_server=info cargo run --bin solid-serverThe server is structured as focused crates that mirror the TypeScript CSS package layout:
HTTP Request
β
βΌ
cli (solid-server binary)
ββ serve.rs parse CLI args, resolve hostname β SocketAddr, build AppConfig
β
βΌ
server-core
ββ app.rs App lifecycle: bind TCP, start Axum, graceful shutdown
ββ middleware.rs CORS + credential extraction + authz tower layers
ββ routing.rs ldp_router() /*path + / for all LDP methods
ββ pipeline.rs RequestPipeline β authz wiring + dispatch to per-operation handlers
ββ handler.rs bridge: Axum extractors β OperationHandler trait
β
ββββΊ http-types
β ββ error.rs SolidError (BadRequest β¦ InternalServerError)
β ββ handler.rs OperationHandler async trait
β ββ identifier.rs ResourceIdentifier + path utilities
β ββ metadata.rs RepresentationMetadata (Content-Type, ETag, β¦)
β ββ operation.rs Operation enum (GET, PUT, POST, PATCH, DELETE, β¦)
β ββ representation.rs Representation (streaming body + metadata)
β
ββββΊ solid-storage
β ββ resource_store.rs ResourceStore trait (implemented by LdpStore)
β ββ key_value.rs KeyValueStorage, ExpiringStorage, Passthrough
β ββ error.rs StorageError (NotFound, AlreadyExpired, β¦)
β ββ backends/
β ββ memory.rs MemoryMapStorage<V>
β ββ file.rs JsonFileStorage<V>
β ββ expiring.rs WrappedExpiringStorage (TTL + background sweep)
β
ββββΊ authz
β ββ credentials.rs Credentials (agent WebID + issuer)
β ββ permissions.rs PermissionReader + AclPermission flags
β ββ authorizer.rs Authorizer async trait
β (wired into pipeline via authz_middleware)
β
ββββΊ identity
β ββ account.rs AccountStore (CRUD + password verify)
β ββ pod.rs PodManager + PodStore
β ββ webid.rs WebIdStore + WebIdLinkStore
β ββ client_credentials.rs ClientCredentialsStore
β
ββββΊ static-assets
ββ entry.rs StaticAssetEntry (URL prefix β fs path)
ββ handler.rs StaticAssetHandler (MIME detection, path traversal guard)
integration-tests (used by solid-test binary)
ββ client.rs SolidClient: reqwest wrapper + TAP assertion helpers
ββ runner.rs TestRunner: suite registry, TAP printer, exit code
ββ suites/
ββ health.rs root resource liveness
ββ resource_crud.rs PUT / GET / DELETE documents
ββ containers.rs LDP container behaviour
ββ content_negotiation.rs Accept / Content-Type negotiation
ββ error_responses.rs 4xx shape and headers
ββ wac.rs WAC access control (401, WAC-Allow)
- Trait-based, not class-based. Every storage backend, authoriser, and handler is an
async_traitβ swap implementations without touching call sites. ChangeMapfor reactive updates. Every mutatingResourceStoremethod returns aHashMap<Url, ChangeMetadata>so monitoring layers (notifications, webhooks) can react to fine-grained changes.- Mirror the TypeScript. Module paths, type names, and test names intentionally match their CSS counterparts to make cross-referencing straightforward.
- Layered authz. Every request passes through
extract_credentials()βPermissionReader::read()βAuthorizer::authorize()before reaching an LDP handler.
See CONTRIBUTING.md for the full contributor guidelines, licence assignment terms, and code of conduct.
# Format
cargo fmt --all
# Lint
cargo clippy --all-targets --all-features -- -D warnings
# Test
cargo test --all
# All-in-one pre-push check
cargo fmt --all && cargo clippy --all-targets -- -D warnings && cargo test --allPlease open an issue before submitting large pull requests.