feature_probe_server_sdk/
feature_probe.rs

1use crate::sync::SyncType;
2use crate::{
3    config::Config,
4    evaluate::{EvalDetail, Repository},
5};
6use crate::{sync::Synchronizer, FPConfig};
7use crate::{sync::UpdateCallback, user::FPUser};
8use crate::{FPDetail, SdkAuthorization, Toggle};
9use event::event::AccessEvent;
10use event::event::CustomEvent;
11use event::event::DebugEvent;
12use event::event::Event;
13use event::recorder::unix_timestamp;
14use event::recorder::EventRecorder;
15use feature_probe_event as event;
16#[cfg(feature = "realtime")]
17use futures_util::FutureExt;
18use parking_lot::RwLock;
19use serde_json::Value;
20#[cfg(feature = "realtime")]
21use socketio_rs::Client;
22use std::collections::HashMap;
23use std::fmt::Debug;
24use std::sync::Arc;
25use tracing::{trace, warn};
26
27#[cfg(feature = "realtime")]
28type SocketCallback = std::pin::Pin<Box<dyn futures_util::Future<Output = ()> + Send>>;
29
30#[derive(Default, Clone)]
31pub struct FeatureProbe {
32    repo: Arc<RwLock<Repository>>,
33    syncer: Option<Synchronizer>,
34    event_recorder: Option<EventRecorder>,
35    config: Config,
36    should_stop: Arc<RwLock<bool>>,
37    #[cfg(feature = "realtime")]
38    socket: Option<Client>,
39}
40
41impl Debug for FeatureProbe {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.debug_tuple("FeatureProbe")
44            .field(&self.repo)
45            .field(&self.syncer)
46            .field(&self.config)
47            .field(&self.should_stop)
48            .finish()
49    }
50}
51
52impl FeatureProbe {
53    pub fn new(config: FPConfig) -> Self {
54        let config = config.build();
55        let mut slf = Self {
56            config,
57            ..Default::default()
58        };
59
60        slf.start();
61        slf
62    }
63
64    pub fn new_for_test(toggle: &str, value: Value) -> Self {
65        let mut toggles = HashMap::new();
66        toggles.insert(toggle.to_owned(), value);
67        FeatureProbe::new_for_tests(toggles)
68    }
69
70    pub fn new_for_tests(toggles: HashMap<String, Value>) -> Self {
71        let mut repo = Repository::default();
72        for (key, val) in toggles {
73            repo.toggles
74                .insert(key.clone(), Toggle::new_for_test(key, val));
75        }
76
77        Self {
78            repo: Arc::new(RwLock::new(repo)),
79            ..Default::default()
80        }
81    }
82
83    pub fn bool_value(&self, toggle: &str, user: &FPUser, default: bool) -> bool {
84        self.generic_eval(toggle, user, default, false, |v| v.as_bool())
85            .value
86    }
87
88    pub fn string_value(&self, toggle: &str, user: &FPUser, default: String) -> String {
89        self.generic_eval(toggle, user, default, false, |v| {
90            v.as_str().map(|s| s.to_owned())
91        })
92        .value
93    }
94
95    pub fn number_value(&self, toggle: &str, user: &FPUser, default: f64) -> f64 {
96        self.generic_eval(toggle, user, default, false, |v| v.as_f64())
97            .value
98    }
99
100    pub fn json_value(&self, toggle: &str, user: &FPUser, default: Value) -> Value {
101        self.generic_eval(toggle, user, default, false, Some).value
102    }
103
104    pub fn bool_detail(&self, toggle: &str, user: &FPUser, default: bool) -> FPDetail<bool> {
105        self.generic_eval(toggle, user, default, true, |v| v.as_bool())
106    }
107
108    pub fn string_detail(&self, toggle: &str, user: &FPUser, default: String) -> FPDetail<String> {
109        self.generic_eval(toggle, user, default, true, |v| {
110            v.as_str().map(|x| x.to_owned())
111        })
112    }
113
114    pub fn number_detail(&self, toggle: &str, user: &FPUser, default: f64) -> FPDetail<f64> {
115        self.generic_eval(toggle, user, default, true, |v| v.as_f64())
116    }
117
118    pub fn json_detail(&self, toggle: &str, user: &FPUser, default: Value) -> FPDetail<Value> {
119        self.generic_eval(toggle, user, default, true, Some)
120    }
121
122    pub fn track(&self, event_name: &str, user: &FPUser, value: Option<f64>) {
123        let recorder = match self.event_recorder.as_ref() {
124            None => {
125                warn!("Event Recorder no ready.");
126                return;
127            }
128            Some(recorder) => recorder,
129        };
130        let event = CustomEvent {
131            kind: "custom".to_string(),
132            time: unix_timestamp(),
133            user: user.key(),
134            name: event_name.to_string(),
135            value,
136        };
137        recorder.record_event(Event::CustomEvent(event));
138    }
139
140    pub fn new_with(server_key: String, repo: Repository) -> Self {
141        Self {
142            config: Config {
143                server_sdk_key: server_key,
144                ..Default::default()
145            },
146            repo: Arc::new(RwLock::new(repo)),
147            syncer: None,
148            event_recorder: None,
149            should_stop: Arc::new(RwLock::new(false)),
150            #[cfg(feature = "realtime")]
151            socket: None,
152        }
153    }
154
155    pub fn close(&self) {
156        trace!("closing featureprobe client");
157        if let Some(recorder) = &self.event_recorder {
158            recorder.flush();
159        }
160        let mut should_stop = self.should_stop.write();
161        *should_stop = true;
162    }
163
164    pub fn initialized(&self) -> bool {
165        match &self.syncer {
166            Some(s) => s.initialized(),
167            None => false,
168        }
169    }
170
171    pub fn set_update_callback(&mut self, update_callback: UpdateCallback) {
172        if let Some(syncer) = &mut self.syncer {
173            syncer.set_update_callback(update_callback)
174        }
175    }
176
177    pub fn version(&self) -> Option<u128> {
178        self.syncer.as_ref().map(|s| s.version()).flatten()
179    }
180
181    fn generic_eval<T: Default + Debug>(
182        &self,
183        toggle: &str,
184        user: &FPUser,
185        default: T,
186        is_detail: bool,
187        transform: fn(Value) -> Option<T>,
188    ) -> FPDetail<T> {
189        let (value, reason, detail) = match self.eval(toggle, user, is_detail) {
190            None => (
191                default,
192                Some(format!("Toggle:[{toggle}] not exist")),
193                Default::default(),
194            ),
195            Some(mut d) => match d.value.take() {
196                None => (default, None, d), // Serve error.
197                Some(v) => match transform(v) {
198                    None => (default, Some("Value type mismatch.".to_string()), d), // Transform error.
199                    Some(typed_v) => (typed_v, None, d),
200                },
201            },
202        };
203
204        FPDetail {
205            value,
206            reason: reason.unwrap_or(detail.reason),
207            rule_index: detail.rule_index,
208            variation_index: detail.variation_index,
209            version: detail.version,
210        }
211    }
212
213    fn eval(&self, toggle: &str, user: &FPUser, is_detail: bool) -> Option<EvalDetail<Value>> {
214        let repo = self.repo.read();
215        let debug_until_time = repo.debug_until_time;
216        let detail = repo.toggles.get(toggle).map(|toggle| {
217            toggle.eval(
218                user,
219                &repo.segments,
220                &repo.toggles,
221                is_detail,
222                self.config.max_prerequisites_deep,
223                debug_until_time,
224            )
225        });
226
227        if let Some(recorder) = &self.event_recorder {
228            let track_access_events = repo
229                .toggles
230                .get(toggle)
231                .map(|t| t.track_access_events())
232                .unwrap_or(false);
233            record_event(
234                recorder.clone(),
235                track_access_events,
236                toggle,
237                user,
238                detail.clone(),
239                debug_until_time,
240            )
241        }
242
243        detail.map(|mut d| {
244            d.debug_until_time = debug_until_time;
245            d
246        })
247    }
248
249    fn start(&mut self) {
250        self.sync();
251
252        #[cfg(feature = "realtime")]
253        self.connect_socket();
254
255        if self.config.track_events {
256            self.flush_events();
257        }
258    }
259
260    fn sync(&mut self) {
261        trace!("sync url {}", &self.config.toggles_url);
262        let toggles_url = self.config.toggles_url.clone();
263        let refresh_interval = self.config.refresh_interval;
264        let auth = SdkAuthorization(self.config.server_sdk_key.clone()).encode();
265        let repo = self.repo.clone();
266        let syncer = Synchronizer::new(
267            toggles_url,
268            refresh_interval,
269            auth,
270            self.config.http_client.clone().unwrap_or_default(),
271            repo,
272        );
273        self.syncer = Some(syncer.clone());
274        syncer.start_sync(self.config.start_wait, self.should_stop.clone());
275    }
276
277    pub fn sync_now(&self, t: SyncType) {
278        trace!("sync now url {}", &self.config.toggles_url);
279        let syncer = match &self.syncer {
280            Some(syncer) => syncer.clone(),
281            None => return,
282        };
283        syncer.sync_now(t);
284    }
285
286    #[cfg(feature = "realtime")]
287    fn connect_socket(&mut self) {
288        let mut slf = self.clone();
289        let slf2 = self.clone();
290        let nsp = self.config.realtime_path.clone();
291        tokio::spawn(async move {
292            let url = slf.config.realtime_url;
293            let server_sdk_key = slf.config.server_sdk_key.clone();
294            trace!("connect_socket {}", url);
295            let client = socketio_rs::ClientBuilder::new(url.clone())
296                .namespace(&nsp)
297                .on(socketio_rs::Event::Connect, move |_, socket, _| {
298                    Self::socket_on_connect(socket, server_sdk_key.clone())
299                })
300                .on(
301                    "update",
302                    move |payload: Option<socketio_rs::Payload>, _, _| {
303                        Self::socket_on_update(slf2.clone(), payload)
304                    },
305                )
306                .on("error", |err, _, _| {
307                    async move { tracing::error!("socket on error: {:#?}", err) }.boxed()
308                })
309                .connect()
310                .await;
311            match client {
312                Err(e) => tracing::error!("connect_socket error: {:?}", e),
313                Ok(client) => slf.socket = Some(client),
314            };
315        });
316    }
317
318    #[cfg(feature = "realtime")]
319    fn socket_on_connect(socket: socketio_rs::Socket, server_sdk_key: String) -> SocketCallback {
320        let sdk_key = server_sdk_key;
321        trace!("socket_on_connect: {:?}", sdk_key);
322        async move {
323            if let Err(e) = socket
324                .emit("register", serde_json::json!({ "key": sdk_key }))
325                .await
326            {
327                tracing::error!("register error: {:?}", e);
328            }
329        }
330        .boxed()
331    }
332
333    #[cfg(feature = "realtime")]
334    fn socket_on_update(slf: Self, payload: Option<socketio_rs::Payload>) -> SocketCallback {
335        trace!("socket_on_update: {:?}", payload);
336        async move {
337            if let Some(syncer) = &slf.syncer {
338                syncer.sync_now(SyncType::Realtime);
339            } else {
340                warn!("socket receive update event, but no synchronizer");
341            }
342        }
343        .boxed()
344    }
345
346    fn flush_events(&mut self) {
347        trace!("flush_events");
348        let events_url = self.config.events_url.clone();
349        let flush_interval = self.config.refresh_interval;
350        let auth = SdkAuthorization(self.config.server_sdk_key.clone()).encode();
351        let should_stop = self.should_stop.clone();
352        let event_recorder = EventRecorder::new(
353            events_url,
354            auth,
355            (*crate::USER_AGENT).clone(),
356            flush_interval,
357            100,
358            should_stop,
359        );
360        self.event_recorder = Some(event_recorder);
361    }
362
363    #[cfg(feature = "internal")]
364    pub fn repo(&self) -> Arc<RwLock<Repository>> {
365        self.repo.clone()
366    }
367}
368
369fn record_event(
370    recorder: EventRecorder,
371    track_access_events: bool,
372    toggle: &str,
373    user: &FPUser,
374    detail: Option<EvalDetail<Value>>,
375    debug_until_time: Option<u64>,
376) {
377    let toggle = toggle.to_owned();
378    let user = user.key();
379    let user_detail = serde_json::to_value(user.clone()).unwrap_or_default();
380
381    tokio::spawn(async move {
382        let ts = unix_timestamp();
383        record_access(
384            &recorder,
385            &toggle,
386            user.clone(),
387            track_access_events,
388            &detail,
389            ts,
390        );
391        record_debug(
392            &recorder,
393            &toggle,
394            user,
395            user_detail,
396            debug_until_time,
397            &detail,
398            ts,
399        );
400    });
401}
402
403fn record_access(
404    recorder: &EventRecorder,
405    toggle: &str,
406    user: String,
407    track_access_events: bool,
408    detail: &Option<EvalDetail<Value>>,
409    ts: u128,
410) -> Option<()> {
411    let detail = detail.as_ref()?;
412    let value = detail.value.as_ref()?;
413    let event = AccessEvent {
414        kind: "access".to_string(),
415        time: ts,
416        key: toggle.to_owned(),
417        user,
418        value: value.clone(),
419        variation_index: detail.variation_index?,
420        version: detail.version,
421        rule_index: detail.rule_index,
422        track_access_events,
423    };
424    recorder.record_event(Event::AccessEvent(event));
425    None
426}
427
428#[allow(clippy::too_many_arguments)]
429fn record_debug(
430    recorder: &EventRecorder,
431    toggle: &str,
432    user: String,
433    user_detail: Value,
434    debug_until_time: Option<u64>,
435    detail: &Option<EvalDetail<Value>>,
436    ts: u128,
437) -> Option<()> {
438    let detail = detail.as_ref()?;
439    let value = detail.value.as_ref()?;
440    if let Some(debug_until_time) = debug_until_time {
441        if debug_until_time as u128 >= ts {
442            let debug = DebugEvent {
443                kind: "debug".to_string(),
444                time: ts,
445                key: toggle.to_owned(),
446                user,
447                user_detail,
448                value: value.clone(),
449                variation_index: detail.variation_index?,
450                version: detail.version,
451                rule_index: detail.rule_index,
452                reason: Some(detail.reason.to_string()),
453            };
454            recorder.record_event(Event::DebugEvent(debug));
455        }
456    }
457    None
458}
459
460#[cfg(test)]
461mod tests {
462    use serde_json::json;
463
464    use super::*;
465    use crate::FPError;
466    use std::fs;
467    use std::path::PathBuf;
468
469    #[test]
470    fn test_feature_probe_bool() {
471        let json = load_local_json("resources/fixtures/repo.json");
472        let fp = FeatureProbe::new_with("secret key".to_string(), json.unwrap());
473        let u = FPUser::new().with("name", "bob").with("city", "1");
474
475        assert!(fp.bool_value("bool_toggle", &u, false));
476        assert!(fp.bool_detail("bool_toggle", &u, false).value);
477    }
478
479    #[test]
480    fn test_feature_probe_number() {
481        let json = load_local_json("resources/fixtures/repo.json");
482        let fp = FeatureProbe::new_with("secret key".to_string(), json.unwrap());
483        let u = FPUser::new().with("name", "bob").with("city", "1");
484
485        assert_eq!(fp.number_value("number_toggle", &u, 0.0), 1.0);
486        assert_eq!(fp.number_detail("number_toggle", &u, 0.0).value, 1.0);
487    }
488
489    #[test]
490    fn test_feature_probe_string() {
491        let json = load_local_json("resources/fixtures/repo.json");
492        let fp = FeatureProbe::new_with("secret key".to_string(), json.unwrap());
493        let u = FPUser::new().with("name", "bob").with("city", "1");
494
495        assert_eq!(
496            fp.string_value("string_toggle", &u, "".to_string()),
497            "1".to_owned()
498        );
499        assert_eq!(
500            fp.string_detail("string_toggle", &u, "".to_owned()).value,
501            "1".to_owned()
502        );
503    }
504
505    #[test]
506    fn test_feature_probe_json() {
507        let json = load_local_json("resources/fixtures/repo.json");
508        let fp = FeatureProbe::new_with("secret key".to_string(), json.unwrap());
509        let u = FPUser::new().with("name", "bob").with("city", "1");
510
511        assert!(fp
512            .json_value("json_toggle", &u, json!(""))
513            .get("variation_0")
514            .is_some());
515        assert!(fp
516            .json_detail("json_toggle", &u, json!(""))
517            .value
518            .get("variation_0")
519            .is_some());
520    }
521
522    #[test]
523    fn test_feature_probe_none_exist_toggle() {
524        let json = load_local_json("resources/fixtures/repo.json");
525        let fp = FeatureProbe::new_with("secret key".to_string(), json.unwrap());
526        let u = FPUser::new();
527
528        assert!(fp.bool_value("none_exist_toggle", &u, true));
529        let d = fp.bool_detail("none_exist_toggle", &u, true);
530        assert!(d.value);
531        assert_eq!(d.rule_index, None);
532    }
533
534    #[test]
535    fn test_for_ut() {
536        let fp = FeatureProbe::new_for_test("toggle_1", Value::Bool(false));
537        let u = FPUser::new();
538        assert!(!fp.bool_value("toggle_1", &u, true));
539
540        let mut toggles: HashMap<String, Value> = HashMap::new();
541        toggles.insert("toggle_2".to_owned(), json!(12.5));
542        toggles.insert("toggle_3".to_owned(), json!("value"));
543        let fp = FeatureProbe::new_for_tests(toggles);
544        assert_eq!(fp.number_value("toggle_2", &u, 20.0), 12.5);
545        assert_eq!(fp.string_value("toggle_3", &u, "val".to_owned()), "value");
546    }
547
548    #[test]
549    fn test_feature_probe_record_debug() {
550        let json = load_local_json("resources/fixtures/repo.json");
551        let mut repo = json.unwrap();
552        repo.debug_until_time = Some(unix_timestamp() as u64 + 60 * 1000);
553        let fp = FeatureProbe::new_with("secret key".to_string(), repo);
554        let u = FPUser::new().with("name", "bob").with("city", "1");
555        fp.bool_value("bool_toggle", &u, false);
556    }
557
558    fn load_local_json(file: &str) -> Result<Repository, FPError> {
559        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
560        path.push(file);
561        let json_str = fs::read_to_string(path).unwrap();
562        let repo = crate::evaluate::load_json(&json_str);
563        assert!(repo.is_ok(), "err is {:?}", repo);
564        repo
565    }
566}
567
568#[cfg(test)]
569mod server_sdk_contract_tests {
570    use crate::{FPDetail, FPError, FPUser, FeatureProbe, Repository};
571    use serde::{Deserialize, Serialize};
572    use serde_json::Value;
573    use std::fmt::Debug;
574    use std::fs;
575    use std::path::PathBuf;
576    use std::string::String;
577
578    #[allow(dead_code)]
579    pub(crate) fn load_tests_json(json_str: &str) -> Result<Tests, FPError> {
580        serde_json::from_str::<Tests>(json_str)
581            .map_err(|e| FPError::JsonError(json_str.to_owned(), e))
582    }
583
584    #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
585    #[serde(rename_all = "camelCase")]
586    pub struct Tests {
587        pub(crate) tests: Vec<Scenario>,
588    }
589
590    #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
591    pub struct Scenario {
592        pub(crate) scenario: String,
593        pub(crate) cases: Vec<Case>,
594        pub(crate) fixture: Repository,
595    }
596
597    #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
598    #[serde(rename_all = "camelCase")]
599    pub struct Case {
600        pub(crate) name: String,
601        pub(crate) user: User,
602        pub(crate) function: Function,
603        pub(crate) expect_result: ExpectResult,
604    }
605
606    #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
607    #[serde(rename_all = "camelCase")]
608    pub struct User {
609        pub(crate) key: String,
610        pub(crate) custom_values: Vec<KeyValue>,
611    }
612
613    #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
614    pub struct KeyValue {
615        pub(crate) key: String,
616        pub(crate) value: String,
617    }
618
619    #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
620    pub struct Function {
621        pub(crate) name: String,
622        pub(crate) toggle: String,
623        pub(crate) default: Value,
624    }
625
626    #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
627    #[serde(rename_all = "camelCase")]
628    pub struct ExpectResult {
629        pub(crate) value: Value,
630        pub(crate) reason: Option<String>,
631        pub(crate) rule_index: Option<usize>,
632        pub(crate) no_rule_index: Option<bool>,
633        pub(crate) version: Option<u64>,
634    }
635
636    #[test]
637    fn test_contract() {
638        let root = load_test_json("resources/fixtures/spec/spec/toggle_simple_spec.json");
639        assert!(root.is_ok());
640
641        for scenario in root.unwrap().tests {
642            println!("scenario: {}", scenario.scenario);
643            assert!(!scenario.cases.is_empty());
644
645            let fp = FeatureProbe::new_with("secret key".to_string(), scenario.fixture);
646
647            for case in scenario.cases {
648                println!("  case: {}", case.name);
649
650                let mut user = FPUser::new().stable_rollout(case.user.key.clone());
651                for custom_value in &case.user.custom_values {
652                    user = user.with(custom_value.key.clone(), custom_value.value.clone());
653                }
654
655                macro_rules! validate_value {
656                    ( $fun:ident, $default:expr, $expect:expr) => {
657                        let ret = fp.$fun(case.function.toggle.as_str(), &user, $default);
658                        assert_eq!(ret, $expect);
659                    };
660                }
661
662                macro_rules! validate_detail {
663                    ( $fun:ident, $default:expr, $expect:expr) => {
664                        let ret = fp.$fun(case.function.toggle.as_str(), &user, $default);
665                        assert_eq!(ret.value, $expect);
666                        assert_detail(&case, ret);
667                    };
668                }
669
670                match case.function.name.as_str() {
671                    "bool_value" => {
672                        validate_value!(
673                            bool_value,
674                            case.function.default.as_bool().unwrap(),
675                            case.expect_result.value.as_bool().unwrap()
676                        );
677                    }
678                    "string_value" => {
679                        validate_value!(
680                            string_value,
681                            case.function.default.as_str().unwrap().to_string(),
682                            case.expect_result.value.as_str().unwrap().to_string()
683                        );
684                    }
685                    "number_value" => {
686                        validate_value!(
687                            number_value,
688                            case.function.default.as_f64().unwrap(),
689                            case.expect_result.value.as_f64().unwrap()
690                        );
691                    }
692                    "json_value" => {
693                        validate_value!(
694                            json_value,
695                            case.function.default,
696                            case.expect_result.value
697                        );
698                    }
699                    "bool_detail" => {
700                        validate_detail!(
701                            bool_detail,
702                            case.function.default.as_bool().unwrap(),
703                            case.expect_result.value
704                        );
705                    }
706                    "string_detail" => {
707                        validate_detail!(
708                            string_detail,
709                            case.function.default.as_str().unwrap().to_string(),
710                            case.expect_result.value
711                        );
712                    }
713                    "number_detail" => {
714                        validate_detail!(
715                            number_detail,
716                            case.function.default.as_f64().unwrap(),
717                            case.expect_result.value
718                        );
719                    }
720                    "json_detail" => {
721                        validate_detail!(
722                            json_detail,
723                            case.function.default.clone(),
724                            case.expect_result.value
725                        );
726                    }
727                    _ => panic!("function name {} not found.", case.function.name),
728                }
729            }
730        }
731    }
732
733    fn assert_detail<T: Default + Debug>(case: &Case, ret: FPDetail<T>) {
734        match &case.expect_result.reason {
735            None => (),
736            Some(r) => {
737                assert!(
738                    ret.reason.contains(r.as_str()),
739                    "reason: \"{}\" does not contains \"{}\"",
740                    ret.reason.as_str(),
741                    r.as_str()
742                );
743            }
744        };
745
746        if case.expect_result.rule_index.is_some() {
747            assert_eq!(
748                case.expect_result.rule_index, ret.rule_index,
749                "rule index not match"
750            );
751        }
752
753        if case.expect_result.no_rule_index.is_some() {
754            assert!(
755                case.expect_result.rule_index.is_none(),
756                "should not have rule index."
757            );
758        }
759
760        if case.expect_result.version.is_some() {
761            assert_eq!(case.expect_result.version, ret.version, "version not match");
762        }
763    }
764
765    fn load_test_json(file: &str) -> Result<Tests, FPError> {
766        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
767        path.push(file);
768        let mut json_str = fs::read_to_string(path.clone());
769        if json_str.is_err() {
770            use std::process::Command;
771            Command::new("git")
772                .args(["submodule", "init"])
773                .status()
774                .expect("init");
775            Command::new("git")
776                .args(["submodule", "update"])
777                .status()
778                .expect("update");
779            json_str = fs::read_to_string(path);
780        }
781        assert!(json_str.is_ok(),
782                "contract test resource not found, run `git submodule init && git submodule update` to fetch");
783        let tests = load_tests_json(&json_str.unwrap());
784        assert!(tests.is_ok(), "err is {:?}", tests);
785        tests
786    }
787}