Skip to content
Merged
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
2 changes: 1 addition & 1 deletion antd/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion antd/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "antd"
version = "0.6.0"
version = "0.6.1"
edition = "2021"

[dependencies]
Expand Down
34 changes: 33 additions & 1 deletion antd/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ paths:
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalError"
"501":
description: >-
visibility:"public" not yet supported on this endpoint —
blocked on upstream ant-client #73.
"503":
$ref: "#/components/responses/ServiceUnavailable"

Expand Down Expand Up @@ -765,6 +769,15 @@ components:
data:
type: string
description: Base64-encoded data to upload
visibility:
type: string
enum: [private, public]
description: >-
Upload visibility. "private" (default) returns the DataMap to
the caller. "public" is reserved — returns 501 until upstream
ant-core exposes data_prepare_upload_with_visibility (tracked
as ant-client PR #73). Use /v1/upload/prepare with a file path
today.

PrepareUploadRequest:
type: object
Expand All @@ -773,6 +786,15 @@ components:
path:
type: string
description: Absolute path to local file
visibility:
type: string
enum: [private, public]
description: >-
Upload visibility. "private" (default) returns the DataMap to
the caller. "public" bundles the serialized DataMap chunk into
the same external-signer payment batch and surfaces its
network address on /v1/upload/finalize via data_map_address.
Omitting this field preserves pre-0.5.0 (private) behavior.

PrepareUploadResponse:
type: object
Expand Down Expand Up @@ -901,7 +923,17 @@ components:
description: Hex-encoded serialized DataMap (always returned)
address:
type: string
description: Network address of the stored DataMap (only set when store_data_map=true)
description: >-
Network address of the DataMap stored via the legacy
store_data_map=true path (paid by the daemon's internal
wallet). Prefer visibility:"public" on prepare instead.
data_map_address:
type: string
description: >-
Network address of the bundled DataMap chunk when the upload
was prepared with visibility:"public". The DataMap chunk's
payment is part of the same external-signer batch as the
data chunks — no separate daemon-wallet payment.
chunks_stored:
type: integer
description: Number of chunks stored on the network
Expand Down
40 changes: 36 additions & 4 deletions antd/src/rest/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ pub async fn prepare_upload(
AntdError::BadRequest("invalid path".into())
})?;

let visibility = parse_visibility(req.visibility.as_deref()).map_err(AntdError::BadRequest)?;

