backstage-client 0.1.4

A Rust client library for interacting with the Backstage Catalog API. Provides type-safe access to Backstage entities with async support, filtering, and comprehensive error handling.
Documentation
//! Example: Get Specific Entity
//!
//! This example demonstrates how to retrieve a specific entity from a Backstage catalog
//! using its kind, namespace, and name.
//!
//! Usage:
//!   BACKSTAGE_URL=https://your-backstage.com BACKSTAGE_TOKEN=your-token cargo run --example get_specific_entity

use backstage_client::{entities::Entity, BackstageClient, ClientError};
use log::{error, info, warn};
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("INFO")).init();

    // Get configuration from environment variables
    let base_url =
        env::var("BACKSTAGE_URL").unwrap_or_else(|_| "https://demo.backstage.io".to_string());
    let token =
        env::var("BACKSTAGE_TOKEN").expect("BACKSTAGE_TOKEN environment variable must be set");

    info!("Connecting to Backstage at: {}", base_url);

    // Create a new Backstage client
    let client = BackstageClient::new(&base_url, &token)?;

    // Example 1: Get a specific component by name (using default namespace)
    info!("\n=== Getting specific component ===");
    let entity_name = env::var("ENTITY_NAME").unwrap_or_else(|_| "my-service".to_string());

    match client
        .get_entity::<Entity>("Component", None, &entity_name)
        .await
    {
        Ok(entity) => {
            display_entity_details(&entity);
        }
        Err(ClientError::ApiError { status: 404, .. }) => {
            warn!("Component '{}' not found in default namespace", entity_name);
            info!("Try setting ENTITY_NAME environment variable to an existing component");
        }
        Err(e) => {
            error!("Error fetching component '{}': {}", entity_name, e);
        }
    }

    // Example 2: Get a specific API with explicit namespace
    info!("\n=== Getting specific API with namespace ===");
    match client
        .get_entity::<Entity>("API", Some("default"), "petstore-api")
        .await
    {
        Ok(entity) => {
            display_entity_details(&entity);
        }
        Err(ClientError::ApiError { status: 404, .. }) => {
            warn!("API 'petstore-api' not found in 'default' namespace");
        }
        Err(e) => {
            error!("Error fetching API: {}", e);
        }
    }

    // Example 3: Get a user entity
    info!("\n=== Getting specific user ===");
    let username = env::var("USERNAME").unwrap_or_else(|_| "guest".to_string());

    match client
        .get_entity::<Entity>("User", Some("default"), &username)
        .await
    {
        Ok(entity) => {
            display_entity_details(&entity);
        }
        Err(ClientError::ApiError { status: 404, .. }) => {
            warn!("User '{}' not found", username);
            info!("Try setting USERNAME environment variable to an existing user");
        }
        Err(e) => {
            error!("Error fetching user '{}': {}", username, e);
        }
    }

    // Example 4: Get a system entity
    info!("\n=== Getting specific system ===");
    match client
        .get_entity::<Entity>("System", Some("default"), "payment-system")
        .await
    {
        Ok(entity) => {
            display_entity_details(&entity);
        }
        Err(ClientError::ApiError { status: 404, .. }) => {
            warn!("System 'payment-system' not found");
        }
        Err(e) => {
            error!("Error fetching system: {}", e);
        }
    }

    // Example 5: Demonstrate error handling for invalid entity kinds
    info!("\n=== Testing error handling ===");
    match client
        .get_entity::<Entity>("", Some("default"), "test")
        .await
    {
        Ok(_) => {
            warn!("Unexpected success with empty kind");
        }
        Err(ClientError::InvalidFilter(msg)) => {
            info!("Correctly caught validation error: {}", msg);
        }
        Err(e) => {
            error!("Unexpected error: {}", e);
        }
    }

    info!("\n=== Get Specific Entity Examples Complete ===");
    info!("You can customize the examples by setting these environment variables:");
    info!("  ENTITY_NAME - Name of component to fetch (default: my-service)");
    info!("  USERNAME    - Name of user to fetch (default: guest)");

    Ok(())
}

/// Display detailed information about an entity
fn display_entity_details(entity: &Entity) {
    info!("📋 Entity Details:");
    info!("  Kind: {}", entity.kind());
    info!("  Name: {}", entity.name());
    info!("  Namespace: {}", entity.namespace());
    info!("  Entity Ref: {}", entity.entity_ref());

    if let Some(description) = entity.description() {
        info!("  Description: {}", description);
    }

    // Display metadata
    let metadata = entity.metadata();
    if let Some(uid) = &metadata.uid {
        info!("  UID: {}", uid);
    }

    // Display tags
    if let Some(tags) = entity.tags() {
        if !tags.is_empty() {
            info!("  Tags: {}", tags.join(", "));
        }
    }

    // Display annotations
    if let Some(annotations) = entity.annotations() {
        if !annotations.is_empty() {
            info!("  Annotations:");
            for (key, value) in annotations.iter().take(5) {
                info!("    {}: {}", key, value);
            }
            if annotations.len() > 5 {
                info!("    ... and {} more annotations", annotations.len() - 5);
            }
        }
    }

    // Display relations
    if let Some(relations) = entity.relations() {
        if !relations.is_empty() {
            info!("  Relations:");
            for relation in relations.iter().take(3) {
                info!(
                    "    {} -> {}:{}/{}",
                    relation.r#type,
                    relation.target.kind,
                    relation.target.namespace,
                    relation.target.name
                );
            }
            if relations.len() > 3 {
                info!("    ... and {} more relations", relations.len() - 3);
            }
        }
    }

    // Show some useful annotations if they exist
    if let Some(view_url) = entity.get_annotation("backstage.io/view-url") {
        info!("  🔗 View URL: {}", view_url);
    }

    if let Some(source_location) = entity.get_annotation("backstage.io/managed-by-location") {
        info!("  📍 Source: {}", source_location);
    }

    info!(""); // Empty line for readability
}