Skip to content

Commit bb5a2e2

Browse files
johnbattyclaude
andcommitted
Update artifacts_download to azure_core 0.33 API
The azure_core crate restructured its public API. Update the hand-written artifacts_download module to use the new paths and renamed functions. Key changes: - Move types under azure_core::http (Pipeline, ClientOptions, RetryOptions, Transport, Context, RawResponse, Method, Request, Url, headers) - Replace removed EMPTY_BODY with Bytes::new() - Replace azure_core::to_json with azure_core::json::to_json - Rename Error::message -> Error::with_message, Error::full -> Error::with_error - Rename ResultExt::context -> ResultExt::with_context - Pipeline::new and Pipeline::send take an extra None options argument - Response body is now sync (RawResponse::into_body() returns ResponseBody, no .collect().await needed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c4a47e1 commit bb5a2e2

2 files changed

Lines changed: 50 additions & 49 deletions

File tree

azure_devops_rust_api/src/artifacts_download/decompress.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use azure_core::error::{Error, ErrorKind, Result};
1818
/// - Nibble-based extended length encoding
1919
pub fn decompress_chunk(compressed: &[u8]) -> Result<Vec<u8>> {
2020
if compressed.len() < 5 {
21-
return Err(Error::message(
21+
return Err(Error::with_message(
2222
ErrorKind::DataConversion,
2323
format!("Compressed data too small: {} bytes", compressed.len()),
2424
));
@@ -165,7 +165,7 @@ fn process_match(
165165
(compressed[nib_idx] >> 4) as usize
166166
} else {
167167
if *ci >= compressed.len() {
168-
return Err(Error::message(
168+
return Err(Error::with_message(
169169
ErrorKind::DataConversion,
170170
"Unexpected end of compressed data in nibble read",
171171
));
@@ -179,7 +179,7 @@ fn process_match(
179179
match_len = nibble_val;
180180
if match_len == 15 {
181181
if *ci >= compressed.len() {
182-
return Err(Error::message(
182+
return Err(Error::with_message(
183183
ErrorKind::DataConversion,
184184
"Unexpected end of compressed data in length extension",
185185
));
@@ -188,7 +188,7 @@ fn process_match(
188188
*ci += 1;
189189
if match_len == 255 {
190190
if *ci + 1 >= compressed.len() {
191-
return Err(Error::message(
191+
return Err(Error::with_message(
192192
ErrorKind::DataConversion,
193193
"Unexpected end of compressed data in 16-bit length",
194194
));
@@ -198,7 +198,7 @@ fn process_match(
198198
*ci += 2;
199199
if match_len == 0 {
200200
if *ci + 3 >= compressed.len() {
201-
return Err(Error::message(
201+
return Err(Error::with_message(
202202
ErrorKind::DataConversion,
203203
"Unexpected end of compressed data in 32-bit length",
204204
));
@@ -208,7 +208,7 @@ fn process_match(
208208
*ci += 4;
209209
}
210210
if match_len < 22 {
211-
return Err(Error::message(
211+
return Err(Error::with_message(
212212
ErrorKind::DataConversion,
213213
format!("Invalid extended match length: {}", match_len),
214214
));
@@ -222,7 +222,7 @@ fn process_match(
222222
match_len += 3;
223223

224224
if offset > output.len() {
225-
return Err(Error::message(
225+
return Err(Error::with_message(
226226
ErrorKind::DataConversion,
227227
format!(
228228
"Match offset {} exceeds output size {} at compressed pos {}",

azure_devops_rust_api/src/artifacts_download/mod.rs

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
mod decompress;
1818

1919
use azure_core::error::{Error, ErrorKind, Result, ResultExt};
20-
use azure_core::headers::{self, HeaderValue};
21-
use azure_core::{Method, Request, Url};
20+
use azure_core::http::headers::{self, HeaderValue};
21+
use azure_core::http::{Method, Request, Url};
2222
use serde::Deserialize;
2323
use std::collections::HashMap;
2424
use std::io::Write;
@@ -88,15 +88,15 @@ pub struct Manifest {
8888
pub struct Client {
8989
credential: crate::Credential,
9090
scopes: Vec<String>,
91-
pipeline: azure_core::Pipeline,
91+
pipeline: azure_core::http::Pipeline,
9292
}
9393

9494
/// Builder for creating an artifacts download [`Client`].
9595
#[derive(Clone)]
9696
pub struct ClientBuilder {
9797
credential: crate::Credential,
9898
scopes: Option<Vec<String>>,
99-
options: azure_core::ClientOptions,
99+
options: azure_core::http::ClientOptions,
100100
}
101101

102102
impl ClientBuilder {
@@ -106,7 +106,7 @@ impl ClientBuilder {
106106
Self {
107107
credential,
108108
scopes: None,
109-
options: azure_core::ClientOptions::default(),
109+
options: azure_core::http::ClientOptions::default(),
110110
}
111111
}
112112

@@ -119,15 +119,15 @@ impl ClientBuilder {
119119

120120
/// Set the retry options.
121121
#[must_use]
122-
pub fn retry(mut self, retry: impl Into<azure_core::RetryOptions>) -> Self {
123-
self.options = self.options.retry(retry);
122+
pub fn retry(mut self, retry: impl Into<azure_core::http::RetryOptions>) -> Self {
123+
self.options.retry = retry.into();
124124
self
125125
}
126126

127127
/// Set the transport options.
128128
#[must_use]
129-
pub fn transport(mut self, transport: impl Into<azure_core::TransportOptions>) -> Self {
130-
self.options = self.options.transport(transport);
129+
pub fn transport(mut self, transport: impl Into<azure_core::http::Transport>) -> Self {
130+
self.options.transport = Some(transport.into());
131131
self
132132
}
133133

@@ -136,12 +136,13 @@ impl ClientBuilder {
136136
let scopes = self
137137
.scopes
138138
.unwrap_or_else(|| vec![crate::ADO_SCOPE.to_string()]);
139-
let pipeline = azure_core::Pipeline::new(
139+
let pipeline = azure_core::http::Pipeline::new(
140140
option_env!("CARGO_PKG_NAME"),
141141
option_env!("CARGO_PKG_VERSION"),
142142
self.options,
143143
Vec::new(),
144144
Vec::new(),
145+
None,
145146
);
146147
Client {
147148
credential: self.credential,
@@ -164,13 +165,13 @@ impl Client {
164165
self.credential
165166
.http_authorization_header(&scopes)
166167
.await?
167-
.ok_or_else(|| Error::message(ErrorKind::Credential, "No credential configured"))
168+
.ok_or_else(|| Error::with_message(ErrorKind::Credential, "No credential configured"))
168169
}
169170

170171
/// Send a request through the pipeline.
171-
async fn send(&self, request: &mut Request) -> Result<azure_core::Response> {
172-
let context = azure_core::Context::default();
173-
self.pipeline.send(&context, request).await
172+
async fn send(&self, request: &mut Request) -> Result<azure_core::http::RawResponse> {
173+
let context = azure_core::http::Context::default();
174+
self.pipeline.send(&context, request, None).await
174175
}
175176

176177
/// Send an authenticated GET request and parse the JSON response.
@@ -183,17 +184,17 @@ impl Client {
183184
HeaderValue::from("application/json; api-version=7.1-preview.1"),
184185
);
185186
req.insert_header("x-tfs-fedauthredirect", HeaderValue::from("Suppress"));
186-
req.set_body(azure_core::EMPTY_BODY);
187+
req.set_body(azure_core::Bytes::new());
187188

188189
let resp = self.send(&mut req).await?;
189-
let bytes = resp.into_body().collect().await?;
190-
serde_json::from_slice(&bytes).map_err(|e| {
191-
Error::full(
190+
let body = resp.into_body();
191+
serde_json::from_slice(&body).map_err(|e| {
192+
Error::with_error(
192193
ErrorKind::DataConversion,
193194
e,
194195
format!(
195196
"Failed to deserialize response:\n{}",
196-
String::from_utf8_lossy(&bytes)
197+
String::from_utf8_lossy(&body)
197198
),
198199
)
199200
})
@@ -202,10 +203,10 @@ impl Client {
202203
/// Send an unauthenticated GET request and return the raw bytes.
203204
async fn get_bytes(&self, url: Url) -> Result<Vec<u8>> {
204205
let mut req = Request::new(url, Method::Get);
205-
req.set_body(azure_core::EMPTY_BODY);
206+
req.set_body(azure_core::Bytes::new());
206207
let resp = self.send(&mut req).await?;
207-
let bytes = resp.into_body().collect().await?;
208-
Ok(bytes.to_vec())
208+
let body = resp.into_body();
209+
Ok(body.to_vec())
209210
}
210211

211212
// --- Service discovery ---
@@ -217,7 +218,7 @@ impl Client {
217218
"https://dev.azure.com/{}/_apis/ResourceAreas",
218219
organization
219220
))
220-
.context(ErrorKind::DataConversion, "invalid organization URL")?;
221+
.with_context(ErrorKind::DataConversion, "invalid organization URL")?;
221222

222223
let areas: ResourceAreasResponse = self.get_json(url).await?;
223224
let map: HashMap<String, String> = areas
@@ -240,7 +241,7 @@ impl Client {
240241
/// Find the blob/dedup service URL from discovered services.
241242
pub fn find_blob_url(services: &HashMap<String, String>) -> Result<String> {
242243
services.get("dedup").cloned().ok_or_else(|| {
243-
Error::message(
244+
Error::with_message(
244245
ErrorKind::Other,
245246
"Could not find 'dedup' service in ResourceAreas",
246247
)
@@ -266,7 +267,7 @@ impl Client {
266267
name,
267268
version,
268269
))
269-
.context(ErrorKind::DataConversion, "invalid package metadata URL")?;
270+
.with_context(ErrorKind::DataConversion, "invalid package metadata URL")?;
270271

271272
url.query_pairs_mut().append_pair("intent", "Download");
272273
self.get_json(url).await
@@ -284,7 +285,7 @@ impl Client {
284285
"{}/_apis/dedup/urls",
285286
blob_service_url.trim_end_matches('/')
286287
))
287-
.context(ErrorKind::DataConversion, "invalid dedup URL")?;
288+
.with_context(ErrorKind::DataConversion, "invalid dedup URL")?;
288289

289290
url.query_pairs_mut().append_pair("allowEdge", "true");
290291

@@ -300,13 +301,13 @@ impl Client {
300301
HeaderValue::from("application/json; api-version=1.0"),
301302
);
302303
req.insert_header("x-tfs-fedauthredirect", HeaderValue::from("Suppress"));
303-
let body = azure_core::to_json(blob_ids)?;
304+
let body = azure_core::json::to_json(blob_ids)?;
304305
req.set_body(body);
305306

306307
let resp = self.send(&mut req).await?;
307-
let bytes = resp.into_body().collect().await?;
308-
serde_json::from_slice(&bytes).map_err(|e| {
309-
Error::full(
308+
let body = resp.into_body();
309+
serde_json::from_slice(&body).map_err(|e| {
310+
Error::with_error(
310311
ErrorKind::DataConversion,
311312
e,
312313
"Failed to parse blob URL response",
@@ -317,7 +318,7 @@ impl Client {
317318
/// Download a blob from a SAS URL (no auth required).
318319
pub async fn download_blob(&self, url: &str) -> Result<Vec<u8>> {
319320
let parsed =
320-
Url::parse(url).context(ErrorKind::DataConversion, "invalid blob download URL")?;
321+
Url::parse(url).with_context(ErrorKind::DataConversion, "invalid blob download URL")?;
321322
self.get_bytes(parsed).await
322323
}
323324

@@ -326,7 +327,7 @@ impl Client {
326327
/// Parse the dedup manifest blob (JSON) to extract file entries.
327328
pub fn parse_manifest(data: &[u8]) -> Result<Manifest> {
328329
serde_json::from_slice(data).map_err(|e| {
329-
Error::full(
330+
Error::with_error(
330331
ErrorKind::DataConversion,
331332
e,
332333
"Failed to parse manifest JSON",
@@ -350,7 +351,7 @@ impl Client {
350351
const ENTRY_SIZE: usize = METADATA_SIZE + HASH_SIZE;
351352

352353
if data.len() < HEADER_SIZE + ENTRY_SIZE {
353-
return Err(Error::message(
354+
return Err(Error::with_message(
354355
ErrorKind::DataConversion,
355356
format!(
356357
"Dedup node blob too small: {} bytes (minimum {})",
@@ -362,7 +363,7 @@ impl Client {
362363

363364
let data_portion = data.len() - HEADER_SIZE;
364365
if data_portion % ENTRY_SIZE != 0 {
365-
return Err(Error::message(
366+
return Err(Error::with_message(
366367
ErrorKind::DataConversion,
367368
format!(
368369
"Dedup node blob has unexpected size: {} bytes \
@@ -385,7 +386,7 @@ impl Client {
385386
}
386387

387388
if chunk_ids.is_empty() {
388-
return Err(Error::message(
389+
return Err(Error::with_message(
389390
ErrorKind::DataConversion,
390391
format!(
391392
"No chunk references found in dedup node blob ({} bytes)",
@@ -426,14 +427,14 @@ impl Client {
426427
.resolve_blob_urls(&blob_service_url, &[metadata.manifest_id.clone()])
427428
.await?;
428429
let manifest_url = manifest_urls.get(&metadata.manifest_id).ok_or_else(|| {
429-
Error::message(ErrorKind::Other, "Manifest URL not found in response")
430+
Error::with_message(ErrorKind::Other, "Manifest URL not found in response")
430431
})?;
431432
let manifest_data = self.download_blob(manifest_url).await?;
432433
let manifest = Self::parse_manifest(&manifest_data)?;
433434

434435
// Step 4: Create output directory
435436
std::fs::create_dir_all(output_path).map_err(|e| {
436-
Error::full(
437+
Error::with_error(
437438
ErrorKind::Io,
438439
e,
439440
format!("Failed to create output directory: {:?}", output_path),
@@ -447,7 +448,7 @@ impl Client {
447448
.await?;
448449
let file_root_url = file_root_urls
449450
.get(&item.blob.id)
450-
.ok_or_else(|| Error::message(ErrorKind::Other, "File root URL not found"))?;
451+
.ok_or_else(|| Error::with_message(ErrorKind::Other, "File root URL not found"))?;
451452
let file_root_data = self.download_blob(file_root_url).await?;
452453

453454
let is_node = item.blob.id.ends_with("02");
@@ -461,7 +462,7 @@ impl Client {
461462
let mut file_data = Vec::with_capacity(item.blob.size as usize);
462463
for chunk_id in &chunk_ids {
463464
let chunk_url = chunk_urls.get(chunk_id).ok_or_else(|| {
464-
Error::message(
465+
Error::with_message(
465466
ErrorKind::Other,
466467
format!("Chunk URL not found for {}", chunk_id),
467468
)
@@ -479,22 +480,22 @@ impl Client {
479480
let file_path = output_path.join(relative_path);
480481
if let Some(parent) = file_path.parent() {
481482
std::fs::create_dir_all(parent).map_err(|e| {
482-
Error::full(
483+
Error::with_error(
483484
ErrorKind::Io,
484485
e,
485486
format!("Failed to create directory: {:?}", parent),
486487
)
487488
})?;
488489
}
489490
let mut file = std::fs::File::create(&file_path).map_err(|e| {
490-
Error::full(
491+
Error::with_error(
491492
ErrorKind::Io,
492493
e,
493494
format!("Failed to create file: {:?}", file_path),
494495
)
495496
})?;
496497
file.write_all(&file_data)
497-
.map_err(|e| Error::full(ErrorKind::Io, e, "Failed to write file data"))?;
498+
.map_err(|e| Error::with_error(ErrorKind::Io, e, "Failed to write file data"))?;
498499
}
499500

500501
Ok(metadata)

0 commit comments

Comments
 (0)