let client = state.client.clone();
let prepared = tokio::spawn(async move {
client
.file_prepare_upload(&path)
.file_prepare_upload_with_visibility(&path, visibility)
.await
.map_err(AntdError::from_core)
})
Expand Down Expand Up @@ -149,6 +151,21 @@ pub async fn prepare_data_upload(
use base64::Engine;
use bytes::Bytes;

let visibility = parse_visibility(req.visibility.as_deref()).map_err(AntdError::BadRequest)?;

// Public visibility on the in-memory data path requires the upstream
// `data_prepare_upload_with_visibility` (ant-client PR #73). Until that
// lands, refuse the public variant rather than silently returning a
// private prepare. The file-path equivalent works today.
if matches!(visibility, ant_core::data::Visibility::Public) {
return Err(AntdError::NotImplemented(
"visibility:\"public\" is not yet supported on /v1/data/prepare; \
use /v1/upload/prepare with a file path, or wait for upstream \
ant-client #73 to land"
.into(),
));
}

let data = BASE64
.decode(&req.data)
.map_err(|e| AntdError::BadRequest(format!("invalid base64: {e}")))?;
Expand Down Expand Up @@ -205,7 +222,7 @@ pub async fn finalize_upload(
let store_on_network = req.store_data_map;
let client = state.client.clone();

let (data_map_hex, address, chunks_stored) = match &prepared.payment_info {
let (data_map_hex, address, data_map_address, chunks_stored) = match &prepared.payment_info {
ant_core::data::ExternalPaymentInfo::WaveBatch { .. } => {
// Wave-batch: require tx_hashes
let tx_hashes_raw = req.tx_hashes.ok_or_else(|| {
Expand Down Expand Up @@ -268,7 +285,14 @@ pub async fn finalize_upload(
None
};

Ok::<_, AntdError>((data_map_hex, address, result.chunks_stored))
let data_map_address = result.data_map_address.map(hex::encode);

Ok::<_, AntdError>((
data_map_hex,
address,
data_map_address,
result.chunks_stored,
))
})
.await
.map_err(|e| AntdError::Internal(format!("task failed: {e}")))??
Expand Down Expand Up @@ -314,7 +338,14 @@ pub async fn finalize_upload(
None
};

Ok::<_, AntdError>((data_map_hex, address, result.chunks_stored))
let data_map_address = result.data_map_address.map(hex::encode);

Ok::<_, AntdError>((
data_map_hex,
address,
data_map_address,
result.chunks_stored,
))
})
.await
.map_err(|e| AntdError::Internal(format!("task failed: {e}")))??
Expand All @@ -324,6 +355,7 @@ pub async fn finalize_upload(
Ok(Json(FinalizeUploadResponse {
data_map: data_map_hex,
address,
data_map_address,
chunks_stored: chunks_stored as u64,
}))
}
109 changes: 108 additions & 1 deletion antd/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,24 @@ pub struct ChunkGetResponse {
#[derive(Deserialize)]
pub struct PrepareUploadRequest {
pub path: String,
/// Upload visibility: `"private"` (default — DataMap returned to the
/// caller) or `"public"` (DataMap chunk bundled into the same payment
/// batch and stored on-network; its address is returned on finalize).
/// Omitting this field is equivalent to `"private"` and preserves
/// pre-0.6.1 behavior.
#[serde(default)]
pub visibility: Option<String>,
}

#[derive(Deserialize)]
pub struct PrepareDataUploadRequest {
pub data: String, // base64
/// Same semantics as [`PrepareUploadRequest::visibility`]. Currently
/// only `"private"` (or omission) is accepted on this endpoint;
/// `"public"` returns 501 until upstream `ant-core` exposes
/// `data_prepare_upload_with_visibility` (tracked as ant-client PR #73).
#[serde(default)]
pub visibility: Option<String>,
}

#[derive(Serialize)]
Expand Down Expand Up @@ -154,9 +167,18 @@ pub struct FinalizeUploadRequest {
pub struct FinalizeUploadResponse {
/// Hex-encoded serialized DataMap. Always returned.
pub data_map: String,
/// Network address of the stored DataMap (only set when store_data_map=true).
/// Network address of the stored DataMap, only set when the legacy
/// `store_data_map=true` path published the DataMap via the daemon's
/// internal wallet. New callers should prefer `visibility:"public"`
/// on prepare and read [`Self::data_map_address`] instead.
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
/// Network address of the bundled DataMap chunk when the upload was
/// prepared with `visibility:"public"`. The DataMap chunk's payment
/// is part of the same external-signer batch as the data chunks, so
/// no separate daemon-wallet payment is required.
#[serde(skip_serializing_if = "Option::is_none")]
pub data_map_address: Option<String>,
/// Number of chunks stored on the network.
pub chunks_stored: u64,
}
Expand Down Expand Up @@ -275,6 +297,19 @@ pub fn format_payment_mode(mode: ant_core::data::PaymentMode) -> String {
}
}

/// Parse a visibility string into ant-core's `Visibility` enum.
///
/// Accepts `"private"`, `"public"`, or absent (defaults to `Private`).
pub fn parse_visibility(s: Option<&str>) -> Result<ant_core::data::Visibility, String> {
match s {
None | Some("private") => Ok(ant_core::data::Visibility::Private),
Some("public") => Ok(ant_core::data::Visibility::Public),
Some(other) => Err(format!(
"invalid visibility: {other:?}. Use \"private\" or \"public\""
)),
}
}

// ── Wallet ──

#[derive(Serialize)]
Expand Down Expand Up @@ -526,4 +561,76 @@ mod tests {
assert_eq!(chunks, 6);
assert_eq!(cost, "0");
}

#[test]
fn prepare_request_visibility_defaults_to_none() {
let req: PrepareUploadRequest = serde_json::from_str(r#"{"path":"/tmp/foo"}"#).unwrap();
assert_eq!(req.path, "/tmp/foo");
assert!(req.visibility.is_none());
}

#[test]
fn prepare_request_visibility_round_trips_public() {
let req: PrepareUploadRequest =
serde_json::from_str(r#"{"path":"/tmp/foo","visibility":"public"}"#).unwrap();
assert_eq!(req.visibility.as_deref(), Some("public"));
}

#[test]
fn prepare_data_request_visibility_defaults_to_none() {
let req: PrepareDataUploadRequest = serde_json::from_str(r#"{"data":"AAA="}"#).unwrap();
assert!(req.visibility.is_none());
}

#[test]
fn parse_visibility_accepts_known_values() {
assert!(matches!(
parse_visibility(None),
Ok(ant_core::data::Visibility::Private)
));
assert!(matches!(
parse_visibility(Some("private")),
Ok(ant_core::data::Visibility::Private)
));
assert!(matches!(
parse_visibility(Some("public")),
Ok(ant_core::data::Visibility::Public)
));
}

#[test]
fn parse_visibility_rejects_unknown_values() {
let err = parse_visibility(Some("Public")).unwrap_err();
assert!(err.contains("Public"), "err was: {err}");
let err = parse_visibility(Some("")).unwrap_err();
assert!(err.contains("\"\""), "err was: {err}");
}

#[test]
fn finalize_response_serializes_data_map_address() {
let resp = FinalizeUploadResponse {
data_map: "deadbeef".into(),
address: None,
data_map_address: Some("cafebabe".into()),
chunks_stored: 4,
};
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["data_map"], "deadbeef");
assert_eq!(json["data_map_address"], "cafebabe");
assert!(json.get("address").is_none());
assert_eq!(json["chunks_stored"], 4);
}

#[test]
fn finalize_response_omits_data_map_address_when_private() {
let resp = FinalizeUploadResponse {
data_map: "deadbeef".into(),
address: None,
data_map_address: None,
chunks_stored: 4,
};
let json = serde_json::to_value(&resp).unwrap();
assert!(json.get("address").is_none());
assert!(json.get("data_map_address").is_none());
}
}
Loading