1use {
12 crate::{AppStoreConnectClient, Result},
13 serde::{Deserialize, Serialize},
14 serde_json::Value,
15 thiserror::Error,
16};
17
18pub const APPLE_NOTARY_SUBMIT_SOFTWARE_URL: &str =
19 "https://appstoreconnect.apple.com/notary/v2/submissions";
20
21#[derive(Clone, Debug, Serialize)]
23#[serde(rename_all = "camelCase")]
24pub struct NewSubmissionRequestNotification {
25 pub channel: String,
26 pub target: String,
27}
28
29#[derive(Clone, Debug, Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct NewSubmissionRequest {
33 pub notifications: Vec<NewSubmissionRequestNotification>,
34 pub sha256: String,
35 pub submission_name: String,
36}
37
38#[derive(Clone, Debug, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct NewSubmissionResponseDataAttributes {
42 pub aws_access_key_id: String,
43 pub aws_secret_access_key: String,
44 pub aws_session_token: String,
45 pub bucket: String,
46 pub object: String,
47}
48
49#[derive(Clone, Debug, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct NewSubmissionResponseData {
54 pub attributes: NewSubmissionResponseDataAttributes,
55 pub id: String,
56 pub r#type: String,
57}
58
59#[derive(Clone, Debug, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct NewSubmissionResponse {
63 pub data: NewSubmissionResponseData,
64 pub meta: Value,
65}
66
67#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
68#[serde(rename_all = "PascalCase")]
69pub enum SubmissionResponseStatus {
70 Accepted,
71 #[serde(rename = "In Progress")]
72 InProgress,
73 Invalid,
74 Rejected,
75 #[serde(other)]
76 Unknown,
77}
78
79impl std::fmt::Display for SubmissionResponseStatus {
80 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
81 let s = match self {
82 Self::Accepted => "accepted",
83 Self::InProgress => "in progress",
84 Self::Invalid => "invalid",
85 Self::Rejected => "rejected",
86 Self::Unknown => "unknown",
87 };
88 f.write_str(s)
89 }
90}
91
92#[derive(Clone, Debug, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct SubmissionResponseDataAttributes {
96 pub created_date: String,
97 pub name: String,
98 pub status: SubmissionResponseStatus,
99}
100
101#[derive(Clone, Debug, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct SubmissionResponseData {
105 pub attributes: SubmissionResponseDataAttributes,
106 pub id: String,
107 pub r#type: String,
108}
109
110#[derive(Clone, Debug, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct SubmissionResponse {
114 pub data: SubmissionResponseData,
115 pub meta: Value,
116}
117
118impl SubmissionResponse {
119 pub fn into_result(self) -> Result<Self> {
123 match self.data.attributes.status {
124 SubmissionResponseStatus::Accepted => Ok(self),
125 status => Err(NotarizationError(status).into()),
126 }
127 }
128}
129
130#[derive(Clone, Debug, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct ListSubmissionResponse {
134 pub data: Vec<SubmissionResponseData>,
135 pub meta: Value,
136}
137
138#[derive(Clone, Copy, Debug, Error)]
139#[error("notarization {0}")]
140pub struct NotarizationError(SubmissionResponseStatus);
141
142#[derive(Clone, Debug, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct SubmissionLogResponseDataAttributes {
146 pub developer_log_url: String,
147}
148
149#[derive(Clone, Debug, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct SubmissionLogResponseData {
153 pub attributes: SubmissionLogResponseDataAttributes,
154 pub id: String,
155 pub r#type: String,
156}
157
158#[derive(Clone, Debug, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct SubmissionLogResponse {
162 pub data: SubmissionLogResponseData,
163 pub meta: Value,
164}
165
166impl AppStoreConnectClient {
167 pub fn create_submission(
169 &self,
170 sha256: &str,
171 submission_name: &str,
172 ) -> Result<NewSubmissionResponse> {
173 let token = self.get_token()?;
174
175 let body = NewSubmissionRequest {
176 notifications: Vec::new(),
177 sha256: sha256.to_string(),
178 submission_name: submission_name.to_string(),
179 };
180 let req = self
181 .client
182 .post(APPLE_NOTARY_SUBMIT_SOFTWARE_URL)
183 .bearer_auth(token)
184 .header("Accept", "application/json")
185 .header("Content-Type", "application/json")
186 .json(&body);
187
188 Ok(self.send_request(req)?.json()?)
189 }
190
191 pub fn get_submission(&self, submission_id: &str) -> Result<SubmissionResponse> {
193 let token = self.get_token()?;
194
195 let req = self
196 .client
197 .get(format!(
198 "{APPLE_NOTARY_SUBMIT_SOFTWARE_URL}/{submission_id}"
199 ))
200 .bearer_auth(token)
201 .header("Accept", "application/json");
202
203 Ok(self.send_request(req)?.json()?)
204 }
205
206 pub fn list_submissions(&self) -> Result<ListSubmissionResponse> {
207 let token = self.get_token()?;
208 let req = self
209 .client
210 .get(APPLE_NOTARY_SUBMIT_SOFTWARE_URL)
211 .bearer_auth(token)
212 .header("Accept", "application/json");
213
214 Ok(self.send_request(req)?.json()?)
215 }
216
217 pub fn get_submission_log(&self, submission_id: &str) -> Result<Value> {
219 let token = self.get_token()?;
220
221 let req = self
222 .client
223 .get(format!(
224 "{APPLE_NOTARY_SUBMIT_SOFTWARE_URL}/{submission_id}/logs"
225 ))
226 .bearer_auth(token)
227 .header("Accept", "application/json");
228
229 let res: SubmissionLogResponse = self.send_request(req)?.json()?;
230
231 let url = res.data.attributes.developer_log_url;
232 let logs = self.client.get(url).send()?.json::<Value>()?;
233
234 Ok(logs)
235 }
236}