diff --git a/flipt-client-java/src/main/java/com/flipt/api/FliptClient.java b/flipt-client-java/src/main/java/com/flipt/api/FliptClient.java index 4aebc03..a6055dc 100644 --- a/flipt-client-java/src/main/java/com/flipt/api/FliptClient.java +++ b/flipt-client-java/src/main/java/com/flipt/api/FliptClient.java @@ -5,12 +5,12 @@ import okhttp3.OkHttpClient; public class FliptClient { - private Evaluation evaluation; + private final Evaluation evaluation; - public FliptClient(String url, String token, int timeout) { + private FliptClient(String url, String clientToken, String jwtToken, int timeout) { OkHttpClient httpClient = new OkHttpClient.Builder().callTimeout(Duration.ofSeconds(timeout)).build(); - this.evaluation = new Evaluation(httpClient, url, token); + this.evaluation = new Evaluation(httpClient, url, clientToken, jwtToken); } public Evaluation evaluation() { @@ -24,7 +24,9 @@ public static FliptClientBuilder builder() { public static final class FliptClientBuilder { private String baseURL = "http://localhost:8080"; - private String token = ""; + private String clientToken = ""; + + private String jwtToken = ""; private int timeout = 60; @@ -35,8 +37,13 @@ public FliptClientBuilder url(String url) { return this; } - public FliptClientBuilder token(String token) { - this.token = token; + public FliptClientBuilder clientToken(String token) { + this.clientToken = token; + return this; + } + + public FliptClientBuilder jwtToken(String token) { + this.jwtToken = token; return this; } @@ -46,7 +53,7 @@ public FliptClientBuilder timeout(int timeout) { } public FliptClient build() { - return new FliptClient(baseURL, token, timeout); + return new FliptClient(baseURL, clientToken, jwtToken, timeout); } } } diff --git a/flipt-client-java/src/main/java/com/flipt/api/evaluation/Evaluation.java b/flipt-client-java/src/main/java/com/flipt/api/evaluation/Evaluation.java index 95afc91..598f47d 100644 --- a/flipt-client-java/src/main/java/com/flipt/api/evaluation/Evaluation.java +++ b/flipt-client-java/src/main/java/com/flipt/api/evaluation/Evaluation.java @@ -14,13 +14,15 @@ public class Evaluation { private final OkHttpClient httpClient; private final String baseURL; - private final String token; + private final String clientToken; + private final String jwtToken; private final ObjectMapper objectMapper; - public Evaluation(OkHttpClient httpClient, String baseURL, String token) { + public Evaluation(OkHttpClient httpClient, String baseURL, String clientToken, String jwtToken) { this.httpClient = httpClient; this.baseURL = baseURL; - this.token = token; + this.clientToken = clientToken; + this.jwtToken = jwtToken; this.objectMapper = JsonMapper.builder() .addModule(new Jdk8Module()) @@ -66,8 +68,10 @@ private Request.Builder makeRequest(EvaluationRequest request, URL url) { Request.Builder httpRequest = new Request.Builder().url(url).method("POST", body); - if (!this.token.isEmpty()) { - httpRequest.addHeader("Authorization", String.format("Bearer %s", this.token)); + if (!this.clientToken.isEmpty()) { + httpRequest.addHeader("Authorization", String.format("Bearer %s", this.clientToken)); + } else if (!this.jwtToken.isEmpty()) { + httpRequest.addHeader("Authorization", String.format("JWT %s", this.jwtToken)); } return httpRequest; @@ -118,8 +122,10 @@ public BatchEvaluationResponse batch(BatchEvaluationRequest request) { Request.Builder httpRequest = new Request.Builder().url(url).method("POST", body); - if (!this.token.isEmpty()) { - httpRequest.addHeader("Authorization", String.format("Bearer %s", this.token)); + if (!this.clientToken.isEmpty()) { + httpRequest.addHeader("Authorization", String.format("Bearer %s", this.clientToken)); + } else if (!this.jwtToken.isEmpty()) { + httpRequest.addHeader("Authorization", String.format("JWT %s", this.jwtToken)); } try { diff --git a/flipt-client-java/src/test/java/TestFliptClient.java b/flipt-client-java/src/test/java/TestFliptClient.java index 2ea2797..3aca9bd 100644 --- a/flipt-client-java/src/test/java/TestFliptClient.java +++ b/flipt-client-java/src/test/java/TestFliptClient.java @@ -13,7 +13,7 @@ void testVariant() { assert !fliptURL.isEmpty(); assert !authToken.isEmpty(); - FliptClient fc = FliptClient.builder().url(fliptURL).token(authToken).build(); + FliptClient fc = FliptClient.builder().url(fliptURL).clientToken(authToken).build(); Map context = new HashMap<>(); context.put("fizz", "buzz"); @@ -35,7 +35,7 @@ void testBoolean() { assert !fliptURL.isEmpty(); assert !authToken.isEmpty(); - FliptClient fc = FliptClient.builder().url(fliptURL).token(authToken).build(); + FliptClient fc = FliptClient.builder().url(fliptURL).clientToken(authToken).build(); Map context = new HashMap<>(); context.put("fizz", "buzz"); @@ -57,7 +57,7 @@ void testBatch() { assert !fliptURL.isEmpty(); assert !authToken.isEmpty(); - FliptClient fc = FliptClient.builder().url(fliptURL).token(authToken).build(); + FliptClient fc = FliptClient.builder().url(fliptURL).clientToken(authToken).build(); Map context = new HashMap<>(); context.put("fizz", "buzz"); diff --git a/flipt-client-node/src/evaluation/index.ts b/flipt-client-node/src/evaluation/index.ts index 7face34..6883555 100644 --- a/flipt-client-node/src/evaluation/index.ts +++ b/flipt-client-node/src/evaluation/index.ts @@ -8,26 +8,34 @@ import { export class Evaluation { private url: string; - private token: string; + private headers: object; private timeout: number; - public constructor(url: string, token: string, timeout: number) { + public constructor( + url: string, + clientToken: string, + jwtToken: string, + timeout: number + ) { this.url = url; - this.token = token; + this.headers = {}; + if (!!clientToken) { + this.headers["Authorization"] = `Bearer ${clientToken}`; + } + if (!!jwtToken) { + this.headers["Authorization"] = `JWT ${jwtToken}`; + } this.timeout = timeout; } public async variant( request: EvaluationRequest ): Promise { - const headers = {}; - if (this.token !== "") { - headers["Authorization"] = `Bearer ${this.token}`; - } - const response = await fetch(`${this.url}/evaluate/v1/variant`, { method: "POST", - headers, + headers: { + ...this.headers + }, body: JSON.stringify(request), signal: AbortSignal.timeout(this.timeout * 1000) }); @@ -46,14 +54,11 @@ export class Evaluation { public async boolean( request: EvaluationRequest ): Promise { - const headers = {}; - if (this.token !== "") { - headers["Authorization"] = `Bearer ${this.token}`; - } - const response = await fetch(`${this.url}/evaluate/v1/boolean`, { method: "POST", - headers, + headers: { + ...this.headers + }, body: JSON.stringify(request), signal: AbortSignal.timeout(this.timeout * 1000) }); @@ -72,14 +77,11 @@ export class Evaluation { public async batch( request: BatchEvaluationRequest ): Promise { - const headers = {}; - if (this.token !== "") { - headers["Authorization"] = `Bearer ${this.token}`; - } - const response = await fetch(`${this.url}/evaluate/v1/batch`, { method: "POST", - headers, + headers: { + ...this.headers + }, body: JSON.stringify(request), signal: AbortSignal.timeout(this.timeout * 1000) }); diff --git a/flipt-client-node/src/index.ts b/flipt-client-node/src/index.ts index e6eb918..f11d8b1 100644 --- a/flipt-client-node/src/index.ts +++ b/flipt-client-node/src/index.ts @@ -1,30 +1,39 @@ import { Evaluation } from "./evaluation"; -interface FliptApiClientOptions { +interface FliptClientOptions { url?: string; - token?: string; + clientToken?: string; + jwtToken?: string; timeout?: number; } -const defaultFliptClientOptions: FliptApiClientOptions = { +const defaultFliptClientOptions: FliptClientOptions = { url: "http://localhost:8080", - token: "", + clientToken: "", + jwtToken: "", timeout: 60 }; export class FliptClient { public evaluation: Evaluation; - public constructor(options?: FliptApiClientOptions) { + public constructor(options?: FliptClientOptions) { const clientOptions = { ...defaultFliptClientOptions }; + if (options?.clientToken !== undefined && options?.jwtToken != undefined) { + throw new Error("can not define both client token and jwt token"); + } + if (options?.url !== undefined) { clientOptions.url = options.url; } - if (options?.token !== undefined) { - clientOptions.token = options.token; + if (options?.clientToken !== undefined) { + clientOptions.clientToken = options.clientToken; + } + if (options?.jwtToken !== undefined) { + clientOptions.jwtToken = options.jwtToken; } if (options?.timeout !== undefined) { clientOptions.timeout = options.timeout; @@ -32,7 +41,8 @@ export class FliptClient { this.evaluation = new Evaluation( clientOptions.url, - clientOptions.token, + clientOptions.clientToken, + clientOptions.jwtToken, clientOptions.timeout ); } diff --git a/flipt-client-node/src/integration.test.ts b/flipt-client-node/src/integration.test.ts index 8d1514c..3a816f4 100644 --- a/flipt-client-node/src/integration.test.ts +++ b/flipt-client-node/src/integration.test.ts @@ -13,7 +13,7 @@ if (!authToken) { } test("variant", async () => { - const client = new FliptClient({ url: fliptUrl, token: authToken }); + const client = new FliptClient({ url: fliptUrl, clientToken: authToken }); const variant = await client.evaluation.variant({ namespaceKey: "default", @@ -30,7 +30,7 @@ test("variant", async () => { }); test("boolean", async () => { - const client = new FliptClient({ url: fliptUrl, token: authToken }); + const client = new FliptClient({ url: fliptUrl, clientToken: authToken }); const boolean = await client.evaluation.boolean({ namespaceKey: "default", @@ -45,7 +45,7 @@ test("boolean", async () => { }); test("batch", async () => { - const client = new FliptClient({ url: fliptUrl, token: authToken }); + const client = new FliptClient({ url: fliptUrl, clientToken: authToken }); const batch = await client.evaluation.batch({ requests: [ { diff --git a/flipt-client-python/flipt/__init__.py b/flipt-client-python/flipt/__init__.py index 9afdd7b..c5473fc 100644 --- a/flipt-client-python/flipt/__init__.py +++ b/flipt-client-python/flipt/__init__.py @@ -1,8 +1,13 @@ +import typing from .evaluation import Evaluation class FliptClient: def __init__( - self, url: str = "http://localhost:8080", token: str = "", timeout: int = 60 + self, + url: str = "http://localhost:8080", + client_token: typing.Optional[str] = None, + jwt_token: typing.Optional[str] = None, + timeout: int = 60, ): - self.evaluation = Evaluation(url, token, timeout) + self.evaluation = Evaluation(url, client_token, jwt_token, timeout) diff --git a/flipt-client-python/flipt/evaluation/__init__.py b/flipt-client-python/flipt/evaluation/__init__.py index 3b960b6..02b263e 100644 --- a/flipt-client-python/flipt/evaluation/__init__.py +++ b/flipt-client-python/flipt/evaluation/__init__.py @@ -1,4 +1,5 @@ import httpx +import typing import json from .models import ( BatchEvaluationRequest, @@ -10,19 +11,25 @@ class Evaluation: - def __init__(self, url: str, token: str, timeout: int): + def __init__( + self, + url: str, + client_token: typing.Optional[str], + jwt_token: typing.Optional[str], + timeout: int, + ): self.url = url - self.token = token + self.headers = {} + if client_token != None: + self.headers["Authorization"] = f"Bearer {client_token}" + if jwt_token != None: + self.headers["Authorization"] = f"JWT {jwt_token}" self.timeout = timeout def variant(self, request: EvaluationRequest) -> VariantEvaluationResponse: - headers = {} - if self.token != "": - headers["Authorization"] = f"Bearer {self.token}" - response = httpx.post( f"{self.url}/evaluate/v1/variant", - headers=headers, + headers=self.headers, json=request.model_dump(), timeout=self.timeout, ) @@ -40,13 +47,9 @@ def variant(self, request: EvaluationRequest) -> VariantEvaluationResponse: return VariantEvaluationResponse.model_validate_json(variant_response) def boolean(self, request: EvaluationRequest) -> BooleanEvaluationResponse: - headers = {} - if self.token != "": - headers["Authorization"] = f"Bearer {self.token}" - response = httpx.post( f"{self.url}/evaluate/v1/boolean", - headers=headers, + headers=self.headers, json=request.model_dump(), timeout=self.timeout, ) @@ -64,13 +67,9 @@ def boolean(self, request: EvaluationRequest) -> BooleanEvaluationResponse: return BooleanEvaluationResponse.model_validate_json(boolean_response) def batch(self, request: BatchEvaluationRequest) -> BatchEvaluationResponse: - headers = {} - if self.token != "": - headers["Authorization"] = f"Bearer {self.token}" - response = httpx.post( f"{self.url}/evaluate/v1/batch", - headers=headers, + headers=self.headers, json=request.model_dump(), timeout=self.timeout, ) diff --git a/flipt-client-python/tests/__init__.py b/flipt-client-python/tests/__init__.py index 8ae4952..e16f403 100644 --- a/flipt-client-python/tests/__init__.py +++ b/flipt-client-python/tests/__init__.py @@ -14,7 +14,7 @@ def setUp(self) -> None: if auth_token is None: raise Exception("FLIPT_AUTH_TOKEN not set") - self.flipt_client = FliptClient(url=flipt_url, token=auth_token) + self.flipt_client = FliptClient(url=flipt_url, client_token=auth_token) def test_variant(self): variant = self.flipt_client.evaluation.variant( diff --git a/flipt-client-rust/Cargo.toml b/flipt-client-rust/Cargo.toml index 1be18c8..8111eb2 100644 --- a/flipt-client-rust/Cargo.toml +++ b/flipt-client-rust/Cargo.toml @@ -7,6 +7,9 @@ author = ["Flipt Devs "] license = "MIT" keywords = ["flipt"] +[features] +flipt_integration = [] + [dependencies] chrono = { version = "0.4.23", default-features = false, features = ["serde", "clock"] } reqwest = { version = "0.11.13", default-features = false, features = ["json", "rustls-tls"] } diff --git a/flipt-client-rust/examples/evaluations.rs b/flipt-client-rust/examples/evaluations.rs index 211100f..b7deda8 100644 --- a/flipt-client-rust/examples/evaluations.rs +++ b/flipt-client-rust/examples/evaluations.rs @@ -2,10 +2,11 @@ use std::collections::HashMap; +use flipt::api::FliptClient; use flipt::evaluation::models::{BatchEvaluationRequest, EvaluationRequest}; -use flipt::FliptClient; #[tokio::main] +#[cfg_attr(not(feature = "flipt_integration"), ignore)] async fn main() -> Result<(), Box> { let client = FliptClient::default(); diff --git a/flipt-client-rust/src/api/mod.rs b/flipt-client-rust/src/api/mod.rs new file mode 100644 index 0000000..fac49ea --- /dev/null +++ b/flipt-client-rust/src/api/mod.rs @@ -0,0 +1,49 @@ +use crate::error::ClientError; +use crate::evaluation::Evaluation; +use crate::{AuthScheme, Config}; +use reqwest::header::HeaderMap; +use std::time::Duration; + +pub struct FliptClient { + pub evaluation: Evaluation, +} + +impl FliptClient { + pub fn new(config: Config) -> Result { + let mut header_map = HeaderMap::new(); + + match config.auth_scheme { + AuthScheme::BearerToken(bearer) => { + header_map.insert( + "Authorization", + format!("Bearer {}", bearer).parse().unwrap(), + ); + } + AuthScheme::JWT(jwt) => { + header_map.insert("Authorization", format!("JWT {}", jwt).parse().unwrap()); + } + AuthScheme::None => {} + }; + + let client = match reqwest::Client::builder() + .timeout(Duration::from_secs(config.timeout)) + .default_headers(header_map) + .build() + { + Ok(client) => client, + Err(e) => { + return Err(ClientError::new(e.to_string())); + } + }; + + Ok(Self { + evaluation: Evaluation::new(client, config.endpoint), + }) + } +} + +impl Default for FliptClient { + fn default() -> Self { + Self::new(Config::default()).unwrap() + } +} diff --git a/flipt-client-rust/src/lib.rs b/flipt-client-rust/src/lib.rs index 35ecf87..c6a3db4 100644 --- a/flipt-client-rust/src/lib.rs +++ b/flipt-client-rust/src/lib.rs @@ -1,47 +1,46 @@ +pub mod api; pub mod error; pub mod evaluation; pub mod util; -use error::ClientError; -use evaluation::Evaluation; -use reqwest::header::HeaderMap; -use std::time::Duration; use url::Url; -pub struct FliptClient { - pub evaluation: Evaluation, +#[derive(Debug, Clone)] +pub struct Config { + endpoint: Url, + auth_scheme: AuthScheme, + timeout: u64, } -impl FliptClient { - pub fn new(url: Url, token: String, timeout: u64) -> Result { - let mut header_map = HeaderMap::new(); - - if !token.is_empty() { - header_map.insert( - "Authorization", - format!("Bearer {}", token).parse().unwrap(), - ); +impl Default for Config { + fn default() -> Self { + Self { + endpoint: Url::parse("http://localhost:8080").unwrap(), + auth_scheme: AuthScheme::None, + timeout: 60, } + } +} - let client = match reqwest::Client::builder() - .timeout(Duration::from_secs(timeout)) - .default_headers(header_map) - .build() - { - Ok(client) => client, - Err(e) => { - return Err(ClientError::new(e.to_string())); - } - }; - - Ok(Self { - evaluation: Evaluation::new(client, url), - }) +impl Config { + pub fn new(endpoint: Url, auth_scheme: AuthScheme, timeout: u64) -> Self { + Self { + endpoint, + auth_scheme, + timeout, + } } } -impl Default for FliptClient { +#[derive(Debug, Clone)] +pub enum AuthScheme { + None, + BearerToken(String), + JWT(String), +} + +impl Default for AuthScheme { fn default() -> Self { - Self::new(Url::parse("http://localhost:8080").unwrap(), "".into(), 60).unwrap() + Self::None } } diff --git a/flipt-client-rust/tests/integration.rs b/flipt-client-rust/tests/integration.rs index 1354cff..29ae1ee 100644 --- a/flipt-client-rust/tests/integration.rs +++ b/flipt-client-rust/tests/integration.rs @@ -1,5 +1,6 @@ +use flipt::api::FliptClient; use flipt::evaluation::models::{BatchEvaluationRequest, EvaluationRequest}; -use flipt::FliptClient; +use flipt::{AuthScheme, Config}; use std::{collections::HashMap, env}; use url::Url; @@ -8,7 +9,12 @@ async fn tests() { let url = env::var("FLIPT_URL").unwrap(); let token = env::var("FLIPT_AUTH_TOKEN").unwrap(); - let flipt_client = FliptClient::new(Url::parse(&url).unwrap(), token, 60).unwrap(); + let flipt_client = FliptClient::new(Config::new( + Url::parse(&url).unwrap(), + AuthScheme::BearerToken(token), + 60, + )) + .unwrap(); let mut context: HashMap = HashMap::new(); context.insert("fizz".into(), "buzz".into()); diff --git a/test/main.go b/test/main.go index 417d30b..00c4dfc 100644 --- a/test/main.go +++ b/test/main.go @@ -144,7 +144,7 @@ func rustTests(ctx context.Context, client *dagger.Client, flipt *dagger.Contain WithServiceBinding("flipt", flipt.WithExec(nil).AsService()). WithEnvVariable("FLIPT_URL", "http://flipt:8080"). WithEnvVariable("FLIPT_AUTH_TOKEN", "secret"). - WithExec([]string{"cargo", "test"}). + WithExec([]string{"cargo", "test", "--features", "flipt_integration", "--test", "integration"}). Sync(ctx) return err