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}