app_store_connect/
notary_api.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7//! App Store Connect Notary API.
8//!
9//! See also <https://developer.apple.com/documentation/notaryapi>.
10
11use {
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/// A notification that the notary service sends you when notarization finishes.
22#[derive(Clone, Debug, Serialize)]
23#[serde(rename_all = "camelCase")]
24pub struct NewSubmissionRequestNotification {
25    pub channel: String,
26    pub target: String,
27}
28
29/// Data that you provide when starting a submission to the notary service.
30#[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/// Information that you use to upload your software for notarization.
39#[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/// Information that the notary service provides for uploading your software for notarization and
50/// tracking the submission.
51#[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/// The notary service’s response to a software submission.
60#[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/// Information about the status of a submission.
93#[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/// Information that the service provides about the status of a notarization submission.
102#[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/// The notary service’s response to a request for the status of a submission.
111#[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    /// Convert the instance into a [Result].
120    ///
121    /// Will yield [Err] if the notarization/upload was not successful.
122    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/// The notary service’s response to a request for the list of submissions.
131#[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/// Information about the log associated with the submission.
143#[derive(Clone, Debug, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct SubmissionLogResponseDataAttributes {
146    pub developer_log_url: String,
147}
148
149/// Data that indicates how to get the log information for a particular submission.
150#[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/// The notary service’s response to a request for the log information about a completed submission.
159#[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    /// Create a submission to the Notary API.
168    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    /// Fetch the status of a Notary API submission.
192    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    /// Fetch details about a single completed notarization.
218    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}