Skip to main content

hypertune/
sdk.rs

1use std::env;
2
3use anyhow::anyhow;
4use anyhow::Result;
5use serde::Deserialize;
6use serde::Serialize;
7use serde_json::Value;
8use thiserror::Error;
9
10use crate::constants;
11use crate::context::Context;
12
13pub use crate::constants::DEFAULT_EDGE_BASE_URL;
14pub use crate::constants::VERSION;
15pub use crate::edge::Language;
16pub use crate::evaluate::EvaluationError;
17pub use crate::node::Node;
18pub use crate::node_props::NodeProps;
19pub use crate::node_props::NodePropsError;
20pub use crate::node_props::NodePropsType;
21pub use crate::primitive_nodes::*;
22pub use crate::types::GraphqlQuery;
23pub use crate::types::InitQuery;
24pub use crate::types::StoredQuery;
25
26#[derive(Error, Debug)]
27pub enum HypertuneError {
28    #[error("hypertune token environment variable must be set if token is not supplied")]
29    MissingToken,
30
31    #[error("failed to deserialize from JSON: {0}")]
32    JsonDeserializationError(serde_json::Error),
33
34    #[error("failed to serialize into JSON: {0}")]
35    JsonSerializationError(serde_json::Error),
36
37    #[error("failed to initialize context: {0}")]
38    ContextInitializationError(anyhow::Error),
39}
40#[derive(Deserialize, Serialize, Clone)]
41pub struct CreateOptions {
42    pub branch_name: Option<String>,
43    pub init_data_refresh_interval_ms: u64,
44    pub logs_flush_interval_ms: u64,
45    pub language: Language,
46    pub edge_base_url: String,
47    pub remote_logging_base_url: String,
48}
49
50impl Default for CreateOptions {
51    fn default() -> Self {
52        Self {
53            branch_name: None,
54            init_data_refresh_interval_ms: constants::DEFAULT_INIT_DATA_REFRESH_INTERVAL_MS,
55            logs_flush_interval_ms: constants::DEFAULT_LOGS_FLUSH_INTERVAL_MS,
56            language: Language::Rust,
57            edge_base_url: constants::DEFAULT_EDGE_BASE_URL.to_string(),
58            remote_logging_base_url: constants::DEFAULT_REMOTE_LOGGING_BASE_URL.to_string(),
59        }
60    }
61}
62
63pub fn create(
64    variable_values: impl TryIntoValue,
65    fallback_init_data: Option<&str>,
66    token: Option<&str>,
67    init_query: &InitQuery,
68    query: &str,
69    options: Option<CreateOptions>,
70) -> Result<NodeProps, HypertuneError> {
71    let options = match options {
72        Some(options) => options,
73        None => Default::default(),
74    };
75
76    match create_inner(
77        variable_values,
78        fallback_init_data,
79        token,
80        init_query,
81        query,
82        options,
83    ) {
84        Ok(node) => Ok(node),
85        Err(error) => {
86            eprintln!("Failed to initialize SDK: {}", error);
87            Err(error)
88        }
89    }
90}
91
92fn create_inner(
93    variable_values: impl TryIntoValue,
94    fallback_init_data: Option<&str>,
95    token: Option<&str>,
96    query_code: &InitQuery,
97    query: &str,
98    options: CreateOptions,
99) -> Result<NodeProps, HypertuneError> {
100    let env_token =
101        env::var(constants::HYPERTUNE_TOKEN_ENV_VAR).map_err(|_| HypertuneError::MissingToken);
102
103    let token = match token {
104        Some(token) => Ok(token.to_string()),
105        None => env_token,
106    }?;
107
108    Context::initialize(
109        variable_values
110            .try_into_value()
111            .map_err(HypertuneError::JsonSerializationError)?,
112        token,
113        query_code.to_owned(),
114        serde_json::from_str(query).map_err(HypertuneError::JsonDeserializationError)?,
115        options.branch_name,
116        options.init_data_refresh_interval_ms,
117        options.logs_flush_interval_ms,
118        options.edge_base_url,
119        options.remote_logging_base_url,
120        options.language,
121        fallback_init_data
122            .map(serde_json::from_str)
123            .transpose()
124            .map_err(HypertuneError::JsonDeserializationError)?,
125    )
126    .map_err(HypertuneError::ContextInitializationError)
127}
128
129pub trait NodeMethods {
130    fn get_field(&self, field: &str, args: impl TryIntoValue) -> NodeProps;
131
132    fn evaluate(&self) -> Result<Value, EvaluationError>;
133
134    fn log_unexpected_type_error(&self);
135
136    fn log_unexpected_value_error(&self, value: Result<Value, EvaluationError>);
137}
138
139fn get_field_helper(node: &Node, field: &str, args: impl TryIntoValue) -> Result<NodeProps> {
140    match node.get_field(
141        field,
142        args.try_into_value()
143            .map_err(HypertuneError::JsonSerializationError)?,
144    ) {
145        Ok(props) => Ok(props),
146        Err(NodePropsError::GetField(props)) => Ok(props),
147    }
148}
149
150impl<T> NodeMethods for T
151where
152    T: GetNode,
153{
154    fn get_field(&self, field: &str, args: impl TryIntoValue) -> NodeProps {
155        let node = self.get_node();
156        match get_field_helper(node, field, args) {
157            Ok(props) => props,
158            Err(_) => node.get_error_node_props(),
159        }
160    }
161
162    fn evaluate(&self) -> Result<Value, EvaluationError> {
163        self.get_node().evaluate()
164    }
165
166    fn log_unexpected_type_error(&self) {
167        self.get_node().log_unexpected_type_error()
168    }
169
170    fn log_unexpected_value_error(&self, value: Result<Value, EvaluationError>) {
171        self.get_node().log_unexpected_value_error(value)
172    }
173}
174
175pub trait TryIntoValue {
176    fn try_into_value(self) -> Result<Value, serde_json::Error>;
177}
178
179impl<T> TryIntoValue for T
180where
181    T: serde::ser::Serialize,
182{
183    fn try_into_value(self) -> Result<Value, serde_json::Error> {
184        serde_json::to_value(self)
185    }
186}
187
188pub trait TryFromValue
189where
190    Self: Sized,
191{
192    fn try_from_value(value: Option<Value>) -> Result<Self>;
193}
194
195impl<T> TryFromValue for T
196where
197    T: serde::de::DeserializeOwned,
198{
199    fn try_from_value(value: Option<Value>) -> Result<Self> {
200        match value {
201            Some(value) => Ok(serde_json::from_value(value)?),
202            None => Err(anyhow!("Empty value supplied")),
203        }
204    }
205}