feat: Expose the calculator over HTTP #17

Merged
typosaurus merged 2 commits from typosaurus/15-expose-the-calculator-over-http into main 2026-07-26 21:59:04 +02:00
3 changed files with 143 additions and 7 deletions
Showing only changes of commit a4a436c020 - Show all commits

122
README.md
View File

@ -1,11 +1,123 @@
# retoor <retoor@molodetz.nl>
# typosaurus-sandbox
Sandbox for Typosaurus end-to-end verification
Sandbox for Typosaurus end-to-end verification.
## Running the web app
A FastAPI application serving arithmetic operations over HTTP with JSON request/response bodies.
```bash
make run
## Configuration
The application uses a single `.env.json` file at the project root as its central point of truth
for configuration. Defaults are plug-and-play and require no setup:
```json
{
"host": "127.0.0.1",
"port": 8000
}
```
The development server starts at http://127.0.0.1:5000.
When no `.env.json` is present, the application starts with these defaults. To customise, create
`.env.json` in the project root and populate only the keys that differ.
## Usage
### Start the server
```sh
python -m typosaurus_sandbox
```
The server listens on `http://127.0.0.1:8000` by default.
### Health check
```
GET /health
```
Response:
```json
{"status": "ok"}
```
## API endpoints
All calculator endpoints accept `POST` requests with a JSON body and return a JSON response.
### POST /api/v1/calculator/add
Add two integers.
Request:
```json
{"left": 3, "right": 5}
```
Response:
```json
{"result": 8}
```
### POST /api/v1/calculator/subtract
Subtract the right integer from the left.
Request:
```json
{"left": 10, "right": 3}
```
Response:
```json
{"result": 7}
```
### POST /api/v1/calculator/clamp
Clamp a value between a low and high bound.
Request:
```json
{"value": 15, "low": 0, "high": 10}
```
Response:
```json
{"result": 10}
```
Boundaries are inclusive. A `low > high` combination produces a 422 validation response.
### POST /api/v1/calculator/clamp-to-byte
Clamp an integer to the byte range [0, 255].
Request:
```json
{"value": 300}
```
Response:
```json
{"result": 255}
```
## Verification
```sh
make verify
```
Runs compile-all checks against all source and test files, then executes the full test suite.
Zero warnings are tolerated.

View File

@ -1,6 +1,6 @@
# retoor <retoor@molodetz.nl>
from fastapi import APIRouter
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from typosaurus_sandbox.domain.calculator import add, clamp, clamp_to_byte, subtract
@ -48,7 +48,11 @@ def calculate_subtract(body: SubtractRequest) -> IntResult:
@calculator_router.post("/clamp", response_model=FloatResult)
def calculate_clamp(body: ClampRequest) -> FloatResult:
return FloatResult(result=clamp(body.value, body.low, body.high))
try:
result = clamp(body.value, body.low, body.high)
except ValueError:
raise HTTPException(status_code=422, detail="low must not exceed high")
return FloatResult(result=result)
@calculator_router.post("/clamp-to-byte", response_model=IntResult)

View File

@ -54,6 +54,14 @@ class TestCalculatorSubtractEndpoint(unittest.TestCase):
response = client.post("/api/v1/calculator/subtract", json={"left": 10, "right": None})
self.assertEqual(response.status_code, 422)
def test_subtract_missing_left_returns_422(self) -> None:
response = client.post("/api/v1/calculator/subtract", json={"right": 3})
self.assertEqual(response.status_code, 422)
def test_subtract_missing_right_returns_422(self) -> None:
response = client.post("/api/v1/calculator/subtract", json={"left": 10})
self.assertEqual(response.status_code, 422)
class TestCalculatorClampEndpoint(unittest.TestCase):
@ -76,6 +84,18 @@ class TestCalculatorClampEndpoint(unittest.TestCase):
response = client.post("/api/v1/calculator/clamp", json={"value": "x", "low": 0, "high": 10})
self.assertEqual(response.status_code, 422)
def test_clamp_low_greater_than_high_returns_422(self) -> None:
response = client.post("/api/v1/calculator/clamp", json={"value": 5, "low": 10, "high": 0})
self.assertEqual(response.status_code, 422)
def test_clamp_missing_low_returns_422(self) -> None:
response = client.post("/api/v1/calculator/clamp", json={"value": 5, "high": 10})
self.assertEqual(response.status_code, 422)
def test_clamp_missing_high_returns_422(self) -> None:
response = client.post("/api/v1/calculator/clamp", json={"value": 5, "low": 0})
self.assertEqual(response.status_code, 422)
class TestCalculatorClampToByteEndpoint(unittest.TestCase):