Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F73673
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
55 KB
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jun 8, 9:14 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47588
Default Alt Text
(55 KB)
Attached To
rCOLLAR collar
Event Timeline
Log In to Comment