use std::env;
use anyhow::anyhow;
use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use thiserror::Error;
use crate::constants;
use crate::context::Context;
pub use crate::constants::DEFAULT_EDGE_BASE_URL;
pub use crate::constants::VERSION;
pub use crate::edge::Language;
pub use crate::evaluate::EvaluationError;
pub use crate::node::Node;
pub use crate::node_props::NodeProps;
pub use crate::node_props::NodePropsError;
pub use crate::node_props::NodePropsType;
pub use crate::primitive_nodes::*;
pub use crate::types::GraphqlQuery;
pub use crate::types::InitQuery;
pub use crate::types::StoredQuery;
#[derive(Error, Debug)]
pub enum HypertuneError {
#[error("hypertune token environment variable must be set if token is not supplied")]
MissingToken,
#[error("failed to deserialize from JSON: {0}")]
JsonDeserializationError(serde_json::Error),
#[error("failed to serialize into JSON: {0}")]
JsonSerializationError(serde_json::Error),
#[error("failed to initialize context: {0}")]
ContextInitializationError(anyhow::Error),
}
#[derive(Deserialize, Serialize, Clone)]
pub struct CreateOptions {
pub branch_name: Option<String>,
pub init_data_refresh_interval_ms: u64,
pub logs_flush_interval_ms: u64,
pub language: Language,
pub edge_base_url: String,
pub remote_logging_base_url: String,
}
impl Default for CreateOptions {
fn default() -> Self {
Self {
branch_name: None,
init_data_refresh_interval_ms: constants::DEFAULT_INIT_DATA_REFRESH_INTERVAL_MS,
logs_flush_interval_ms: constants::DEFAULT_LOGS_FLUSH_INTERVAL_MS,
language: Language::Rust,
edge_base_url: constants::DEFAULT_EDGE_BASE_URL.to_string(),
remote_logging_base_url: constants::DEFAULT_REMOTE_LOGGING_BASE_URL.to_string(),
}
}
}
pub fn create(
variable_values: impl TryIntoValue,
fallback_init_data: Option<&str>,
token: Option<&str>,
init_query: &InitQuery,
query: &str,
options: Option<CreateOptions>,
) -> Result<NodeProps, HypertuneError> {
let options = match options {
Some(options) => options,
None => Default::default(),
};
match create_inner(
variable_values,
fallback_init_data,
token,
init_query,
query,
options,
) {
Ok(node) => Ok(node),
Err(error) => {
eprintln!("Failed to initialize SDK: {}", error);
Err(error)
}
}
}
fn create_inner(
variable_values: impl TryIntoValue,
fallback_init_data: Option<&str>,
token: Option<&str>,
query_code: &InitQuery,
query: &str,
options: CreateOptions,
) -> Result<NodeProps, HypertuneError> {
let env_token =
env::var(constants::HYPERTUNE_TOKEN_ENV_VAR).map_err(|_| HypertuneError::MissingToken);
let token = match token {
Some(token) => Ok(token.to_string()),
None => env_token,
}?;
Context::initialize(
variable_values
.try_into_value()
.map_err(HypertuneError::JsonSerializationError)?,
token,
query_code.to_owned(),
serde_json::from_str(query).map_err(HypertuneError::JsonDeserializationError)?,
options.branch_name,
options.init_data_refresh_interval_ms,
options.logs_flush_interval_ms,
options.edge_base_url,
options.remote_logging_base_url,
options.language,
fallback_init_data
.map(serde_json::from_str)
.transpose()
.map_err(HypertuneError::JsonDeserializationError)?,
)
.map_err(HypertuneError::ContextInitializationError)
}
pub trait NodeMethods {
fn get_field(&self, field: &str, args: impl TryIntoValue) -> NodeProps;
fn evaluate(&self) -> Result<Value, EvaluationError>;
fn log_unexpected_type_error(&self);
fn log_unexpected_value_error(&self, value: Result<Value, EvaluationError>);
}
fn get_field_helper(node: &Node, field: &str, args: impl TryIntoValue) -> Result<NodeProps> {
match node.get_field(
field,
args.try_into_value()
.map_err(HypertuneError::JsonSerializationError)?,
) {
Ok(props) => Ok(props),
Err(NodePropsError::GetField(props)) => Ok(props),
}
}
impl<T> NodeMethods for T
where
T: GetNode,
{
fn get_field(&self, field: &str, args: impl TryIntoValue) -> NodeProps {
let node = self.get_node();
match get_field_helper(node, field, args) {
Ok(props) => props,
Err(_) => node.get_error_node_props(),
}
}
fn evaluate(&self) -> Result<Value, EvaluationError> {
self.get_node().evaluate()
}
fn log_unexpected_type_error(&self) {
self.get_node().log_unexpected_type_error()
}
fn log_unexpected_value_error(&self, value: Result<Value, EvaluationError>) {
self.get_node().log_unexpected_value_error(value)
}
}
pub trait TryIntoValue {
fn try_into_value(self) -> Result<Value, serde_json::Error>;
}
impl<T> TryIntoValue for T
where
T: serde::ser::Serialize,
{
fn try_into_value(self) -> Result<Value, serde_json::Error> {
serde_json::to_value(self)
}
}
pub trait TryFromValue
where
Self: Sized,
{
fn try_from_value(value: Option<Value>) -> Result<Self>;
}
impl<T> TryFromValue for T
where
T: serde::de::DeserializeOwned,
{
fn try_from_value(value: Option<Value>) -> Result<Self> {
match value {
Some(value) => Ok(serde_json::from_value(value)?),
None => Err(anyhow!("Empty value supplied")),
}
}
}