forked from nulang-org/nulang
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.rs
More file actions
102 lines (93 loc) · 3.7 KB
/
Copy pathclient.rs
File metadata and controls
102 lines (93 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Synchronous HTTP client for the package registry, built on `ureq`.
//!
//! Talks to `crate::registry::server::RegistryServer` over the registry API:
//! `PUT /api/v1/packages/<name>/<version>` to publish, `GET ...` to fetch or
//! list versions. Non-2xx responses surface as `Err(String)`.
use std::io::Read;
/// Client for a package registry server.
pub struct RegistryClient {
registry_url: String,
token: Option<String>,
}
impl RegistryClient {
pub fn new(registry_url: String, token: Option<String>) -> Self {
RegistryClient {
registry_url,
token,
}
}
fn base_url(&self) -> String {
self.registry_url.trim_end_matches('/').to_string()
}
/// Publish a tarball for `name@version`. Returns `Ok(())` on 201 Created;
/// a 409 Conflict (version already exists) or other failure is `Err`.
pub fn publish(&self, name: &str, version: &str, tarball: &[u8]) -> Result<(), String> {
let url = format!("{}/api/v1/packages/{}/{}", self.base_url(), name, version);
let request = match &self.token {
Some(token) => ureq::put(&url).set("Authorization", &format!("Bearer {}", token)),
None => ureq::put(&url),
};
match request.send_bytes(tarball) {
Ok(_) => Ok(()),
Err(err) => Err(Self::describe_error(err)),
}
}
/// Fetch the stored tarball for `name@version`.
pub fn fetch(&self, name: &str, version: &str) -> Result<Vec<u8>, String> {
let url = format!("{}/api/v1/packages/{}/{}", self.base_url(), name, version);
match ureq::get(&url).call() {
Ok(response) => {
let mut bytes = Vec::new();
response
.into_reader()
.read_to_end(&mut bytes)
.map_err(|e| e.to_string())?;
Ok(bytes)
}
Err(err) => Err(Self::describe_error(err)),
}
}
/// List published versions of `name`.
pub fn list_versions(&self, name: &str) -> Result<Vec<String>, String> {
let url = format!("{}/api/v1/packages/{}", self.base_url(), name);
match ureq::get(&url).call() {
Ok(response) => {
let text = response.into_string().map_err(|e| e.to_string())?;
Self::parse_versions(&text)
}
Err(err) => Err(Self::describe_error(err)),
}
}
/// Parse the version list JSON: either the server's
/// `{"name": ..., "versions": [...]}` object or a bare JSON array.
fn parse_versions(text: &str) -> Result<Vec<String>, String> {
let value: serde_json::Value = serde_json::from_str(text)
.map_err(|e| format!("invalid JSON response from registry: {}", e))?;
let versions: Vec<String> = match &value {
serde_json::Value::Array(items) => items
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect(),
serde_json::Value::Object(map) => map
.get("versions")
.and_then(|v| v.as_array())
.map(|items| {
items
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default(),
_ => return Err("unexpected JSON response from registry".to_string()),
};
Ok(versions)
}
fn describe_error(err: ureq::Error) -> String {
match err {
ureq::Error::Status(code, response) => {
format!("HTTP {} {}", code, response.status_text())
}
other => other.to_string(),
}
}
}