Skip to main content

http_request_zabbix/
lib.rs

1//! A Rust library for interacting with the Zabbix API.
2//!
3//! This crate provides a convenient and idiomatic way to communicate with a Zabbix server,
4//! handling authentication, version checking, and raw API requests.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use http_request_zabbix::{ZabbixInstance, AuthType};
10//!
11//! let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
12//!     .build()
13//!     .unwrap()
14//!     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
15//!     .unwrap();
16//!
17//! println!("Zabbix Version: {}", zabbix.get_version().unwrap());
18//! ```
19
20use reqwest::blocking::Client;
21use semver::{Version, VersionReq};
22use serde_json::Value;
23use thiserror::Error;
24use uuid::Uuid;
25
26/// Enum representing the type of authentication to use.
27///
28/// # Examples
29///
30/// ```no_run
31/// use http_request_zabbix::AuthType;
32///
33/// let auth_type = AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string());
34/// ```
35///
36/// ```no_run
37/// use http_request_zabbix::AuthType;
38///
39/// let auth_type = AuthType::Token("817dc89d0ae1d347fbcacdd6c00f322d0ec0651a8df60115304216dc768205db".to_string());
40/// ```
41pub enum AuthType {
42    /// Token authentication.
43    /// Use it if you have a token from Zabbix. More info: https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/users/api_tokens  
44    Token(String),
45    /// Username and password authentication.
46    /// Use it if you have a username and password for Zabbix.
47    UsernamePassword(String, String),
48}
49
50/// An enum representing the types of parameters that can be passed to the Zabbix API.
51///
52/// # Examples
53///
54/// ```no_run
55/// use http_request_zabbix::{ApiRequestParams, AuthType, ZabbixInstance};
56///
57/// let params_json = ApiRequestParams::from(serde_json::json!({"output": ["host", "name"], "limit": 1}));
58/// let params_string = ApiRequestParams::from("{\"output\": [\"host\", \"name\"], \"limit\": 1}");
59/// ```
60pub enum ApiRequestParams {
61    /// A raw pre-parsed JSON Value.
62    Json(Value),
63    /// A raw JSON string.
64    String(String),
65}
66
67impl From<Value> for ApiRequestParams {
68    fn from(v: Value) -> Self {
69        ApiRequestParams::Json(v)
70    }
71}
72
73impl From<&str> for ApiRequestParams {
74    fn from(s: &str) -> Self {
75        ApiRequestParams::String(s.to_string())
76    }
77}
78
79impl From<String> for ApiRequestParams {
80    fn from(s: String) -> Self {
81        ApiRequestParams::String(s)
82    }
83}
84
85/// Error type for Zabbix interactions.
86///
87/// # Errors
88///
89/// This method will return a `ZabbixError` if:
90/// * The provided URL is invalid or unreachable (`ZabbixError::Network`).
91/// * The server responds with invalid JSON (`ZabbixError::Json`).
92/// * The server returns a version string that cannot be parsed by semantic versioning rules (`ZabbixError::VersionParse`).
93/// * The server returns an API error (`ZabbixError::ApiError`). If future Zabbix versions return a different error format, this enum variant may need to be updated.
94#[derive(Error, Debug)]
95pub enum ZabbixError {
96    #[error("Network error: {0}")]
97    Network(#[from] reqwest::Error),
98    #[error("JSON error: {0}")]
99    Json(#[from] serde_json::Error),
100    #[error("Version parse error: {0}")]
101    VersionParse(#[from] semver::Error),
102    #[error("Zabbix API Error: {message} {data}")]
103    ApiError { message: String, data: String },
104    #[error("Unknown error: {0}")]
105    Other(String),
106}
107
108/// Represents an active connection to a Zabbix server.
109pub struct ZabbixInstance {
110    id: String,
111    url: String,
112    token: String,
113    request_client: Client,
114    need_auth_in_body: bool,
115    version: String,
116    need_logout: bool,
117}
118
119impl ZabbixInstance {
120    /// Creates a new `ZabbixInstanceBuilder` to configure the connection.
121    ///
122    /// The URL should be the base URL of the Zabbix server, without the `/api_jsonrpc.php` suffix.
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use http_request_zabbix::ZabbixInstance;
128    ///
129    /// let builder = ZabbixInstance::builder("http://localhost/zabbix");
130    /// ```
131    pub fn builder(url: &str) -> ZabbixInstanceBuilder {
132        ZabbixInstanceBuilder::new(url)
133    }
134}
135
136/// A builder for creating a `ZabbixInstance`.
137pub struct ZabbixInstanceBuilder {
138    url: String,
139    accept_invalid_certs: bool,
140    client: Option<Client>,
141    need_auth_in_body: bool,
142    version: String,
143}
144
145impl ZabbixInstanceBuilder {
146    /// Creates a new builder with the given Zabbix URL.
147    ///
148    /// The URL should be the base URL of the Zabbix server, without the `/api_jsonrpc.php` suffix.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use http_request_zabbix::ZabbixInstance;
154    ///
155    /// let builder = ZabbixInstance::builder("http://localhost/zabbix");
156    /// ```
157    pub fn new(url: &str) -> Self {
158        Self {
159            url: url.to_string(),
160            accept_invalid_certs: false,
161            client: None,
162            need_auth_in_body: false,
163            version: "".to_string(),
164        }
165    }
166
167    /// Configures whether the client should verify the server's TLS certificates.
168    ///
169    /// Setting this to `true` is dangerous and should only be used for testing
170    /// or when using self-signed certificates in a trusted environment.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use http_request_zabbix::ZabbixInstance;
176    ///
177    /// let builder = ZabbixInstance::builder("http://localhost/zabbix/api_jsonrpc.php")
178    ///     .danger_accept_invalid_certs(true);
179    /// ```
180    pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
181        self.accept_invalid_certs = accept;
182        self
183    }
184
185    /// Builds the `ZabbixInstance` by connecting to the server and verifying the API version.
186    ///
187    /// This method will make an initial unauthenticated request to the Zabbix server
188    /// to determine its version (using `apiinfo.version`). This is required because
189    /// Zabbix >= 6.4 changed the authentication flow (using Bearer tokens instead of
190    /// passing auth in the request body).
191    ///
192    /// # Examples
193    ///
194    /// ```no_run
195    /// use http_request_zabbix::ZabbixInstance;
196    ///
197    /// let zabbix_result = ZabbixInstance::builder("http://zabbix.example.com/api_jsonrpc.php")
198    ///     .danger_accept_invalid_certs(true)
199    ///     .build();
200    ///     
201    /// assert!(zabbix_result.is_ok());
202    /// ```
203    pub fn build(mut self) -> Result<Self, ZabbixError> {
204        let client = Client::builder()
205            .danger_accept_invalid_certs(self.accept_invalid_certs)
206            .build()?;
207
208        let v6_4_req = VersionReq::parse(">=6.4")?;
209
210        let version_str_raw = ZabbixInstance::zabbix_raw_request(
211            &client,
212            &self.url,
213            "apiinfo.version",
214            serde_json::json!([]),
215            "",
216            false,
217        )?;
218        let version_str = version_str_raw.trim_matches('"');
219
220        let current_v = Version::parse(version_str)?;
221
222        self.need_auth_in_body = !(v6_4_req.matches(&current_v));
223
224        self.client = Some(client);
225
226        self.version = version_str.to_string();
227
228        Ok(self)
229    }
230
231    /// Logs in to the Zabbix server using the provided authentication type.
232    ///
233    /// # Arguments
234    ///
235    /// * `auth_type` - The authentication type to use for logging in.
236    ///
237    /// # Examples
238    ///
239    /// ```no_run
240    /// use http_request_zabbix::{ZabbixInstance, AuthType};
241    ///
242    /// let auth_type = AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string());
243    ///
244    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
245    ///     .build()
246    ///     .unwrap()
247    ///     .login(auth_type)
248    ///     .unwrap();
249    /// ```
250    ///
251    /// ```no_run
252    /// use http_request_zabbix::{ZabbixInstance, AuthType};
253    ///
254    /// let auth_type = AuthType::Token("817dc89d0ae1...".to_string());
255    ///
256    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
257    ///     .build()
258    ///     .unwrap()
259    ///     .login(auth_type)
260    ///     .unwrap();
261    /// ```
262    pub fn login(self, auth_type: AuthType) -> Result<ZabbixInstance, ZabbixError> {
263        match auth_type {
264            AuthType::Token(token) => self.login_with_token(token),
265            AuthType::UsernamePassword(username, password) => {
266                self.login_with_username_password(username, password)
267            }
268        }
269    }
270
271    fn login_with_token(self, token: String) -> Result<ZabbixInstance, ZabbixError> {
272        let client = self.client.ok_or_else(|| {
273            ZabbixError::Other("Client not initialized. Did you call build()?".to_string())
274        })?;
275
276        match ZabbixInstance::zabbix_raw_request(
277            &client,
278            &self.url,
279            "user.checkAuthentication",
280            serde_json::json!({"token": token}),
281            "",
282            self.need_auth_in_body,
283        ) {
284            Ok(_) => {
285                return Ok(ZabbixInstance {
286                    id: Uuid::new_v4().to_string(),
287                    need_auth_in_body: self.need_auth_in_body,
288                    token: token,
289                    request_client: client,
290                    url: self.url,
291                    version: self.version,
292                    need_logout: false,
293                });
294            }
295            Err(e) => {
296                return Err(ZabbixError::ApiError {
297                    message: "Invalid token".to_string(),
298                    data: e.to_string(),
299                });
300            }
301        }
302    }
303
304    fn login_with_username_password(
305        self,
306        username: String,
307        password: String,
308    ) -> Result<ZabbixInstance, ZabbixError> {
309        let v5_2 = Version::parse("5.2.0")?;
310        let current_v = Version::parse(&self.version)?;
311        let user_param = if current_v <= v5_2 {
312            "user"
313        } else {
314            "username"
315        };
316
317        let client = self.client.ok_or_else(|| {
318            ZabbixError::Other("Client not initialized. Did you call build()?".to_string())
319        })?;
320
321        let token = ZabbixInstance::zabbix_raw_request(
322            &client,
323            &self.url,
324            "user.login",
325            serde_json::json!({user_param: username, "password": password}),
326            "",
327            self.need_auth_in_body,
328        )?;
329
330        Ok(ZabbixInstance {
331            id: Uuid::new_v4().to_string(),
332            need_auth_in_body: self.need_auth_in_body,
333            token: token,
334            request_client: client,
335            url: self.url,
336            version: self.version,
337            need_logout: true,
338        })
339    }
340}
341
342impl ZabbixInstance {
343    /// Returns the internally generated UUID for this instance.
344    ///
345    /// You can use it to identify the instance in your logs.
346    ///
347    /// # Examples
348    ///
349    /// ```no_run
350    /// use http_request_zabbix::{ZabbixInstance, AuthType};
351    ///
352    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
353    ///     .build()
354    ///     .unwrap()
355    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
356    ///     .unwrap();
357    /// println!("Instance ID: {}", zabbix.id());
358    /// ```
359    pub fn id(&self) -> &str {
360        &self.id
361    }
362
363    /// Returns the Zabbix API URL this instance connects to (without the `/api_jsonrpc.php` suffix).
364    ///
365    /// # Examples
366    ///
367    /// ```no_run
368    /// use http_request_zabbix::{ZabbixInstance, AuthType};
369    ///
370    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
371    ///     .build()
372    ///     .unwrap()
373    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
374    ///     .unwrap();
375    /// println!("Instance URL: {}", zabbix.url());
376    /// ```
377    pub fn url(&self) -> &str {
378        &self.url
379    }
380
381    /// Logs out of the Zabbix server and invalidates the current token.
382    ///
383    /// Call automatically when the instance is dropped.
384    ///
385    /// # Examples
386    ///
387    /// ```no_run
388    /// use http_request_zabbix::{ZabbixInstance, AuthType};
389    ///
390    /// let mut zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
391    ///     .build()
392    ///     .unwrap()
393    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
394    ///     .unwrap();
395    /// zabbix.logout().unwrap();
396    /// ```
397    pub fn logout(&mut self) -> Result<&mut Self, ZabbixError> {
398        if !self.need_logout {
399            return Ok(self);
400        }
401
402        match Self::zabbix_raw_request(
403            &self.request_client,
404            &self.url,
405            "user.logout",
406            serde_json::json!([]),
407            self.token.as_ref(),
408            self.need_auth_in_body,
409        ) {
410            Ok(_) => {
411                self.token = "".to_string();
412                self.need_logout = false;
413                Ok(self)
414            }
415            Err(e) => Err(e),
416        }
417    }
418
419    /// Retrieves the Zabbix server API version.
420    ///
421    /// Use this method to check the version instead of directly calling `zabbix_request` with `apiinfo.version`,
422    /// because this method requires no authentication.
423    ///
424    /// # Examples
425    ///
426    /// ```no_run
427    /// use http_request_zabbix::{ZabbixInstance, AuthType};
428    ///
429    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
430    ///     .build()
431    ///     .unwrap()
432    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
433    ///     .unwrap();
434    /// println!("Zabbix Version: {}", zabbix.get_version().unwrap());
435    /// ```
436    pub fn get_version(&self) -> Result<String, ZabbixError> {
437        let version_str = Self::zabbix_raw_request(
438            &self.request_client,
439            &self.url,
440            "apiinfo.version",
441            serde_json::json!([]),
442            "",
443            false,
444        )?;
445
446        Ok(version_str)
447    }
448
449    /// Checks if the connected Zabbix server's version matches a semantic version requirement.
450    /// Example requirement: `>=6.4, <7.0`
451    ///
452    /// You can use this method to quickly check if the connected Zabbix server's version satisfies your requirements.
453    ///
454    /// # Examples
455    ///
456    /// ```no_run
457    /// use http_request_zabbix::{ZabbixInstance, AuthType};
458    ///
459    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
460    ///     .build()
461    ///     .unwrap()
462    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
463    ///     .unwrap();
464    /// println!("Is >= 6.4: {}", zabbix.check_version(">=6.4").unwrap());
465    /// ```
466    pub fn check_version(&self, version_req: &str) -> Result<bool, ZabbixError> {
467        let version_req = VersionReq::parse(version_req)?;
468        let current_v = Version::parse(&self.version)?;
469
470        Ok(version_req.matches(&current_v))
471    }
472
473    /// Makes a raw JSON-RPC request to the Zabbix API.
474    ///
475    /// `params` can be either a `serde_json::Value` (like `json!({...})`), a string slice `&str`, or a `String`.
476    ///
477    /// # Examples
478    ///
479    /// ```no_run
480    /// use http_request_zabbix::{ApiRequestParams, AuthType, ZabbixInstance};
481    ///
482    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix").build().unwrap()
483    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string())).unwrap();
484    ///
485    /// let params_json = ApiRequestParams::from(serde_json::json!({"output": ["host", "name"], "limit": 1}));
486    /// let params_string = ApiRequestParams::from("{\"output\": [\"host\", \"name\"], \"limit\": 1}");
487    ///
488    /// let result_json = zabbix.zabbix_request("host.get", params_json).unwrap();
489    /// let result_string = zabbix.zabbix_request("host.get", params_string).unwrap();
490    /// ```
491    pub fn zabbix_request<P: Into<ApiRequestParams>>(
492        &self,
493        method: &str,
494        params: P,
495    ) -> Result<String, ZabbixError> {
496        let params_val = match params.into() {
497            ApiRequestParams::Json(val) => val,
498            ApiRequestParams::String(s) => serde_json::from_str(&s).map_err(ZabbixError::from)?,
499        };
500
501        Self::zabbix_raw_request(
502            &self.request_client,
503            &self.url,
504            method,
505            params_val,
506            &self.token,
507            self.need_auth_in_body,
508        )
509    }
510
511    fn zabbix_raw_request(
512        client: &Client,
513        url: &str,
514        method: &str,
515        params: Value,
516        token: &str,
517        need_auth_in_body: bool,
518    ) -> Result<String, ZabbixError> {
519        let mut request_builder = client
520            .post(format!("{}/api_jsonrpc.php", url))
521            .header("Content-Type", "application/json-rpc");
522
523        let mut payload = serde_json::json!({
524            "jsonrpc": "2.0",
525            "method": method,
526            "params": params,
527            "id": Uuid::new_v4().to_string()
528        });
529
530        if token != "" {
531            if !need_auth_in_body {
532                request_builder =
533                    request_builder.header("Authorization", format!("Bearer {}", token));
534            } else {
535                if let Some(obj) = payload.as_object_mut() {
536                    obj.insert("auth".to_string(), Value::String(String::from(token)));
537                }
538            }
539        }
540
541        let response = request_builder.json(&payload).send()?;
542
543        if !response.status().is_success() {
544            return Err(ZabbixError::Other(format!(
545                "HTTP Error: {}",
546                response.status()
547            )));
548        }
549
550        let text = response.text()?;
551
552        let json: Value = serde_json::from_str(&text)?;
553
554        if let Some(error) = json.get("error") {
555            if error.is_object() {
556                let msg = error
557                    .get("message")
558                    .and_then(|v| v.as_str())
559                    .unwrap_or("Unknown error");
560                let data = error.get("data").and_then(|v| v.as_str()).unwrap_or("");
561                return Err(ZabbixError::ApiError {
562                    message: msg.to_string(),
563                    data: data.to_string(),
564                });
565            }
566            return Err(ZabbixError::Other(error.to_string()));
567        }
568
569        if let Some(result) = json.get("result") {
570            if let Some(s) = result.as_str() {
571                return Ok(s.to_string());
572            }
573            return Ok(result.to_string());
574        }
575
576        Err(ZabbixError::Other("Unknown response format".to_string()))
577    }
578}
579
580impl Drop for ZabbixInstance {
581    fn drop(&mut self) {
582        if self.need_logout {
583            self.logout().ok();
584        }
585    }
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591    use mockito::Server;
592
593    #[test]
594    fn test_login_with_token_success() {
595        let mut server = Server::new();
596        let url = server.url();
597
598        let mock_version = server
599            .mock("POST", "/api_jsonrpc.php")
600            .with_status(200)
601            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
602            .create();
603
604        let mock_auth = server
605            .mock("POST", "/api_jsonrpc.php")
606            .with_status(200)
607            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
608            .create();
609
610        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
611        let result = builder.login(AuthType::Token("test_token".to_string()));
612
613        assert!(result.is_ok());
614        mock_version.assert();
615        mock_auth.assert();
616    }
617
618    #[test]
619    fn test_login_with_password_success() {
620        let mut server = Server::new();
621        let url = server.url();
622
623        let mock_version = server
624            .mock("POST", "/api_jsonrpc.php")
625            .with_status(200)
626            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
627            .create();
628
629        let mock_auth = server
630            .mock("POST", "/api_jsonrpc.php")
631            .with_status(200)
632            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
633            .create();
634
635        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
636        let result = builder.login(AuthType::UsernamePassword(
637            "Admin".to_string(),
638            "zabbix".to_string(),
639        ));
640
641        assert!(result.is_ok());
642        mock_version.assert();
643        mock_auth.assert();
644    }
645
646    #[test]
647    fn test_login_with_password_failure() {
648        let mut server = Server::new();
649        let url = server.url();
650
651        let mock_version = server
652            .mock("POST", "/api_jsonrpc.php")
653            .with_status(200)
654            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
655            .create();
656
657        let mock_auth = server
658            .mock("POST", "/api_jsonrpc.php")
659            .with_status(401)
660            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"Invalid username or password"},"id":1}"#)
661            .create();
662
663        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
664        let result = builder.login(AuthType::UsernamePassword(
665            "Admin".to_string(),
666            "zabbix".to_string(),
667        ));
668
669        assert!(result.is_err());
670        mock_version.assert();
671        mock_auth.assert();
672    }
673
674    #[test]
675    fn test_login_with_token_failure() {
676        let mut server = Server::new();
677        let url = server.url();
678
679        let mock_version = server
680            .mock("POST", "/api_jsonrpc.php")
681            .with_status(200)
682            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
683            .create();
684
685        let mock_auth = server
686            .mock("POST", "/api_jsonrpc.php")
687            .with_status(200)
688            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"Token is invalid"},"id":1}"#)
689            .create();
690
691        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
692        let result = builder.login(AuthType::Token("test_token".to_string()));
693
694        assert!(result.is_err());
695        mock_version.assert();
696        mock_auth.assert();
697    }
698
699    #[test]
700    fn test_request_json_success() {
701        let mut server = Server::new();
702        let url = server.url();
703
704        let mock_version = server
705            .mock("POST", "/api_jsonrpc.php")
706            .with_status(200)
707            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
708            .create();
709
710        let mock_auth = server
711            .mock("POST", "/api_jsonrpc.php")
712            .with_status(200)
713            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
714            .create();
715
716        let mock_request = server
717            .mock("POST", "/api_jsonrpc.php")
718            .with_status(200)
719            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_result","id":1}"#)
720            .create();
721
722        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
723        let result = builder.login(AuthType::Token("test_token".to_string()));
724        let host_get = result.unwrap().zabbix_request(
725            "host.get",
726            serde_json::json!({"output": ["host", "name"], "limit": 1}),
727        );
728
729        assert!(host_get.is_ok());
730        mock_version.assert();
731        mock_auth.assert();
732        mock_request.assert();
733    }
734
735    #[test]
736    fn test_request_json_failure() {
737        let mut server = Server::new();
738        let url = server.url();
739
740        let mock_version = server
741            .mock("POST", "/api_jsonrpc.php")
742            .with_status(200)
743            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
744            .create();
745
746        let mock_auth = server
747            .mock("POST", "/api_jsonrpc.php")
748            .with_status(200)
749            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
750            .create();
751
752        let mock_request = server
753            .mock("POST", "/api_jsonrpc.php")
754            .with_status(200)
755            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"BlahBlahBlah"},"id":1}"#)
756            .create();
757
758        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
759        let result = builder.login(AuthType::Token("test_token".to_string()));
760        let host_get = result.unwrap().zabbix_request(
761            "host.get",
762            serde_json::json!({"output": ["host", "name"], "limit": 1}),
763        );
764
765        assert!(host_get.is_err());
766        mock_version.assert();
767        mock_auth.assert();
768        mock_request.assert();
769    }
770
771    #[test]
772    fn test_request_string_success() {
773        let mut server = Server::new();
774        let url = server.url();
775
776        let mock_version = server
777            .mock("POST", "/api_jsonrpc.php")
778            .with_status(200)
779            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
780            .create();
781
782        let mock_auth = server
783            .mock("POST", "/api_jsonrpc.php")
784            .with_status(200)
785            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
786            .create();
787
788        let mock_request = server
789            .mock("POST", "/api_jsonrpc.php")
790            .with_status(200)
791            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_result","id":1}"#)
792            .create();
793
794        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
795        let result = builder.login(AuthType::Token("test_token".to_string()));
796        let host_get = result
797            .unwrap()
798            .zabbix_request("host.get", r#"{"output": ["host", "name"], "limit": 1}"#);
799
800        assert!(host_get.is_ok());
801        mock_version.assert();
802        mock_auth.assert();
803        mock_request.assert();
804    }
805
806    #[test]
807    fn test_request_string_failure() {
808        let mut server = Server::new();
809        let url = server.url();
810
811        let mock_version = server
812            .mock("POST", "/api_jsonrpc.php")
813            .with_status(200)
814            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
815            .create();
816
817        let mock_auth = server
818            .mock("POST", "/api_jsonrpc.php")
819            .with_status(200)
820            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
821            .create();
822
823        let mock_request = server
824            .mock("POST", "/api_jsonrpc.php")
825            .with_status(200)
826            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"BlahBlahBlah"},"id":1}"#)
827            .create();
828
829        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
830        let result = builder.login(AuthType::Token("test_token".to_string()));
831        let host_get = result
832            .unwrap()
833            .zabbix_request("host.get", r#"{"output": ["host", "name"], "limit": 1}"#);
834
835        assert!(host_get.is_err());
836        mock_version.assert();
837        mock_auth.assert();
838        mock_request.assert();
839    }
840}