#![allow(dead_code)]
use reqwest::{Client, Error};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct MeterClient {
base_url: String,
token: String,
http_client: Client,
}
impl MeterClient {
pub fn new(base_url: String, token: String) -> Self {
MeterClient {
base_url,
token,
http_client: Client::new(),
}
}
fn get_auth_header(&self) -> String {
format!("Bearer {}", self.token)
}
pub async fn create_meter(&self, body: &CreateMeterRequest) -> Result<Meter, Error> {
let url = format!("{}/api/v1/meters", self.base_url);
let resp = self
.http_client
.post(url)
.header("Authorization", self.get_auth_header())
.header("Content-Type", "application/json")
.json(&body)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Meter>().await?)
}
pub async fn delete_meter(&self, meter_id_or_slug: &str) -> Result<(), Error> {
let url = format!("{}/api/v1/meters/{}", self.base_url, meter_id_or_slug);
self.http_client
.delete(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn get_meter(&self, meter_id_or_slug: &str) -> Result<Meter, Error> {
let url = format!("{}/api/v1/meters/{}", self.base_url, meter_id_or_slug);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Meter>().await?)
}
pub async fn list_meters(&self) -> Result<Vec<Meter>, Error> {
let url = format!("{}/api/v1/meters", self.base_url);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<Meter>>().await?)
}
pub async fn list_meter_subjects(&self, meter_id_or_slug: &str) -> Result<Vec<String>, Error> {
let url = format!(
"{}/api/v1/meters/{}/subjects",
self.base_url, meter_id_or_slug
);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<String>>().await?)
}
pub async fn query_meter(
&self,
meter_id_or_slug: &str,
params: &QueryParams,
) -> Result<QueryResponse, Error> {
let url = format!("{}/api/v1/meters/{}/query", self.base_url, meter_id_or_slug);
let request_builder = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header());
let mut query_params = vec![];
if let Some(ref from) = params.from {
query_params.push(("from", from.clone()));
}
if let Some(ref to) = params.to {
query_params.push(("to", to.clone()));
}
if let Some(ref window_size) = params.window_size {
query_params.push(("windowSize", window_size.clone()));
}
if let Some(ref window_tz) = params.window_time_zone {
query_params.push(("windowTimeZone", window_tz.clone()));
}
if let Some(subjects) = ¶ms.subject {
for s in subjects {
query_params.push(("subject", s.clone()));
}
}
let resp = request_builder
.query(&query_params)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<QueryResponse>().await?)
}
pub async fn update_meter(
&self,
meter_id_or_slug: &str,
body: &UpdateMeterRequest,
) -> Result<Meter, Error> {
let url = format!("{}/api/v1/meters/{}", self.base_url, meter_id_or_slug);
let resp = self
.http_client
.put(url)
.header("Authorization", self.get_auth_header())
.header("Content-Type", "application/json")
.json(&body)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Meter>().await?)
}
pub async fn ingest_events(&self, events: &[CloudEvent]) -> Result<(), Error> {
let url = format!("{}/api/v1/events", self.base_url);
self.http_client
.post(url)
.header("Authorization", self.get_auth_header())
.header("Content-Type", "application/json")
.json(&events)
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn list_events(
&self,
params: &ListEventsParams,
) -> Result<Vec<IngestedEvent>, Error> {
let url = format!("{}/api/v1/events", self.base_url);
let mut query_params = vec![];
if let Some(ref client_id) = params.client_id {
query_params.push(("clientId", client_id.clone()));
}
if let Some(ref ingested_at_from) = params.ingested_at_from {
query_params.push(("ingestedAtFrom", ingested_at_from.clone()));
}
if let Some(ref ingested_at_to) = params.ingested_at_to {
query_params.push(("ingestedAtTo", ingested_at_to.clone()));
}
if let Some(ref id) = params.id {
query_params.push(("id", id.clone()));
}
if let Some(ref subject) = params.subject {
query_params.push(("subject", subject.clone()));
}
if let Some(ref from) = params.from {
query_params.push(("from", from.clone()));
}
if let Some(ref to) = params.to {
query_params.push(("to", to.clone()));
}
if let Some(limit) = params.limit {
query_params.push(("limit", format!("{}", limit)));
}
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.query(&query_params)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<IngestedEvent>>().await?)
}
pub async fn create_entitlement(
&self,
subject_id_or_key: &str,
req: CreateEntitlementRequest,
) -> Result<Entitlement, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements",
self.base_url, subject_id_or_key
);
let resp = self
.http_client
.post(url)
.header("Authorization", self.get_auth_header())
.json(&req)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Entitlement>().await?)
}
pub async fn create_feature(&self, req: CreateFeatureRequest) -> Result<Feature, Error> {
let url = format!("{}/api/v1/features", self.base_url);
let resp = self
.http_client
.post(url)
.header("Authorization", self.get_auth_header())
.json(&req)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Feature>().await?)
}
pub async fn create_grant(
&self,
subject_id_or_key: &str,
entitlement_id_or_feature_key: &str,
req: GrantRequest,
) -> Result<Grant, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}/grants",
self.base_url, subject_id_or_key, entitlement_id_or_feature_key
);
let resp = self
.http_client
.post(url)
.header("Authorization", self.get_auth_header())
.json(&req)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Grant>().await?)
}
pub async fn delete_entitlement(
&self,
subject_id_or_key: &str,
entitlement_id: &str,
) -> Result<(), Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}",
self.base_url, subject_id_or_key, entitlement_id
);
self.http_client
.delete(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn delete_feature(&self, feature_id: &str) -> Result<(), Error> {
let url = format!("{}/api/v1/features/{}", self.base_url, feature_id);
self.http_client
.delete(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn get_entitlement(
&self,
subject_id_or_key: &str,
entitlement_id: &str,
) -> Result<Entitlement, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}",
self.base_url, subject_id_or_key, entitlement_id
);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Entitlement>().await?)
}
pub async fn get_entitlement_by_id(&self, entitlement_id: &str) -> Result<Entitlement, Error> {
let url = format!("{}/api/v1/entitlements/{}", self.base_url, entitlement_id);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Entitlement>().await?)
}
pub async fn get_entitlement_history(
&self,
subject_id_or_key: &str,
entitlement_id: &str,
from: Option<String>,
to: Option<String>,
window_size: String, window_time_zone: Option<String>,
) -> Result<Value, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}/history",
self.base_url, subject_id_or_key, entitlement_id
);
let mut query_params = vec![("windowSize", window_size)];
if let Some(f) = from {
query_params.push(("from", f));
}
if let Some(t) = to {
query_params.push(("to", t));
}
if let Some(tz) = window_time_zone {
query_params.push(("windowTimeZone", tz));
}
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.query(&query_params)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Value>().await?)
}
pub async fn get_entitlement_value(
&self,
subject_id_or_key: &str,
entitlement_id_or_feature_key: &str,
time: Option<String>,
) -> Result<Value, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}/value",
self.base_url, subject_id_or_key, entitlement_id_or_feature_key
);
let mut query_params = vec![];
if let Some(t) = time {
query_params.push(("time", t));
}
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.query(&query_params)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Value>().await?)
}
pub async fn get_feature(&self, feature_id: &str) -> Result<Feature, Error> {
let url = format!("{}/api/v1/features/{}", self.base_url, feature_id);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Feature>().await?)
}
pub async fn list_all_entitlements(&self) -> Result<Vec<Entitlement>, Error> {
let url = format!("{}/api/v1/entitlements", self.base_url);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<Entitlement>>().await?)
}
pub async fn list_entitlement_grants(
&self,
subject_id_or_key: &str,
entitlement_id_or_feature_key: &str,
) -> Result<Vec<Grant>, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}/grants",
self.base_url, subject_id_or_key, entitlement_id_or_feature_key
);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<Grant>>().await?)
}
pub async fn list_entitlements(
&self,
subject_id_or_key: &str,
) -> Result<Vec<Entitlement>, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements",
self.base_url, subject_id_or_key
);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<Entitlement>>().await?)
}
pub async fn list_features(&self) -> Result<Vec<Feature>, Error> {
let url = format!("{}/api/v1/features", self.base_url);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<Feature>>().await?)
}
pub async fn list_grants(&self) -> Result<Vec<Grant>, Error> {
let url = format!("{}/api/v1/grants", self.base_url);
let resp = self
.http_client
.get(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Vec<Grant>>().await?)
}
pub async fn override_entitlement(
&self,
subject_id_or_key: &str,
entitlement_id_or_feature_key: &str,
req: CreateEntitlementRequest,
) -> Result<Entitlement, Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}/override",
self.base_url, subject_id_or_key, entitlement_id_or_feature_key
);
let resp = self
.http_client
.put(url)
.header("Authorization", self.get_auth_header())
.json(&req)
.send()
.await?
.error_for_status()?;
Ok(resp.json::<Entitlement>().await?)
}
pub async fn reset_entitlement(
&self,
subject_id_or_key: &str,
entitlement_id: &str,
req: ResetEntitlementRequest,
) -> Result<(), Error> {
let url = format!(
"{}/api/v1/subjects/{}/entitlements/{}/reset",
self.base_url, subject_id_or_key, entitlement_id
);
self.http_client
.post(url)
.header("Authorization", self.get_auth_header())
.json(&req)
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn void_grant(&self, grant_id: &str) -> Result<(), Error> {
let url = format!("{}/api/v1/grants/{}", self.base_url, grant_id);
self.http_client
.delete(url)
.header("Authorization", self.get_auth_header())
.send()
.await?
.error_for_status()?;
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateMeterRequest {
#[serde(rename = "slug")]
pub slug: String,
#[serde(rename = "name")]
pub name: Option<String>,
#[serde(rename = "description")]
pub description: Option<String>,
#[serde(rename = "aggregation")]
pub aggregation: String,
#[serde(rename = "eventType")]
pub event_type: String,
#[serde(rename = "eventFrom")]
pub event_from: Option<String>,
#[serde(rename = "valueProperty")]
pub value_property: Option<String>,
#[serde(rename = "groupBy")]
pub group_by: Option<Value>,
#[serde(rename = "metadata")]
pub metadata: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateMeterRequest {
#[serde(rename = "name")]
pub name: Option<String>,
#[serde(rename = "description")]
pub description: Option<String>,
#[serde(rename = "groupBy")]
pub group_by: Option<Value>,
#[serde(rename = "metadata")]
pub metadata: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Meter {
pub id: String,
pub slug: String,
pub name: String,
pub description: Option<String>,
pub aggregation: String,
pub eventType: String,
pub valueProperty: Option<String>,
pub groupBy: Option<Value>,
pub createdAt: String,
pub updatedAt: String,
}
#[derive(Debug, Default)]
pub struct QueryParams {
pub from: Option<String>,
pub to: Option<String>,
pub window_size: Option<String>,
pub window_time_zone: Option<String>,
pub subject: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryResponse {
pub from: String,
pub to: String,
pub windowSize: Option<String>,
pub data: Vec<QueryData>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryData {
pub value: f64,
pub windowStart: Option<String>,
pub windowEnd: Option<String>,
pub subject: Option<String>,
#[serde(default)]
pub groupBy: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CloudEvent {
pub id: String,
pub source: String,
pub specversion: String,
#[serde(rename = "type")]
pub r#type: String,
pub subject: String,
pub time: Option<String>,
pub dataschema: Option<String>,
pub datacontenttype: Option<String>,
pub data: Option<Value>,
}
#[derive(Debug, Default)]
pub struct ListEventsParams {
pub client_id: Option<String>,
pub ingested_at_from: Option<String>,
pub ingested_at_to: Option<String>,
pub id: Option<String>,
pub subject: Option<String>,
pub from: Option<String>,
pub to: Option<String>,
pub limit: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IngestedEvent {
pub event: CloudEvent,
pub ingestedAt: String,
pub storedAt: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateEntitlementRequest {
pub feature_key: String,
pub feature_id: Option<String>,
pub r#type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_soft_limit: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_unlimited: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_period: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub measure_usage_from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Entitlement {
pub id: String,
pub feature_id: String,
pub feature_key: String,
pub subject_key: String,
pub r#type: String,
pub is_soft_limit: bool,
pub is_unlimited: bool,
pub created_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateFeatureRequest {
pub key: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub meter_slug: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meter_group_by_filters: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Feature {
pub id: String,
pub key: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GrantRequest {
pub amount: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<u8>,
pub effective_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expiration: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_rollover_amount: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_rollover_amount: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recurrence: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Grant {
pub id: String,
pub entitlement_id: String,
pub amount: f64,
pub effective_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResetEntitlementRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub effective_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retain_anchor: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preserve_overage: Option<bool>,
}