Page MenuHomePhabricator

No OneTemporary

diff --git a/crates/api/src/internal/mod.rs b/crates/api/src/internal/mod.rs
new file mode 100644
index 0000000..38c33d5
--- /dev/null
+++ b/crates/api/src/internal/mod.rs
@@ -0,0 +1,15 @@
+//! Debug and development tools that will be removed in the future
+//! These modules provide endpoints for internal testing and debugging
+
+pub mod namespace;
+pub mod ng;
+pub mod range;
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn internal_modules_available() {
+ // This test just ensures the module structure compiles correctly
+ assert!(true);
+ }
+}
\ No newline at end of file
diff --git a/crates/api/src/internal/namespace.rs b/crates/api/src/internal/namespace.rs
new file mode 100644
index 0000000..0f4b74a
--- /dev/null
+++ b/crates/api/src/internal/namespace.rs
@@ -0,0 +1,45 @@
+use std::sync::Arc;
+
+use axum::{
+ extract::{Path, State},
+};
+
+use crate::AppState;
+
+/// Debug endpoint to create a new namespace
+/// This is for development purposes and will be removed in the future
+pub 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())
+}
+
+/// Debug endpoint to set a key-value pair in a namespace
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
+/// Debug endpoint to get a key from a namespace
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
diff --git a/crates/api/src/internal/ng.rs b/crates/api/src/internal/ng.rs
new file mode 100644
index 0000000..148834c
--- /dev/null
+++ b/crates/api/src/internal/ng.rs
@@ -0,0 +1,17 @@
+use std::sync::Arc;
+
+use axum::{
+ extract::{Path, State},
+};
+
+use crate::AppState;
+
+/// Debug endpoint for creating network interfaces
+/// This is for development purposes and will be removed in the future
+pub 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))
+}
\ No newline at end of file
diff --git a/crates/api/src/internal/range.rs b/crates/api/src/internal/range.rs
new file mode 100644
index 0000000..08b950c
--- /dev/null
+++ b/crates/api/src/internal/range.rs
@@ -0,0 +1,88 @@
+use std::sync::Arc;
+
+use axum::{
+ extract::{Path, State},
+};
+
+use crate::AppState;
+
+/// Debug endpoint to define a new range
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
+/// Debug endpoint to assign a value to a range
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
+/// Debug endpoint to unassign a value from a range
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
+/// Debug endpoint to get a value at a specific position in a range
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
+/// Debug endpoint to list all assignments in a range
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
+/// Debug endpoint to list all ranges
+/// This is for development purposes and will be removed in the future
+pub 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)),
+ }
+}
+
diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs
index 69e8a59..7025faa 100644
--- a/crates/api/src/lib.rs
+++ b/crates/api/src/lib.rs
@@ -1,318 +1,93 @@
//! # Collar API
//!
//! HTTP API for network management functionality.
//!
//! ## Network Endpoints
//!
//! - `GET /networks` - List all networks
//! - `POST /networks` - Create a new network
//! - `GET /networks/{name}` - Get network details
//! - `DELETE /networks/{name}` - Delete a network
//!
//! ## Network Creation
//!
//! Networks can be created with different provider and assigner configurations:
//!
//! ### Basic Network
//! ```json
//! {
//! "name": "test-network",
//! "cidr": "192.168.1.0/24"
//! }
//! ```
//!
//! ### Network with Basic Assigner
//! ```json
//! {
//! "name": "test-network",
//! "cidr": "192.168.1.0/24",
//! "assigner_type": "basic",
//! "assigner_config": {
//! "strategy": "sequential"
//! }
//! }
//! ```
//!
//! ### Network with Range Assigner
//! ```json
//! {
//! "name": "test-network",
//! "cidr": "192.168.1.0/24",
//! "assigner_type": "range",
//! "assigner_config": {
//! "range_name": "my-range"
//! }
//! }
//! ```
+//!
+//! ## Internal Debug Endpoints
+//!
+//! The API also provides several internal debug endpoints that will be removed in the future.
+//! These are organized in the `internal` module.
use std::sync::Arc;
-use axum::{
- routing::get,
- extract::{State, Path},
- routing::post,
- response::{Response, IntoResponse},
- http::StatusCode,
- Json,
-};
-use serde::{Deserialize, Serialize};
-use store::network::{NetRange, BasicProvider, BasicAssigner, RangeAssigner};
+use axum::routing::get;
pub use axum::serve;
-struct AppState {
+mod network;
+mod internal;
+
+pub 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
+ // build our application with routes
let app = axum::Router::new()
.route("/", get(|| async { "collared." }))
- .route("/networks", get(get_networks))
- .route("/networks", post(post_networks))
- .route("/networks/{name}", get(get_network))
- .route("/networks/{name}", axum::routing::delete(delete_network))
- .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/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))
+ // Network management routes
+ .route("/networks", get(network::get_networks))
+ .route("/networks", axum::routing::post(network::post_networks))
+ .route("/networks/{name}", get(network::get_network))
+ .route("/networks/{name}", axum::routing::delete(network::delete_network))
+ // Internal debug routes
+ .route("/internal/ng/eiface/{name}", axum::routing::post(internal::ng::post_ng_eiface))
+ .route("/internal/store/namespace/{name}", axum::routing::post(internal::namespace::post_ns))
+ .route("/internal/store/namespace/{name}/{key}/{value}", axum::routing::post(internal::namespace::post_ns_key))
+ .route("/internal/store/namespace/{name}/{key}", get(internal::namespace::get_ns_key))
+ .route("/internal/store/range/new/{name}/{size}", axum::routing::post(internal::range::post_range_define))
+ .route("/internal/store/range/{name}/assign/{value}", axum::routing::post(internal::range::post_range_assign))
+ .route("/internal/store/range/{name}/unassign/{value}", axum::routing::post(internal::range::post_range_unassign))
+ .route("/internal/store/range/{name}/{position}", get(internal::range::get_range_position))
+ .route("/internal/store/range/{name}", get(internal::range::get_range_list))
+ .route("/internal/store/ranges", get(internal::range::get_ranges_list))
.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)),
- }
-}
-
-#[derive(Serialize, Deserialize)]
-struct CreateNetworkRequest {
- name: String,
- cidr: String,
- provider_type: Option<String>,
- provider_config: Option<serde_json::Value>,
- assigner_type: Option<String>,
- assigner_config: Option<serde_json::Value>,
-}
-
-async fn get_networks(State(state): State<Arc<AppState>>) -> impl IntoResponse {
- let networks = state.collar.store.networks();
-
- match networks.list() {
- Ok(network_list) => (StatusCode::OK, Json(network_list)).into_response(),
- Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to list networks: {:?}", e)).into_response(),
- }
-}
-
-async fn post_networks(State(state): State<Arc<AppState>>, Json(req): Json<CreateNetworkRequest>) -> impl IntoResponse {
- let networks = state.collar.store.networks();
-
- // Parse the CIDR
- let netrange = match NetRange::from_cidr(&req.cidr) {
- Ok(range) => range,
- Err(e) => return (StatusCode::BAD_REQUEST, format!("Invalid CIDR '{}': {:?}", req.cidr, e)).into_response(),
- };
-
- // Create basic provider with optional config
- let provider = if let Some(config) = req.provider_config {
- BasicProvider::new().with_config("config", &config.to_string())
- } else {
- BasicProvider::new()
- };
-
- // Handle different assigner types with separate create calls
- let result = if let Some(assigner_type) = req.assigner_type {
- match assigner_type.as_str() {
- "basic" => {
- let mut basic_assigner = BasicAssigner::new("sequential");
- if let Some(config) = req.assigner_config {
- basic_assigner = basic_assigner.with_config("config", &config.to_string());
- }
- networks.create(&req.name, netrange, provider, Some(basic_assigner))
- },
- "range" => {
- if let Some(config) = req.assigner_config {
- if let Some(range_name) = config.get("range_name").and_then(|v| v.as_str()) {
- let mut range_assigner = RangeAssigner::with_range_name(range_name);
- if let Some(extra_config) = config.get("config") {
- range_assigner = range_assigner.with_config("config", &extra_config.to_string());
- }
- networks.create(&req.name, netrange, provider, Some(range_assigner))
- } else {
- return (StatusCode::BAD_REQUEST, "Range assigner requires 'range_name' in config".to_string()).into_response();
- }
- } else {
- return (StatusCode::BAD_REQUEST, "Range assigner requires config with 'range_name'".to_string()).into_response();
- }
- },
- _ => return (StatusCode::BAD_REQUEST, format!("Unknown assigner type: {}", assigner_type)).into_response(),
- }
- } else {
- networks.create(&req.name, netrange, provider, None::<BasicAssigner>)
- };
-
- // Handle the result
- match result {
- Ok(_) => (StatusCode::OK, format!("Created network '{}'", req.name)).into_response(),
- Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create network '{}': {:?}", req.name, e)).into_response(),
- }
-}
-
-async fn get_network(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> impl IntoResponse {
- let networks = state.collar.store.networks();
-
- match networks.get(&name) {
- Ok(Some(network)) => {
- let network_json = serde_json::json!({
- "name": network.name,
- "netrange": match network.netrange {
- NetRange::V4 { addr, prefix } => {
- serde_json::json!({
- "type": "v4",
- "addr": addr.to_string(),
- "prefix": prefix
- })
- },
- NetRange::V6 { addr, prefix } => {
- serde_json::json!({
- "type": "v6",
- "addr": addr.to_string(),
- "prefix": prefix
- })
- }
- },
- "provider_type": network.provider_type,
- "provider_config": serde_json::from_str::<serde_json::Value>(&network.provider_config).unwrap_or(serde_json::Value::Null),
- "assigner_type": network.assigner_type,
- "assigner_config": network.assigner_config.as_ref()
- .and_then(|config| serde_json::from_str::<serde_json::Value>(config).ok())
- .unwrap_or(serde_json::Value::Null)
- });
- (StatusCode::OK, Json(network_json)).into_response()
- },
- Ok(None) => (StatusCode::NOT_FOUND, format!("Network '{}' not found", name)).into_response(),
- Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get network '{}': {:?}", name, e)).into_response(),
- }
-}
-
-async fn delete_network(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> impl IntoResponse {
- let networks = state.collar.store.networks();
-
- match networks.delete(&name) {
- Ok(true) => (StatusCode::OK, format!("Deleted network '{}'", name)).into_response(),
- Ok(false) => (StatusCode::NOT_FOUND, format!("Network '{}' not found", name)).into_response(),
- Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to delete network '{}': {:?}", name, e)).into_response(),
- }
-}
-
-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/crates/api/src/network.rs b/crates/api/src/network.rs
new file mode 100644
index 0000000..0f568a9
--- /dev/null
+++ b/crates/api/src/network.rs
@@ -0,0 +1,133 @@
+use std::sync::Arc;
+
+use axum::{
+ extract::{Path, State},
+ http::StatusCode,
+ response::IntoResponse,
+ Json,
+};
+use serde::{Deserialize, Serialize};
+use store::network::{BasicAssigner, BasicProvider, NetRange, RangeAssigner};
+
+use crate::AppState;
+
+#[derive(Serialize, Deserialize)]
+pub struct CreateNetworkRequest {
+ pub name: String,
+ pub cidr: String,
+ pub provider_type: Option<String>,
+ pub provider_config: Option<serde_json::Value>,
+ pub assigner_type: Option<String>,
+ pub assigner_config: Option<serde_json::Value>,
+}
+
+pub async fn get_networks(State(state): State<Arc<AppState>>) -> impl IntoResponse {
+ let networks = state.collar.store.networks();
+
+ match networks.list() {
+ Ok(network_list) => (StatusCode::OK, Json(network_list)).into_response(),
+ Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to list networks: {:?}", e)).into_response(),
+ }
+}
+
+pub async fn post_networks(State(state): State<Arc<AppState>>, Json(req): Json<CreateNetworkRequest>) -> impl IntoResponse {
+ let networks = state.collar.store.networks();
+
+ // Parse the CIDR
+ let netrange = match NetRange::from_cidr(&req.cidr) {
+ Ok(range) => range,
+ Err(e) => return (StatusCode::BAD_REQUEST, format!("Invalid CIDR '{}': {:?}", req.cidr, e)).into_response(),
+ };
+
+ // Create basic provider with optional config
+ let provider = if let Some(config) = req.provider_config {
+ BasicProvider::new().with_config("config", &config.to_string())
+ } else {
+ BasicProvider::new()
+ };
+
+ // Handle different assigner types with separate create calls
+ let result = if let Some(assigner_type) = req.assigner_type {
+ match assigner_type.as_str() {
+ "basic" => {
+ let mut basic_assigner = BasicAssigner::new("sequential");
+ if let Some(config) = req.assigner_config {
+ basic_assigner = basic_assigner.with_config("config", &config.to_string());
+ }
+ networks.create(&req.name, netrange, provider, Some(basic_assigner))
+ },
+ "range" => {
+ if let Some(config) = req.assigner_config {
+ if let Some(range_name) = config.get("range_name").and_then(|v| v.as_str()) {
+ let mut range_assigner = RangeAssigner::with_range_name(range_name);
+ if let Some(extra_config) = config.get("config") {
+ range_assigner = range_assigner.with_config("config", &extra_config.to_string());
+ }
+ networks.create(&req.name, netrange, provider, Some(range_assigner))
+ } else {
+ return (StatusCode::BAD_REQUEST, "Range assigner requires 'range_name' in config".to_string()).into_response();
+ }
+ } else {
+ return (StatusCode::BAD_REQUEST, "Range assigner requires config with 'range_name'".to_string()).into_response();
+ }
+ },
+ _ => return (StatusCode::BAD_REQUEST, format!("Unknown assigner type: {}", assigner_type)).into_response(),
+ }
+ } else {
+ networks.create(&req.name, netrange, provider, None::<BasicAssigner>)
+ };
+
+ // Handle the result
+ match result {
+ Ok(_) => (StatusCode::OK, format!("Created network '{}'", req.name)).into_response(),
+ Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create network '{}': {:?}", req.name, e)).into_response(),
+ }
+}
+
+pub async fn get_network(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> impl IntoResponse {
+ let networks = state.collar.store.networks();
+
+ match networks.get(&name) {
+ Ok(Some(network)) => {
+ let network_json = serde_json::json!({
+ "name": network.name,
+ "netrange": match network.netrange {
+ NetRange::V4 { addr, prefix } => {
+ serde_json::json!({
+ "type": "v4",
+ "addr": addr.to_string(),
+ "prefix": prefix
+ })
+ },
+ NetRange::V6 { addr, prefix } => {
+ serde_json::json!({
+ "type": "v6",
+ "addr": addr.to_string(),
+ "prefix": prefix
+ })
+ }
+ },
+ "provider_type": network.provider_type,
+ "provider_config": serde_json::from_str::<serde_json::Value>(&network.provider_config).unwrap_or(serde_json::Value::Null),
+ "assigner_type": network.assigner_type,
+ "assigner_config": network.assigner_config.as_ref()
+ .and_then(|config| serde_json::from_str::<serde_json::Value>(config).ok())
+ .unwrap_or(serde_json::Value::Null)
+ });
+ (StatusCode::OK, Json(network_json)).into_response()
+ },
+ Ok(None) => (StatusCode::NOT_FOUND, format!("Network '{}' not found", name)).into_response(),
+ Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get network '{}': {:?}", name, e)).into_response(),
+ }
+}
+
+pub async fn delete_network(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> impl IntoResponse {
+ let networks = state.collar.store.networks();
+
+ match networks.delete(&name) {
+ Ok(true) => (StatusCode::OK, format!("Deleted network '{}'", name)).into_response(),
+ Ok(false) => (StatusCode::NOT_FOUND, format!("Network '{}' not found", name)).into_response(),
+ Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to delete network '{}': {:?}", name, e)).into_response(),
+ }
+}
+
diff --git a/crates/api/tests/network_api_tests.rs b/crates/api/tests/network_api_tests.rs
index 9204e6f..deb35ac 100644
--- a/crates/api/tests/network_api_tests.rs
+++ b/crates/api/tests/network_api_tests.rs
@@ -1,249 +1,249 @@
use axum::http::StatusCode;
use axum_test::TestServer;
use serde_json::json;
use tempfile::TempDir;
async fn create_test_app() -> (TestServer, TempDir) {
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
let db_path = temp_dir.path().join("test_db");
let collar_state = libcollar::new_test(&db_path.to_string_lossy()).expect("Failed to create collar state");
let app = api::app(collar_state);
let server = TestServer::new(app).expect("Failed to create test server");
(server, temp_dir)
}
#[tokio::test]
async fn test_get_networks_empty() {
let (server, _temp_dir) = create_test_app().await;
let response = server.get("/networks").await;
response.assert_status(StatusCode::OK);
let networks: Vec<String> = response.json();
assert!(networks.is_empty());
}
#[tokio::test]
async fn test_create_network_basic() {
let (server, _temp_dir) = create_test_app().await;
let create_request = json!({
"name": "test-network",
"cidr": "192.168.1.0/24"
});
let response = server.post("/networks").json(&create_request).await;
response.assert_status(StatusCode::OK);
response.assert_text("Created network 'test-network'");
}
#[tokio::test]
async fn test_create_network_with_assigner() {
let (server, _temp_dir) = create_test_app().await;
let create_request = json!({
"name": "test-network-assigner",
"cidr": "10.0.0.0/16",
"assigner_type": "basic",
"assigner_config": {
"strategy": "sequential"
}
});
let response = server.post("/networks").json(&create_request).await;
response.assert_status(StatusCode::OK);
response.assert_text("Created network 'test-network-assigner'");
}
#[tokio::test]
async fn test_create_network_with_range_assigner() {
let (server, _temp_dir) = create_test_app().await;
// First create a range
- let range_response = server.post("/store/range/new/test-range/256").await;
+ let range_response = server.post("/internal/store/range/new/test-range/256").await;
range_response.assert_status(StatusCode::OK);
let create_request = json!({
"name": "test-range-network",
"cidr": "172.16.0.0/24",
"assigner_type": "range",
"assigner_config": {
"range_name": "test-range"
}
});
let response = server.post("/networks").json(&create_request).await;
response.assert_status(StatusCode::OK);
response.assert_text("Created network 'test-range-network'");
}
#[tokio::test]
async fn test_create_network_invalid_cidr() {
let (server, _temp_dir) = create_test_app().await;
let create_request = json!({
"name": "invalid-network",
"cidr": "invalid-cidr"
});
let response = server.post("/networks").json(&create_request).await;
response.assert_status(StatusCode::BAD_REQUEST);
let error_text = response.text();
assert!(error_text.contains("Invalid CIDR"));
}
#[tokio::test]
async fn test_get_network_after_creation() {
let (server, _temp_dir) = create_test_app().await;
// Create network first
let create_request = json!({
"name": "get-test-network",
"cidr": "192.168.100.0/24",
"provider_config": {
"region": "us-west-2"
}
});
let create_response = server.post("/networks").json(&create_request).await;
create_response.assert_status(StatusCode::OK);
// Get the network
let response = server.get("/networks/get-test-network").await;
response.assert_status(StatusCode::OK);
let network: serde_json::Value = response.json();
assert_eq!(network["name"], "get-test-network");
assert_eq!(network["netrange"]["type"], "v4");
assert_eq!(network["netrange"]["addr"], "192.168.100.0");
assert_eq!(network["netrange"]["prefix"], 24);
assert_eq!(network["provider_type"], "basic");
}
#[tokio::test]
async fn test_get_nonexistent_network() {
let (server, _temp_dir) = create_test_app().await;
let response = server.get("/networks/nonexistent").await;
response.assert_status(StatusCode::NOT_FOUND);
let error_text = response.text();
assert!(error_text.contains("not found"));
}
#[tokio::test]
async fn test_list_networks_after_creation() {
let (server, _temp_dir) = create_test_app().await;
// Create multiple networks
let networks = vec![
("network-1", "192.168.1.0/24"),
("network-2", "192.168.2.0/24"),
("network-v6", "2001:db8::/64"),
];
for (name, cidr) in &networks {
let create_request = json!({
"name": name,
"cidr": cidr
});
let response = server.post("/networks").json(&create_request).await;
response.assert_status(StatusCode::OK);
}
// List networks
let response = server.get("/networks").await;
response.assert_status(StatusCode::OK);
let network_list: Vec<String> = response.json();
assert_eq!(network_list.len(), 3);
for (name, _) in &networks {
assert!(network_list.contains(&name.to_string()));
}
}
#[tokio::test]
async fn test_delete_network() {
let (server, _temp_dir) = create_test_app().await;
// Create network first
let create_request = json!({
"name": "delete-test-network",
"cidr": "10.10.0.0/16"
});
let create_response = server.post("/networks").json(&create_request).await;
create_response.assert_status(StatusCode::OK);
// Verify it exists
let get_response = server.get("/networks/delete-test-network").await;
get_response.assert_status(StatusCode::OK);
// Delete the network
let delete_response = server.delete("/networks/delete-test-network").await;
delete_response.assert_status(StatusCode::OK);
delete_response.assert_text("Deleted network 'delete-test-network'");
// Verify it's gone
let get_after_delete = server.get("/networks/delete-test-network").await;
get_after_delete.assert_status(StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn test_delete_nonexistent_network() {
let (server, _temp_dir) = create_test_app().await;
let response = server.delete("/networks/nonexistent").await;
response.assert_status(StatusCode::NOT_FOUND);
let error_text = response.text();
assert!(error_text.contains("not found"));
}
#[tokio::test]
async fn test_ipv6_network_creation_and_retrieval() {
let (server, _temp_dir) = create_test_app().await;
let create_request = json!({
"name": "ipv6-network",
"cidr": "2001:db8::/32"
});
let create_response = server.post("/networks").json(&create_request).await;
create_response.assert_status(StatusCode::OK);
// Get the IPv6 network
let response = server.get("/networks/ipv6-network").await;
response.assert_status(StatusCode::OK);
let network: serde_json::Value = response.json();
assert_eq!(network["name"], "ipv6-network");
assert_eq!(network["netrange"]["type"], "v6");
assert_eq!(network["netrange"]["addr"], "2001:db8::");
assert_eq!(network["netrange"]["prefix"], 32);
}
#[tokio::test]
async fn test_create_duplicate_network() {
let (server, _temp_dir) = create_test_app().await;
let create_request = json!({
"name": "duplicate-network",
"cidr": "192.168.50.0/24"
});
// Create first network
let first_response = server.post("/networks").json(&create_request).await;
first_response.assert_status(StatusCode::OK);
// Try to create duplicate
let duplicate_response = server.post("/networks").json(&create_request).await;
duplicate_response.assert_status(StatusCode::INTERNAL_SERVER_ERROR);
let error_text = duplicate_response.text();
assert!(error_text.contains("Failed to create network"));
}
\ No newline at end of file
diff --git a/crates/store/src/combined.rs b/crates/store/src/combined.rs
index 0905601..8f3ca7e 100644
--- a/crates/store/src/combined.rs
+++ b/crates/store/src/combined.rs
@@ -1,563 +1,562 @@
//! Generic transaction module for atomic operations across multiple stores.
//!
//! # Example
//!
//! This module provides a generic transaction mechanism for atomic operations across
//! different types of stores. Each store implementation can plug into this system
//! by implementing the `TransactionProvider` trait.
//!
//! ```no_run
//! use store::{Transaction, TransactionContext};
//!
//! // Assuming you have stores and trees
//! # fn example_usage() -> store::Result<()> {
//! # let temp_dir = tempfile::tempdir().unwrap();
//! # let db = sled::open(temp_dir.path())?;
//! # let range_store = store::RangeStore::open(&db)?;
//! # let namespace_store = store::NamespaceStore::open(&db)?;
//! # let metadata_tree = db.open_tree("metadata")?;
//!
//! // Create a transaction with the stores you want to include
//! let transaction = Transaction::new()
//! .with_store("ranges", &range_store)
//! .with_store("namespaces", &namespace_store)
//! .with_tree(&metadata_tree);
//!
//! // Execute the transaction
//! let result = transaction.execute(|ctx| {
//! // Access stores by name
//! let range_trees = ctx.store_trees("ranges")?;
//! let namespace_trees = ctx.store_trees("namespaces")?;
//!
//! // Access additional trees by index
//! let metadata = ctx.tree(0)?;
//!
-//! metadata.insert("operation", "test")
-//! .map_err(|e| store::Error::StoreError(e))?;
+//! metadata.insert("operation", "test")?;
//!
//! Ok(())
//! })?;
//! # Ok(())
//! # }
//! ```
use crate::{Result, Error};
use sled::Transactional;
use std::collections::HashMap;
/// Helper function to convert transaction errors
fn convert_transaction_error<T>(e: sled::transaction::ConflictableTransactionError<T>, default_error: Error) -> Error {
match e {
sled::transaction::ConflictableTransactionError::Storage(storage_err) => Error::StoreError(storage_err),
sled::transaction::ConflictableTransactionError::Abort(_) => default_error,
_ => Error::StoreError(sled::Error::Unsupported("Unknown transaction error".to_string())),
}
}
/// Trait for types that can provide trees to a transaction
pub trait TransactionProvider {
/// Return the trees that should be included in a transaction
fn transaction_trees(&self) -> Vec<&sled::Tree>;
}
/// Implement TransactionProvider for individual trees
impl TransactionProvider for sled::Tree {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
vec![self]
}
}
/// Implement TransactionProvider for RangeStore
impl TransactionProvider for crate::RangeStore {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
vec![&self.names, &self.map, &self.assign]
}
}
/// Implement TransactionProvider for NamespaceStore
impl TransactionProvider for crate::NamespaceStore {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
vec![&self.names, &self.spaces]
}
}
/// Implement TransactionProvider for NetworkStore
impl TransactionProvider for crate::NetworkStore {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
let mut trees = self.namespaces.transaction_trees();
trees.push(&self.networks);
trees
}
}
/// Generic transaction context provided to transaction operations
pub struct TransactionContext<'ctx> {
store_map: HashMap<String, (usize, usize)>, // name -> (start_idx, end_idx)
trees: Vec<&'ctx sled::transaction::TransactionalTree>,
transactional_trees: &'ctx [sled::transaction::TransactionalTree],
additional_trees_start: usize,
}
impl<'ctx> TransactionContext<'ctx> {
/// Create a new transaction context
fn new(
store_map: HashMap<String, (usize, usize)>,
trees: Vec<&'ctx sled::transaction::TransactionalTree>,
transactional_trees: &'ctx [sled::transaction::TransactionalTree],
additional_trees_start: usize,
) -> Self {
Self {
store_map,
trees,
transactional_trees,
additional_trees_start,
}
}
/// Get trees for a store by name
pub fn store_trees(&self, store_name: &str) -> Result<&[&sled::transaction::TransactionalTree]> {
let (start_idx, end_idx) = self.store_map
.get(store_name)
.ok_or_else(|| Error::StoreError(sled::Error::Unsupported(format!("Store '{}' not found in transaction", store_name))))?;
Ok(&self.trees[*start_idx..*end_idx])
}
/// Access additional trees by index
pub fn tree(&self, index: usize) -> Result<&sled::transaction::TransactionalTree> {
self.trees.get(self.additional_trees_start + index)
.copied()
.ok_or_else(|| Error::StoreError(sled::Error::Unsupported(format!("Tree at index {} not found", index))))
}
/// Access a raw transactional tree by its absolute index
pub fn raw_tree(&self, index: usize) -> Result<&sled::transaction::TransactionalTree> {
self.trees.get(index)
.copied()
.ok_or_else(|| Error::StoreError(sled::Error::Unsupported(format!("Raw tree at index {} not found", index))))
}
/// Access the entire slice of transactional trees
pub fn all_trees(&self) -> &[sled::transaction::TransactionalTree] {
self.transactional_trees
}
/// Get store map for debugging or extension purposes
pub fn store_map(&self) -> &HashMap<String, (usize, usize)> {
&self.store_map
}
}
/// Generic transaction struct for atomic operations across multiple stores
pub struct Transaction<'a> {
stores: HashMap<String, &'a dyn TransactionProvider>,
additional_trees: Vec<&'a sled::Tree>,
}
impl<'a> Transaction<'a> {
/// Create a new empty transaction
pub fn new() -> Self {
Self {
stores: HashMap::new(),
additional_trees: Vec::new(),
}
}
/// Add a store with a name identifier
pub fn with_store<T: TransactionProvider>(mut self, name: &str, store: &'a T) -> Self {
self.stores.insert(name.to_string(), store);
self
}
/// Add a single tree to the transaction
pub fn with_tree(mut self, tree: &'a sled::Tree) -> Self {
self.additional_trees.push(tree);
self
}
/// Execute a transaction with the configured stores
pub fn execute<F, R>(&self, operations: F) -> Result<R>
where
F: Fn(&TransactionContext) -> Result<R>,
{
// Collect all trees for the transaction
let mut all_trees = Vec::new();
let mut store_map = HashMap::new();
// Add trees from stores
for (name, store) in &self.stores {
let start_idx = all_trees.len();
all_trees.extend(store.transaction_trees());
let end_idx = all_trees.len();
store_map.insert(name.clone(), (start_idx, end_idx));
}
// Add additional trees
let additional_trees_start = all_trees.len();
all_trees.extend(&self.additional_trees);
// Execute the transaction
let result = all_trees.transaction(|trees| {
let context = TransactionContext::new(
store_map.clone(),
trees.into_iter().collect(),
trees,
additional_trees_start,
);
operations(&context).map_err(|e| match e {
Error::StoreError(store_err) => sled::transaction::ConflictableTransactionError::Storage(store_err),
_ => sled::transaction::ConflictableTransactionError::Abort(()),
})
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
}
impl<'a> Default for Transaction<'a> {
fn default() -> Self {
Self::new()
}
}
/// Legacy alias for backward compatibility
pub type CombinedTransaction<'a> = Transaction<'a>;
/// Legacy alias for backward compatibility
pub type CombinedTransactionContext<'ctx> = TransactionContext<'ctx>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{RangeStore, NamespaceStore};
use tempfile::tempdir;
fn create_test_stores() -> Result<(RangeStore, NamespaceStore, sled::Db)> {
let temp_dir = tempdir().unwrap();
let db = sled::open(temp_dir.path())?;
let range_store = RangeStore::open(&db)?;
let namespace_store = NamespaceStore::open(&db)?;
Ok((range_store, namespace_store, db))
}
#[test]
fn test_generic_transaction_basic() -> Result<()> {
let (range_store, namespace_store, db) = create_test_stores()?;
// Setup: define range and namespace
range_store.define("test_range", 100)?;
namespace_store.define("test_namespace")?;
// Create additional tree for testing
let extra_tree = db.open_tree("extra")?;
let transaction = Transaction::new()
.with_store("ranges", &range_store)
.with_store("namespaces", &namespace_store)
.with_tree(&extra_tree);
// Execute transaction using generic interface
transaction.execute(|ctx| {
// Access range store trees
let range_trees = ctx.store_trees("ranges")?;
assert_eq!(range_trees.len(), 3); // names, map, assign
// Access namespace store trees
let namespace_trees = ctx.store_trees("namespaces")?;
assert_eq!(namespace_trees.len(), 2); // names, spaces
// Use additional tree
let tree = ctx.tree(0)?;
tree.insert("test_key", "test_value")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
Ok(())
})?;
// Verify the additional tree was modified
let value = extra_tree.get("test_key")?;
assert_eq!(value, Some("test_value".as_bytes().into()));
Ok(())
}
#[test]
fn test_transaction_with_single_store() -> Result<()> {
let (range_store, _, _) = create_test_stores()?;
// Setup
range_store.define("test_range", 50)?;
let transaction = Transaction::new()
.with_store("ranges", &range_store);
// Execute transaction with just one store
transaction.execute(|ctx| {
let range_trees = ctx.store_trees("ranges")?;
assert_eq!(range_trees.len(), 3);
// Verify we can access the trees
let _names_tree = range_trees[0];
let _map_tree = range_trees[1];
let _assign_tree = range_trees[2];
Ok(())
})?;
Ok(())
}
#[test]
fn test_transaction_with_only_trees() -> Result<()> {
let (_, _, db) = create_test_stores()?;
let tree1 = db.open_tree("tree1")?;
let tree2 = db.open_tree("tree2")?;
let transaction = Transaction::new()
.with_tree(&tree1)
.with_tree(&tree2);
transaction.execute(|ctx| {
let t1 = ctx.tree(0)?;
let t2 = ctx.tree(1)?;
t1.insert("key1", "value1")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
t2.insert("key2", "value2")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
Ok(())
})?;
// Verify the trees were modified
let value1 = tree1.get("key1")?;
assert_eq!(value1, Some("value1".as_bytes().into()));
let value2 = tree2.get("key2")?;
assert_eq!(value2, Some("value2".as_bytes().into()));
Ok(())
}
#[test]
fn test_multiple_stores_same_type() -> Result<()> {
let (range_store1, _, db) = create_test_stores()?;
let range_store2 = RangeStore::open(&db)?;
// Setup both stores
range_store1.define("range1", 50)?;
range_store2.define("range2", 100)?;
let transaction = Transaction::new()
.with_store("ranges1", &range_store1)
.with_store("ranges2", &range_store2);
transaction.execute(|ctx| {
let trees1 = ctx.store_trees("ranges1")?;
let trees2 = ctx.store_trees("ranges2")?;
assert_eq!(trees1.len(), 3);
assert_eq!(trees2.len(), 3);
// Verify different stores have different trees
assert_ne!(trees1[0] as *const _, trees2[0] as *const _);
Ok(())
})?;
Ok(())
}
#[test]
fn test_store_not_found_error() -> Result<()> {
let (range_store, _, _) = create_test_stores()?;
let transaction = Transaction::new()
.with_store("ranges", &range_store);
let result: Result<()> = transaction.execute(|ctx| {
// Try to access a store that doesn't exist
let _trees = ctx.store_trees("nonexistent")?;
Ok(())
});
assert!(result.is_err());
Ok(())
}
#[test]
fn test_tree_index_out_of_bounds() -> Result<()> {
let (_, _, db) = create_test_stores()?;
let tree = db.open_tree("single_tree")?;
let transaction = Transaction::new()
.with_tree(&tree);
let result: Result<()> = transaction.execute(|ctx| {
// Try to access tree at index that doesn't exist
let _tree = ctx.tree(5)?;
Ok(())
});
assert!(result.is_err());
Ok(())
}
#[test]
fn test_transaction_rollback() -> Result<()> {
let (_, _, db) = create_test_stores()?;
let tree = db.open_tree("rollback_test")?;
// First, insert some initial data
tree.insert("initial", "data")?;
let transaction = Transaction::new()
.with_tree(&tree);
// Execute a transaction that should fail
let result: Result<()> = transaction.execute(|ctx| {
let t = ctx.tree(0)?;
// Insert some data
t.insert("temp", "value")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
// Force an error to trigger rollback
Err(Error::StoreError(sled::Error::Unsupported("Forced error".to_string())))
});
assert!(result.is_err());
// Verify rollback - temp key should not exist
let temp_value = tree.get("temp")?;
assert_eq!(temp_value, None);
// But initial data should still be there
let initial_value = tree.get("initial")?;
assert_eq!(initial_value, Some("data".as_bytes().into()));
Ok(())
}
#[test]
fn test_complex_multi_store_transaction() -> Result<()> {
let (range_store, namespace_store, db) = create_test_stores()?;
// Setup
range_store.define("ip_pool", 100)?;
namespace_store.define("users")?;
let metadata_tree = db.open_tree("metadata")?;
let logs_tree = db.open_tree("logs")?;
let transaction = Transaction::new()
.with_store("ranges", &range_store)
.with_store("namespaces", &namespace_store)
.with_tree(&metadata_tree)
.with_tree(&logs_tree);
// Complex transaction
transaction.execute(|ctx| {
let range_trees = ctx.store_trees("ranges")?;
let namespace_trees = ctx.store_trees("namespaces")?;
let metadata = ctx.tree(0)?;
let logs = ctx.tree(1)?;
// Verify we have the right number of trees
assert_eq!(range_trees.len(), 3);
assert_eq!(namespace_trees.len(), 2);
// Use metadata tree
metadata.insert("operation", "complex_transaction")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
// Use logs tree
logs.insert("log_entry_1", "Started complex operation")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
Ok(())
})?;
// Verify all operations succeeded
let op_value = metadata_tree.get("operation")?;
assert_eq!(op_value, Some("complex_transaction".as_bytes().into()));
let log_value = logs_tree.get("log_entry_1")?;
assert_eq!(log_value, Some("Started complex operation".as_bytes().into()));
Ok(())
}
#[test]
fn test_raw_tree_access() -> Result<()> {
let (range_store, namespace_store, db) = create_test_stores()?;
let extra_tree = db.open_tree("extra")?;
let transaction = Transaction::new()
.with_store("ranges", &range_store)
.with_store("namespaces", &namespace_store)
.with_tree(&extra_tree);
transaction.execute(|ctx| {
// Test raw tree access by absolute index
let tree0 = ctx.raw_tree(0)?; // First range store tree
let tree3 = ctx.raw_tree(3)?; // First namespace store tree
let tree5 = ctx.raw_tree(5)?; // Extra tree
// All should be valid trees
tree0.insert("raw0", "value0")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
tree3.insert("raw3", "value3")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
tree5.insert("raw5", "value5")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
// Test accessing out of bounds
let invalid_result = ctx.raw_tree(10);
assert!(invalid_result.is_err());
Ok(())
})?;
Ok(())
}
#[test]
fn test_store_map_access() -> Result<()> {
let (range_store, namespace_store, _) = create_test_stores()?;
let transaction = Transaction::new()
.with_store("my_ranges", &range_store)
.with_store("my_namespaces", &namespace_store);
transaction.execute(|ctx| {
let store_map = ctx.store_map();
// Verify store map contains our stores
assert!(store_map.contains_key("my_ranges"));
assert!(store_map.contains_key("my_namespaces"));
// Verify ranges
let (start, end) = store_map.get("my_ranges").unwrap();
assert_eq!(*start, 0);
assert_eq!(*end, 3); // RangeStore has 3 trees
// Verify namespaces
let (start, end) = store_map.get("my_namespaces").unwrap();
assert_eq!(*start, 3);
assert_eq!(*end, 5); // NamespaceStore has 2 trees
Ok(())
})?;
Ok(())
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jun 8, 9:14 AM (19 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47588
Default Alt Text
(55 KB)

Event Timeline