use crate::client::{Pagination, PortClient};
use crate::error::PortError;
use crate::types::filters::FilterBuilder;
use crate::types::IdentifiedResource;
use serde::de::DeserializeOwned;
use serde::Serialize;
#[derive(Serialize)]
struct EmptyQuery;
#[inline]
fn build_path<I, S>(segments: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut path = String::new();
for segment in segments {
let trimmed = segment.as_ref().trim_matches('/');
if trimmed.is_empty() {
continue;
}
if !path.is_empty() {
path.push('/');
}
path.push_str(trimmed);
}
path
}
#[inline]
async fn list_helper<R, Q>(
client: &PortClient,
path: &str,
query: Option<&Q>,
pagination: Option<&Pagination>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
Q: Serialize + ?Sized,
{
match (query, pagination) {
(Some(q), Some(p)) => client.get_paginated(path, q, p).await,
(Some(q), None) => client.get_with_query(path, q).await,
(None, Some(p)) => client.get_paginated(path, &EmptyQuery, p).await,
(None, None) => client.get(path).await,
}
}
pub mod blueprints {
use super::*;
use crate::types::blueprints::BlueprintDefinition;
const RESOURCE: &str = "blueprints";
pub async fn list<R>(
client: &PortClient,
pagination: Option<&Pagination>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
{
let path = build_path([RESOURCE]);
list_helper(client, &path, None::<&EmptyQuery>, pagination).await
}
pub async fn list_with_query<R, Q>(
client: &PortClient,
query: &Q,
pagination: Option<&Pagination>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
Q: Serialize + ?Sized,
{
let path = build_path([RESOURCE]);
list_helper(client, &path, Some(query), pagination).await
}
pub async fn get<R>(client: &PortClient, identifier: impl AsRef<str>) -> Result<R, PortError>
where
R: DeserializeOwned,
{
let path = build_path([RESOURCE, identifier.as_ref()]);
client.get(&path).await
}
pub async fn create<B, R>(client: &PortClient, payload: &B) -> Result<R, PortError>
where
B: Serialize + ?Sized,
R: DeserializeOwned,
{
let path = build_path([RESOURCE]);
client.post(&path, payload).await
}
pub async fn update<B, R>(
client: &PortClient,
identifier: impl AsRef<str>,
payload: &B,
) -> Result<R, PortError>
where
B: Serialize + ?Sized,
R: DeserializeOwned,
{
let path = build_path([RESOURCE, identifier.as_ref()]);
client.put(&path, payload).await
}
pub async fn patch<B, R>(
client: &PortClient,
identifier: impl AsRef<str>,
payload: &B,
) -> Result<R, PortError>
where
B: Serialize + ?Sized,
R: DeserializeOwned,
{
let path = build_path([RESOURCE, identifier.as_ref()]);
client.patch(&path, payload).await
}
pub async fn delete<R>(client: &PortClient, identifier: impl AsRef<str>) -> Result<R, PortError>
where
R: DeserializeOwned,
{
let path = build_path([RESOURCE, identifier.as_ref()]);
client.delete(&path).await
}
pub async fn create_and_publish<R>(
client: &PortClient,
payload: &BlueprintDefinition,
) -> Result<R, PortError>
where
R: DeserializeOwned,
{
let created = create::<_, BlueprintDefinition>(client, payload).await?;
patch::<_, R>(client, &created.identifier, payload).await
}
}
pub mod entities {
use super::*;
use crate::types::entities::{EntityRequest, EntityResponse};
const RESOURCE: &str = "entities";
fn entity_collection_path(blueprint_id: &str) -> String {
build_path(["blueprints", blueprint_id, RESOURCE])
}
fn entity_item_path(blueprint_id: &str, entity_id: &str) -> String {
build_path(["blueprints", blueprint_id, RESOURCE, entity_id])
}
pub async fn list<R, Q>(
client: &PortClient,
blueprint_id: impl AsRef<str>,
query: Option<&Q>,
pagination: Option<&Pagination>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
Q: Serialize + ?Sized,
{
let blueprint_id = blueprint_id.as_ref();
let path = entity_collection_path(blueprint_id);
list_helper(client, &path, query, pagination).await
}
pub async fn list_all<R>(
client: &PortClient,
blueprint_id: impl AsRef<str>,
pagination: Option<&Pagination>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
{
list(client, blueprint_id, None::<&EmptyQuery>, pagination).await
}
pub async fn get<R>(
client: &PortClient,
blueprint_id: impl AsRef<str>,
entity_id: impl AsRef<str>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
{
let path = entity_item_path(blueprint_id.as_ref(), entity_id.as_ref());
client.get(&path).await
}
pub async fn create(
client: &PortClient,
blueprint_id: impl AsRef<str>,
payload: &EntityRequest,
) -> Result<EntityResponse, PortError> {
let blueprint_id = blueprint_id.as_ref();
let path = entity_collection_path(blueprint_id);
let response: EntityResponse = client.post(&path, payload).await?;
let composite_id = format!("{}/{}", blueprint_id, response.identifier);
client.record_creation(payload.resource_type(), &composite_id);
Ok(response)
}
pub async fn upsert(
client: &PortClient,
blueprint_id: impl AsRef<str>,
entity_id: impl AsRef<str>,
payload: &EntityRequest,
) -> Result<EntityResponse, PortError> {
let blueprint_id = blueprint_id.as_ref();
let entity_id = entity_id.as_ref();
let path = entity_item_path(blueprint_id, entity_id);
let response: EntityResponse = client.put(&path, payload).await?;
let composite_id = format!("{}/{}", blueprint_id, response.identifier);
client.record_creation(payload.resource_type(), &composite_id);
Ok(response)
}
pub async fn patch<B, R>(
client: &PortClient,
blueprint_id: impl AsRef<str>,
entity_id: impl AsRef<str>,
payload: &B,
) -> Result<R, PortError>
where
B: Serialize,
R: DeserializeOwned,
{
let path = entity_item_path(blueprint_id.as_ref(), entity_id.as_ref());
client.patch(&path, payload).await
}
pub async fn delete<R>(
client: &PortClient,
blueprint_id: impl AsRef<str>,
entity_id: impl AsRef<str>,
) -> Result<R, PortError>
where
R: DeserializeOwned,
{
let blueprint_id = blueprint_id.as_ref();
let entity_id = entity_id.as_ref();
let path = entity_item_path(blueprint_id, entity_id);
let response = client.delete(&path).await?;
let composite_id = format!("{}/{}", blueprint_id, entity_id);
client.record_deletion("entity", &composite_id);
Ok(response)
}
pub fn filters() -> FilterBuilder {
FilterBuilder::new()
}
}
pub mod scorecards {
use super::*;
use crate::types::scorecards::ScorecardRef;
const RESOURCE: &str = "scorecards";
pub async fn list(client: &PortClient) -> Result<Vec<ScorecardRef>, PortError> {
let path = build_path([RESOURCE]);
client.get(&path).await
}
}
pub mod runs {
use super::*;
use crate::types::runs::AutomationRun;
const RESOURCE: &str = "runs";
pub async fn get(
client: &PortClient,
run_id: impl AsRef<str>,
) -> Result<AutomationRun, PortError> {
let path = build_path([RESOURCE, run_id.as_ref()]);
client.get(&path).await
}
}
pub mod workflows {
use super::*;
use crate::types::blueprints::BlueprintDefinition;
use crate::types::entities::EntityRequest;
pub async fn bootstrap_blueprint(
client: &PortClient,
blueprint: &BlueprintDefinition,
entity: &EntityRequest,
) -> Result<(), PortError> {
super::blueprints::create::<_, BlueprintDefinition>(client, blueprint).await?;
super::entities::create(client, &entity.blueprint, entity).await?;
Ok(())
}
}