Skip to content

Commit 17eefc7

Browse files
committed
Refactor and enhance documentation across core modules for clarity and consistency
1 parent 188569d commit 17eefc7

9 files changed

Lines changed: 179 additions & 79 deletions

File tree

minikv-core/src/lib.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626
//!
2727
//! # High-Level Architecture
2828
//!
29-
//! - `hashing` Key-to-path and volume scoring primitives.
30-
//! - `volumes` Replica selection and rebalance detection.
31-
//! - `record` On-disk metadata encoding format.
32-
//! - `storage` Metadata storage abstraction.
33-
//! - `replication` HTTP primitives for volume interaction.
34-
//! - `rebuild` Metadata reconstruction from volumes.
35-
//! - `rebalance` Migration to ideal replica sets.
36-
//! - `locking` Per-key concurrency control.
29+
//! - `hashing` : Key-to-path and volume scoring primitives.
30+
//! - `volumes` : Replica selection and rebalance detection.
31+
//! - `record` : On-disk metadata encoding format.
32+
//! - `storage` : Metadata storage abstraction.
33+
//! - `replication` : HTTP primitives for volume interaction.
34+
//! - `rebuild` : Metadata reconstruction from volumes.
35+
//! - `rebalance` : Migration to ideal replica sets.
36+
//! - `locking` : Per-key concurrency control.
3737
//!
3838
//! This crate is intended to be embedded in a higher-level service.
3939

