diff --git a/RANGE_API_SUMMARY.md b/RANGE_API_SUMMARY.md deleted file mode 100644 index 05a0e22..0000000 --- a/RANGE_API_SUMMARY.md +++ /dev/null @@ -1,71 +0,0 @@ -# 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 9027b90..a15ccca 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -1,142 +1,142 @@ use std::sync::Arc; use axum::{ routing::get, 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/new/{name}/{size}", post(post_range_define)) .route("/store/range/{name}/assign/{value}", post(post_range_assign)) + .route("/store/range/{name}/unassign/{value}", post(post_range_unassign)) .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>, Path(name): Path) -> Result { 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>, Path((name, key, value)): Path<(String, String, String)>) -> Result { 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>, Path((name, key)): Path<(String, String)>) -> Result { 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>, Path(name): Path) -> Result { 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>, Path((name, size)): Path<(String, u64)>) -> Result { 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>, Path((name, value)): Path<(String, String)>) -> Result { 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>, Path((name, position)): Path<(String, u64)>) -> Result { 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>, Path(name): Path) -> Result { 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>) -> Result { 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>, Path((name, value)): Path<(String, String)>) -> Result { 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 deleted file mode 100644 index d607744..0000000 --- a/docs/range_api.md +++ /dev/null @@ -1,184 +0,0 @@ -# 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 deleted file mode 100755 index 0732365..0000000 --- a/test_range_api.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/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