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}