Abscissa Microframework Documentation

repository·main·Indexed 20 days ago

https://github.com/iqlusioninc/abscissa

Abscissa is a Rust microframework for building CLI tools and network/web services. Version 0.9.0 provides a security-focused feature set including a minimalist component architecture, declarative option parsing via clap, TOML configuration, secrets management, and unified error handling. The ecosystem includes the abscissa CLI for application generation, abscissa_core for the framework library, and abscissa_tokio for integrating Tokio runtimes as components.

Tokens
20.6K
Snippets
63
Records
71
Agent score
68%

What's inside Abscissa

  1. How the Tokio runtime works in Abscissa

    main

    In standard Rust applications, you typically use the tokio::main macro to launch a Tokio runtime. In Abscissa, the application lifecycle is managed by abscissa_core::boot called from main().

    abscissa_tokio provides a specialized way to handle this by instantiating the Tokio runtime as an Abscissa Component. This approach offers two main benefits:

    1. Dependency Injection: Other application components can declare a Tokio dependency, allowing Abscissa to inject the runtime component automatically.
    2. Registration Lifecycle: It ensures all application subcomponents are registered with the runtime before it starts. This allows Runnable types to start the runtime without needing to hold a lock on the global application state.
  2. How Abscissa works: Core concepts

    main

    Abscissa is a microframework designed for building CLI tools and network/web services with minimal dependencies and a focus on security.

    Key Abstractions

    • Component Architecture: Uses a minimalist component system (similar to an ECS) for extensibility. It handles dependency ordering and provides hooks into the application lifecycle. Default components include terminal and logging.
    • Declarative Option Parsing: Built on clap, providing improved UX and tight integration (e.g., allowing command-line options to override configuration settings).
    • Configuration: Uses TOML files parsed into serde-compatible types that can be updated dynamically at runtime.
    • Secrets Management: Includes an optional secrets module with a Secret type that implements serde::Deserialize for handling credentials from config, environment variables, or network requests.
    • Unified Error Handling: Provides a centralized subsystem with a generic error type.
  3. Understand Abscissa dependency relationships and Cargo features

    main

    Abscissa uses [Cargo features] to allow users to disable unused parts of the framework, minimizing the compiled dependency tree. Use the following table to understand which features enable specific dependencies and which crates require them.

    | Crate Name             | [Cargo Features]   | Required By                |
    |------------------------|--------------------|----------------------------|
    | [abscissa_core]        | -                  | ⊤                          |
    | [abscissa_derive]      | -                  | [abscissa_core]            |
    | [aho-corasick]         | `trace`, `testing` | [regex]                    |
    | [ansi_term]            | `trace`            | [tracing-subscriber]       |
    | [arc-swap]             | `signals`          | [signal-hook-registry]     |
    | [atty]                 | `terminal`         | [color-backtrace]          |
    | [autocfg]              | `time`             | [num-integer]              |
    | [backtrace]            | -                  | [abscissa_core]            |
    | [backtrace-sys]        | -                  | [backtrace]                |
    | [byteorder]            | `trace`            | [regex-automata]           |
    | [canonical-path]       | -                  | [abscissa_core]            |
    | [cc]                   | -                  | [backtrace-sys]            |
    | [cfg-if]               | -                  | [backtrace], [log]         |
    | [color-backtrace]      | `terminal`         | [abscissa_core]            |
    | [chrono]               | `time`             | [abscissa_core]            |
    | [clap]                 | `option`           | [abscissa_core]            |
    | [clap_derive]        | `option`           | [clap]                     |
    | [darling]            | -                  | [abscissa_derive]          |
    | [darling_core]       | -                  | [darling], [darling_macro] |
    | [darling_macro]      | -                  | [darling]                  |
    | [fs-err]               | -                  | [abscissa_core]            |
    | [fnv]                | -                  | [darling_core]             |
    | [generational-arena]   | `application`      | [abscissa_core]            |
    | [ident_case]         | -                  | [abscissa_derive], [darling_core] |
    | [lazy_static]          | `testing`, `trace` | [thread_local], [tracing-core], [tracing-log], [tracing-subscriber] |
    | [libc]                 | `signals`          | [abscissa_core]            |
    | [log]                  | `logging`          | [abscissa_core]            |
    | [matchers]             | `trace`            | [tracing-subscriber]       |
    | [memchr]               | `trace`, `testing` | [aho-corasick]             |
    | [maybe-uninit]         | `trace`            | [smallvec]                 |
    | [num-integer]          | `time`             | [chrono]                   |
    | [num-traits]           | `time`             | [chrono], [num-integer]    |
    | [once_cell]            | -                  | [abscissa_core]            |
    | [proc-macro2]        | -                  | [abscissa_derive], [darling], [quote], [serde_derive], [syn] |
    | [quote]              | -                  | [abscissa_derive], [darling], [serde_derive] |
    | [redox_syscall]        | `time`             | [time]                     |
    | [regex]                | `trace`, `testing` | [abscissa_core]            |
    | [regex-automata]       | `trace`            | [matchers]                 |
    | [regex-syntax]         | `trace`, `testing` | [abscissa_core]            |
    | [rustc-demangle]       | -                  | [backtrace]                |
    | [secrecy]              | `secrets`          | [abscissa_core]            |
    | [semver]               | `application`      | [abscissa_core]            |
    | [semver-parser]        | `application`      | [abscissa_core]            |
    | [serde]                | `config`           | [abscissa_core]            |
    | [serde_derive]       | `config`           | [serde]                    |
    | [signal-hook]          | `signals`          | [abscissa_core]            |
    | [sharded-slab]         | `trace`            | [tracing-subscriber]       |
    | [signal-hook-registry] | `signals`          | [signal-hook]             |
    | [smallvec]             | `trace`            | [tracing-subscriber]       |
    | [strsim]             | -                  | [darling_core]             |
    | [syn]                | -                  | [abscissa_derive], [darling], [serde_derive] |
    | [termcolor]            | `terminal`         | [abscissa_core]            |
    | [thiserror]          | -                  | Abscissa boilerplate       |
    | [thread_local]         | `trace`, `testing` | [regex]                    |
    | [time]                 | `logging`          | [chrono]                   |
    | [tracing]              | `trace`            | [abscissa_core]            |
    | [tracing-attributes] | `trace`            | [tracing]                  |
    | [tracing-core]         | `trace`            | [tracing]                  |
    | [tracing-log]          | `trace`            | [abscissa_core]            |
    | [tracing-subscriber]   | `trace`            | [abscissa_core]            |
    | [unicode-xid]       | -                  | [proc-macro2], [syn]     |
    | [utf8-ranges]          | `trace`, `testing` | [regex]                    |
    | [wait-timeout]       | `testing`         | [abscissa_core]            |
    | [winapi]§              | -                  | [termcolor], [time], [winapi-util] |
    | [winapi-util]          | -                  | [termcolor]                |
    | [wincolor]             | `terminal`         | [termcolor]                |
    | [zeroize]              | -                  | [abscissa_core]            |
  4. Understand the Abscissa crate structure

    main

    Abscissa is organized into several crates depending on your needs:

    • abscissa: The CLI utility used for application generation (cargo install abscissa).
    • abscissa_core: The main framework library used to build applications.
    • abscissa_derive: Provides custom derive support (internal implementation detail of abscissa_core).
    • abscissa_tokio: Provides support for launching Tokio runtimes within Abscissa applications.
  5. Run an Abscissa application

    main

    Once a project is generated, you can run it using cargo run. You can invoke specific subcommands or request help.

    To run a specific subcommand (e.g., start):

    $ cargo run -- start world

    To view help information:

    $ cargo run -- --help
  6. How to use abscissa_derive macros

    main

    The abscissa_derive crate provides custom derive implementations for the Abscissa microframework.

    Important: You should not add abscissa_derive directly to your Cargo.toml. Instead, add the main abscissa crate to your dependencies. The procedural macros will be automatically available in scope when you import the relevant types from abscissa.

  7. How to manually test Abscissa framework changes

    main

    If you are developing the Abscissa framework itself, you can test your changes by generating a temporary application that uses your local version of the crates via a path dependency.

    1. Clone the Abscissa repository.
    2. Run the new command with the --patch-crates-io flag to point to your local directory.
    3. Run tests, formatting checks, and clippy on the generated application.
    # Inside the abscissa directory
    $ cargo run -- new /tmp/example_app --patch-crates-io='abscissa = { path = "$PWD" }'
    
    # Test the generated app
    $ cd /tmp/example_app
    $ cargo test
    $ cargo fmt -- --check
    $ cargo clippy
  8. Create a new Abscissa application

    main

    Use the abscissa new command to generate a complete application skeleton. The resulting project is a standard Cargo project with a pre-configured structure for commands, configuration, and error handling.

    Generated Project Structure

    • src/application.rs: Defines the main Abscissa application type.
    • src/commands*: Contains application entrypoints and subcommands (refer to start.rs for subcommand implementation examples).
    • src/config.rs: Defines application configuration types.
    • src/error.rs: Defines application-specific error types.
    • src/bin/: Contains the binary entrypoint.

    Note: Abscissa applications are implemented as Rust libraries with a src/bin subdirectory for the binary.

    $ cargo install abscissa
    $ abscissa new my_cool_app
    
    # Run the generated app
    $ cd my_cool_app
    $ cargo run -- start world
  9. Manually generate and test Abscissa applications

    main

    If you are developing changes to the Abscissa framework itself, you can manually drive the application generation and testing process to ensure your changes don't break generated apps.

    1. Generate an app using your local Abscissa source: Use the --patch-crates-io flag to point the new application to your local directory.

      $ cargo run -- new /tmp/example_app --patch-crates-io='abscissa = { path = "$PWD" }'
    2. Run tests and linting on the generated app:

      $ cd /tmp/example_app
      $ cargo test
      $ cargo fmt -- --check
      $ cargo clippy
    $ cargo run -- new /tmp/example_app --patch-crates-io='abscissa = { path = "$PWD" }'
  10. How kill switches work for cooperative thread termination

    main

    Abscissa uses a cooperative approach for thread termination via KillSwitch. Instead of forcefully killing a thread, a KillSwitch acts as a signal that a thread can periodically check to determine if it should exit gracefully.

    To use this mechanism, a thread must first have a KillSwitch registered to it using set(). Once registered, the thread can call is_thrown() during its execution loop. If another part of the application calls throw() on the shared KillSwitch instance, is_thrown() will return true, signaling the thread to terminate.

    Note: Calling is_thrown() in a thread that has not had a kill switch configured via set() will cause a panic.

    // Conceptual flow:
    // 1. Create a KillSwitch
    // 2. Register it to the thread using `set(Arc::new(kill_switch))`
    // 3. Periodically check `is_thrown()` in the thread's main loop
    // 4. Call `throw()` from another thread to trigger termination
  11. Implement the Application trait to build services and CLI tools

    main

    To create an Abscissa application, you must implement the Application trait. This trait defines the lifecycle of your application, including command-line parsing, configuration loading, and component management.

    An implementation requires defining three associated types:

    • type Cmd: The application entrypoint, which must implement Command, Configurable<Self::Cfg>, and clap::Parser.
    • type Cfg: The configuration type, which must implement Config.
    • type Paths: The resource paths, which must implement Default, ExePath, and RootPath.

    Key lifecycle methods you will typically override include:

    • register_components: Used to register all components used by the application.
    • after_config: A callback invoked after configuration is loaded to allow components to react to the new configuration.
    • term_colors: To customize ColorChoice (defaults to ColorChoice::Auto).
    • tracing_config: To customize the trace::Config.
    impl Application for MyService {
        type Cmd = MyCliCommand;
        type Cfg = MyConfig;
        type Paths = MyPaths;
    
        fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> {
            // Register components here
            Ok(())
        }
    
        fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> {
            // Handle post-config logic
            Ok(())
        }
    }