1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use local_handler::LocalHandler;
use noop_handler::NoOpHandler;
use once_cell::sync::Lazy;
use rustc_version_runtime::version;
use serde::Serialize;
use std::io::Error;
use voidstar_handler::VoidstarHandler;

mod local_handler;
mod noop_handler;
mod voidstar_handler;

#[derive(Serialize, Debug)]
struct AntithesisLanguageInfo {
    name: &'static str,
    version: String,
}

#[derive(Serialize, Debug)]
struct AntithesisVersionInfo {
    language: AntithesisLanguageInfo,
    sdk_version: &'static str,
    protocol_version: &'static str,
}

#[derive(Serialize, Debug)]
struct AntithesisSDKInfo {
    antithesis_sdk: AntithesisVersionInfo,
}

// Hardly ever changes, refers to the underlying JSON representation
const PROTOCOL_VERSION: &str = "1.0.0";

// Tracks SDK releases
const SDK_VERSION: &str = env!("CARGO_PKG_VERSION");

pub const LOCAL_OUTPUT: &str = "ANTITHESIS_SDK_LOCAL_OUTPUT";

pub(crate) static LIB_HANDLER: Lazy<Box<dyn LibHandler + Sync + Send>> = Lazy::new(|| {
    let handler: Box<dyn LibHandler + Sync + Send> = match VoidstarHandler::try_load() {
        Ok(handler) => Box::new(handler),
        Err(_) => match LocalHandler::new() {
            Some(h) => Box::new(h),
            None => Box::new(NoOpHandler::new()),
        },
    };
    let s = serde_json::to_string(&sdk_info()).unwrap_or("{}".to_owned());
    let _ = handler.output(s.as_str());
    handler
});

pub(crate) trait LibHandler {
    fn output(&self, value: &str) -> Result<(), Error>;
    fn random(&self) -> u64;
}

// Made public so it can be invoked from the antithesis_sdk::random module
pub(crate) fn dispatch_random() -> u64 {
    LIB_HANDLER.random()
}

// Ignore any and all errors - either the output is completed,
// or it fails silently.
//
// For a Voidstar handler, there is no indication that something failed
//
// For a Local handler, either:
// - Output was not requested (so not really an error)
// - Output was requested and attempted, but an io::Error was detected
// in this case the io::Error is silently ignored.
//
// It would be possible to distinguish between these two cases
// and report detected io:Error's but there is no requirement
// to implement this.
//
// Made public so it can be invoked from the antithesis_sdk::lifecycle
// and antithesis_sdk::assert module
pub fn dispatch_output<T: Serialize + ?Sized>(json_data: &T) {
    let s = serde_json::to_string(json_data).unwrap_or("{}".to_owned());
    let _ = LIB_HANDLER.output(s.as_str());
}

fn sdk_info() -> AntithesisSDKInfo {
    let language_data = AntithesisLanguageInfo {
        name: "Rust",
        version: version().to_string(),
    };

    let version_data = AntithesisVersionInfo {
        language: language_data,
        sdk_version: SDK_VERSION,
        protocol_version: PROTOCOL_VERSION,
    };

    AntithesisSDKInfo {
        antithesis_sdk: version_data,
    }
}