1use async_trait::async_trait;
46use open_feature::provider::{FeatureProvider, ProviderMetadata, ResolutionDetails};
47use open_feature::{
48 EvaluationContext, EvaluationContextFieldValue, EvaluationError, EvaluationErrorCode,
49 EvaluationResult, StructValue, Value,
50};
51use reqwest::Client;
52use reqwest::StatusCode;
53use serde_json;
54use tracing::{debug, error, instrument};
55
56use crate::FlagdOptions;
57
58#[derive(Debug)]
60pub struct RestResolver {
61 endpoint: String,
63 metadata: ProviderMetadata,
65 client: Client,
67}
68
69impl RestResolver {
70 pub fn new(options: &FlagdOptions) -> Self {
80 let endpoint = if let Some(uri) = &options.target_uri {
81 format!("http://{}", uri)
82 } else {
83 format!("http://{}:{}", options.host, options.port)
84 };
85 Self {
86 endpoint,
87 metadata: ProviderMetadata::new("flagd-rest-provider"),
88 client: Client::new(),
89 }
90 }
91}
92
93#[async_trait]
94impl FeatureProvider for RestResolver {
95 fn metadata(&self) -> &ProviderMetadata {
96 &self.metadata
97 }
98
99 #[instrument(skip(self, evaluation_context), fields(flag_key = %flag_key))]
110 async fn resolve_bool_value(
111 &self,
112 flag_key: &str,
113 evaluation_context: &EvaluationContext,
114 ) -> EvaluationResult<ResolutionDetails<bool>> {
115 debug!("Resolving boolean flag");
116
117 let payload = serde_json::json!({
118 "context": context_to_json(evaluation_context)
119 });
120
121 let response = self
122 .client
123 .post(format!(
124 "{}/ofrep/v1/evaluate/flags/{}",
125 self.endpoint, flag_key
126 ))
127 .header("Content-Type", "application/json")
128 .json(&payload)
129 .send()
130 .await
131 .map_err(|e| {
132 error!(error = %e, "Failed to resolve boolean value");
133 EvaluationError {
134 code: EvaluationErrorCode::General(
135 "Failed to resolve boolean value".to_string(),
136 ),
137 message: Some(e.to_string()),
138 }
139 })?;
140
141 debug!(status = response.status().as_u16(), "Received response");
142
143 match response.status() {
144 StatusCode::BAD_REQUEST => {
145 return Err(EvaluationError {
146 code: EvaluationErrorCode::InvalidContext,
147 message: Some("Invalid context".to_string()),
148 });
149 }
150 StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
151 return Err(EvaluationError {
152 code: EvaluationErrorCode::General(
153 "authentication/authorization error".to_string(),
154 ),
155 message: Some("authentication/authorization error".to_string()),
156 });
157 }
158 StatusCode::NOT_FOUND => {
159 return Err(EvaluationError {
160 code: EvaluationErrorCode::FlagNotFound,
161 message: Some(format!("Flag: {flag_key} not found")),
162 });
163 }
164 _ => {
165 let result = response.json::<serde_json::Value>().await.map_err(|e| {
166 error!(error = %e, "Failed to parse boolean response");
167 EvaluationError {
168 code: EvaluationErrorCode::ParseError,
169 message: Some(e.to_string()),
170 }
171 })?;
172
173 let value = result["value"].as_bool().ok_or_else(|| {
174 error!("Invalid boolean value in response");
175 EvaluationError {
176 code: EvaluationErrorCode::ParseError,
177 message: Some("Invalid boolean value".to_string()),
178 }
179 })?;
180
181 debug!(value = value, variant = ?result["variant"], "Flag evaluated");
182 Ok(ResolutionDetails {
183 value,
184 variant: result["variant"].as_str().map(String::from),
185 reason: Some(open_feature::EvaluationReason::Static),
186 flag_metadata: Default::default(),
187 })
188 }
189 }
190 }
191
192 #[instrument(skip(self, evaluation_context), fields(flag_key = %flag_key))]
203 async fn resolve_string_value(
204 &self,
205 flag_key: &str,
206 evaluation_context: &EvaluationContext,
207 ) -> EvaluationResult<ResolutionDetails<String>> {
208 debug!("Resolving string flag");
209
210 let payload = serde_json::json!({
211 "context": context_to_json(evaluation_context)
212 });
213
214 let response = self
215 .client
216 .post(format!(
217 "{}/ofrep/v1/evaluate/flags/{}",
218 self.endpoint, flag_key
219 ))
220 .header("Content-Type", "application/json")
221 .json(&payload)
222 .send()
223 .await
224 .map_err(|e| {
225 error!(error = %e, "Failed to resolve string value");
226 EvaluationError {
227 code: EvaluationErrorCode::General(
228 "Failed to resolve string value".to_string(),
229 ),
230 message: Some(e.to_string()),
231 }
232 })?;
233
234 debug!(status = response.status().as_u16(), "Received response");
235
236 match response.status() {
237 StatusCode::BAD_REQUEST => {
238 return Err(EvaluationError {
239 code: EvaluationErrorCode::InvalidContext,
240 message: Some("Invalid context".to_string()),
241 });
242 }
243 StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
244 return Err(EvaluationError {
245 code: EvaluationErrorCode::General(
246 "authentication/authorization error".to_string(),
247 ),
248 message: Some("authentication/authorization error".to_string()),
249 });
250 }
251 StatusCode::NOT_FOUND => {
252 return Err(EvaluationError {
253 code: EvaluationErrorCode::FlagNotFound,
254 message: Some(format!("Flag: {flag_key} not found")),
255 });
256 }
257 _ => {
258 let result = response.json::<serde_json::Value>().await.map_err(|e| {
259 error!(error = %e, "Failed to parse string response");
260 EvaluationError {
261 code: EvaluationErrorCode::ParseError,
262 message: Some(e.to_string()),
263 }
264 })?;
265
266 let value = result["value"]
267 .as_str()
268 .ok_or_else(|| {
269 error!("Invalid string value in response");
270 EvaluationError {
271 code: EvaluationErrorCode::ParseError,
272 message: Some("Invalid string value".to_string()),
273 }
274 })?
275 .to_string();
276
277 debug!(value = %value, variant = ?result["variant"], "Flag evaluated");
278 Ok(ResolutionDetails {
279 value,
280 variant: result["variant"].as_str().map(String::from),
281 reason: Some(open_feature::EvaluationReason::Static),
282 flag_metadata: Default::default(),
283 })
284 }
285 }
286 }
287
288 #[instrument(skip(self, evaluation_context), fields(flag_key = %flag_key))]
299 async fn resolve_float_value(
300 &self,
301 flag_key: &str,
302 evaluation_context: &EvaluationContext,
303 ) -> EvaluationResult<ResolutionDetails<f64>> {
304 debug!("Resolving float flag");
305
306 let payload = serde_json::json!({
307 "context": context_to_json(evaluation_context)
308 });
309
310 let response = self
311 .client
312 .post(format!(
313 "{}/ofrep/v1/evaluate/flags/{}",
314 self.endpoint, flag_key
315 ))
316 .header("Content-Type", "application/json")
317 .json(&payload)
318 .send()
319 .await
320 .map_err(|e| {
321 error!(error = %e, "Failed to resolve float value");
322 EvaluationError {
323 code: EvaluationErrorCode::General("Failed to resolve float value".to_string()),
324 message: Some(e.to_string()),
325 }
326 })?;
327
328 debug!(status = response.status().as_u16(), "Received response");
329
330 match response.status() {
331 StatusCode::BAD_REQUEST => {
332 return Err(EvaluationError {
333 code: EvaluationErrorCode::InvalidContext,
334 message: Some("Invalid context".to_string()),
335 });
336 }
337 StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
338 return Err(EvaluationError {
339 code: EvaluationErrorCode::General(
340 "authentication/authorization error".to_string(),
341 ),
342 message: Some("authentication/authorization error".to_string()),
343 });
344 }
345 StatusCode::NOT_FOUND => {
346 return Err(EvaluationError {
347 code: EvaluationErrorCode::FlagNotFound,
348 message: Some(format!("Flag: {flag_key} not found")),
349 });
350 }
351 _ => {
352 let result = response.json::<serde_json::Value>().await.map_err(|e| {
353 error!(error = %e, "Failed to parse float response");
354 EvaluationError {
355 code: EvaluationErrorCode::ParseError,
356 message: Some(e.to_string()),
357 }
358 })?;
359
360 let value = result["value"].as_f64().ok_or_else(|| {
361 error!("Invalid float value in response");
362 EvaluationError {
363 code: EvaluationErrorCode::ParseError,
364 message: Some("Invalid float value".to_string()),
365 }
366 })?;
367
368 debug!(value = value, variant = ?result["variant"], "Flag evaluated");
369 Ok(ResolutionDetails {
370 value,
371 variant: result["variant"].as_str().map(String::from),
372 reason: Some(open_feature::EvaluationReason::Static),
373 flag_metadata: Default::default(),
374 })
375 }
376 }
377 }
378
379 #[instrument(skip(self, evaluation_context), fields(flag_key = %flag_key))]
390 async fn resolve_int_value(
391 &self,
392 flag_key: &str,
393 evaluation_context: &EvaluationContext,
394 ) -> EvaluationResult<ResolutionDetails<i64>> {
395 debug!("Resolving integer flag");
396
397 let payload = serde_json::json!({
398 "context": context_to_json(evaluation_context)
399 });
400
401 let response = self
402 .client
403 .post(format!(
404 "{}/ofrep/v1/evaluate/flags/{}",
405 self.endpoint, flag_key
406 ))
407 .header("Content-Type", "application/json")
408 .json(&payload)
409 .send()
410 .await
411 .map_err(|e| {
412 error!(error = %e, "Failed to resolve integer value");
413 EvaluationError {
414 code: EvaluationErrorCode::General(
415 "Failed to resolve integer value".to_string(),
416 ),
417 message: Some(e.to_string()),
418 }
419 })?;
420
421 debug!(status = response.status().as_u16(), "Received response");
422
423 match response.status() {
424 StatusCode::BAD_REQUEST => {
425 return Err(EvaluationError {
426 code: EvaluationErrorCode::InvalidContext,
427 message: Some("Invalid context".to_string()),
428 });
429 }
430 StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
431 return Err(EvaluationError {
432 code: EvaluationErrorCode::General(
433 "authentication/authorization error".to_string(),
434 ),
435 message: Some("authentication/authorization error".to_string()),
436 });
437 }
438 StatusCode::NOT_FOUND => {
439 return Err(EvaluationError {
440 code: EvaluationErrorCode::FlagNotFound,
441 message: Some(format!("Flag: {flag_key} not found")),
442 });
443 }
444 _ => {
445 let result = response.json::<serde_json::Value>().await.map_err(|e| {
446 error!(error = %e, "Failed to parse integer response");
447 EvaluationError {
448 code: EvaluationErrorCode::ParseError,
449 message: Some(e.to_string()),
450 }
451 })?;
452
453 let value = result["value"].as_i64().ok_or_else(|| {
454 error!("Invalid integer value in response");
455 EvaluationError {
456 code: EvaluationErrorCode::ParseError,
457 message: Some("Invalid integer value".to_string()),
458 }
459 })?;
460
461 debug!(value = value, variant = ?result["variant"], "Flag evaluated");
462 Ok(ResolutionDetails {
463 value,
464 variant: result["variant"].as_str().map(String::from),
465 reason: Some(open_feature::EvaluationReason::Static),
466 flag_metadata: Default::default(),
467 })
468 }
469 }
470 }
471
472 #[instrument(skip(self, evaluation_context), fields(flag_key = %flag_key))]
484 async fn resolve_struct_value(
485 &self,
486 flag_key: &str,
487 evaluation_context: &EvaluationContext,
488 ) -> EvaluationResult<ResolutionDetails<StructValue>> {
489 debug!("Resolving struct flag");
490
491 let payload = serde_json::json!({
492 "context": context_to_json(evaluation_context)
493 });
494
495 let response = self
496 .client
497 .post(format!(
498 "{}/ofrep/v1/evaluate/flags/{}",
499 self.endpoint, flag_key
500 ))
501 .header("Content-Type", "application/json")
502 .json(&payload)
503 .send()
504 .await
505 .map_err(|e| {
506 error!(error = %e, "Failed to resolve struct value");
507 EvaluationError {
508 code: EvaluationErrorCode::General(
509 "Failed to resolve struct value".to_string(),
510 ),
511 message: Some(e.to_string()),
512 }
513 })?;
514
515 debug!(status = response.status().as_u16(), "Received response");
516
517 match response.status() {
518 StatusCode::BAD_REQUEST => {
519 return Err(EvaluationError {
520 code: EvaluationErrorCode::InvalidContext,
521 message: Some("Invalid context".to_string()),
522 });
523 }
524 StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
525 return Err(EvaluationError {
526 code: EvaluationErrorCode::General(
527 "authentication/authorization error".to_string(),
528 ),
529 message: Some("authentication/authorization error".to_string()),
530 });
531 }
532 StatusCode::NOT_FOUND => {
533 return Err(EvaluationError {
534 code: EvaluationErrorCode::FlagNotFound,
535 message: Some(format!("Flag: {flag_key} not found")),
536 });
537 }
538 _ => {
539 let result = response.json::<serde_json::Value>().await.map_err(|e| {
540 error!(error = %e, "Failed to parse struct response");
541 EvaluationError {
542 code: EvaluationErrorCode::ParseError,
543 message: Some(e.to_string()),
544 }
545 })?;
546
547 let value = result["value"]
548 .clone()
549 .into_feature_value()
550 .as_struct()
551 .ok_or_else(|| {
552 error!("Invalid struct value in response");
553 EvaluationError {
554 code: EvaluationErrorCode::ParseError,
555 message: Some("Invalid struct value".to_string()),
556 }
557 })?
558 .clone();
559
560 debug!(variant = ?result["variant"], "Flag evaluated");
561 Ok(ResolutionDetails {
562 value,
563 variant: result["variant"].as_str().map(String::from),
564 reason: Some(open_feature::EvaluationReason::Static),
565 flag_metadata: Default::default(),
566 })
567 }
568 }
569 }
570}
571
572fn context_to_json(context: &EvaluationContext) -> serde_json::Value {
582 let mut fields = serde_json::Map::new();
583
584 if let Some(targeting_key) = &context.targeting_key {
585 fields.insert(
586 "targetingKey".to_string(),
587 serde_json::Value::String(targeting_key.clone()),
588 );
589 }
590
591 for (key, value) in &context.custom_fields {
592 let json_value = match value {
593 EvaluationContextFieldValue::String(s) => serde_json::Value::String(s.clone()),
594 EvaluationContextFieldValue::Bool(b) => serde_json::Value::Bool(*b),
595 EvaluationContextFieldValue::Int(i) => serde_json::Value::Number((*i).into()),
596 EvaluationContextFieldValue::Float(f) => {
597 if let Some(n) = serde_json::Number::from_f64(*f) {
598 serde_json::Value::Number(n)
599 } else {
600 serde_json::Value::Null
601 }
602 }
603 EvaluationContextFieldValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
604 EvaluationContextFieldValue::Struct(s) => serde_json::Value::String(format!("{:?}", s)),
605 };
606 fields.insert(key.clone(), json_value);
607 }
608
609 serde_json::Value::Object(fields)
610}
611
612trait IntoFeatureValue {
614 fn into_feature_value(self) -> Value;
616}
617
618impl IntoFeatureValue for serde_json::Value {
619 fn into_feature_value(self) -> Value {
620 match self {
621 serde_json::Value::Bool(b) => Value::Bool(b),
622 serde_json::Value::Number(n) => {
623 if let Some(i) = n.as_i64() {
624 Value::Int(i)
625 } else if let Some(f) = n.as_f64() {
626 Value::Float(f)
627 } else {
628 Value::Int(0)
629 }
630 }
631 serde_json::Value::String(s) => Value::String(s),
632 serde_json::Value::Array(arr) => {
633 Value::Array(arr.into_iter().map(|v| v.into_feature_value()).collect())
634 }
635 serde_json::Value::Object(obj) => {
636 let mut struct_value = StructValue::default();
637 for (k, v) in obj {
638 struct_value.add_field(k, v.into_feature_value());
639 }
640 Value::Struct(struct_value)
641 }
642 serde_json::Value::Null => Value::String("".to_string()),
643 }
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use serde_json::json;
651 use test_log::test;
652 use wiremock::matchers::{method, path};
653 use wiremock::{Mock, MockServer, ResponseTemplate};
654
655 async fn setup_mock_server() -> (MockServer, RestResolver) {
656 let mock_server = MockServer::start().await;
657 let options = FlagdOptions {
658 host: mock_server.address().ip().to_string(),
659 port: mock_server.address().port(),
660 target_uri: None,
661 ..Default::default()
662 };
663 let resolver = RestResolver::new(&options);
664 (mock_server, resolver)
665 }
666
667 #[test(tokio::test)]
668 async fn test_resolve_bool_value() {
669 let (mock_server, resolver) = setup_mock_server().await;
670
671 Mock::given(method("POST"))
672 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
673 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
674 "value": true,
675 "variant": "on",
676 "reason": "STATIC"
677 })))
678 .mount(&mock_server)
679 .await;
680
681 let context = EvaluationContext::default().with_targeting_key("test-user");
682 let result = resolver
683 .resolve_bool_value("test-flag", &context)
684 .await
685 .unwrap();
686
687 assert_eq!(result.value, true);
688 assert_eq!(result.variant, Some("on".to_string()));
689 assert_eq!(result.reason, Some(open_feature::EvaluationReason::Static));
690 }
691
692 #[test(tokio::test)]
693 async fn test_resolve_string_value() {
694 let (mock_server, resolver) = setup_mock_server().await;
695
696 Mock::given(method("POST"))
697 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
698 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
699 "value": "test-value",
700 "variant": "key1",
701 "reason": "STATIC"
702 })))
703 .mount(&mock_server)
704 .await;
705
706 let context = EvaluationContext::default().with_targeting_key("test-user");
707 let result = resolver
708 .resolve_string_value("test-flag", &context)
709 .await
710 .unwrap();
711
712 assert_eq!(result.value, "test-value");
713 assert_eq!(result.variant, Some("key1".to_string()));
714 assert_eq!(result.reason, Some(open_feature::EvaluationReason::Static));
715 }
716
717 #[test(tokio::test)]
718 async fn test_resolve_float_value() {
719 let (mock_server, resolver) = setup_mock_server().await;
720
721 Mock::given(method("POST"))
722 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
723 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
724 "value": 1.23,
725 "variant": "one",
726 "reason": "STATIC"
727 })))
728 .mount(&mock_server)
729 .await;
730
731 let context = EvaluationContext::default().with_targeting_key("test-user");
732 let result = resolver
733 .resolve_float_value("test-flag", &context)
734 .await
735 .unwrap();
736
737 assert_eq!(result.value, 1.23);
738 assert_eq!(result.variant, Some("one".to_string()));
739 assert_eq!(result.reason, Some(open_feature::EvaluationReason::Static));
740 }
741
742 #[test(tokio::test)]
743 async fn test_resolve_int_value() {
744 let (mock_server, resolver) = setup_mock_server().await;
745
746 Mock::given(method("POST"))
747 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
748 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
749 "value": 42,
750 "variant": "one",
751 "reason": "STATIC"
752 })))
753 .mount(&mock_server)
754 .await;
755
756 let context = EvaluationContext::default().with_targeting_key("test-user");
757 let result = resolver
758 .resolve_int_value("test-flag", &context)
759 .await
760 .unwrap();
761
762 assert_eq!(result.value, 42);
763 assert_eq!(result.variant, Some("one".to_string()));
764 assert_eq!(result.reason, Some(open_feature::EvaluationReason::Static));
765 }
766
767 #[test(tokio::test)]
768 async fn test_resolve_struct_value() {
769 let (mock_server, resolver) = setup_mock_server().await;
770
771 Mock::given(method("POST"))
772 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
773 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
774 "value": {
775 "key": "val",
776 "number": 42,
777 "boolean": true,
778 "nested": {
779 "inner": "value"
780 }
781 },
782 "variant": "object1",
783 "reason": "STATIC"
784 })))
785 .mount(&mock_server)
786 .await;
787
788 let context = EvaluationContext::default().with_targeting_key("test-user");
789 let result = resolver
790 .resolve_struct_value("test-flag", &context)
791 .await
792 .unwrap();
793
794 let value = &result.value;
795 assert_eq!(value.fields.get("key").unwrap().as_str().unwrap(), "val");
796 assert_eq!(value.fields.get("number").unwrap().as_i64().unwrap(), 42);
797 assert_eq!(
798 value.fields.get("boolean").unwrap().as_bool().unwrap(),
799 true
800 );
801
802 let nested = value.fields.get("nested").unwrap().as_struct().unwrap();
803 assert_eq!(
804 nested.fields.get("inner").unwrap().as_str().unwrap(),
805 "value"
806 );
807
808 assert_eq!(result.variant, Some("object1".to_string()));
809 assert_eq!(result.reason, Some(open_feature::EvaluationReason::Static));
810 }
811
812 #[test(tokio::test)]
813 async fn test_error_400() {
814 let (mock_server, resolver) = setup_mock_server().await;
815
816 Mock::given(method("POST"))
817 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
818 .respond_with(ResponseTemplate::new(400).set_body_json(json!({})))
819 .mount(&mock_server)
820 .await;
821
822 let context = EvaluationContext::default();
823 let result_bool = resolver.resolve_bool_value("test-flag", &context).await;
824 let result_int = resolver.resolve_int_value("test-flag", &context).await;
825 let result_float = resolver.resolve_float_value("test-flag", &context).await;
826 let result_string = resolver.resolve_string_value("test-flag", &context).await;
827 let result_struct = resolver.resolve_struct_value("test-flag", &context).await;
828
829 assert!(result_bool.is_err());
830 assert!(result_int.is_err());
831 assert!(result_float.is_err());
832 assert!(result_string.is_err());
833 assert!(result_struct.is_err());
834
835 assert_eq!(
836 result_bool.unwrap_err().code,
837 EvaluationErrorCode::InvalidContext
838 );
839 assert_eq!(
840 result_int.unwrap_err().code,
841 EvaluationErrorCode::InvalidContext
842 );
843 assert_eq!(
844 result_float.unwrap_err().code,
845 EvaluationErrorCode::InvalidContext
846 );
847 assert_eq!(
848 result_string.unwrap_err().code,
849 EvaluationErrorCode::InvalidContext
850 );
851 assert_eq!(
852 result_struct.unwrap_err().code,
853 EvaluationErrorCode::InvalidContext
854 );
855 }
856
857 #[test(tokio::test)]
858 async fn test_error_401() {
859 let (mock_server, resolver) = setup_mock_server().await;
860
861 Mock::given(method("POST"))
862 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
863 .respond_with(ResponseTemplate::new(401).set_body_json(json!({})))
864 .mount(&mock_server)
865 .await;
866
867 let context = EvaluationContext::default();
868
869 let result_bool = resolver.resolve_bool_value("test-flag", &context).await;
870 let result_int = resolver.resolve_int_value("test-flag", &context).await;
871 let result_float = resolver.resolve_float_value("test-flag", &context).await;
872 let result_string = resolver.resolve_string_value("test-flag", &context).await;
873 let result_struct = resolver.resolve_struct_value("test-flag", &context).await;
874
875 assert!(result_bool.is_err());
876 assert!(result_int.is_err());
877 assert!(result_float.is_err());
878 assert!(result_string.is_err());
879 assert!(result_struct.is_err());
880
881 assert_eq!(
882 result_bool.unwrap_err().code,
883 EvaluationErrorCode::General("authentication/authorization error".to_string())
884 );
885 assert_eq!(
886 result_int.unwrap_err().code,
887 EvaluationErrorCode::General("authentication/authorization error".to_string())
888 );
889 assert_eq!(
890 result_float.unwrap_err().code,
891 EvaluationErrorCode::General("authentication/authorization error".to_string())
892 );
893 assert_eq!(
894 result_string.unwrap_err().code,
895 EvaluationErrorCode::General("authentication/authorization error".to_string())
896 );
897 assert_eq!(
898 result_struct.unwrap_err().code,
899 EvaluationErrorCode::General("authentication/authorization error".to_string())
900 );
901 }
902
903 #[test(tokio::test)]
904 async fn test_error_403() {
905 let (mock_server, resolver) = setup_mock_server().await;
906
907 Mock::given(method("POST"))
908 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
909 .respond_with(ResponseTemplate::new(403).set_body_json(json!({})))
910 .mount(&mock_server)
911 .await;
912
913 let context = EvaluationContext::default();
914
915 let result_bool = resolver.resolve_bool_value("test-flag", &context).await;
916 let result_int = resolver.resolve_int_value("test-flag", &context).await;
917 let result_float = resolver.resolve_float_value("test-flag", &context).await;
918 let result_string = resolver.resolve_string_value("test-flag", &context).await;
919 let result_struct = resolver.resolve_struct_value("test-flag", &context).await;
920
921 assert!(result_bool.is_err());
922 assert!(result_int.is_err());
923 assert!(result_float.is_err());
924 assert!(result_string.is_err());
925 assert!(result_struct.is_err());
926
927 assert_eq!(
928 result_bool.unwrap_err().code,
929 EvaluationErrorCode::General("authentication/authorization error".to_string())
930 );
931 assert_eq!(
932 result_int.unwrap_err().code,
933 EvaluationErrorCode::General("authentication/authorization error".to_string())
934 );
935 assert_eq!(
936 result_float.unwrap_err().code,
937 EvaluationErrorCode::General("authentication/authorization error".to_string())
938 );
939 assert_eq!(
940 result_string.unwrap_err().code,
941 EvaluationErrorCode::General("authentication/authorization error".to_string())
942 );
943 assert_eq!(
944 result_struct.unwrap_err().code,
945 EvaluationErrorCode::General("authentication/authorization error".to_string())
946 );
947 }
948
949 #[test(tokio::test)]
950 async fn test_error_404() {
951 let (mock_server, resolver) = setup_mock_server().await;
952
953 Mock::given(method("POST"))
954 .and(path("/ofrep/v1/evaluate/flags/test-flag"))
955 .respond_with(ResponseTemplate::new(404).set_body_json(json!({})))
956 .mount(&mock_server)
957 .await;
958
959 let context = EvaluationContext::default();
960
961 let result_bool = resolver.resolve_bool_value("test-flag", &context).await;
962 let result_int = resolver.resolve_int_value("test-flag", &context).await;
963 let result_float = resolver.resolve_float_value("test-flag", &context).await;
964 let result_string = resolver.resolve_string_value("test-flag", &context).await;
965 let result_struct = resolver.resolve_struct_value("test-flag", &context).await;
966
967 assert!(result_bool.is_err());
968 assert!(result_int.is_err());
969 assert!(result_float.is_err());
970 assert!(result_string.is_err());
971 assert!(result_struct.is_err());
972
973 let result_bool_error = result_bool.unwrap_err();
974 assert_eq!(result_bool_error.code, EvaluationErrorCode::FlagNotFound);
975 assert_eq!(
976 result_bool_error.message.unwrap(),
977 "Flag: test-flag not found"
978 );
979
980 let result_int_error = result_int.unwrap_err();
981 assert_eq!(result_int_error.code, EvaluationErrorCode::FlagNotFound);
982 assert_eq!(
983 result_int_error.message.unwrap(),
984 "Flag: test-flag not found"
985 );
986
987 let result_float_error = result_float.unwrap_err();
988 assert_eq!(result_float_error.code, EvaluationErrorCode::FlagNotFound);
989 assert_eq!(
990 result_float_error.message.unwrap(),
991 "Flag: test-flag not found"
992 );
993
994 let result_string_error = result_string.unwrap_err();
995 assert_eq!(result_string_error.code, EvaluationErrorCode::FlagNotFound);
996 assert_eq!(
997 result_string_error.message.unwrap(),
998 "Flag: test-flag not found"
999 );
1000
1001 let result_struct_error = result_struct.unwrap_err();
1002 assert_eq!(result_struct_error.code, EvaluationErrorCode::FlagNotFound);
1003 assert_eq!(
1004 result_struct_error.message.unwrap(),
1005 "Flag: test-flag not found"
1006 );
1007 }
1008}