hexser 0.5.0

Zero-boilerplate hexagonal architecture with graph-based introspection
Documentation
//! Architecture Visualization Example
//!
//! Demonstrates how hex automatically tracks and visualizes your architecture.
//!
//! Run with: cargo run --example architecture_visualization

use hexser::prelude::*;

// Domain Layer - Core business logic
#[derive(HexDomain, HexEntity)]
struct Customer {
  id: String,
  name: String,
  email: String,
}

#[derive(HexDomain, HexEntity)]
struct Order {
  id: String,
  customer_id: String,
  total: f64,
}

#[derive(HexDomain, HexEntity)]
struct Product {
  id: String,
  name: String,
  price: f64,
}

// Port Layer - Interfaces
#[derive(HexPort, HexRepository)]
struct CustomerRepository;

#[derive(HexPort, HexRepository)]
struct OrderRepository;

#[derive(HexPort, HexRepository)]
struct ProductRepository;

// Adapter Layer - Implementations
#[derive(HexAdapter)]
struct PostgresCustomerRepository {
  connection_string: String,
}

#[derive(HexAdapter)]
struct PostgresOrderRepository {
  connection_string: String,
}

#[derive(HexAdapter)]
struct RedisProductCache {
  redis_url: String,
}

// Application Layer - Use cases
#[derive(HexDirective)]
struct CreateOrderDirective {
  customer_id: String,
  products: Vec<String>,
}

#[derive(HexQuery)]
struct GetCustomerOrdersQuery {
  customer_id: String,
}

fn main() {
  println!("πŸ—οΈ  hex Architecture Visualization\n");
  println!("{}", "═".repeat(70));

  let graph = HexGraph::current();

  // Overview
  println!("\nπŸ“Š ARCHITECTURE OVERVIEW");
  println!("{}", "─".repeat(70));
  graph.pretty_print();

  // Layer-by-layer breakdown
  println!("\nπŸ›οΈ  LAYER-BY-LAYER BREAKDOWN");
  println!("{}", "─".repeat(70));

  visualize_layer(&graph, Layer::Domain, "πŸ“¦ Domain Layer (Business Logic)");
  visualize_layer(&graph, Layer::Port, "πŸ”Œ Port Layer (Interfaces)");
  visualize_layer(&graph, Layer::Adapter, "πŸ”§ Adapter Layer (Implementations)");
  visualize_layer(
    &graph,
    Layer::Application,
    "⚑ Application Layer (Use Cases)",
  );

  // ASCII Architecture Diagram
  println!("\n🎨 ASCII ARCHITECTURE DIAGRAM");
  println!("{}", "─".repeat(70));
  print_ascii_architecture(&graph);

  // Metrics
  println!("\nπŸ“ˆ ARCHITECTURE METRICS");
  println!("{}", "─".repeat(70));
  println!("Total Components: {}", graph.node_count());
  println!(
    "Domain Entities: {}",
    graph.nodes_by_role(Role::Entity).len()
  );
  println!(
    "Repository Ports: {}",
    graph.nodes_by_layer(Layer::Port).len()
  );
  println!(
    "Adapter Implementations: {}",
    graph.nodes_by_layer(Layer::Adapter).len()
  );
  println!("Directives: {}", graph.nodes_by_role(Role::Directive).len());
  println!("Queries: {}", graph.nodes_by_role(Role::Query).len());

  // Health Check
  println!("\nβœ… ARCHITECTURE HEALTH CHECK");
  println!("{}", "─".repeat(70));
  health_check(&graph);

  println!("{}", "═".repeat(70));
  println!("πŸŽ‰ Your architecture is automatically documented and visualized!");
  println!("{}", "═".repeat(70));
}

fn visualize_layer(graph: &HexGraph, layer: Layer, title: &str) {
  println!("\n{title}");
  let nodes: Vec<_> = graph.nodes_by_layer(layer).into_iter().collect();

  if nodes.is_empty() {
    println!("  (no components)");
    return;
  }

  for node in nodes {
    println!("  β€’ {} ({:?})", node.short_name(), node.role());
  }
}

fn print_ascii_architecture(graph: &HexGraph) {
  let domain_count = graph.nodes_by_layer(Layer::Domain).len();
  let port_count = graph.nodes_by_layer(Layer::Port).len();
  let adapter_count = graph.nodes_by_layer(Layer::Adapter).len();
  let app_count = graph.nodes_by_layer(Layer::Application).len();

  println!(
    r#"
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚      Application Layer ({app_count:2})        β”‚
        β”‚   [Directives, Queries]            β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚         Port Layer ({port_count:2})            β”‚
        β”‚    [Repository Interfaces]         β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”
            β”‚       β”‚       β”‚
        β”Œβ”€β”€β”€β–Όβ”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”
        β”‚Domainβ”‚ β”‚Adaptβ”‚ β”‚Infrastβ”‚
        β”‚ ({domain_count:2}) β”‚ β”‚ ({adapter_count:2})β”‚ β”‚  (0)  β”‚
        β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜
    "#
  );
}

fn health_check(graph: &HexGraph) {
  let domain_count = graph.nodes_by_layer(Layer::Domain).len();
  let port_count = graph.nodes_by_layer(Layer::Port).len();
  let adapter_count = graph.nodes_by_layer(Layer::Adapter).len();

  if domain_count > 0 {
    println!("βœ… Domain layer present ({domain_count} entities)");
  } else {
    println!("⚠️  No domain entities found");
  }

  if port_count > 0 {
    println!("βœ… Port layer present ({port_count} ports)");
  } else {
    println!("⚠️  No ports defined");
  }

  if adapter_count > 0 {
    println!("βœ… Adapter layer present ({adapter_count} adapters)");
  } else {
    println!("⚠️  No adapters implemented");
  }

  if port_count > 0 && adapter_count == 0 {
    println!("πŸ’‘ Tip: Ports without adapters need implementations!");
  }

  if domain_count == 0 && port_count == 0 && adapter_count == 0 {
    println!("πŸ“š Getting started: Check out the tutorials!");
  }
}