Skip to main content

sdk_rust/
client.rs

1use std::collections::BTreeMap;
2
3use serde::{Serialize, de::DeserializeOwned};
4
5use crate::{
6    builder::ClientBuilder,
7    error::SdkError,
8    models::{
9        CallerIdentityResponse, SdkArtifactRegisterRequest, SdkArtifactRegisterResponse,
10        SdkBootstrapResponse, SdkCapabilitiesResponse, SdkEvidenceIngestRequest,
11        SdkEvidenceIngestResponse, SdkKeyAccessPlanRequest, SdkKeyAccessPlanResponse,
12        SdkPolicyResolveRequest, SdkPolicyResolveResponse, SdkProtectionPlanRequest,
13        SdkProtectionPlanResponse,
14    },
15    providers::ManagedSymmetricKeyProviderRegistry,
16};
17
18pub(crate) enum ClientAuthStrategy {
19    StaticBearer(String),
20}
21
22pub struct Client {
23    pub(crate) base_url: String,
24    pub(crate) agent: ureq::Agent,
25    pub(crate) default_headers: BTreeMap<String, String>,
26    pub(crate) auth_strategy: Option<ClientAuthStrategy>,
27    pub(crate) managed_symmetric_key_provider_registry: ManagedSymmetricKeyProviderRegistry,
28}
29
30impl Client {
31    pub(crate) fn new(
32        base_url: String,
33        agent: ureq::Agent,
34        default_headers: BTreeMap<String, String>,
35        auth_strategy: Option<ClientAuthStrategy>,
36        managed_symmetric_key_provider_registry: ManagedSymmetricKeyProviderRegistry,
37    ) -> Self {
38        Self {
39            base_url,
40            agent,
41            default_headers,
42            auth_strategy,
43            managed_symmetric_key_provider_registry,
44        }
45    }
46
47    pub fn builder(base_url: impl Into<String>) -> ClientBuilder {
48        ClientBuilder::new(base_url)
49    }
50
51    pub fn base_url(&self) -> &str {
52        &self.base_url
53    }
54
55    pub fn capabilities(&self) -> Result<SdkCapabilitiesResponse, SdkError> {
56        self.get_json("/v1/sdk/capabilities")
57    }
58
59    pub fn whoami(&self) -> Result<CallerIdentityResponse, SdkError> {
60        self.get_json("/v1/sdk/whoami")
61    }
62
63    pub fn bootstrap(&self) -> Result<SdkBootstrapResponse, SdkError> {
64        self.get_json("/v1/sdk/bootstrap")
65    }
66
67    pub fn protection_plan(
68        &self,
69        request: &SdkProtectionPlanRequest,
70    ) -> Result<SdkProtectionPlanResponse, SdkError> {
71        self.post_json("/v1/sdk/protection-plan", request)
72    }
73
74    pub fn policy_resolve(
75        &self,
76        request: &SdkPolicyResolveRequest,
77    ) -> Result<SdkPolicyResolveResponse, SdkError> {
78        self.post_json("/v1/sdk/policy-resolve", request)
79    }
80
81    pub fn key_access_plan(
82        &self,
83        request: &SdkKeyAccessPlanRequest,
84    ) -> Result<SdkKeyAccessPlanResponse, SdkError> {
85        self.post_json("/v1/sdk/key-access-plan", request)
86    }
87
88    pub fn artifact_register(
89        &self,
90        request: &SdkArtifactRegisterRequest,
91    ) -> Result<SdkArtifactRegisterResponse, SdkError> {
92        self.post_json("/v1/sdk/artifact-register", request)
93    }
94
95    pub fn evidence(
96        &self,
97        request: &SdkEvidenceIngestRequest,
98    ) -> Result<SdkEvidenceIngestResponse, SdkError> {
99        self.post_json("/v1/sdk/evidence", request)
100    }
101
102    fn get_json<T>(&self, path: &str) -> Result<T, SdkError>
103    where
104        T: DeserializeOwned,
105    {
106        let response = self
107            .apply_headers(self.agent.get(&self.endpoint(path)))?
108            .call()
109            .map_err(map_ureq_error)?;
110        decode_response(response)
111    }
112
113    fn post_json<TReq, TRes>(&self, path: &str, payload: &TReq) -> Result<TRes, SdkError>
114    where
115        TReq: Serialize,
116        TRes: DeserializeOwned,
117    {
118        let payload_json = serde_json::to_string(payload).map_err(|error| {
119            SdkError::Serialization(format!("failed to serialize request payload: {error}"))
120        })?;
121        let response = self
122            .apply_headers(
123                self.agent
124                    .post(&self.endpoint(path))
125                    .set("Content-Type", "application/json"),
126            )?
127            .send_string(&payload_json)
128            .map_err(map_ureq_error)?;
129        decode_response(response)
130    }
131
132    fn endpoint(&self, path: &str) -> String {
133        format!("{}{}", self.base_url, path)
134    }
135
136    fn apply_headers(&self, mut request: ureq::Request) -> Result<ureq::Request, SdkError> {
137        for (name, value) in &self.default_headers {
138            request = request.set(name, value);
139        }
140
141        if let Some(authorization_header) = self.resolve_authorization_header()? {
142            request = request.set("Authorization", &authorization_header);
143        }
144
145        Ok(request)
146    }
147
148    fn resolve_authorization_header(&self) -> Result<Option<String>, SdkError> {
149        match self.auth_strategy.as_ref() {
150            Some(ClientAuthStrategy::StaticBearer(header)) => Ok(Some(header.clone())),
151            None => Ok(None),
152        }
153    }
154}
155
156fn decode_response<T>(response: ureq::Response) -> Result<T, SdkError>
157where
158    T: DeserializeOwned,
159{
160    let body = response.into_string().map_err(|error| {
161        SdkError::Connection(format!("failed to read HTTP response body: {error}"))
162    })?;
163    serde_json::from_str(&body).map_err(|error| {
164        SdkError::Serialization(format!("failed to decode JSON response body: {error}"))
165    })
166}
167
168fn map_ureq_error(error: ureq::Error) -> SdkError {
169    match error {
170        ureq::Error::Status(status, response) => {
171            let body = response.into_string().unwrap_or_default();
172            SdkError::Server(format!("HTTP {status}: {body}"))
173        }
174        ureq::Error::Transport(transport) => SdkError::Connection(transport.to_string()),
175    }
176}