From b1d696de849d8e1b350bae77895ddf4aa7c50bc5 Mon Sep 17 00:00:00 2001 From: Vincent Stuyck Date: Sat, 28 Feb 2026 01:07:48 +0100 Subject: [PATCH] update how the client is created with marcos instead of relying on traits. this makes a cleaner api --- src/api/folders.rs | 51 +++------ src/api/hosts.rs | 37 +++---- src/api/mod.rs | 261 ++++++++++++++++++++++++++++++++++++++++++--- src/api/tags.rs | 29 ++--- src/client.rs | 120 +-------------------- 5 files changed, 286 insertions(+), 212 deletions(-) diff --git a/src/api/folders.rs b/src/api/folders.rs index d70b964..3b5bf06 100644 --- a/src/api/folders.rs +++ b/src/api/folders.rs @@ -10,16 +10,12 @@ use schemars::JsonSchema; use crate::api::rules::contactgroups::HostContactGroups; use crate::api::rules::snmp::SnmpCommunity; -use crate::api::{BulkReadDomainExtension, BulkUpdateDomainExtension, DomainExtension}; +use crate::api::{DomainExtension, domain_bulk_read, domain_bulk_update, domain_client, domain_create, domain_delete, domain_read, domain_update}; use crate::{ApiClient, Client, Result}; pub const ROOT_FOLDER: &str = "/"; -impl Client { - pub fn folder_api(&self) -> ApiClient { - self.into() - } -} +domain_client!(FolderConfig, folder_api); #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -136,12 +132,6 @@ pub struct MetaData { impl DomainExtension for FolderConfig { const DOMAIN_TYPE: super::DomainType = super::DomainType::FolderConfig; - - type CreationRequest = FolderCreationRequest; - type CreationQuery = (); - type ReadQuery = (); - type UpdateRequest = FolderUpdateRequest; - type DeleteQuery = FolderDeleteQuery; } #[derive(Debug, Default, Clone, Serialize)] @@ -259,10 +249,6 @@ pub enum FolderDeleteMode { AbortOnNonEmpty, } -impl BulkReadDomainExtension for FolderConfig { - type BulkReadQuery = FolderBulkReadQuery; -} - #[derive(Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderBulkReadQuery { @@ -276,10 +262,6 @@ impl FolderBulkReadQuery { } } -impl BulkUpdateDomainExtension for FolderConfig { - type BulkUpdateRequest = FolderBulkUpdateRequest; -} - #[derive(Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct FolderBulkUpdateRequest { @@ -297,28 +279,27 @@ impl FolderBulkUpdateRequest { } } -#[derive(Serialize)] -#[cfg_attr(feature = "schemars", derive(JsonSchema))] -pub struct MoveFolderRequest { - destination: String, -} - -impl MoveFolderRequest { - pub fn new(destination: String) -> Self { - Self { destination } - } -} - impl ApiClient { - pub async fn move_folder(&self, id: impl Display, request: &MoveFolderRequest) -> Result<()> { + pub async fn move_folder(&self, id: impl Display, destination: &str) -> Result<()> { + #[derive(Serialize)] + struct MoveFolderRequest<'a> { + destination: &'a str, + } + let request = MoveFolderRequest { destination }; + let url = format!( "{}/actions/move/invoke", self.inner.object_url(FolderConfig::DOMAIN_TYPE, id) ); - let request = self.inner.http.post(url).json(request).build().unwrap(); + let request = self.inner.http.post(url).json(&request).build().unwrap(); self.inner.invoke_api(request).await } } -// TODO: add show hosts api call +domain_create!(FolderConfig, FolderCreationRequest); +domain_read!(FolderConfig); +domain_update!(FolderConfig, FolderUpdateRequest); +domain_delete!(FolderConfig, FolderDeleteQuery); +domain_bulk_read!(FolderConfig, FolderBulkReadQuery); +domain_bulk_update!(FolderConfig, FolderBulkUpdateRequest); diff --git a/src/api/hosts.rs b/src/api/hosts.rs index 2099fc6..5fe38ec 100644 --- a/src/api/hosts.rs +++ b/src/api/hosts.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::net::{Ipv4Addr, Ipv6Addr}; use serde::{Deserialize, Serialize}; @@ -6,16 +7,12 @@ use schemars::JsonSchema; use crate::api::folders::FolderAttributes; use crate::api::{ - BulkCreateDomainExtension, BulkDeleteDomainExtension, BulkReadDomainExtension, - BulkUpdateDomainExtension, DomainExtension, + DomainExtension, domain_bulk_create, domain_bulk_delete, domain_bulk_read, domain_bulk_update, domain_client, domain_create, domain_delete, domain_read, domain_update }; -use crate::{ApiClient, Client}; +use crate::{ApiClient, Client, Result}; -impl Client { - pub fn host_api(&self) -> ApiClient { - self.into() - } -} + +domain_client!(HostConfig, host_api); #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -49,12 +46,6 @@ pub struct HostAttributes { impl DomainExtension for HostConfig { const DOMAIN_TYPE: super::DomainType = super::DomainType::HostConfig; - - type CreationRequest = HostCreationRequest; - type CreationQuery = HostCreationQuery; - type ReadQuery = HostReadQuery; - type UpdateRequest = HostUpdateRequest; - type DeleteQuery = (); } #[derive(Serialize)] @@ -133,15 +124,6 @@ impl HostUpdateRequest { } } -impl BulkCreateDomainExtension for HostConfig {} -impl BulkReadDomainExtension for HostConfig { - type BulkReadQuery = HostBulkReadQuery; -} -impl BulkUpdateDomainExtension for HostConfig { - type BulkUpdateRequest = HostBulkUpdateRequest; -} -impl BulkDeleteDomainExtension for HostConfig {} - #[derive(Debug, Default, Clone, Serialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] pub struct HostBulkReadQuery { @@ -216,3 +198,12 @@ impl HostBulkUpdateRequest { } } } + +domain_create!(HostConfig, HostCreationRequest, HostCreationQuery); +domain_read!(HostConfig, HostReadQuery); +domain_update!(HostConfig, HostUpdateRequest); +domain_delete!(HostConfig); +domain_bulk_create!(HostConfig, HostCreationQuery); +domain_bulk_read!(HostConfig, HostBulkReadQuery); +domain_bulk_update!(HostConfig, HostBulkUpdateRequest); +domain_bulk_delete!(HostConfig); diff --git a/src/api/mod.rs b/src/api/mod.rs index 26860bc..5562f6e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -5,10 +5,13 @@ pub mod groups; pub(crate) mod rules; pub mod tags; -use std::fmt; +use std::fmt::{self, Display}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use crate::Result; +use crate::client::InnerClient; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum DomainType { @@ -88,22 +91,248 @@ pub(crate) struct DomainCollection { pub trait DomainExtension: DeserializeOwned + Serialize { const DOMAIN_TYPE: DomainType; - - type CreationRequest: Serialize; - type CreationQuery: Serialize; - type ReadQuery: Serialize; - type UpdateRequest: Serialize; - type DeleteQuery: Serialize; } -// these are essential tags for domain extensions to signal that bulk operations are possible -// not every extension supports bulk operations. and not all extensions that do support all of them -// and those that do tend to have a bit different types -pub trait BulkCreateDomainExtension: DomainExtension {} -pub trait BulkReadDomainExtension: DomainExtension { - type BulkReadQuery: Serialize; +impl InnerClient { + pub(crate) async fn create_domain_object( + &self, + object: &O, + query: &Q, + ) -> Result<()> + where + E: DomainExtension, + O: Serialize, + Q: Serialize + { + self.create(E::DOMAIN_TYPE, object, query).await + } + pub(crate) async fn read_domain_object( + &self, + id: impl Display, + query: &Q, + ) -> Result> + where + E: DomainExtension, + Q: Serialize + { + self.read(E::DOMAIN_TYPE, id, query).await + } + pub(crate) async fn update_domain_object( + &self, + id: impl Display, + object: &O, + ) -> Result<()> + where + E: DomainExtension, + O: Serialize + { + self.update(E::DOMAIN_TYPE, id, object).await + } + pub(crate) async fn delete_domain_object( + &self, + id: impl Display, + query: &Q, + ) -> Result<()> + where + E: DomainExtension, + Q: Serialize + { + self.delete(E::DOMAIN_TYPE, id, query).await + } + + pub(crate) async fn bulk_create_domain_objects( + &self, + entries: &[O], + query: &Q, + ) -> Result<()> + where + E: DomainExtension, + O: Serialize, + Q: Serialize + { + self.bulk_create(E::DOMAIN_TYPE, entries, query).await + } + pub(crate) async fn bulk_read_domain_objects( + &self, + query: &Q, + ) -> Result>> + where + E: DomainExtension, + Q: Serialize + { + self.bulk_read(E::DOMAIN_TYPE, query).await + } + pub(crate) async fn bulk_update_domain_objects( + &self, + entries: &[O], + ) -> Result<()> + where + E: DomainExtension, + O: Serialize + { + self.bulk_update(E::DOMAIN_TYPE, entries).await + } + pub(crate) async fn bulk_delete_domain_objects( + &self, + entries: &[String], + ) -> Result<()> + where + E: DomainExtension + { + self.bulk_delete(E::DOMAIN_TYPE, entries).await + } } -pub trait BulkUpdateDomainExtension: DomainExtension { - type BulkUpdateRequest: Serialize; + + + +macro_rules! domain_client { + ($id:ident, $func:ident) => { + impl Client { + pub fn $func(&self) -> ApiClient<$id> { + self.into() + } + } + domain_client!($id); + }; + ($id:ident) => { + impl From<&Client> for ApiClient<$id> { + fn from(value: &Client) -> Self { + Self::new(value) + } + } + } } -pub trait BulkDeleteDomainExtension: DomainExtension {} +pub(super) use domain_client; + +macro_rules! domain_create { + ($id:ident, $object:ident) => { + impl ApiClient<$id> { + pub async fn create(&self, object: &$object) -> Result<()> { + self.inner.create_domain_object::<$id, _, _>(object, &()).await + } + } + }; + ($id:ident, $object:ident, $qry:ident) => { + impl ApiClient<$id> { + pub async fn create(&self, object: &$object, query: &$qry) -> Result<()> { + self.inner.create_domain_object::<$id, _, _>(object, query).await + } + } + }; +} +pub(super) use domain_create; + +macro_rules! domain_read { + ($id:ident) => { + impl ApiClient<$id> { + pub async fn read(&self, id: impl Display) -> Result<$id> { + self.inner.read_domain_object::<$id, _>(id, &()).await + .map(|obj| obj.extensions) + } + } + }; + ($id:ident, $qry:ident) => { + impl ApiClient<$id> { + pub async fn read(&self, id: impl Display, query: &$qry) -> Result<$id> { + self.inner.read_domain_object::<$id, _>(id, &query).await + .map(|obj| obj.extensions) + } + } + }; +} +pub(super) use domain_read; + +macro_rules! domain_update { + ($id:ident, $object:ident) => { + impl ApiClient<$id> { + pub async fn update(&self, id: impl Display, object: &$object) -> Result<()> { + self.inner.update_domain_object::<$id, _>(id, object).await + } + } + }; +} +pub(super) use domain_update; + +macro_rules! domain_delete { + ($id:ident) => { + impl ApiClient<$id> { + pub async fn delete(&self, id: impl Display) -> Result<()> { + self.inner.delete_domain_object::<$id, _>(id, &()).await + } + } + }; + ($id:ident, $qry:ident) => { + impl ApiClient<$id> { + pub async fn delete(&self, id: impl Display, query: &$qry) -> Result<()> { + self.inner.delete_domain_object::<$id, _>(id, &query).await + } + } + }; +} +pub(super) use domain_delete; + +macro_rules! domain_bulk_create { + ($id:ident, $object:ident) => { + impl ApiClient<$id> { + pub async fn bulk_create(&self, entries: &[$object]) -> Result<()> { + self.inner.bulk_create_domain_objects::<$id, _, _>(entries, &()).await + } + } + }; + ($id:ident, $object:ident, $qry:ident) => { + impl ApiClient<$id> { + pub async fn bulk_create(&self, entries: &[$object], query: &$qry) -> Result<()> { + self.inner.bulk_create_domain_objects::<$id, _, _>(entries, query).await + } + } + }; +} +pub(super) use domain_bulk_create; + +macro_rules! domain_bulk_read { + ($id:ident) => { + impl ApiClient<$id> { + pub async fn bulk_read(&self) -> Result> { + let objs = self.inner.bulk_read_domain_objects::<$id, _>(&()).await?; + let objs = objs.into_iter() + .map(|obj| obj.extensions) + .collect(); + Ok(objs) + } + } + }; + ($id:ident, $qry:ident) => { + impl ApiClient<$id> { + pub async fn bulk_read(&self, query: &$qry) -> Result> { + let objs = self.inner.bulk_read_domain_objects::<$id, _>(&query).await?; + let objs = objs.into_iter() + .map(|obj| obj.extensions) + .collect(); + Ok(objs) + } + } + }; +} +pub(super) use domain_bulk_read; + +macro_rules! domain_bulk_update { + ($id:ident, $object:ident) => { + impl ApiClient<$id> { + pub async fn bulk_update(&self, entries: &[$object]) -> Result<()> { + self.inner.bulk_update_domain_objects::<$id, _>(entries).await + } + } + }; +} +pub(super) use domain_bulk_update; + +macro_rules! domain_bulk_delete { + ($id:ident) => { + impl ApiClient<$id> { + pub async fn bulk_delete(&self, entries: &[String]) -> Result<()> { + self.inner.bulk_delete_domain_objects::<$id>(entries).await + } + } + }; +} +pub(super) use domain_bulk_delete; diff --git a/src/api/tags.rs b/src/api/tags.rs index da69e79..abcb97d 100644 --- a/src/api/tags.rs +++ b/src/api/tags.rs @@ -1,15 +1,13 @@ +use crate::api::Display; + use serde::{Deserialize, Serialize}; #[cfg(feature = "schemars")] use schemars::JsonSchema; -use crate::api::{BulkReadDomainExtension, DomainExtension, DomainType}; -use crate::{ApiClient, Client}; +use crate::api::{DomainExtension, DomainType, domain_bulk_read, domain_client, domain_create, domain_delete, domain_read, domain_update}; +use crate::{ApiClient, Client, Result}; -impl Client { - pub fn tag_api(&self) -> ApiClient { - self.into() - } -} +domain_client!(TagGroup, tag_api); #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -151,17 +149,10 @@ pub enum TagDeleteMode { impl DomainExtension for TagGroup { const DOMAIN_TYPE: super::DomainType = DomainType::HostTagGroup; - - type CreationRequest = ExtendedTagGroup; - type CreationQuery = (); - type ReadQuery = (); - type UpdateRequest = TagUpdateRequest; - type DeleteQuery = (); } -// TODO: id and title are in the "DomainObject". -// should be added to during query? -// maybe add "with_title_and_id" to DomainExtension, defaulting to no-op? -impl BulkReadDomainExtension for TagGroup { - type BulkReadQuery = (); -} +domain_create!(TagGroup, ExtendedTagGroup); +domain_read!(TagGroup); +domain_update!(TagGroup, TagUpdateRequest); +domain_delete!(TagGroup); +domain_bulk_read!(TagGroup); diff --git a/src/client.rs b/src/client.rs index e9ad1d1..9ec907a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,8 +11,7 @@ use serde::de::DeserializeOwned; use tokio::sync::Semaphore; use crate::api::{ - BulkCreateDomainExtension, BulkDeleteDomainExtension, BulkReadDomainExtension, - BulkUpdateDomainExtension, DomainCollection, DomainExtension, DomainObject, DomainType, + DomainCollection, DomainObject, DomainType, }; use crate::{Error, Result}; @@ -306,13 +305,6 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn create_domain_object( - &self, - object: &E::CreationRequest, - query: &E::CreationQuery, - ) -> Result<()> { - self.create(E::DOMAIN_TYPE, object, query).await - } pub(crate) async fn read( &self, @@ -334,14 +326,6 @@ impl InnerClient { self.query_api(request).await } - pub(crate) async fn read_domain_object( - &self, - id: impl Display, - query: &E::ReadQuery, - ) -> Result> { - self.read(E::DOMAIN_TYPE, id, query).await - } - pub(crate) async fn update( &self, domain_type: DomainType, @@ -364,14 +348,6 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn update_domain_object( - &self, - id: impl Display, - object: &E::UpdateRequest, - ) -> Result<()> { - self.update(E::DOMAIN_TYPE, id, object).await - } - pub(crate) async fn delete( &self, domain_type: DomainType, @@ -391,13 +367,6 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn delete_domain_object( - &self, - id: impl Display, - query: &E::DeleteQuery, - ) -> Result<()> { - self.delete(E::DOMAIN_TYPE, id, query).await - } pub(crate) async fn bulk_create( &self, @@ -426,13 +395,6 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn bulk_create_domain_objects( - &self, - entries: &[E::CreationRequest], - query: &E::CreationQuery, - ) -> Result<()> { - self.bulk_create(E::DOMAIN_TYPE, entries, query).await - } pub(crate) async fn bulk_read( &self, @@ -454,13 +416,6 @@ impl InnerClient { Ok(response.value) } - pub(crate) async fn bulk_read_domain_objects( - &self, - query: &E::BulkReadQuery, - ) -> Result>> { - self.bulk_read(E::DOMAIN_TYPE, query).await - } - pub(crate) async fn bulk_update( &self, domain_type: DomainType, @@ -486,13 +441,6 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn bulk_update_domain_objects( - &self, - entries: &[E::BulkUpdateRequest], - ) -> Result<()> { - self.bulk_update(E::DOMAIN_TYPE, entries).await - } - pub(crate) async fn bulk_delete( &self, domain_type: DomainType, @@ -515,12 +463,6 @@ impl InnerClient { self.invoke_api(request).await } - pub(crate) async fn bulk_delete_domain_objects( - &self, - entries: &[String], - ) -> Result<()> { - self.bulk_delete(E::DOMAIN_TYPE, entries).await - } } pub(crate) enum BulkAction { @@ -554,63 +496,3 @@ impl ApiClient { } } } - -impl From<&Client> for ApiClient { - fn from(value: &Client) -> Self { - Self::new(value) - } -} - -impl ApiClient { - pub async fn create( - &self, - request: &D::CreationRequest, - query: &D::CreationQuery, - ) -> Result<()> { - self.inner.create_domain_object::(request, query).await - } - pub async fn read(&self, id: impl Display, query: &D::ReadQuery) -> Result { - self.inner - .read_domain_object(id, query) - .await - .map(|obj| obj.extensions) - } - pub async fn update(&self, id: impl Display, request: &D::UpdateRequest) -> Result<()> { - self.inner.update_domain_object::(id, request).await - } - pub async fn delete(&self, id: impl Display, query: &D::DeleteQuery) -> Result<()> { - self.inner.delete_domain_object::(id, query).await - } -} - -impl ApiClient { - pub async fn bulk_create( - &self, - request: &[D::CreationRequest], - query: &D::CreationQuery, - ) -> Result<()> { - self.inner - .bulk_create_domain_objects::(request, query) - .await - } -} - -impl ApiClient { - pub async fn bulk_read(&self, query: &D::BulkReadQuery) -> Result> { - let objs = self.inner.bulk_read_domain_objects::(query).await?; - let exts = objs.into_iter().map(|obj| obj.extensions).collect(); - Ok(exts) - } -} - -impl ApiClient { - pub async fn bulk_update(&self, entries: &[D::BulkUpdateRequest]) -> Result<()> { - self.inner.bulk_update_domain_objects::(entries).await - } -} - -impl ApiClient { - pub async fn bulk_delete(&self, ids: &[String]) -> Result<()> { - self.inner.bulk_delete_domain_objects::(ids).await - } -}