6.2 KiB
Dynamic Schema Records API
A lightweight, high-performance JSON records service built with Rust, Axum, and SQLite.
The service validates incoming JSON payloads against a dynamic schema specification defined in a JSON configuration file. Schemas support validation modes (strict vs. loose), data type constraints, and exact forced values (e.g., pinning API payload versions). Schemas can be reloaded at runtime without restarting the server.
Features
- Dynamic Schema Validation:
- Enforce required types:
string,integer,number,boolean,object,array,null. - Force exact values on any field (e.g., pin
"version": 2). - Validation modes:
strict: All defined fields are required, and unknown fields are rejected.loose: All defined fields are required, but additional unknown fields are accepted.
- Enforce required types:
- Hot-Reloadable Configuration: Reload
config.jsonvia an HTTP admin endpoint with atomic swap and rollback on error. - Persistent Storage: Validated records are stored with a generated UUID and ISO-8601 UTC timestamp in an SQLite database (
messages.db). - Structured Logging: Built with
tracingoutputting to standard output.
Prerequisites
- Rust toolchain (1.75+ recommended)
- SQLite development libraries (if linking dynamically)
Configuration
The service looks for a configuration file at the path specified by the APP_CONFIG environment variable, defaulting to config.json in the current working directory.
Example: config.json
{
"mode": "strict",
"fields": {
"version": {
"type": "integer",
"value": 2
},
"message": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
}
}
Schema Specification
| Property | Type | Description |
|---|---|---|
mode |
string |
"strict" or "loose". |
fields |
object |
Map of field names to validation specifications. Must contain at least one field. |
fields.<name>.type |
string |
Expected JSON type: string, integer, number, boolean, object, array, null. |
fields.<name>.value |
any (optional) | Expected exact value. Rejects any request where the value does not strictly match. |
Getting Started
1. Build and Run
# Clone the repository and enter the directory
cd your-project-name
# Create your config file
cat <<EOF > config.json
{
"mode": "strict",
"fields": {
"version": {
"type": "integer",
"value": 1
},
"message": {
"type": "string"
}
}
}
EOF
# Run the server (default: 127.0.0.1:3000)
cargo run
To specify a custom configuration file path:
APP_CONFIG=custom_config.json cargo run
API Reference
Health / Service Info
- Endpoint:
GET /orGET /api/now - Description: Returns server timestamp and status payload.
- Example Response:
{ "uuid": "4fc736f8-45e0-47da-8da9-6c30fef6a72e", "date": "2026-10-25T10:19:00Z", "data": { "version": 1, "message": "Hello from Rust!" } }
Create Record
- Endpoint:
POST /api/records - Headers:
Content-Type: application/json - Request Body:
{ "data": { "version": 1, "message": "Hello, world!" } } - Response (
201 Created):{ "uuid": "f3b392b9-dc81-42cb-b9ef-dc7e90aa7ce5", "date": "2026-10-25T10:20:15Z", "data": { "version": 1, "message": "Hello, world!" } } - Error Responses (
400 Bad Request):- Missing field:
Missing required field: message - Wrong type:
Field 'version' must be of type integer - Value mismatch:
Field 'version' must equal 1 - Unknown field (in strict mode):
Unknown field: extra_field
- Missing field:
List Records
- Endpoint:
GET /api/records - Description: Returns up to 100 most recent records ordered by
date DESC. - Response (
200 OK):[ { "uuid": "f3b392b9-dc81-42cb-b9ef-dc7e90aa7ce5", "date": "2026-10-25T10:20:15Z", "data": { "version": 1, "message": "Hello, world!" } } ]
Get Active Configuration
- Endpoint:
GET /api/admin/config - Description: Inspect the currently running schema and mode.
- Response (
200 OK):{ "mode": "strict", "fields": { "version": { "type": "integer", "value": 1 }, "message": { "type": "string" } } }
Hot-Reload Configuration
- Endpoint:
POST /api/admin/reload-config - Description: Reads and validates the file at
APP_CONFIG. If valid, atomically updates the active schema in memory. - Response (
200 OK):{ "status": "ok", "mode": "strict", "fields": 2 } - Error Response (
422 Unprocessable Entity): Returned if the configuration file is unreadable, invalid JSON, or contains no fields. The active schema remains untouched.
Example Usage Workflow
# 1. Create a valid record
curl -i -X POST http://127.0.0.1:3000/api/records \
-H "Content-Type: application/json" \
-d '{"data": {"version": 1, "message": "First record"}}'
# 2. Modify config.json (e.g. bump required version to 2)
# Update "value": 2 in config.json...
# 3. Reload config without downtime
curl -i -X POST http://127.0.0.1:3000/api/admin/reload-config
# 4. Old version requests are now rejected
curl -i -X POST http://127.0.0.1:3000/api/records \
-H "Content-Type: application/json" \
-d '{"data": {"version": 1, "message": "Rejected"}}'
# -> HTTP 400: Field 'version' must equal 2
# 5. New version requests succeed
curl -i -X POST http://127.0.0.1:3000/api/records \
-H "Content-Type: application/json" \
-d '{"data": {"version": 2, "message": "Accepted"}}'
Database Architecture
On startup, the application creates a local SQLite database named messages.db with the following schema:
CREATE TABLE IF NOT EXISTS records (
uuid TEXT PRIMARY KEY,
date TEXT NOT NULL,
data TEXT NOT NULL
);
This project is licensed under the MIT License (or choose your preferred license).
License
This project is licensed under the MIT License.