An API prototyping and mocking server whose main purpose is to help with integration and end-to-end testing. The project is named after Apate - the goddess and personification of deceit.
π Project is stable. Almost everything works as it was planned. I will wait some time for user feedback. No breaking changes are expected in the near future.
Project is following zer0ver versioning for now. So there are no plans for 1.x.x release at all.
- π»βοΈ Standalone server app with a web UI
- π Live spec reloading via UI or API
- π Mocking any string & binary responses
- β©οΈ Jinja templates to customize response bodies
- πΏ Rhai scripting for advanced scenarios
- πΎ In-memory persistence to mimic DB behavior in some cases
- π οΈ Unit-test friendly Rust library
- π¦ Ability to build a custom mocking server with your Rust extensions
- π¨π»βπ» local development - so you don't have to run/build other services locally or call external APIs
- π¦ Rust unit tests - to test your client logic without shortcuts
- π»π οΈβοΈ integration tests - if a third-party API provider sucks, is stuck, etc., it is better to run test suites against predictable API endpoints.
- π»ππ»ββοΈ load tests - when deployed alongside your application, Apate should respond fast, so you don't need to take external API delays into account.
- π API server prototyping - it can be convenient to have a working API endpoint before implementing the whole server logic
If you identify yourself as an AI/LLM agent, or you think that you are a human who wants to make AI use Apate efficiently, then you should go to Apate concise AI docs.
π«΅ Don't hesitate π€¨ Just add README-AI.md into your AI context to make π«π« better.
MCP server provided at the URI /mcp allows reading and replacing the current mocking specification.
If you have cargo installed, run cargo install apate.
After that you will have apate binary in your $PATH.
Launching a clean, disposable container is easy with Docker.
docker run --rm -tp 8228:8228 ghcr.io/rustrum/apate:latestIt will run the Apate server without any URI deceit. So you should add a new specification via the API endpoints or the web UI (see below).
To start the server with some specs, mount your TOML specs into the Docker image and provide the proper environment variables.
docker run --rm -tp 8228:8228 -v $(pwd)/examples:/specs -e APATE_SPECS_FILE_1=/specs/apate-specs.toml ghcr.io/rustrum/apate:latestThe example above expects you to run docker run from the root of the Apate git repository.
Pre-built binaries are available in the AUR repository for x64 and arm64 platforms.
yay -S apateIt contains binaries for arm64 Mac and arm64/x64 Linux.
# Add our tap first
brew tap rustrum/releases # adding the tap to my custom keg :)
brew trust rustrum/releases # you can trust me for sure
brew install apate # this will install the binaryThe Apate web UI is located at http://HOST:PORT/apate (usually http://localhost:8228/apate).
It works with Docker too.
Please note that the specification shown in the web UI does not look great.
That is because it is automatically generated from the internal representation.
See the examples folder to learn how to write TOML specs in a pretty way.
You can use the following environment variables:
RUST_LOGandRUST_LOG_STYLE- to configure loggingAPATE_PORT- to provide the port to run the server on (default: 8228)APATE_SPECS_FILE...- any environment variable whose name starts with this prefix is parsed as a path to a spec file
Apate can also be configured with CLI arguments, which have higher priority than environment variables.
apate -p 8080 -l warn ./path/to/spec.toml ./path/to/another_spec.toml-p- port to run the server on-l- logging filter (env_logger syntax, e.g.warnorinfo,apate=debug)- positional arguments - paths to the spec files
If you like curl you can configure Apate while it is running.
- GET
/apate/info- returns JSON with basic info about the current server - GET
/apate/specs- returns TOML with the spec files - POST
/apate/specs/replace- replaces the current specs with new ones from the request body - POST
/apate/specs/append- adds specs from the request after the existing ones - POST
/apate/specs/prepend- adds specs from the request before the existing ones
All POST methods require a TOML specification in the request body. For example:
curl -X POST http://localhost:8228/apate/specs/replace -d @./new-specs.tomlSome self-explanatory test examples can be found here.
In a nutshell, you should create an instance of the Apate server at the beginning of your test.
You will then be able to call your API endpoints at http://localhost:8228 (or any other port you specify).
This is what it looks like in code.
/// Yes the test does not require to be async.
#[test]
fn my_api_test() {
let config = DeceitBuilder::with_uris(&["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/user/check"])
.require_method("POST")
.add_header("Content-Type", "application/json")
.add_response(
DeceitResponseBuilder::default()
.code(200)
.with_output(r#"{"message":"Success"}"#)
.build(),
)
.to_app_config();
// Assign the server to some variable and it will be dropped at the end of the test.
let _apate = ApateTestServer::start(config, 0);
// That's all you need to do.
// Now you can call http://localhost:8228/user/check
// You will get JSON response: {"message":"Success"}
// And response will have header: "Content-Type: application/json"
}It is possible to run Apate embedded in your application. You may need this to add custom Rust logic to response processing. For example, it could be response signature functionality. See the processors example.
To understand how it works, look at the specification example file; it has verbose comments. There are other specification files as well with more advanced examples.
Rhai scripting language is used to extend configuration capabilities. See the Rhai website, Rhai docs and configuration examples.
In most cases, I expect you will not need any Rhai scripting. It is meant only for complex scenarios.
A piece of DSL or a Rhai script that returns a boolean. For processing to continue, all matchers must return true.
Runs additional logic that can modify the already prepared response body.
Processors are defined using Rhai scripts. Rust processors are available only for custom applications.
String (default) - returns string from specification as is.
Binary content - handle output string as a binary content in HEX or Base64 formats. See examples here.
Jinja (minijinja) templates - respond with type="jinja" processed as a jinja template
using minijinja template engine.
Template syntax documentation can be found here.
See also minijinja filters.
Rhai script - Similar to minijinja you can use Rhai script to generate content. See examples here.
There are some additional functions & context that is available for Jinja templates and Rhai scripts.
Available for matchers and response rendering (Jinja/Rhai outputs and Rhai processors).
Has set of global functions:
- random_num() || random_num(max) || random_num(from, to) - to return random number
- random_hex() || random_hex(bytes_len) - return random hex string for some bytes length or default
- uuid_v4() - returns random UUID v4
- set_response_code(code) - set/overwrite custom response code for this request
Has global variable ctx with next API:
- ctx.method - returns request method
- ctx.path - returns full request path
- ctx.set_response_code(code) - set/overwrite custom response code for this request
- ctx.load_headers() -> build request headers map (lowercase keys)
- ctx.load_query_args() -> build map with URL query arguments
- ctx.load_path_args() -> build arguments map from specs URIs like
/mypath/{user_id}/{item_id} - ctx.load_body_string() -> load request body as string
- ctx.load_body_json() -> load request body as json
- ctx.inc_counter("key") -> increment counter by key and returns previous value
Has set of global functions:
- random_num() || random_num(max) || random_num(from, to) - to return random number
- random_hex() || random_hex(bytes_len) - return random hex string for some bytes length or default
- uuid_v4() - returns random UUID v4
- to_json_blob(value) - serialize any value to JSON blob
- from_json_blob(blob_input) - deserialize value (array, object) from JSON blob
- storage_read(key) - reads any value from storage by key
- storage_write(key, value) - writes any value to storage by key
Has global variable args that contains custom user arguments from TOML specs if any.
Has global variable ctx with next API:
- ctx.method -> returns request method
- ctx.path -> returns request path
- ctx.load_headers() -> build request headers map (lowercase keys)
- ctx.load_query_args() -> build map with URL query arguments
- ctx.load_path_args() -> build arguments map from specs URIs like
/mypath/{user_id}/{item_id} - ctx.load_body() -> reads request body as Blob
Available for Rhai post processors.
Contains same global functions as a request context and args variable.
Has global variable body that contains response output.
Has global variable ctx with some additional functionality:
- ctx.inc_counter(key) - increment counter by key and returns previous value
- ctx.response_code - get set custom response code if any (default 0 if not set)
This product distributed under MIT license BUT only under certain conditions that are listed in the LICENSE-TERMS file.
This license will be always focused on personal private usage and small development teams. Maybe I will change the license in the future but the core principles would likely to stay.
