1use crate::io::api::{require_non_empty_secret, ApiResult, Configuration, Endpoint, Fallback, Param, Params, RemoteResource};
6use crate::param;
7use crate::prelude::var;
8use crate::schema::pid::raid::Metadata;
9use crate::schema::validate::is_ror;
10use crate::util::constants::env::RAID_TOKEN_VARIABLE_NAMES;
11use crate::util::Label;
12use bon::Builder;
13use color_eyre::eyre::eyre;
14use dotenvy;
15use serde::{Deserialize, Serialize};
16use tracing::debug;
17use validator::Validate;
18
19#[derive(Builder, Clone, Debug, Serialize, Deserialize)]
38#[builder(start_fn = with_token, on(String, into))]
39pub struct ErrorResponse {
40 #[serde(rename = "type")]
42 pub error_type: String,
43 pub title: String,
45 pub status: u16,
47 pub detail: String,
49 pub instance: String,
51 pub failures: Option<Vec<ErrorResponseFailure>>,
53}
54#[derive(Builder, Clone, Debug, Serialize, Deserialize)]
64#[builder(start_fn = with_token, on(String, into))]
65pub struct ErrorResponseFailure {
66 #[serde(rename = "fieldId")]
68 pub field_id: String,
69 #[serde(rename = "errorType")]
71 pub error_type: String,
72 pub message: String,
74}
75#[derive(Builder, Clone, Debug)]
79#[builder(start_fn = with_token, on(String, into))]
80pub struct Options {
81 #[builder(start_fn)]
83 pub token: String,
84 pub body: Option<String>,
86 #[builder(default = String::from("api.raid.org"))]
88 pub domain: String,
89 pub identifier: Option<String>,
91 pub metadata: Option<Metadata>,
93 pub prefix: Option<String>,
95 pub suffix: Option<String>,
97 #[builder(default = vec![])]
99 pub custom_params: Vec<Param>,
100}
101#[derive(Builder, Debug, Deserialize, Serialize, Validate)]
105#[builder(start_fn = with_token, on(String, into))]
106pub struct ServicePoint {
107 pub identifier: String,
109 #[validate(url)]
111 pub url: String,
112 pub token: Option<String>,
114}
115#[derive(Debug, Deserialize, Serialize, Validate)]
135#[serde(deny_unknown_fields, rename_all = "camelCase")]
136pub struct ServicePointResponse {
137 #[serde(alias = "id")]
139 pub identifier: u64,
140 pub name: String,
142 #[validate(custom(function = "is_ror"))]
144 #[serde(alias = "identifierOwner")]
145 pub owner: String,
146 #[serde(alias = "repositoryId")]
148 pub repository: Option<String>,
149 pub prefix: String,
151 #[serde(alias = "groupId")]
153 pub group: String,
154 #[serde(alias = "techEmail")]
156 pub tech_email: String,
157 #[serde(alias = "adminEmail")]
159 pub admin_email: String,
160 pub enabled: bool,
162 #[serde(alias = "appWritesEnabled")]
164 pub app_writes_enabled: bool,
165}
166impl Configuration for Options {
167 fn from_env() -> Self {
171 if let Err(why) = dotenvy::from_filename(".env") {
172 debug!("=> {} Load .env — {why}", Label::skip());
173 }
174 Self {
175 token: var("RAID_API_TOKEN").unwrap_or_default(),
176 body: None,
177 domain: var("RAID_SERVER_HOST").unwrap_or_else(|_| String::from("api.raid.org")),
178 prefix: None,
179 suffix: None,
180 identifier: None,
181 metadata: None,
182 custom_params: vec![],
183 }
184 }
185 fn with_body(self, value: impl Into<String>) -> Self {
187 Self {
188 body: Some(value.into()),
189 ..self
190 }
191 }
192 fn with_domain(self, value: impl Into<String>) -> Self {
194 Self {
195 domain: value.into(),
196 ..self
197 }
198 }
199 fn with_identifier(self, value: impl Into<String>) -> Self {
201 Self {
202 identifier: Some(value.into()),
203 ..self
204 }
205 }
206 fn token(&self) -> &str {
208 &self.token
209 }
210 fn domain(&self) -> &str {
212 &self.domain
213 }
214 fn identifier(&self) -> Option<&str> {
216 self.identifier.as_deref()
217 }
218 fn with_params(self, params: Vec<Param>) -> Self {
220 Self {
221 custom_params: params,
222 ..self
223 }
224 }
225 fn params(&self) -> &[Param] {
227 &self.custom_params
228 }
229}
230impl Options {
231 pub fn with_metadata(self, value: impl Into<Metadata>) -> Self {
233 Self {
234 metadata: Some(value.into()),
235 ..self
236 }
237 }
238}
239pub async fn create_record(options: &Options) -> ApiResult<Metadata> {
241 let template = "raid::api";
242 let action = "record::create";
243 let path = format!("{template}::{action}");
244 match require_non_empty_secret(&options.token, &path, &RAID_TOKEN_VARIABLE_NAMES) {
245 | Ok(token) => match Endpoint::from_template(template).map(|e| e.with_domain(&options.domain)) {
246 | Ok(endpoint) => match &options.metadata {
247 | Some(value) => match serde_json::to_string(value) {
248 | Ok(body) => {
249 let params = Params::new()
250 .with_auth(&token, None)
251 .with(param!(Body, &body))
252 .with_custom(options.params())
253 .build();
254 let response = endpoint.invoke(action, Some(params)).await;
255 endpoint.handle_or::<Metadata, Fallback<ErrorResponse>>(response)
256 }
257 | Err(why) => Err(eyre!("Failed to serialize RAiD metadata to JSON — {why:#?}")),
258 },
259 | None => Err(eyre!("RAiD metadata is required for creating a record")),
260 },
261 | Err(why) => Err(why),
262 },
263 | Err(why) => Err(why),
264 }
265}
266pub async fn record(options: &Options) -> ApiResult<Vec<Metadata>> {
278 let template = "raid::api";
279 let action = "record";
280 let path = format!("{template}::{action}");
281 match require_non_empty_secret(&options.token, &path, &RAID_TOKEN_VARIABLE_NAMES) {
282 | Ok(token) => match Endpoint::from_template(template).map(|e| e.with_domain(&options.domain)) {
283 | Ok(endpoint) => match &options.identifier {
284 | Some(value) if !value.is_empty() => {
285 let params = Params::new()
286 .with_auth(&token, None)
287 .with_template("identifier", Some(value))
288 .with_custom(options.params())
289 .build();
290 let response = endpoint.invoke(action, Some(params)).await;
291 endpoint.handle::<Metadata>(response).map(|r| vec![r])
292 }
293 | Some(_) | None => {
294 let params = Params::new().with_auth(&token, None).with_custom(options.params()).build();
295 let response = endpoint.invoke(action, Some(params)).await;
296 endpoint.handle::<Vec<Metadata>>(response)
297 }
298 },
299 | Err(why) => Err(why),
300 },
301 | Err(why) => Err(why),
302 }
303}
304pub async fn service_point(options: &Options) -> ApiResult<Vec<ServicePointResponse>> {
316 let template = "raid::api";
317 let action = "service-point";
318 let path = format!("{template}::{action}");
319 match require_non_empty_secret(&options.token, &path, &RAID_TOKEN_VARIABLE_NAMES) {
320 | Ok(token) => match Endpoint::from_template(template).map(|e| e.with_domain(&options.domain)) {
321 | Ok(endpoint) => match &options.identifier {
322 | Some(value) if !value.is_empty() => {
323 let params = Params::new()
324 .with_auth(&token, None)
325 .with_template("identifier", Some(value))
326 .with_custom(options.params())
327 .build();
328 let response = endpoint.invoke(action, Some(params)).await;
329 endpoint.handle::<ServicePointResponse>(response).map(|r| vec![r])
330 }
331 | Some(_) | None => {
332 let params = Params::new().with_auth(&token, None).with_custom(options.params()).build();
333 let response = endpoint.invoke(action, Some(params)).await;
334 endpoint.handle::<Vec<ServicePointResponse>>(response)
335 }
336 },
337 | Err(why) => Err(why),
338 },
339 | Err(why) => Err(why),
340 }
341}