minikv-core/src/record.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ mod tests {
304304

305305
#[test]
306306
fn decode_rejects_short_hash() {
307-
// HASH prefix followed by only 10 hex chars — must fail
307+
// HASH prefix followed by only 10 hex chars
308308
let bad = b"HASH0123456789hello";
309309
assert!(Record::decode(bad).is_err());
310310
}

minikv-core/src/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub struct AppState {
6262
/// public-facing address (e.g. `"localhost:8001"`).
6363
///
6464
/// Built once at startup from `--volumes` + `--public-volumes`.
65-
/// Empty when `--public-volumes` is not set Location headers will
65+
/// Empty when `--public-volumes` is not set. Location headers will
6666
/// then use internal addresses unchanged (correct for bare-metal).
6767
pub vol_rewrite: HashMap<String, String>,
6868

minikv/src/cli.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
/// CLI definition for `minikv`.
2-
///
3-
/// # Subcommands
4-
/// - `server` run the HTTP metadata coordinator
5-
/// - `rebuild` reconstruct LevelDB from volume server autoindex
6-
/// - `rebalance` move all keys to their ideal volume set
7-
/// - `print-nginx-config` emit the nginx volume server config to stdout
1+
//! CLI definition for `minikv`.
2+
//!
3+
//! # Subcommands
4+
//! - `server` run the HTTP metadata coordinator
5+
//! - `rebuild` reconstruct LevelDB from volume server autoindex
6+
//! - `rebalance` move all keys to their ideal volume set
7+
88
use std::path::PathBuf;
99
use std::time::Duration;
1010

minikv/src/http/handlers.rs

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,78 @@
1-
/// HTTP request handlers
2-
///
3-
/// All standard and custom methods (UNLINK, REBALANCE) are handled here.
4-
///
5-
/// # Method dispatch
6-
/// ```text
7-
/// GET / HEAD → 302 redirect to volume server (after probe)
8-
/// PUT → write to replicas
9-
/// POST ?uploads → initiate multipart
10-
/// POST ?uploadId=X → complete multipart
11-
/// POST ?delete → batch delete
12-
/// DELETE → hard delete (requires prior UNLINK if protect=true)
13-
/// UNLINK → soft delete
14-
/// REBALANCE → move key to ideal volumes
15-
/// GET ?list → list active keys
16-
/// GET ?unlinked→ list soft-deleted keys
17-
/// ```
18-
use std::sync::Arc;
1+
//! HTTP request handlers.
2+
//!
3+
//! This module exposes the single entry point for all object routes
4+
//! (`/*key`) and dispatches based on HTTP method and query parameters.
5+
//!
6+
//! The handler coordinates:
7+
//! - per-key locking for mutating operations,
8+
//! - metadata reads/writes via `AppState`,
9+
//! - replica selection and rebalance,
10+
//! - redirect construction (302 or X-Accel-Redirect),
11+
//! - multipart upload lifecycle,
12+
//! - soft and hard deletion.
13+
//!
14+
//! ## Method semantics
15+
//!
16+
//! GET / HEAD
17+
//! Look up metadata, probe volumes, and redirect to a reachable
18+
//! replica. Returns 404 if the key is soft-deleted, hard-deleted,
19+
//! or unreachable on all volumes (unless a fallback is configured).
20+
//!
21+
//! PUT
22+
//! Writes a new object to the ideal replica set. Overwrites are
23+
//! rejected. Also handles multipart part uploads.
24+
//!
25+
//! POST ?uploads
26+
//! Initiates a multipart upload and returns an upload ID.
27+
//!
28+
//! POST ?uploadId=X
29+
//! Completes a multipart upload by concatenating parts and writing
30+
//! the final object to replicas.
31+
//!
32+
//! POST ?delete
33+
//! Batch delete under a prefix.
34+
//!
35+
//! DELETE
36+
//! Hard delete. If `protect=true`, requires a prior UNLINK.
37+
//!
38+
//! UNLINK
39+
//! Soft delete. Metadata remains but the key is treated as absent.
40+
//!
41+
//! REBALANCE
42+
//! Moves the object to its ideal replica set if needed.
43+
//!
44+
//! GET ?list / ?unlinked
45+
//! Query endpoints for active and soft-deleted keys.
46+
//!
47+
//! ## Concurrency model
48+
//!
49+
//! Mutating operations acquire a per-key lock using `KeyLock`.
50+
//! Multipart part uploads are locked per `(key, partNumber)`.
51+
//!
52+
//! ## Redirect modes
53+
//!
54+
//! - In 302 mode, clients are redirected directly to a volume server.
55+
//! - In X-Accel-Redirect mode, nginx performs an internal redirect and
56+
//! serves the object while preserving coordinator-provided headers.
57+
58+
use crate::http::query::handle_query;
59+
use crate::http::s3::{CompleteMultipartUpload, Delete};
1960

2061
use axum::body::Body;
2162
use axum::extract::{Path, RawQuery, State};
2263
use axum::http::{HeaderMap, Method, StatusCode};
2364
use axum::response::{IntoResponse, Response};
2465
use bytes::Bytes;
25-
use rand::seq::SliceRandom;
26-
use tracing::{debug, info, instrument, warn};
27-
use uuid::Uuid;
28-
29-
use crate::http::query::handle_query;
30-
use crate::http::s3::{CompleteMultipartUpload, Delete};
3166
use minikv_core::hashing::key_to_path;
3267
use minikv_core::rebalance::rebalance_key;
3368
use minikv_core::record::Deleted;
3469
use minikv_core::replication::remote_head;
3570
use minikv_core::state::AppState;
3671
use minikv_core::volumes::key_to_volume;
72+
use rand::seq::SliceRandom;
73+
use std::sync::Arc;
74+
use tracing::{debug, info, instrument, warn};
75+
use uuid::Uuid;
3776

3877
/// Top-level axum handler for all routes (`/*key`).
3978
///
@@ -187,7 +226,7 @@ async fn handle_get_head(state: &AppState, key: &[u8], _method: &Method) -> Resp
187226
// are discarded by nginx unless we use the variable persistence trick:
188227
//
189228
// 1. We set X-Content-Type on the coordinator response.
190-
// 2. nginx captures it as $upstream_http_x_content_type — this variable
229+
// 2. nginx captures it as $upstream_http_x_content_type. This variable
191230
// persists across the internal redirect (same ngx_http_request_t).
192231
// 3. The internal /accel/ location uses:
193232
// proxy_hide_header Content-Type;
@@ -203,7 +242,7 @@ async fn handle_get_head(state: &AppState, key: &[u8], _method: &Method) -> Resp
203242
resp_builder = resp_builder
204243
.header(axum::http::header::CONTENT_TYPE, ct.as_str())
205244
// X-Content-Type persists as $upstream_http_x_content_type
206-
// across nginx's internal redirect — see nginx-frontend.conf.
245+
// across nginx's internal redirect. See nginx-frontend.conf.
207246
.header("X-Content-Type", ct.as_str());
208247
}
209248

minikv/src/http/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
//! HTTP API wiring for the minikv coordinator.
2+
//!
3+
//! This crate provides the Axum router and request entry points that
4+
//! expose the minikv HTTP interface.
5+
//!
6+
//! All object paths are routed through a single catch-all handler
7+
//! (`/*key`). The handler internally dispatches based on HTTP method
8+
//! and query parameters, including support for non-standard methods
9+
//! such as `UNLINK` and `REBALANCE`.
10+
//!
11+
//! `build_router` constructs the `Router` and attaches shared
12+
//! `AppState`, which contains configuration, metadata store access,
13+
//! locking, and volume topology.
14+
115
pub mod handlers;
216
pub mod query;
317
pub mod s3;

minikv/src/http/query.rs

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1-
/// Query parameter parsing and list/unlinked operations.
2-
///
3-
/// Handles GET requests with a query string, which are routing differently
4-
/// from plain GET (redirect) requests.
5-
///
6-
/// Supported operations:
7-
/// - `?list[&start=X][&limit=N]` list active keys under prefix
8-
/// - `?unlinked[&start=X][&limit=N]` list soft-deleted keys
9-
/// - `?list-type=2&prefix=X` S3-style listing
1+
//! Query handling for list-style operations.
2+
//!
3+
//! This module processes GET requests that include a query string.
4+
//! Plain GET requests (without a query) are handled elsewhere and
5+
//! result in object redirects.
6+
//!
7+
//! Supported query forms:
8+
//!
9+
//! - `?list[&start=X][&limit=N]`
10+
//! Lists active (non-deleted) keys under the provided prefix.
11+
//!
12+
//! - `?unlinked[&start=X][&limit=N]`
13+
//! Lists soft-deleted keys under the provided prefix.
14+
//!
15+
//! - `?list-type=2&prefix=X`
16+
//! Provides an S3-compatible XML listing.
17+
//!
18+
//! Listing is prefix-based and backed by a metadata scan (`scan_prefix`).
19+
//! Results are filtered by deletion state at decode time.
20+
//!
21+
//! `start` acts as a cursor (lexicographic lower bound).
22+
//! `limit` bounds the number of returned keys. A hard cap of
23+
//! `MAX_KEYS` prevents unbounded responses.
24+
1025
use std::sync::Arc;
1126

1227
use axum::http::StatusCode;
@@ -17,30 +32,46 @@ use tracing::debug;
1732
use minikv_core::record::Deleted;
1833
use minikv_core::state::AppState;
1934

20-
/// JSON response for list operations.
35+
/// JSON response body returned by `?list` and `?unlinked`.
36+
///
37+
/// `next` is a continuation cursor. It is empty if no further
38+
/// results are available.
39+
///
40+
/// `keys` contains UTF-8 representations of matching object keys.
2141
#[derive(Debug, Serialize)]
2242
pub struct ListResponse {
2343
pub next: String,
2444
pub keys: Vec<String>,
2545
}
2646

27-
/// Query parameters common to list and unlinked operations.
47+
/// Optional query parameters used by `?list` and `?unlinked`.
48+
///
49+
/// `start` is a lexicographic cursor. Keys strictly smaller than
50+
/// this value are skipped.
51+
///
52+
/// `limit` bounds the number of keys returned. If omitted,
53+
/// all matching keys up to `MAX_KEYS` may be returned.
2854
#[allow(unused)]
2955
#[derive(Debug, Deserialize)]
3056
pub struct ListParams {
3157
pub start: Option<String>,
3258
pub limit: Option<usize>,
3359
}
3460

35-
/// Maximum number of keys returned in a single list response before a
36-
/// 413 (Payload Too Large) is returned. Matches Go's hard limit of 1,000,000.
61+
/// Absolute upper bound on keys returned in a single response.
62+
///
63+
/// If this limit is exceeded during iteration, the request
64+
/// fails with `413 Payload Too Large`.
3765
const MAX_KEYS: usize = 1_000_000;
3866

39-
/// Handle a GET request that has a non-empty query string.
67+
/// Entry point for GET requests with a non-empty query string.
4068
///
41-
/// Dispatches to:
42-
/// - S3-style listing (`?list-type=2`)
43-
/// - Our own `?list` / `?unlinked` operations
69+
/// Dispatches based on query parameters:
70+
///
71+
/// - `?list-type=2` → S3-compatible XML listing
72+
/// - `?list` / `?unlinked` → JSON listing
73+
///
74+
/// Any other query results in `403 Forbidden`.
4475
pub async fn handle_query(state: Arc<AppState>, key_prefix: &[u8], raw_query: &str) -> Response {
4576
// S3-style listing: ?list-type=2&prefix=...
4677
if raw_query.contains("list-type=2") {
@@ -56,7 +87,15 @@ pub async fn handle_query(state: Arc<AppState>, key_prefix: &[u8], raw_query: &s
5687
}
5788
}
5889

59-
/// Handle `?list` and `?unlinked` queries.
90+
/// Handles `?list` and `?unlinked` queries.
91+
///
92+
/// Performs a prefix scan over metadata, applies cursor and limit,
93+
/// filters by deletion state, and returns a JSON response.
94+
///
95+
/// Returns:
96+
/// - `200 OK` with JSON body on success
97+
/// - `413 Payload Too Large` if `MAX_KEYS` is exceeded
98+
/// - `500 Internal Server Error` on metadata or encoding failure
6099
async fn handle_list(
61100
state: Arc<AppState>,
62101
key_prefix: &[u8],
@@ -131,7 +170,13 @@ async fn handle_list(
131170
.into_response()
132171
}
133172

134-
/// Handle `?list-type=2` S3-style listing.
173+
/// Handles `?list-type=2` S3-style listing.
174+
///
175+
/// Extends the provided prefix with the S3 `prefix` parameter,
176+
/// scans metadata, filters out deleted records, and returns
177+
/// a minimal XML response compatible with S3 clients.
178+
///
179+
/// Only active (non-deleted) keys are included.
135180
async fn handle_s3_list(state: Arc<AppState>, key_prefix: &[u8], raw_query: &str) -> Response {
136181
// Append the S3 `prefix` parameter to our key prefix.
137182
let s3_prefix = extract_param(raw_query, "prefix").unwrap_or_default();
@@ -170,7 +215,12 @@ async fn handle_s3_list(state: Arc<AppState>, key_prefix: &[u8], raw_query: &str
170215
.into_response()
171216
}
172217

173-
/// Extract a named query parameter from a raw query string.
218+
/// Extracts a query parameter from a raw query string.
219+
///
220+
/// The query string is not URL-decoded. This function performs
221+
/// a simple `key=value` match split by `&`.
222+
///
223+
/// Returns `None` if the parameter is not present.
174224
fn extract_param(raw_query: &str, name: &str) -> Option<String> {
175225
for part in raw_query.split('&') {
176226
if let Some((k, v)) = part.split_once('=')

minikv/src/http/s3.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
/// S3-compatible XML request body parsing.
2-
///
3-
/// Handles two S3 API shapes:
4-
/// - `CompleteMultipartUpload` finalize a multipart upload.
5-
/// - `Delete` batch-delete multiple objects.
6-
///
7-
/// Uses `quick-xml` with serde for zero-copy XML deserialization.
1+
//! S3-compatible XML request body parsing.
2+
//!
3+
//! Handles two S3 API shapes:
4+
//! - `CompleteMultipartUpload` finalize a multipart upload.
5+
//! - `Delete` batch-delete multiple objects.
6+
//!
7+
//! Uses `quick-xml` with serde for zero-copy XML deserialization.
8+
89
use serde::Deserialize;
910

1011
use crate::error::Error;

minikv/tests/integration.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1+
//! Integration tests
2+
//!
3+
//! Each test spins up a real axum server with an in-memory metadata store
4+
//! and wiremock volume servers, then exercises the full HTTP flow.
5+
16
use std::collections::{BTreeMap, HashMap};
2-
/// Integration tests
3-
///
4-
/// Each test spins up a real axum server with an in-memory metadata store
5-
/// and wiremock volume servers, then exercises the full HTTP flow.
6-
///
7-
/// # Running
8-
/// ```sh
9-
/// cargo test --test integration
10-
/// ```
117
use std::sync::Arc;
128
use std::time::Duration;
139

0 commit comments

Comments
 (0)