#features #toggle #flags #web-ui #flipper #web-api #json-api

feattle

Featture toggles for Rust, extensible and with background synchronization and administration UI

13 releases (2 stable)

2.0.0 Jun 26, 2024
1.0.0 Jun 28, 2023
0.10.0 Apr 21, 2023
0.9.0 Jul 11, 2022
0.2.5 Oct 23, 2020

#217 in Web programming

Download history 186/week @ 2024-08-16 171/week @ 2024-08-23 316/week @ 2024-08-30 213/week @ 2024-09-06 325/week @ 2024-09-13 123/week @ 2024-09-20 257/week @ 2024-09-27 125/week @ 2024-10-04 216/week @ 2024-10-11 224/week @ 2024-10-18 109/week @ 2024-10-25 33/week @ 2024-11-01 8/week @ 2024-11-08 54/week @ 2024-11-15 198/week @ 2024-11-22 141/week @ 2024-11-29

404 downloads per month

MIT/Apache

165KB
2.5K SLoC

Rust 2.5K SLoC // 0.0% comments JavaScript 198 SLoC Handlebars 177 SLoC

feattle

Crates.io Docs.rs CI Coverage Status

Featture toggles for Rust (called "feattles", for short), extensible and with background synchronization and administration UI.

Features

  • Feature toggles that synchronize automatically with a backing storage
  • Feature toggles can be as simple bool, but can also be lists, maps and arbitrary tpes ( (through the FeattleValue trait).
  • Web UI with documentation, change history, validation
  • JSON API to read and set the toggles
  • Modular and extensible: use as much or as little of the bundled features as you want. Want to use a different Web UI? A different storage layer? No problem.

Example

use feattle::*;
use std::sync::Arc;

/// A struct with your feature toggles: you can use primitive types (like `bool`, `i32`, etc),
/// standard collections (like `Vec`, `BTreeSet`, etc) or any arbitrary type that implements
/// the required trait.
feattles! {
    struct MyFeattles {
        /// Is this usage considered cool?
        is_cool: bool = true,
        /// Limit the number of "blings" available.
        /// This will not change the number of "blengs", though!
        max_blings: i32,
        /// List the actions that should not be available
        blocked_actions: Vec<String>,
    }
}

#[tokio::main]
async fn main() {
    // Store their values and history in AWS' S3
    use std::future::IntoFuture;
    use std::time::Duration;
    use tokio::net::TcpListener;
    let config = aws_config::load_from_env().await;
    let persistence = Arc::new(S3::new(
        &config,
        "my-bucket".to_owned(),
        "some/s3/prefix/".to_owned(),
    ));

    // Create a new instance
    let my_feattles = Arc::new(MyFeattles::new(persistence));

    // Poll the storage in the background
    BackgroundSync::new(&my_feattles).start().await;

    // Start the admin UI with `warp`
    let admin_panel = Arc::new(AdminPanel::new(my_feattles.clone(), "Project Panda - DEV".to_owned()));
    tokio::spawn(run_warp_server(admin_panel.clone(), ([127, 0, 0, 1], 3030)));

    // Or serve the admin panel with `axum`
    let router = axum_router(admin_panel);
    let listener = TcpListener::bind(("127.0.0.1", 3031)).await.unwrap();
    tokio::spawn(axum::serve(listener, router.into_make_service()).into_future());

    // Read values (note the use of `*`)
    assert_eq!(*my_feattles.is_cool(), true);
    assert_eq!(*my_feattles.max_blings(), 0);
    assert_eq!(*my_feattles.blocked_actions(), Vec::<String>::new());
}

You can run a full example locally with: cargo run --example full --features='s3 uuid warp axum'.

With this code, you'll get an Web Admin UI like:

Home Web Admin UI

You can use the UI to edit the current values and see their change history. For example, this is what you can expect when editing an enum:

Edit enum

It also supports complex types with a JSON editor and helpful error diagnostics:

Edit JSON

How it works

The macro will generate a struct with the given name and visibility modifier (assuming private by default). The generated struct implements Feattles and also exposes one method for each feattle.

The methods created for each feattle allow reading their current value. For example, for a feattle is_cool: bool, there will be a method like pub fn is_cool(&self) -> MappedRwLockReadGuard<bool>. Note the use of parking_lot::MappedRwLockReadGuard because the interior of the struct is stored behind a RwLock to control concurrent access.

A feattle is created with the syntax $key: $type [= $default]. You can use doc coments ( starting with ///) to describe nicely what they do in your system. You can use any type that implements FeattleValue and optionally provide a default. If not provided, the default will be created with Default::default().

Minimum supported Rust version

As of this release, the MSRV is 1.76.0, as tested in the CI. A patch release will never require a newer MSRV.

Optional features

You can easily declare feattles with your custom types, use another persistance storage logic or Web Framework (or any at all). For some out-of-the-box functionality, you can activate these cargo features:

  • uuid: will add support for uuid::Uuid.
  • rusoto_s3: provides RusotoS3 to integrate with AWS' S3
  • aws_sdk_s3: provides [S3] to integrate with AWS' S3
  • warp: provides run_warp_server for a read-to-use integration with warp
  • axum: provides axum_router for a read-to-use integration with axum

Crate's organization

This crate is a simple re-export of these three components:

  • feattle-core: Crates.io
  • feattle-sync: Crates.io
  • feattle-ui: Crates.io

Having them separate allows for leaner lower-level integration. If you're creating a crate to provide a different storage or admin, you just need feattle-core.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

See CONTRIBUTING.md.

Dependencies

~7–21MB
~288K SLoC