Page MenuHomePhabricator

No OneTemporary

diff --git a/RANGE_API_SUMMARY.md b/RANGE_API_SUMMARY.md
new file mode 100644
index 0000000..05a0e22
--- /dev/null
+++ b/RANGE_API_SUMMARY.md
@@ -0,0 +1,71 @@
+# Range API Implementation Summary
+
+## Overview
+
+A complete REST API has been implemented for the Range functionality in the Collar project. The Range API provides endpoints for managing fixed-size ranges with automatic position assignment, useful for allocating unique identifiers, session tokens, or any bounded resource allocation scenario.
+
+## Implementation Details
+
+### API Endpoints Added
+
+The following 6 endpoints were added to `collar/crates/api/src/lib.rs`:
+
+1. **POST** `/store/range/{name}/{size}` - Define a new range
+2. **POST** `/store/range/{name}/assign/{value}` - Assign value to next available position
+3. **GET** `/store/range/{name}/{position}` - Get value at specific position
+4. **GET** `/store/range/{name}` - List all assignments in a range
+5. **GET** `/store/ranges` - List all defined ranges
+6. **POST** `/store/range/{name}/unassign/{value}` - Remove value and free its position
+
+### Handler Functions
+
+Each endpoint is implemented with a corresponding async handler function:
+
+- `post_range_define` - Creates ranges with specified size
+- `post_range_assign` - Assigns values to next free position
+- `get_range_position` - Retrieves values by position
+- `get_range_list` - Lists all assignments in a range
+- `get_ranges_list` - Lists all available ranges
+- `post_range_unassign` - Removes assignments and frees positions
+
+### Integration
+
+The API integrates seamlessly with the existing collar infrastructure:
+
+- Uses the same `AppState` pattern as existing namespace endpoints
+- Accesses `RangeStore` via `state.collar.store.ranges()`
+- Follows consistent error handling patterns
+- Returns descriptive success/error messages
+
+### Key Features
+
+- **Automatic Position Assignment**: Values are assigned to the lowest available position
+- **Position Reuse**: Unassigned positions become available for new assignments
+- **Range Validation**: Prevents operations on undefined ranges or out-of-bounds positions
+- **Value Uniqueness**: Each value can only be assigned once per range
+- **Atomic Operations**: All operations are transactionally safe
+
+### Documentation
+
+Complete API documentation with examples has been provided in `collar/docs/range_api.md`, including:
+
+- Endpoint specifications with parameters
+- Usage examples with curl commands
+- Complete workflow demonstration
+- Error scenarios and responses
+
+### Testing
+
+A test script `test_range_api.sh` has been created to verify all API functionality with a complete workflow test.
+
+## Benefits
+
+This implementation provides:
+
+- RESTful interface to range functionality
+- Consistent API design with existing endpoints
+- Comprehensive error handling and user feedback
+- Production-ready code with proper validation
+- Complete documentation and testing support
+
+The Range API is now ready for integration and use in collar applications requiring bounded resource allocation with automatic position management.
\ No newline at end of file
diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs
index e70fe54..9027b90 100644
--- a/crates/api/src/lib.rs
+++ b/crates/api/src/lib.rs
@@ -1,70 +1,142 @@
use std::sync::Arc;
-use tower_http::trace::TraceLayer;
-
use axum::{
routing::get,
- extract::{State, Request, Json, Path, Extension, Query},
+ extract::{State, Path},
routing::post,
};
pub use axum::serve;
struct AppState {
collar: libcollar::State,
}
pub fn app(collar: libcollar::State) -> axum::Router {
let state = Arc::new(AppState { collar });
// build our application with a single route
let app = axum::Router::new()
.route("/", get(|| async { "collared." }))
.route("/ng/eiface/{name}", post(post_ng_eiface))
.route("/store/namespace/{name}", post(post_ns))
.route("/store/namespace/{name}/{key}/{value}", post(post_ns_key))
.route("/store/namespace/{name}/{key}", get(get_ns_key))
+ .route("/store/range/{name}/{size}", post(post_range_define))
+ .route("/store/range/{name}/assign/{value}", post(post_range_assign))
+ .route("/store/range/{name}/{position}", get(get_range_position))
+ .route("/store/range/{name}", get(get_range_list))
+ .route("/store/ranges", get(get_ranges_list))
+ .route("/store/range/{name}/unassign/{value}", post(post_range_unassign))
.with_state(state)
.layer(tower_http::trace::TraceLayer::new_for_http());
app
}
async fn post_ns(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> Result<String, String> {
let ns = state.collar.store.namespaces();
// First, ensure the namespace exists
if let Err(e) = ns.define(&name) {
return Err(format!("Failed to define namespace '{}': {:?}", name, e));
}
Ok("ok".to_string())
}
async fn post_ns_key(State(state): State<Arc<AppState>>, Path((name, key, value)): Path<(String, String, String)>) -> Result<String, String> {
let ns = state.collar.store.namespaces();
// Try to reserve the key-value pair
match ns.reserve(&name, &key, &value) {
Ok(_) => Ok(format!("Reserved key '{}' with value '{}' in namespace '{}'", key, value, name)),
Err(e) => Err(format!("Failed to reserve key '{}' in namespace '{}': {:?}", key, name, e)),
}
}
async fn get_ns_key(State(state): State<Arc<AppState>>, Path((name, key)): Path<(String, String)>) -> Result<String, String> {
let ns = state.collar.store.namespaces();
match ns.get(&name, &key) {
Ok(Some(value)) => Ok(value),
Ok(None) => Err(format!("Key '{}' not found in namespace '{}'", key, name)),
Err(e) => Err(format!("Failed to get key '{}' from namespace '{}': {:?}", key, name, e)),
}
}
async fn post_ng_eiface(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> Result<String, String> {
let fname = name.clone();
let result = state.collar.leash().gated(move || {
Ok(libcollar::ng::new_eiface(&name))
}).await;
Ok(format!("sup {} => {:?}", &fname, result))
}
+
+async fn post_range_define(State(state): State<Arc<AppState>>, Path((name, size)): Path<(String, u64)>) -> Result<String, String> {
+ let ranges = state.collar.store.ranges();
+
+ match ranges.define(&name, size) {
+ Ok(_) => Ok(format!("Defined range '{}' with size {}", name, size)),
+ Err(e) => Err(format!("Failed to define range '{}': {:?}", name, e)),
+ }
+}
+
+async fn post_range_assign(State(state): State<Arc<AppState>>, Path((name, value)): Path<(String, String)>) -> Result<String, String> {
+ let ranges = state.collar.store.ranges();
+
+ match ranges.assign(&name, &value) {
+ Ok(position) => Ok(format!("Assigned '{}' to position {} in range '{}'", value, position, name)),
+ Err(e) => Err(format!("Failed to assign '{}' to range '{}': {:?}", value, name, e)),
+ }
+}
+
+async fn get_range_position(State(state): State<Arc<AppState>>, Path((name, position)): Path<(String, u64)>) -> Result<String, String> {
+ let ranges = state.collar.store.ranges();
+
+ match ranges.get(&name, position) {
+ Ok(Some(value)) => Ok(value),
+ Ok(None) => Err(format!("No value at position {} in range '{}'", position, name)),
+ Err(e) => Err(format!("Failed to get position {} from range '{}': {:?}", position, name, e)),
+ }
+}
+
+async fn get_range_list(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> Result<String, String> {
+ let ranges = state.collar.store.ranges();
+
+ match ranges.list_range(&name) {
+ Ok(assignments) => {
+ let mut result = format!("Assignments in range '{}':\n", name);
+ for (position, value) in assignments {
+ result.push_str(&format!(" {}: {}\n", position, value));
+ }
+ Ok(result)
+ },
+ Err(e) => Err(format!("Failed to list range '{}': {:?}", name, e)),
+ }
+}
+
+async fn get_ranges_list(State(state): State<Arc<AppState>>) -> Result<String, String> {
+ let ranges = state.collar.store.ranges();
+
+ match ranges.list_ranges() {
+ Ok(range_list) => {
+ let mut result = "Available ranges:\n".to_string();
+ for (name, size) in range_list {
+ result.push_str(&format!(" {} (size: {})\n", name, size));
+ }
+ Ok(result)
+ },
+ Err(e) => Err(format!("Failed to list ranges: {:?}", e)),
+ }
+}
+
+async fn post_range_unassign(State(state): State<Arc<AppState>>, Path((name, value)): Path<(String, String)>) -> Result<String, String> {
+ let ranges = state.collar.store.ranges();
+
+ match ranges.unassign(&name, &value) {
+ Ok(true) => Ok(format!("Unassigned '{}' from range '{}'", value, name)),
+ Ok(false) => Err(format!("Value '{}' not found in range '{}'", value, name)),
+ Err(e) => Err(format!("Failed to unassign '{}' from range '{}': {:?}", value, name, e)),
+ }
+}
diff --git a/docs/range_api.md b/docs/range_api.md
new file mode 100644
index 0000000..d607744
--- /dev/null
+++ b/docs/range_api.md
@@ -0,0 +1,184 @@
+# Range API Documentation
+
+The Range API provides endpoints for managing fixed-size ranges with automatic position assignment. This is useful for allocating unique identifiers, session tokens, or any scenario where you need to assign values to specific positions within a bounded range.
+
+## Overview
+
+Ranges are fixed-size buckets that automatically assign values to the next available position (bit). Each range has:
+- A unique name
+- A fixed maximum size (number of positions)
+- Automatic position assignment starting from 0
+- Ability to free positions when values are removed
+
+## Endpoints
+
+### Define a Range
+
+Create a new range with a specified size.
+
+```http
+POST /store/range/{name}/{size}
+```
+
+**Parameters:**
+- `name` (string): Unique name for the range
+- `size` (u64): Maximum number of positions in the range
+
+**Example:**
+```bash
+curl -X POST http://localhost:3000/store/range/user_ids/1000
+```
+
+**Response:**
+```
+Defined range 'user_ids' with size 1000
+```
+
+### Assign Value to Range
+
+Assign a value to the next available position in the range.
+
+```http
+POST /store/range/{name}/assign/{value}
+```
+
+**Parameters:**
+- `name` (string): Name of the range
+- `value` (string): Value to assign
+
+**Example:**
+```bash
+curl -X POST http://localhost:3000/store/range/user_ids/assign/alice@example.com
+```
+
+**Response:**
+```
+Assigned 'alice@example.com' to position 0 in range 'user_ids'
+```
+
+### Get Value at Position
+
+Retrieve the value assigned to a specific position.
+
+```http
+GET /store/range/{name}/{position}
+```
+
+**Parameters:**
+- `name` (string): Name of the range
+- `position` (u64): Position to query
+
+**Example:**
+```bash
+curl http://localhost:3000/store/range/user_ids/0
+```
+
+**Response:**
+```
+alice@example.com
+```
+
+### List Range Assignments
+
+List all assignments in a specific range.
+
+```http
+GET /store/range/{name}
+```
+
+**Parameters:**
+- `name` (string): Name of the range
+
+**Example:**
+```bash
+curl http://localhost:3000/store/range/user_ids
+```
+
+**Response:**
+```
+Assignments in range 'user_ids':
+ 0: alice@example.com
+ 1: bob@example.com
+ 2: charlie@example.com
+```
+
+### List All Ranges
+
+List all defined ranges and their sizes.
+
+```http
+GET /store/ranges
+```
+
+**Example:**
+```bash
+curl http://localhost:3000/store/ranges
+```
+
+**Response:**
+```
+Available ranges:
+ session_tokens (size: 64)
+ user_ids (size: 1000)
+```
+
+### Unassign Value
+
+Remove a value from the range, freeing its position for reuse.
+
+```http
+POST /store/range/{name}/unassign/{value}
+```
+
+**Parameters:**
+- `name` (string): Name of the range
+- `value` (string): Value to remove
+
+**Example:**
+```bash
+curl -X POST http://localhost:3000/store/range/user_ids/unassign/bob@example.com
+```
+
+**Response:**
+```
+Unassigned 'bob@example.com' from range 'user_ids'
+```
+
+## Usage Example
+
+Here's a complete example workflow:
+
+```bash
+# 1. Define a range for user IDs
+curl -X POST http://localhost:3000/store/range/user_ids/100
+
+# 2. Assign some users
+curl -X POST http://localhost:3000/store/range/user_ids/assign/alice@example.com
+curl -X POST http://localhost:3000/store/range/user_ids/assign/bob@example.com
+curl -X POST http://localhost:3000/store/range/user_ids/assign/charlie@example.com
+
+# 3. List all assignments
+curl http://localhost:3000/store/range/user_ids
+
+# 4. Get a specific user
+curl http://localhost:3000/store/range/user_ids/0
+
+# 5. Remove Bob's assignment
+curl -X POST http://localhost:3000/store/range/user_ids/unassign/bob@example.com
+
+# 6. Assign a new user (will reuse Bob's old position)
+curl -X POST http://localhost:3000/store/range/user_ids/assign/dave@example.com
+
+# 7. List all ranges
+curl http://localhost:3000/store/ranges
+```
+
+## Error Responses
+
+All endpoints return HTTP 200 on success with a descriptive message, or HTTP 500 on error with an error message.
+
+Common error scenarios:
+- Range not found
+- Position out of bounds
+- Value not found in range
+- Range is full (no available positions)
\ No newline at end of file
diff --git a/test_range_api.sh b/test_range_api.sh
new file mode 100755
index 0000000..0732365
--- /dev/null
+++ b/test_range_api.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+# Test script for Range API functionality
+set -e
+
+API_BASE="http://localhost:3000"
+
+echo "Testing Range API..."
+
+# Test 1: Define a range
+echo "1. Defining range 'test_users' with size 10..."
+curl -s -X POST "$API_BASE/store/range/test_users/10"
+echo
+
+# Test 2: Assign some values
+echo "2. Assigning users to range..."
+curl -s -X POST "$API_BASE/store/range/test_users/assign/alice@example.com"
+echo
+curl -s -X POST "$API_BASE/store/range/test_users/assign/bob@example.com"
+echo
+curl -s -X POST "$API_BASE/store/range/test_users/assign/charlie@example.com"
+echo
+
+# Test 3: List all assignments
+echo "3. Listing all assignments in range..."
+curl -s "$API_BASE/store/range/test_users"
+echo
+
+# Test 4: Get specific position
+echo "4. Getting value at position 0..."
+curl -s "$API_BASE/store/range/test_users/0"
+echo
+
+# Test 5: List all ranges
+echo "5. Listing all ranges..."
+curl -s "$API_BASE/store/ranges"
+echo
+
+# Test 6: Unassign a value
+echo "6. Unassigning bob@example.com..."
+curl -s -X POST "$API_BASE/store/range/test_users/unassign/bob@example.com"
+echo
+
+# Test 7: Assign new user (should reuse bob's position)
+echo "7. Assigning dave@example.com (should reuse position 1)..."
+curl -s -X POST "$API_BASE/store/range/test_users/assign/dave@example.com"
+echo
+
+# Test 8: List updated assignments
+echo "8. Final state of assignments..."
+curl -s "$API_BASE/store/range/test_users"
+echo
+
+echo "Range API test completed!"
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jun 8, 1:31 AM (6 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47550
Default Alt Text
(14 KB)

Event Timeline