Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "flagsmith"
version = "0.2.0"
authors = ["Tomasz Zdybał <tomek@zdybal.lap.pl>"]
edition = "2018"
version = "1.0.0"
authors = ["Gagan Trivedi <gagan.trivedi@flagsmith.com>"]
edition = "2021"
license = "BSD-3-Clause"
description = "Flagsmith SDK for Rust"
homepage = "https://flagsmith.com/"
Expand All @@ -16,5 +16,13 @@ keywords = ["Flagsmith", "feature-flag", "remote-config"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "^0.10", features = ["blocking","json"] }
reqwest = { version = "0.11", features = ["json", "blocking"] }
url = "2.1"
chrono = { version = "0.4"}
log = "0.4"

flagsmith-flag-engine = "0.1.1"

[dev-dependencies]
httpmock = "0.6"
rstest = "0.12.0"
19 changes: 19 additions & 0 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[net]
git-fetch-with-cli = true # use the `git` executable for git operations

[dependencies]
rocket = "0.4.10"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
rocket_contrib = {version = "0.4.10", features=["tera_templates"]}
flagsmith = {path="../"}

flagsmith-flag-engine = "0.1.0"

3 changes: 3 additions & 0 deletions example/Rocket.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[global]
port = 5000
workers = 1
21 changes: 21 additions & 0 deletions example/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Flagsmith Basic Rust Example

This directory contains a basic Rocket application which utilises Flagsmith. To run the example application, you'll
need to go through the following steps:

1. Create an account, organisation and project on [Flagsmith](https://flagsmith.com)
2. Create a feature in the project called "secret_button"
3. Give the feature a value using the json editor as follows:

```json
{"colour": "#ababab"}
```

4. Set the environment variable `FLAGSMITH_ENVIRONMENT_KEY` with the environment key of one of the environments
in flagsmith (This can be found on the 'settings' page accessed from the menu on the left under the chosen environment.)
5. Run the app using `cargo run`
6. Browse to http://localhost:5000

Now you can play around with the 'secret_button' feature in flagsmith, turn it on to show it and edit the colour in the
json value to edit the colour of the button. You can also identify as a given user and then update the settings for the
secret button feature for that user in the flagsmith interface to see the affect that has too.
95 changes: 95 additions & 0 deletions example/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;
#[macro_use]
extern crate serde_derive;
extern crate flagsmith;
extern crate rocket_contrib;
extern crate serde_json;

use std::env;

use rocket_contrib::templates::Template;

use flagsmith::{Flag, Flagsmith, FlagsmithOptions};
use flagsmith_flag_engine::identities::Trait;
use flagsmith_flag_engine::types::{FlagsmithValue, FlagsmithValueType};

#[derive(Serialize)]
struct TemplateContext {
show_button: bool,
button_colour: String,
identifier: String,
}
fn default_flag_handler(feature_name: &str) -> Flag {
let mut flag: Flag = Default::default();
if feature_name == "secret_button" {
flag.value.value_type = FlagsmithValueType::String;
flag.value.value = serde_json::json!({"colour": "#b8b8b8"}).to_string();
}
return flag;
}

#[get("/?<identifier>&<trait_key>&<trait_value>")]
fn home(
identifier: Option<String>,
trait_key: Option<String>,
trait_value: Option<String>,
) -> Template {
let options = FlagsmithOptions {
default_flag_handler: Some(default_flag_handler),
enable_local_evaluation: true,
..Default::default()
};

let flagsmith = Flagsmith::new(
env::var("FLAGSMITH_ENVIRONMENT_KEY")
.expect("FLAGSMITH_ENVIRONMENT_KEY not found in environment"),
options,
);
let flags;
if identifier.is_some() {
let traits = match trait_key {
Some(trait_key) if trait_key != "".to_string() => Some(vec![Trait {
trait_key,
trait_value: FlagsmithValue {
value: trait_value.unwrap_or("".to_string()),
value_type: FlagsmithValueType::None,
},
}]),
Some(_) => None,
None => None,
};
flags = flagsmith
.get_identity_flags(identifier.as_ref().unwrap(), traits)
.unwrap();
} else {
// Get the default flags for the current environment
flags = flagsmith.get_environment_flags().unwrap();
}

let show_button = flags.is_feature_enabled("secret_button").unwrap();
let button_data = flags.get_feature_value_as_string("secret_button").unwrap();

let button_json: serde_json::Value = serde_json::from_str(&button_data).unwrap();
let button_colour = button_json["colour"].as_str().unwrap().to_string();

let context = TemplateContext {
show_button,
button_colour,
identifier: identifier.unwrap_or("World".to_string()),
};

Template::render("home", &context)
}

fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![home])
.attach(Template::fairing())
}

fn main() {
rocket().launch();
}
25 changes: 25 additions & 0 deletions example/templates/home.html.tera
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!doctype html>
<head>
<style></style>
</head>
<title>Flagsmith Example</title>
<body>
<p>Hello, {{ identifier }}.</p>
{% if show_button %}
<button style="background-color: {{ button_colour }}">A secret button</button>
{% endif %}

<p></p>

<form action="/" method="GET">
<h3>Identify as a user</h3>
<label for="identifier">Identifier: </label><input name="identifier" id="identifier"><br>

<p>... with an optional user trait</p>
<label for="trait_key">Trait key: </label><input name="trait_key" id="trait_key"><br>
<label for="trait_value">Trait value: </label><input name="trait_value" id="trait_value"><br><br>

<button type="submit">Identify!</button>
</form>

</body>
44 changes: 20 additions & 24 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,47 @@ use std::fmt;
/// Wraps several types of errors.
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
desc: String,
pub kind: ErrorKind,
pub msg: String,
}

/// Defines error kind.
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum ErrorKind {
ParseError,
RequestError,
AppError,
FlagsmithClientError,
FlagsmithAPIError,
}
impl Error{
pub fn new(kind: ErrorKind, msg: String) -> Error{
Error{
kind,
msg
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
ErrorKind::ParseError => write!(f, "URL parsing error: {}", &self.desc),
ErrorKind::RequestError => write!(f, "REST API request error: {}", &self.desc),
ErrorKind::AppError => write!(f, "Application error: {}", &self.desc),
ErrorKind::FlagsmithClientError => write!(f, "Flagsmith API error: {}", &self.msg),
ErrorKind::FlagsmithAPIError => write!(f, "Flagsmith client error: {}", &self.msg),
}
}
}

impl From<url::ParseError> for Error {
fn from(e: url::ParseError) -> Self {
Error {
kind: ErrorKind::ParseError,
desc: e.to_string(),
}
Error::new(ErrorKind::FlagsmithClientError, e.to_string())
}
}

impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
Error {
kind: ErrorKind::RequestError,
desc: e.to_string(),
}
Error::new(ErrorKind::FlagsmithAPIError, e.to_string())
}
}

impl From<String> for Error {
fn from(s: String) -> Self {
Error {
kind: ErrorKind::AppError,
desc: s,
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::new(ErrorKind::FlagsmithAPIError, e.to_string())
}
}
Loading