zammad-api 0.4.0

Async, idiomatic Rust client for the Zammad Service Platform
Documentation
// Verifies the Knowledge Base write path against a live instance.
// Needs a token with Knowledge Base *editor* permission.
//
//   ZAMMAD_URL=... ZAMMAD_API_TOKEN=... \
//     cargo run --example knowledge_base_write_smoke
//
// Runs a full CRUD cycle: creates a category and an answer in it, updates
// and publishes/unpublishes the answer, then deletes both. If a step fails
// mid-way, the ids of whatever was already created are printed as
// CLEANUP_CATEGORY_ID / CLEANUP_ANSWER_ID.
use zammad_api::api::{KbAnswerPatch, KbCategoryPatch, NewKbAnswer, NewKbCategory};
use zammad_api::{Auth, Client};

#[tokio::main]
async fn main() -> zammad_api::Result<()> {
    let url = std::env::var("ZAMMAD_URL").unwrap();
    let token = std::env::var("ZAMMAD_API_TOKEN").unwrap();
    let z = Client::builder().url(url).auth(Auth::bearer(token)).build()?;
    let kb = z.knowledge_base();

    let mut cleanup_category: Option<u64> = None;
    let mut cleanup_answer: Option<u64> = None;

    let run = async {
        println!("=== list knowledge bases ===");
        let kbs = kb.list_knowledge_bases().await?;
        let kb_id = kbs
            .first()
            .map(|k| k.id)
            .ok_or_else(|| zammad_api::Error::Config("no knowledge bases found".into()))?;
        println!("  using kb {kb_id}");

        println!("=== create category ===");
        let cat = kb
            .create_category(
                kb_id,
                NewKbCategory {
                    title: "__mcp_write_smoke_cat".into(),
                    icon: None,
                    parent_id: None,
                },
            )
            .await?;
        let cat_id = cat.id;
        cleanup_category = Some(cat_id);
        println!("  id={cat_id} title={:?}", cat.title);

        println!("=== list/get category ===");
        let cats = kb.list_categories().await?;
        println!("  {} categories", cats.len());
        let fetched = kb.get_category(cat_id).await?;
        println!("  id={cat_id} title={:?}", fetched.title);

        println!("=== update category ===");
        let updated = kb
            .update_category(
                cat_id,
                KbCategoryPatch {
                    title: Some("__mcp_write_smoke_cat (edited)".into()),
                    ..Default::default()
                },
            )
            .await?;
        println!("  title={:?}", updated.title);

        println!("=== create published answer ===");
        let created = kb
            .create_answer(NewKbAnswer {
                category_id: cat_id,
                title: "__mcp_write_smoke".into(),
                body: "<p>original body</p>".into(),
                published: true,
            })
            .await?;
        let id = created.id;
        cleanup_answer = Some(id);
        if !created.published {
            return Err(zammad_api::Error::Api {
                status: 0,
                message: "answer was not published after create".into(),
            });
        }
        println!("  id={id} published={} title={:?}", created.published, created.title);
        println!("  body={:?}", created.body);

        println!("=== update title+body ===");
        let updated = kb
            .update_answer(
                id,
                KbAnswerPatch {
                    title: Some("__mcp_write_smoke (edited)".into()),
                    body: Some("<p>edited body</p>".into()),
                    ..Default::default()
                },
            )
            .await?;
        println!("  title={:?} body={:?}", updated.title, updated.body);

        println!("=== unpublish ===");
        let unpubd = kb
            .update_answer(
                id,
                KbAnswerPatch {
                    published: Some(false),
                    ..Default::default()
                },
            )
            .await?;
        if unpubd.published {
            return Err(zammad_api::Error::Api {
                status: 0,
                message: "answer was still published after unpublish".into(),
            });
        }
        println!("  published={}", unpubd.published);

        println!("=== delete answer ===");
        kb.delete_answer(id).await?;
        cleanup_answer = None;

        println!("=== delete category ===");
        kb.delete_category(cat_id).await?;
        cleanup_category = None;

        Ok(())
    };

    if let Err(e) = run.await {
        if let Some(id) = cleanup_category {
            println!("CLEANUP_CATEGORY_ID={id}");
        }
        if let Some(id) = cleanup_answer {
            println!("CLEANUP_ANSWER_ID={id}");
        }
        return Err(e);
    }
    println!("OK — nothing to clean up");
    Ok(())
}