Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl MonoceClient {
}
}

pub async fn allocate_chunks(&self, chunks: &[(Cid, u64)]) -> Result<AllocationResponse> {
pub async fn allocate_chunks(&self, chunks: &[(Cid, u64)], replication_factor: u32) -> Result<AllocationResponse> {
let url = format!("{}/api/v1/sessions", self.endpoint);
let request = AllocateRequest {
chunks: chunks.iter()
Expand All @@ -27,6 +27,7 @@ impl MonoceClient {
size: *size,
})
.collect(),
replication_factor,
};

let response = self
Expand Down Expand Up @@ -61,6 +62,29 @@ impl MonoceClient {
.error_for_status()
.map_err(map_reqwest_err)?;

// Register chunk location with gateway
self.register_chunk_location(&cid.to_hex(), node_addr).await?;

Ok(())
}

Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new public method register_chunk_location lacks documentation. Consider adding a doc comment explaining its purpose, parameters, and expected behavior, especially since it's a public API that could be used by other parts of the codebase.

Suggested change
/// Register the location of a chunk with the gateway.
///
/// * `cid_hex` - Hex-encoded CID of the chunk whose location is being registered.
/// * `node_addr` - Address of the node storing the chunk. May be either a full
/// `http://` or `https://` URL or a bare host[:port], which will be normalized
/// to an `http://` URL before being sent to the gateway.
///
/// Returns an error if the underlying HTTP request fails or the gateway responds
/// with a non-success status code.

Copilot uses AI. Check for mistakes.
pub async fn register_chunk_location(&self, cid_hex: &str, node_addr: &str) -> Result<()> {
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node_addr parameter is not validated before use. If an empty string is passed, this would create an invalid URL "http://". Consider adding validation to ensure node_addr is not empty and has a valid format before processing.

Copilot uses AI. Check for mistakes.
let url = format!("{}/api/v1/chunks/{}/locations", self.endpoint, cid_hex);
let node_address = if node_addr.starts_with("http://") || node_addr.starts_with("https://") {
node_addr.to_string()
} else {
format!("http://{}", node_addr)
};
Comment on lines +73 to +77
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL normalization logic (checking for http:// or https:// prefix and adding http:// if needed) is duplicated between upload_chunk and register_chunk_location. Consider extracting this into a helper method to reduce code duplication and ensure consistency.

Copilot uses AI. Check for mistakes.

self.http
.post(&url)
.json(&serde_json::json!({"node_address": node_address}))
.send()
.await
.map_err(map_reqwest_err)?
.error_for_status()
.map_err(map_reqwest_err)?;

Ok(())
}

Expand Down Expand Up @@ -264,6 +288,7 @@ impl MonoceClient {
#[derive(Serialize)]
struct AllocateRequest {
chunks: Vec<ChunkAllocInfo>,
replication_factor: u32,
}

#[derive(Serialize)]
Expand Down
14 changes: 7 additions & 7 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::path::Path;
use crate::chunker::{Chunker, DEFAULT_CHUNK_SIZE};
use crate::client::{MonoceClient, NamespaceEntry};

pub async fn put(endpoint: &str, path: &str, name: Option<&str>) -> Result<()> {
pub async fn put(endpoint: &str, path: &str, name: Option<&str>, replication_factor: u32) -> Result<()> {
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replication_factor parameter should be validated to ensure it's not zero. A replication factor of 0 would be meaningless and could cause issues in the distributed storage system. Consider adding validation to ensure the value is at least 1 before making the API call.

Copilot uses AI. Check for mistakes.
let file = File::open(path).context("failed to open file")?;
let file_size = file.metadata()?.len();
let file_name = Path::new(path)
Expand All @@ -32,7 +32,7 @@ pub async fn put(endpoint: &str, path: &str, name: Option<&str>) -> Result<()> {
.map(|c| (c.cid.clone(), c.size))
.collect();
let allocation = client
.allocate_chunks(&chunk_info)
.allocate_chunks(&chunk_info, replication_factor)
.await
.context("failed to allocate chunks")?;

Expand Down Expand Up @@ -155,19 +155,19 @@ pub async fn get(endpoint: &str, target: &str, output: Option<&str>) -> Result<(
println!("Downloading chunks...");

for (i, chunk_ref) in manifest.chunks.iter().enumerate() {
let chunk_cid = chunk_ref.chunk_cid.to_string();
let chunk_cid_hex = chunk_ref.chunk_cid.to_hex();
let locations = client
.get_chunk_locations(&chunk_cid)
.get_chunk_locations(&chunk_cid_hex)
.await
.context("failed to get chunk locations")?;

if locations.is_empty() {
anyhow::bail!("no nodes available for chunk {}", chunk_cid);
anyhow::bail!("no nodes available for chunk {}", chunk_cid_hex);
}

let mut downloaded = false;
for node_addr in &locations {
match client.download_chunk(&chunk_cid, node_addr).await {
match client.download_chunk(&chunk_cid_hex, node_addr).await {
Ok(data) => {
writer.write_all(&data)?;
downloaded = true;
Expand All @@ -180,7 +180,7 @@ pub async fn get(endpoint: &str, target: &str, output: Option<&str>) -> Result<(
}

if !downloaded {
anyhow::bail!("failed to download chunk {} from any node", chunk_cid);
anyhow::bail!("failed to download chunk {} from any node", chunk_cid_hex);
}

print!("\r [{}/{}] chunks", i + 1, manifest.chunks.len());
Expand Down
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ enum Commands {
path: String,
#[arg(long, help = "Target namespace path")]
name: Option<String>,
#[arg(short = 'r', long, default_value = "1", help = "Replication factor")]
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding validation to reject values above a reasonable maximum (e.g., 10 or 100) to prevent accidentally setting an extremely high replication factor that could overwhelm the storage cluster. Use clap's value_parser with a range check.

Suggested change
#[arg(short = 'r', long, default_value = "1", help = "Replication factor")]
#[arg(
short = 'r',
long,
default_value_t = 1u32,
value_parser = clap::value_parser!(u32).range(1..=100),
help = "Replication factor (1-100)"
)]

Copilot uses AI. Check for mistakes.
replication_factor: u32,
},
Get {
#[arg(help = "CID or namespace path")]
Expand Down Expand Up @@ -50,8 +52,8 @@ async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

match cli.command {
Commands::Put { path, name } => {
commands::put(&cli.endpoint, &path, name.as_deref()).await?;
Commands::Put { path, name, replication_factor } => {
commands::put(&cli.endpoint, &path, name.as_deref(), replication_factor).await?;
}
Commands::Get { target, output } => {
commands::get(&cli.endpoint, &target, output.as_deref()).await?;
Expand Down