port-sdk 0.1.0

Rust SDK for Port APIs.
Documentation
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;

/// Trait implemented by types that expose a stable identifier for resource tracking.
pub trait IdentifiedResource {
    fn identifier(&self) -> &str;
    fn resource_type(&self) -> &'static str;
}

pub mod ids {
    use super::*;

    macro_rules! define_id {
        ($name:ident) => {
            #[derive(
                Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Ord, PartialOrd,
            )]
            #[serde(transparent)]
            pub struct $name(pub String);

            impl $name {
                pub fn as_str(&self) -> &str {
                    &self.0
                }
            }

            impl AsRef<str> for $name {
                fn as_ref(&self) -> &str {
                    self.as_str()
                }
            }

            impl From<&str> for $name {
                fn from(value: &str) -> Self {
                    Self(value.to_string())
                }
            }

            impl From<String> for $name {
                fn from(value: String) -> Self {
                    Self(value)
                }
            }

            impl From<$name> for String {
                fn from(value: $name) -> Self {
                    value.0
                }
            }

            impl fmt::Display for $name {
                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                    self.0.fmt(f)
                }
            }
        };
    }

    define_id!(BlueprintId);
    define_id!(EntityId);
    define_id!(AutomationRunId);
}

pub mod common {
    use super::*;

    /// Shared pagination response metadata.
    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
    #[serde(rename_all = "camelCase")]
    pub struct PageInfo {
        #[serde(default)]
        pub page: Option<u32>,
        #[serde(default)]
        pub page_size: Option<u32>,
        #[serde(default)]
        pub total: Option<u64>,
        #[serde(default)]
        pub next_cursor: Option<String>,
    }
}

pub mod filters {
    use super::*;

    /// Builder for query parameters that mirror Port's filtering syntax.
    #[derive(Default, Clone, Debug)]
    pub struct FilterBuilder {
        params: BTreeMap<String, String>,
    }

    impl FilterBuilder {
        pub fn new() -> Self {
            Self::default()
        }

        pub fn eq(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
            self.params.insert(format!("filter[{}][eq]", field.into()), value.into());
            self
        }

        pub fn contains(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
            self.params.insert(format!("filter[{}][contains]", field.into()), value.into());
            self
        }

        pub fn build(self) -> BTreeMap<String, String> {
            self.params
        }
    }
}

pub mod blueprints {
    use super::{ids::BlueprintId, IdentifiedResource};
    use serde::{Deserialize, Serialize};

    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct BlueprintDefinition {
        pub identifier: BlueprintId,
        pub title: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub description: Option<String>,
    }

    impl BlueprintDefinition {
        pub fn new(identifier: impl Into<BlueprintId>, title: impl Into<String>) -> Self {
            Self { identifier: identifier.into(), title: title.into(), description: None }
        }

        pub fn description(mut self, description: impl Into<String>) -> Self {
            self.description = Some(description.into());
            self
        }
    }

    impl IdentifiedResource for BlueprintDefinition {
        fn identifier(&self) -> &str {
            self.identifier.as_str()
        }

        fn resource_type(&self) -> &'static str {
            "blueprint"
        }
    }

    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct BlueprintCollection {
        pub blueprints: Vec<BlueprintDefinition>,
    }
}

pub mod entities {
    use super::{
        ids::{BlueprintId, EntityId},
        IdentifiedResource,
    };
    use serde::{Deserialize, Serialize};
    use serde_json::Value;

    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct EntityRequest {
        pub identifier: EntityId,
        pub blueprint: BlueprintId,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub title: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub properties: Option<Value>,
    }

    impl EntityRequest {
        pub fn new(
            identifier: impl Into<EntityId>,
            blueprint: impl Into<BlueprintId>,
            properties: Option<Value>,
        ) -> Self {
            EntityRequest {
                identifier: identifier.into(),
                blueprint: blueprint.into(),
                title: None,
                properties,
            }
        }

        pub fn title(mut self, title: impl Into<String>) -> Self {
            self.title = Some(title.into());
            self
        }
    }

    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct EntityResponse {
        pub identifier: EntityId,
        pub blueprint: BlueprintId,
        pub title: Option<String>,
        #[serde(default)]
        pub properties: Option<Value>,
    }

    impl IdentifiedResource for EntityRequest {
        fn identifier(&self) -> &str {
            self.identifier.as_str()
        }

        fn resource_type(&self) -> &'static str {
            "entity"
        }
    }

    impl IdentifiedResource for EntityResponse {
        fn identifier(&self) -> &str {
            self.identifier.as_str()
        }

        fn resource_type(&self) -> &'static str {
            "entity"
        }
    }
}

pub mod scorecards {
    use serde::{Deserialize, Serialize};

    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct ScorecardRef {
        pub identifier: String,
        pub name: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub description: Option<String>,
    }
}

pub mod runs {
    use super::ids::AutomationRunId;
    use serde::{Deserialize, Serialize};

    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct AutomationRun {
        pub identifier: AutomationRunId,
        pub automation_id: String,
        #[serde(default)]
        pub status: Option<String>,
    }
}