use crate::io::api::{require_non_empty_secret, ApiResult, Configuration, Endpoint, Fallback, Param, Params, RemoteResource};
use crate::param;
use crate::prelude::var;
use crate::schema::pid::raid::Metadata;
use crate::schema::validate::is_ror;
use crate::util::constants::env::RAID_TOKEN_VARIABLE_NAMES;
use crate::util::Label;
use bon::Builder;
use color_eyre::eyre::eyre;
use dotenvy;
use serde::{Deserialize, Serialize};
use tracing::debug;
use validator::Validate;
#[derive(Builder, Clone, Debug, Serialize, Deserialize)]
#[builder(start_fn = with_token, on(String, into))]
pub struct ErrorResponse {
#[serde(rename = "type")]
pub error_type: String,
pub title: String,
pub status: u16,
pub detail: String,
pub instance: String,
pub failures: Option<Vec<ErrorResponseFailure>>,
}
#[derive(Builder, Clone, Debug, Serialize, Deserialize)]
#[builder(start_fn = with_token, on(String, into))]
pub struct ErrorResponseFailure {
#[serde(rename = "fieldId")]
pub field_id: String,
#[serde(rename = "errorType")]
pub error_type: String,
pub message: String,
}
#[derive(Builder, Clone, Debug)]
#[builder(start_fn = with_token, on(String, into))]
pub struct Options {
#[builder(start_fn)]
pub token: String,
pub body: Option<String>,
#[builder(default = String::from("api.raid.org"))]
pub domain: String,
pub identifier: Option<String>,
pub metadata: Option<Metadata>,
pub prefix: Option<String>,
pub suffix: Option<String>,
#[builder(default = vec![])]
pub custom_params: Vec<Param>,
}
#[derive(Builder, Debug, Deserialize, Serialize, Validate)]
#[builder(start_fn = with_token, on(String, into))]
pub struct ServicePoint {
pub identifier: String,
#[validate(url)]
pub url: String,
pub token: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ServicePointResponse {
#[serde(alias = "id")]
pub identifier: u64,
pub name: String,
#[validate(custom(function = "is_ror"))]
#[serde(alias = "identifierOwner")]
pub owner: String,
#[serde(alias = "repositoryId")]
pub repository: Option<String>,
pub prefix: String,
#[serde(alias = "groupId")]
pub group: String,
#[serde(alias = "techEmail")]
pub tech_email: String,
#[serde(alias = "adminEmail")]
pub admin_email: String,
pub enabled: bool,
#[serde(alias = "appWritesEnabled")]
pub app_writes_enabled: bool,
}
impl Configuration for Options {
fn from_env() -> Self {
if let Err(why) = dotenvy::from_filename(".env") {
debug!("=> {} Load .env — {why}", Label::skip());
}
Self {
token: var("RAID_API_TOKEN").unwrap_or_default(),
body: None,
domain: var("RAID_SERVER_HOST").unwrap_or_else(|_| String::from("api.raid.org")),
prefix: None,
suffix: None,
identifier: None,
metadata: None,
custom_params: vec![],
}
}
fn with_body(self, value: impl Into<String>) -> Self {
Self {
body: Some(value.into()),
..self
}
}
fn with_domain(self, value: impl Into<String>) -> Self {
Self {
domain: value.into(),
..self
}
}
fn with_identifier(self, value: impl Into<String>) -> Self {
Self {
identifier: Some(value.into()),
..self
}
}
fn token(&self) -> &str {
&self.token
}
fn domain(&self) -> &str {
&self.domain
}
fn identifier(&self) -> Option<&str> {
self.identifier.as_deref()
}
fn with_params(self, params: Vec<Param>) -> Self {
Self {
custom_params: params,
..self
}
}
fn params(&self) -> &[Param] {
&self.custom_params
}
}
impl Options {
pub fn with_metadata(self, value: impl Into<Metadata>) -> Self {
Self {
metadata: Some(value.into()),
..self
}
}
}
pub async fn create_record(options: &Options) -> ApiResult<Metadata> {
let template = "raid::api";
let action = "record::create";
let path = format!("{template}::{action}");
match require_non_empty_secret(&options.token, &path, &RAID_TOKEN_VARIABLE_NAMES) {
| Ok(token) => match Endpoint::from_template(template).map(|e| e.with_domain(&options.domain)) {
| Ok(endpoint) => match &options.metadata {
| Some(value) => match serde_json::to_string(value) {
| Ok(body) => {
let params = Params::new()
.with_auth(&token, None)
.with(param!(Body, &body))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<Metadata, Fallback<ErrorResponse>>(response)
}
| Err(why) => Err(eyre!("Failed to serialize RAiD metadata to JSON — {why:#?}")),
},
| None => Err(eyre!("RAiD metadata is required for creating a record")),
},
| Err(why) => Err(why),
},
| Err(why) => Err(why),
}
}
pub async fn record(options: &Options) -> ApiResult<Vec<Metadata>> {
let template = "raid::api";
let action = "record";
let path = format!("{template}::{action}");
match require_non_empty_secret(&options.token, &path, &RAID_TOKEN_VARIABLE_NAMES) {
| Ok(token) => match Endpoint::from_template(template).map(|e| e.with_domain(&options.domain)) {
| Ok(endpoint) => match &options.identifier {
| Some(value) if !value.is_empty() => {
let params = Params::new()
.with_auth(&token, None)
.with_template("identifier", Some(value))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle::<Metadata>(response).map(|r| vec![r])
}
| Some(_) | None => {
let params = Params::new().with_auth(&token, None).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle::<Vec<Metadata>>(response)
}
},
| Err(why) => Err(why),
},
| Err(why) => Err(why),
}
}
pub async fn service_point(options: &Options) -> ApiResult<Vec<ServicePointResponse>> {
let template = "raid::api";
let action = "service-point";
let path = format!("{template}::{action}");
match require_non_empty_secret(&options.token, &path, &RAID_TOKEN_VARIABLE_NAMES) {
| Ok(token) => match Endpoint::from_template(template).map(|e| e.with_domain(&options.domain)) {
| Ok(endpoint) => match &options.identifier {
| Some(value) if !value.is_empty() => {
let params = Params::new()
.with_auth(&token, None)
.with_template("identifier", Some(value))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle::<ServicePointResponse>(response).map(|r| vec![r])
}
| Some(_) | None => {
let params = Params::new().with_auth(&token, None).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle::<Vec<ServicePointResponse>>(response)
}
},
| Err(why) => Err(why),
},
| Err(why) => Err(why),
}
}