Goal
The goal for today is to implement API endpoints to upload and download chunks.
Plan
- Add an endpoint for
PUT /chunks/ID?label=LABEL. - Add an endpoint for
GET /chunks/ID - Add verification scenario or scenarios for the endpoints.
Notes
- Preparation: existing code builds and tests pass.
- in a checkout of
main, runmake - it succeeds
- all good
- in a checkout of
- Create branch
chunks. - In
obnam-server/src/api.rsadd a route forPUT /chunks/ID, which just prints out that it's been called.
let app = Router::new()
.route("/validate", get(validate))
+ .route("/chunks", put(new_chunk))
.with_state(self.api_state.clone());
+#[debug_handler]
+async fn new_chunk(
+ _: State<ApiState>,
+ TokenExtractor(token): TokenExtractor,
+ Path(chunk_id): Path<String>,
+) -> &'static str {
+ eprintln!("new_chunk: chunk_id={chunk_id:?} {token:?}");
+ "OK"
+}
+
- Initialize server.
cargo run --bin obnam-server -- --config obnam-server-config.yaml init[]
- Generate token.
cargo run --bin obnam-server -- --config obnam-server-config.yaml token --allow append --output token.txt
- Run server.
cargo run --bin obnam-server -- --config obnam-server-config.yaml serve
- Call API endpoint.
curl -sv -H "Authorization: Bearer $(cat token.txt)" -X PUT http://localhost:8000/chunks/1- failed: 404 not found
- There's something about the way the route is set up that fails. I'll simplify so no chunk id is extracted.
+#[debug_handler]
+async fn new_chunk() -> &'static str {
+ eprintln!("new_chunk");
+ "new_chunk OK"
+}
+
- This works. It doesn't do anythin useful, but the route is set up correctly. Commit.
- Aha! I need to annotate the route with the id.
- .route("/chunks", put(new_chunk))
+ .route("/chunks/{id}", put(new_chunk))
PUTwith query parameters seem to be more complicated than I thought. I'll put the label in the path as well. There's certainly a way to this with query parameters, but I want simple and clear.- API design thinking: I need to decide which endpoints to implement:
GET /chunks/{id}-- I find this simplest and most elegantGET /chunks?id=IDwould be easily complemented byGET /chunks?label=LABELbut then the function implemting that route would need to handle query parameters with more complicated logic
- After a quick ponder, I'm landing on the following, at least for now:
- upload chunk:
PUT /chunks/{id}/{label} - download chunk:
GET /chunks/{id} - search for chunks:
GET /chunks?label=LABEL - delete chunk:
DELETE /chunks/{id}
- upload chunk:
- Principles:
- when operating on a specific chunk, the path always starts with
/chunks/{id} - I'd like to set label when uploading with a query parameter
- when operating on a specific chunk, the path always starts with
- Implemented routes:
- .route("/chunks/{id}/{label}", put(new_chunk))
- .route("/chunks/{id}", get(get_chunk))
+ .route("/chunks/{id}", put(new_chunk).get(get_chunk))
-async fn new_chunk(Path((id, label)): Path<(String, String)>) -> &'static str {
+async fn new_chunk(Path(id): Path<String>, label: Query<LabelQuery>) -> &'static str {
- Added a route for
DELETE /chunks/{id}as well. - To implement actually managing chunks I'll need to implement something like the local store in the client. Ideally, use the same code. However, I will first implement an in-memory mockup of that, for simplicity. This will let me implement most of the verification scenarios needed. I can then think about how to implement this persistentl later on, with more time.
- Did that using a
HashMap. - Manual testing indicated that this works. Time to automate that.
- Immediately realize I don't actually do anything with chunk contents. Oops.
- Also interesting to think about status code for the deletion route.
- if chunk doesn't exist, 404
- if exists, 204, not 200, because no body needed in response
- I haven't even done anything about authorization yet.
- Fixed storing chunk contents in store.
- Added authorization checks, and also to the verification scenario.
Summary
Got everything done that I planned, and also searching by label and deleting chunks. This turned out to be easy, because I had laid down some groundwork to make it easy. Well done, past me.
Comments?
If you have feedback on this, please use the following fediverse thread: https://toot.liw.fi/@liw/116193242847864394.