1mod api_key;
8mod api_token;
9pub mod bundle_api;
10pub mod certs_api;
11pub mod cli;
12pub mod device_api;
13pub mod notary_api;
14pub mod profile_api;
15
16use {
17 reqwest::blocking::{Client, ClientBuilder, RequestBuilder, Response},
18 serde_json::Value,
19 std::{path::Path, sync::Mutex},
20 thiserror::Error,
21};
22
23pub use crate::api_key::{InvalidPemPrivateKey, UnifiedApiKey};
24pub use crate::api_token::{AppStoreConnectToken, ConnectTokenEncoder, MissingApiKey};
25
26pub type Result<T> = anyhow::Result<T>;
27
28pub struct AppStoreConnectClient {
32 client: Client,
33 connect_token: ConnectTokenEncoder,
34 token: Mutex<Option<AppStoreConnectToken>>,
35}
36
37impl AppStoreConnectClient {
38 pub fn from_json_path(path: &Path) -> Result<Self> {
39 let key = UnifiedApiKey::from_json_path(path)?;
40 AppStoreConnectClient::new(key.try_into()?)
41 }
42
43 pub fn new(connect_token: ConnectTokenEncoder) -> Result<Self> {
45 let client = ClientBuilder::default()
46 .user_agent("asconnect crate (https://crates.io/crates/asconnect)")
47 .build()?;
48 Ok(Self {
49 client,
50 connect_token,
51 token: Mutex::new(None),
52 })
53 }
54
55 pub fn get_token(&self) -> Result<String> {
56 let mut token = self.token.lock().unwrap();
57
58 if token.is_none() {
60 token.replace(self.connect_token.new_token(300)?);
61 }
62
63 Ok(token.as_ref().unwrap().clone())
64 }
65
66 pub fn send_request(&self, request: RequestBuilder) -> Result<Response> {
67 let request = request.build()?;
68 let method = request.method().to_string();
69 let url = request.url().to_string();
70
71 log::debug!("{} {}", request.method(), url);
72
73 let response = self.client.execute(request)?;
74
75 if response.status().is_success() {
76 Ok(response)
77 } else {
78 let body = response.bytes()?;
79
80 let message = if let Ok(value) = serde_json::from_slice::<Value>(body.as_ref()) {
81 serde_json::to_string_pretty(&value)?
82 } else {
83 String::from_utf8_lossy(body.as_ref()).into()
84 };
85
86 Err(AppStoreConnectError {
87 method,
88 url,
89 message,
90 }
91 .into())
92 }
93 }
94}
95
96#[derive(Clone, Debug, Error)]
97#[error("appstore connect error:\n{method} {url}\n{message}")]
98pub struct AppStoreConnectError {
99 method: String,
100 url: String,
101 message: String,
102}