Skip to content

Commit c79affd

Browse files
committed
Feat/client site rewrite (#3)
* update lib.rs * fix(models/Flag): use flagsmith value type * fixup! wip: get_identity_flags: Add signature * rewrite error module * fix(analytics): make new public * expand public interface and add default handler and analytics * fix references * Add analytics to models * update author * misc: improve interface remove redundant code * use flag engine from github * make traits optional for get_identity_flags * Add default trait to Flag * update example with default handler * exit threads on flagsmith client being dropped * fix warnings * update example: add readme * minor refactoring * Add tests for Flag * fix(*._identity_flags): use &str for identifier * feat(flag/model): Add a method to get value as string * tests(analytics_processor): Add tests and minor tweaks * tests: Add fixtures * feat(flag): Add value_as_type getters * test(polling): Add tests for polling thread and minor fixes * fix(default_handler): Add tests * test(error/mod): Add tests for errors * expose segments * Add flag engine * update flag engine crate in example cargo.toml * fix typo * bump flag-engine * Update default url to edge * fixup! bump version
1 parent 7b4137e commit c79affd

File tree

13 files changed

+1795
-404
lines changed

13 files changed

+1795
-404
lines changed

Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "flagsmith"
33
version = "1.0.0"
4-
authors = ["Tomasz Zdybał <[email protected]>"]
5-
edition = "2018"
4+
authors = ["Gagan Trivedi <[email protected]>"]
5+
edition = "2021"
66
license = "BSD-3-Clause"
77
description = "Flagsmith SDK for Rust"
88
homepage = "https://flagsmith.com/"
@@ -16,5 +16,13 @@ keywords = ["Flagsmith", "feature-flag", "remote-config"]
1616
[dependencies]
1717
serde = { version = "1.0", features = ["derive"] }
1818
serde_json = "1.0"
19-
reqwest = { version = "^0.10", features = ["blocking","json"] }
19+
reqwest = { version = "0.11", features = ["json", "blocking"] }
2020
url = "2.1"
21+
chrono = { version = "0.4"}
22+
log = "0.4"
23+
24+
flagsmith-flag-engine = "0.1.1"
25+
26+
[dev-dependencies]
27+
httpmock = "0.6"
28+
rstest = "0.12.0"

example/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
[net]
8+
git-fetch-with-cli = true # use the `git` executable for git operations
9+
10+
[dependencies]
11+
rocket = "0.4.10"
12+
serde = "1.0"
13+
serde_derive = "1.0"
14+
serde_json = "1.0"
15+
rocket_contrib = {version = "0.4.10", features=["tera_templates"]}
16+
flagsmith = {path="../"}
17+
18+
flagsmith-flag-engine = "0.1.0"
19+

example/Rocket.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[global]
2+
port = 5000
3+
workers = 1

example/readme.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Flagsmith Basic Rust Example
2+
3+
This directory contains a basic Rocket application which utilises Flagsmith. To run the example application, you'll
4+
need to go through the following steps:
5+
6+
1. Create an account, organisation and project on [Flagsmith](https://flagsmith.com)
7+
2. Create a feature in the project called "secret_button"
8+
3. Give the feature a value using the json editor as follows:
9+
10+
```json
11+
{"colour": "#ababab"}
12+
```
13+
14+
4. Set the environment variable `FLAGSMITH_ENVIRONMENT_KEY` with the environment key of one of the environments
15+
in flagsmith (This can be found on the 'settings' page accessed from the menu on the left under the chosen environment.)
16+
5. Run the app using `cargo run`
17+
6. Browse to http://localhost:5000
18+
19+
Now you can play around with the 'secret_button' feature in flagsmith, turn it on to show it and edit the colour in the
20+
json value to edit the colour of the button. You can also identify as a given user and then update the settings for the
21+
secret button feature for that user in the flagsmith interface to see the affect that has too.

example/src/main.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#![feature(proc_macro_hygiene, decl_macro)]
2+
3+
#[macro_use]
4+
extern crate rocket;
5+
#[macro_use]
6+
extern crate serde_derive;
7+
extern crate flagsmith;
8+
extern crate rocket_contrib;
9+
extern crate serde_json;
10+
11+
use std::env;
12+
13+
use rocket_contrib::templates::Template;
14+
15+
use flagsmith::{Flag, Flagsmith, FlagsmithOptions};
16+
use flagsmith_flag_engine::identities::Trait;
17+
use flagsmith_flag_engine::types::{FlagsmithValue, FlagsmithValueType};
18+
19+
#[derive(Serialize)]
20+
struct TemplateContext {
21+
show_button: bool,
22+
button_colour: String,
23+
identifier: String,
24+
}
25+
fn default_flag_handler(feature_name: &str) -> Flag {
26+
let mut flag: Flag = Default::default();
27+
if feature_name == "secret_button" {
28+
flag.value.value_type = FlagsmithValueType::String;
29+
flag.value.value = serde_json::json!({"colour": "#b8b8b8"}).to_string();
30+
}
31+
return flag;
32+
}
33+
34+
use std::{thread, time::Duration};
35+
#[get("/?<identifier>&<trait_key>&<trait_value>")]
36+
fn home(
37+
identifier: Option<String>,
38+
trait_key: Option<String>,
39+
trait_value: Option<String>,
40+
) -> Template {
41+
let options = FlagsmithOptions {
42+
default_flag_handler: Some(default_flag_handler),
43+
enable_local_evaluation: true,
44+
..Default::default()
45+
};
46+
47+
let flagsmith = Flagsmith::new(env::var("FLAGSMITH_ENVIRONMENT_KEY").expect("FLAGSMITH_ENVIRONMENT_KEY not found in environment"), options);
48+
let flags;
49+
if identifier.is_some() {
50+
let traits = match trait_key {
51+
Some(trait_key) if trait_key != "".to_string() => Some(vec![Trait {
52+
trait_key,
53+
trait_value: FlagsmithValue {
54+
value: trait_value.unwrap_or("".to_string()),
55+
value_type: FlagsmithValueType::None,
56+
},
57+
}]),
58+
Some(_) => None,
59+
None => None,
60+
};
61+
flags = flagsmith
62+
.get_identity_flags(identifier.as_ref().unwrap().to_string(), traits)
63+
.unwrap();
64+
} else {
65+
// Get the default flags for the current environment
66+
flags = flagsmith.get_environment_flags().unwrap();
67+
}
68+
69+
let show_button = flags.is_feature_enabled("secret_button").unwrap();
70+
let button_data = flags.get_feature_value_as_string("secret_button").unwrap();
71+
72+
let button_json: serde_json::Value = serde_json::from_str(&button_data).unwrap();
73+
let button_colour = button_json["colour"].as_str().unwrap().to_string();
74+
75+
let context = TemplateContext {
76+
show_button,
77+
button_colour,
78+
identifier: identifier.unwrap_or("World".to_string()),
79+
};
80+
81+
Template::render("home", &context)
82+
}
83+
84+
fn rocket() -> rocket::Rocket {
85+
rocket::ignite()
86+
.mount("/", routes![home])
87+
.attach(Template::fairing())
88+
}
89+
90+
fn main() {
91+
rocket().launch();
92+
}

example/templates/home.html.tera

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!doctype html>
2+
<head>
3+
<style></style>
4+
</head>
5+
<title>Flagsmith Example</title>
6+
<body>
7+
<p>Hello, {{ identifier }}.</p>
8+
{% if show_button %}
9+
<button style="background-color: {{ button_colour }}">A secret button</button>
10+
{% endif %}
11+
12+
<p></p>
13+
14+
<form action="/" method="GET">
15+
<h3>Identify as a user</h3>
16+
<label for="identifier">Identifier: </label><input name="identifier" id="identifier"><br>
17+
18+
<p>... with an optional user trait</p>
19+
<label for="trait_key">Trait key: </label><input name="trait_key" id="trait_key"><br>
20+
<label for="trait_value">Trait value: </label><input name="trait_value" id="trait_value"><br><br>
21+
22+
<button type="submit">Identify!</button>
23+
</form>
24+
25+
</body>

src/error.rs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,47 @@ use std::fmt;
44
/// Wraps several types of errors.
55
#[derive(Debug)]
66
pub struct Error {
7-
kind: ErrorKind,
8-
desc: String,
7+
pub kind: ErrorKind,
8+
pub msg: String,
99
}
1010

1111
/// Defines error kind.
12-
#[derive(Debug)]
12+
#[derive(Debug, PartialEq)]
1313
pub enum ErrorKind {
14-
ParseError,
15-
RequestError,
16-
AppError,
14+
FlagsmithClientError,
15+
FlagsmithAPIError,
16+
}
17+
impl Error{
18+
pub fn new(kind: ErrorKind, msg: String) -> Error{
19+
Error{
20+
kind,
21+
msg
22+
}
23+
}
1724
}
18-
1925
impl fmt::Display for Error {
2026
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2127
match self.kind {
22-
ErrorKind::ParseError => write!(f, "URL parsing error: {}", &self.desc),
23-
ErrorKind::RequestError => write!(f, "REST API request error: {}", &self.desc),
24-
ErrorKind::AppError => write!(f, "Application error: {}", &self.desc),
28+
ErrorKind::FlagsmithClientError => write!(f, "Flagsmith API error: {}", &self.msg),
29+
ErrorKind::FlagsmithAPIError => write!(f, "Flagsmith client error: {}", &self.msg),
2530
}
2631
}
2732
}
2833

2934
impl From<url::ParseError> for Error {
3035
fn from(e: url::ParseError) -> Self {
31-
Error {
32-
kind: ErrorKind::ParseError,
33-
desc: e.to_string(),
34-
}
36+
Error::new(ErrorKind::FlagsmithClientError, e.to_string())
3537
}
3638
}
3739

3840
impl From<reqwest::Error> for Error {
3941
fn from(e: reqwest::Error) -> Self {
40-
Error {
41-
kind: ErrorKind::RequestError,
42-
desc: e.to_string(),
43-
}
42+
Error::new(ErrorKind::FlagsmithAPIError, e.to_string())
4443
}
4544
}
4645

47-
impl From<String> for Error {
48-
fn from(s: String) -> Self {
49-
Error {
50-
kind: ErrorKind::AppError,
51-
desc: s,
52-
}
46+
impl From<serde_json::Error> for Error {
47+
fn from(e: serde_json::Error) -> Self {
48+
Error::new(ErrorKind::FlagsmithAPIError, e.to_string())
5349
}
5450
}

0 commit comments

Comments
 (0)