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 body = serde_json::json!({
205            "jsonrpc": "2.0",
206            "method": "apiinfo.version",
207            "params": [],
208            "id": 1
209        });
210
211        let client = Client::builder()
212            .danger_accept_invalid_certs(self.accept_invalid_certs)
213            .build()?;
214
215        let v6_4_req = VersionReq::parse(">=6.4")?;
216
217        let version_str_raw =
218            ZabbixInstance::zabbix_raw_request(&client, &self.url, body, "", false)?;
219        let version_str = version_str_raw.trim_matches('"');
220
221        let current_v = Version::parse(version_str)?;
222
223        self.need_auth_in_body = !(v6_4_req.matches(&current_v));
224
225        self.client = Some(client);
226
227        self.version = version_str.to_string();
228
229        Ok(self)
230    }
231
232    /// Logs in to the Zabbix server using the provided authentication type.
233    ///
234    /// # Arguments
235    ///
236    /// * `auth_type` - The authentication type to use for logging in.
237    ///
238    /// # Examples
239    ///
240    /// ```no_run
241    /// use http_request_zabbix::{ZabbixInstance, AuthType};
242    ///
243    /// let auth_type = AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string());
244    ///
245    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
246    ///     .build()
247    ///     .unwrap()
248    ///     .login(auth_type)
249    ///     .unwrap();
250    /// ```
251    ///
252    /// ```no_run
253    /// use http_request_zabbix::{ZabbixInstance, AuthType};
254    ///
255    /// let auth_type = AuthType::Token("817dc89d0ae1...".to_string());
256    ///
257    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
258    ///     .build()
259    ///     .unwrap()
260    ///     .login(auth_type)
261    ///     .unwrap();
262    /// ```
263    pub fn login(self, auth_type: AuthType) -> Result<ZabbixInstance, ZabbixError> {
264        match auth_type {
265            AuthType::Token(token) => self.login_with_token(token),
266            AuthType::UsernamePassword(username, password) => {
267                self.login_with_username_password(username, password)
268            }
269        }
270    }
271
272    fn login_with_token(self, token: String) -> Result<ZabbixInstance, ZabbixError> {
273        let body = serde_json::json!({
274            "jsonrpc": "2.0",
275            "method": "user.checkAuthentication",
276            "params": {
277                "token": token
278            },
279            "id": 1
280        });
281
282        let client = self.client.ok_or_else(|| {
283            ZabbixError::Other("Client not initialized. Did you call build()?".to_string())
284        })?;
285
286        match ZabbixInstance::zabbix_raw_request(
287            &client,
288            &self.url,
289            body,
290            "",
291            self.need_auth_in_body,
292        ) {
293            Ok(_) => {
294                return Ok(ZabbixInstance {
295                    id: Uuid::new_v4().to_string(),
296                    need_auth_in_body: self.need_auth_in_body,
297                    token: token,
298                    request_client: client,
299                    url: self.url,
300                    version: self.version,
301                    need_logout: false,
302                });
303            }
304            Err(e) => {
305                return Err(ZabbixError::ApiError {
306                    message: "Invalid token".to_string(),
307                    data: e.to_string(),
308                });
309            }
310        }
311    }
312
313    fn login_with_username_password(
314        self,
315        username: String,
316        password: String,
317    ) -> Result<ZabbixInstance, ZabbixError> {
318        let v5_2 = Version::parse("5.2.0")?;
319        let current_v = Version::parse(&self.version)?;
320        let user_param = if current_v <= v5_2 {
321            "user"
322        } else {
323            "username"
324        };
325        let body = serde_json::json!({
326            "jsonrpc": "2.0",
327            "method": "user.login",
328            "params": {
329                user_param: username,
330                "password": password
331            },
332            "id": 1
333        });
334
335        let client = self.client.ok_or_else(|| {
336            ZabbixError::Other("Client not initialized. Did you call build()?".to_string())
337        })?;
338
339        let token = ZabbixInstance::zabbix_raw_request(
340            &client,
341            &self.url,
342            body,
343            "",
344            self.need_auth_in_body,
345        )?;
346
347        Ok(ZabbixInstance {
348            id: Uuid::new_v4().to_string(),
349            need_auth_in_body: self.need_auth_in_body,
350            token: token,
351            request_client: client,
352            url: self.url,
353            version: self.version,
354            need_logout: true,
355        })
356    }
357}
358
359impl ZabbixInstance {
360    /// Returns the internally generated UUID for this instance.
361    ///
362    /// You can use it to identify the instance in your logs.
363    ///
364    /// # Examples
365    ///
366    /// ```no_run
367    /// use http_request_zabbix::{ZabbixInstance, AuthType};
368    ///
369    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
370    ///     .build()
371    ///     .unwrap()
372    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
373    ///     .unwrap();
374    /// println!("Instance ID: {}", zabbix.id());
375    /// ```
376    pub fn id(&self) -> &str {
377        &self.id
378    }
379
380    /// Returns the Zabbix API URL this instance connects to (without the `/api_jsonrpc.php` suffix).
381    ///
382    /// # Examples
383    ///
384    /// ```no_run
385    /// use http_request_zabbix::{ZabbixInstance, AuthType};
386    ///
387    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
388    ///     .build()
389    ///     .unwrap()
390    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
391    ///     .unwrap();
392    /// println!("Instance URL: {}", zabbix.url());
393    /// ```
394    pub fn url(&self) -> &str {
395        &self.url
396    }
397
398    /// Logs out of the Zabbix server and invalidates the current token.
399    ///
400    /// Call automatically when the instance is dropped.
401    ///
402    /// # Examples
403    ///
404    /// ```no_run
405    /// use http_request_zabbix::{ZabbixInstance, AuthType};
406    ///
407    /// let mut zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
408    ///     .build()
409    ///     .unwrap()
410    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
411    ///     .unwrap();
412    /// zabbix.logout().unwrap();
413    /// ```
414    pub fn logout(&mut self) -> Result<&mut Self, ZabbixError> {
415        if !self.need_logout {
416            return Ok(self);
417        }
418
419        let body = serde_json::json!({
420            "jsonrpc": "2.0",
421            "method": "user.logout",
422            "params": [],
423            "id": 1
424        });
425
426        match Self::zabbix_raw_request(
427            &self.request_client,
428            &self.url,
429            body,
430            self.token.as_ref(),
431            self.need_auth_in_body,
432        ) {
433            Ok(_) => {
434                self.token = "".to_string();
435                self.need_logout = false;
436                Ok(self)
437            }
438            Err(e) => Err(e),
439        }
440    }
441
442    /// Retrieves the Zabbix server API version.
443    ///
444    /// Use this method to check the version instead of directly calling `zabbix_request` with `apiinfo.version`,
445    /// because this method requires no authentication.
446    ///
447    /// # Examples
448    ///
449    /// ```no_run
450    /// use http_request_zabbix::{ZabbixInstance, AuthType};
451    ///
452    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
453    ///     .build()
454    ///     .unwrap()
455    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
456    ///     .unwrap();
457    /// println!("Zabbix Version: {}", zabbix.get_version().unwrap());
458    /// ```
459    pub fn get_version(&self) -> Result<String, ZabbixError> {
460        let body = serde_json::json!({
461            "jsonrpc": "2.0",
462            "method": "apiinfo.version",
463            "params": [],
464            "id": 1
465        });
466
467        let version_str =
468            Self::zabbix_raw_request(&self.request_client, &self.url, body, "", false)?;
469
470        Ok(version_str)
471    }
472
473    /// Checks if the connected Zabbix server's version matches a semantic version requirement.
474    /// Example requirement: `>=6.4, <7.0`
475    ///
476    /// You can use this method to quickly check if the connected Zabbix server's version satisfies your requirements.
477    ///
478    /// # Examples
479    ///
480    /// ```no_run
481    /// use http_request_zabbix::{ZabbixInstance, AuthType};
482    ///
483    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix")
484    ///     .build()
485    ///     .unwrap()
486    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string()))
487    ///     .unwrap();
488    /// println!("Is >= 6.4: {}", zabbix.check_version(">=6.4").unwrap());
489    /// ```
490    pub fn check_version(&self, version_req: &str) -> Result<bool, ZabbixError> {
491        let version_req = VersionReq::parse(version_req)?;
492        let current_v = Version::parse(&self.version)?;
493
494        Ok(version_req.matches(&current_v))
495    }
496
497    /// Makes a raw JSON-RPC request to the Zabbix API.
498    ///
499    /// `params` can be either a `serde_json::Value` (like `json!({...})`), a string slice `&str`, or a `String`.
500    ///
501    /// # Examples
502    ///
503    /// ```no_run
504    /// use http_request_zabbix::{ApiRequestParams, AuthType, ZabbixInstance};
505    ///
506    /// let zabbix = ZabbixInstance::builder("http://zabbix.example.com/zabbix").build().unwrap()
507    ///     .login(AuthType::UsernamePassword("Admin".to_string(), "zabbix".to_string())).unwrap();
508    ///
509    /// let params_json = ApiRequestParams::from(serde_json::json!({"output": ["host", "name"], "limit": 1}));
510    /// let params_string = ApiRequestParams::from("{\"output\": [\"host\", \"name\"], \"limit\": 1}");
511    ///
512    /// let result_json = zabbix.zabbix_request("host.get", params_json).unwrap();
513    /// let result_string = zabbix.zabbix_request("host.get", params_string).unwrap();
514    /// ```
515    pub fn zabbix_request<P: Into<ApiRequestParams>>(
516        &self,
517        method: &str,
518        params: P,
519    ) -> Result<String, ZabbixError> {
520        let params_val = match params.into() {
521            ApiRequestParams::Json(val) => val,
522            ApiRequestParams::String(s) => serde_json::from_str(&s).map_err(ZabbixError::from)?,
523        };
524
525        let body = serde_json::json!({
526            "jsonrpc": "2.0",
527            "method": method,
528            "params": params_val,
529            "id": Uuid::new_v4().to_string()
530        });
531
532        Self::zabbix_raw_request(
533            &self.request_client,
534            &self.url,
535            body,
536            &self.token,
537            self.need_auth_in_body,
538        )
539    }
540
541    fn zabbix_raw_request(
542        client: &Client,
543        url: &str,
544        mut payload: Value,
545        token: &str,
546        need_auth_in_body: bool,
547    ) -> Result<String, ZabbixError> {
548        let mut request_builder = client
549            .post(format!("{}/api_jsonrpc.php", url))
550            .header("Content-Type", "application/json-rpc");
551
552        if token != "" {
553            if !need_auth_in_body {
554                request_builder =
555                    request_builder.header("Authorization", format!("Bearer {}", token));
556            } else {
557                if let Some(obj) = payload.as_object_mut() {
558                    obj.insert("auth".to_string(), Value::String(String::from(token)));
559                }
560            }
561        }
562
563        let response = request_builder.json(&payload).send()?;
564
565        if !response.status().is_success() {
566            return Err(ZabbixError::Other(format!(
567                "HTTP Error: {}",
568                response.status()
569            )));
570        }
571
572        let text = response.text()?;
573
574        let json: Value = serde_json::from_str(&text)?;
575
576        if let Some(error) = json.get("error") {
577            if error.is_object() {
578                let msg = error
579                    .get("message")
580                    .and_then(|v| v.as_str())
581                    .unwrap_or("Unknown error");
582                let data = error.get("data").and_then(|v| v.as_str()).unwrap_or("");
583                return Err(ZabbixError::ApiError {
584                    message: msg.to_string(),
585                    data: data.to_string(),
586                });
587            }
588            return Err(ZabbixError::Other(error.to_string()));
589        }
590
591        if let Some(result) = json.get("result") {
592            if let Some(s) = result.as_str() {
593                return Ok(s.to_string());
594            }
595            return Ok(result.to_string());
596        }
597
598        Err(ZabbixError::Other("Unknown response format".to_string()))
599    }
600}
601
602impl Drop for ZabbixInstance {
603    fn drop(&mut self) {
604        if self.need_logout {
605            self.logout().ok();
606        }
607    }
608}
609
610#[cfg(test)]
611mod tests {
612    use super::*;
613    use mockito::Server;
614
615    #[test]
616    fn test_login_with_token_success() {
617        let mut server = Server::new();
618        let url = server.url();
619
620        let mock_version = server
621            .mock("POST", "/api_jsonrpc.php")
622            .with_status(200)
623            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
624            .create();
625
626        let mock_auth = server
627            .mock("POST", "/api_jsonrpc.php")
628            .with_status(200)
629            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
630            .create();
631
632        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
633        let result = builder.login(AuthType::Token("test_token".to_string()));
634
635        assert!(result.is_ok());
636        mock_version.assert();
637        mock_auth.assert();
638    }
639
640    #[test]
641    fn test_login_with_password_success() {
642        let mut server = Server::new();
643        let url = server.url();
644
645        let mock_version = server
646            .mock("POST", "/api_jsonrpc.php")
647            .with_status(200)
648            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
649            .create();
650
651        let mock_auth = server
652            .mock("POST", "/api_jsonrpc.php")
653            .with_status(200)
654            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
655            .create();
656
657        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
658        let result = builder.login(AuthType::UsernamePassword(
659            "Admin".to_string(),
660            "zabbix".to_string(),
661        ));
662
663        assert!(result.is_ok());
664        mock_version.assert();
665        mock_auth.assert();
666    }
667
668    #[test]
669    fn test_login_with_password_failure() {
670        let mut server = Server::new();
671        let url = server.url();
672
673        let mock_version = server
674            .mock("POST", "/api_jsonrpc.php")
675            .with_status(200)
676            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
677            .create();
678
679        let mock_auth = server
680            .mock("POST", "/api_jsonrpc.php")
681            .with_status(401)
682            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"Invalid username or password"},"id":1}"#)
683            .create();
684
685        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
686        let result = builder.login(AuthType::UsernamePassword(
687            "Admin".to_string(),
688            "zabbix".to_string(),
689        ));
690
691        assert!(result.is_err());
692        mock_version.assert();
693        mock_auth.assert();
694    }
695
696    #[test]
697    fn test_login_with_token_failure() {
698        let mut server = Server::new();
699        let url = server.url();
700
701        let mock_version = server
702            .mock("POST", "/api_jsonrpc.php")
703            .with_status(200)
704            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
705            .create();
706
707        let mock_auth = server
708            .mock("POST", "/api_jsonrpc.php")
709            .with_status(200)
710            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"Token is invalid"},"id":1}"#)
711            .create();
712
713        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
714        let result = builder.login(AuthType::Token("test_token".to_string()));
715
716        assert!(result.is_err());
717        mock_version.assert();
718        mock_auth.assert();
719    }
720
721    #[test]
722    fn test_request_json_success() {
723        let mut server = Server::new();
724        let url = server.url();
725
726        let mock_version = server
727            .mock("POST", "/api_jsonrpc.php")
728            .with_status(200)
729            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
730            .create();
731
732        let mock_auth = server
733            .mock("POST", "/api_jsonrpc.php")
734            .with_status(200)
735            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
736            .create();
737
738        let mock_request = server
739            .mock("POST", "/api_jsonrpc.php")
740            .with_status(200)
741            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_result","id":1}"#)
742            .create();
743
744        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
745        let result = builder.login(AuthType::Token("test_token".to_string()));
746        let host_get = result.unwrap().zabbix_request(
747            "host.get",
748            serde_json::json!({"output": ["host", "name"], "limit": 1}),
749        );
750
751        assert!(host_get.is_ok());
752        mock_version.assert();
753        mock_auth.assert();
754        mock_request.assert();
755    }
756
757    #[test]
758    fn test_request_json_failure() {
759        let mut server = Server::new();
760        let url = server.url();
761
762        let mock_version = server
763            .mock("POST", "/api_jsonrpc.php")
764            .with_status(200)
765            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
766            .create();
767
768        let mock_auth = server
769            .mock("POST", "/api_jsonrpc.php")
770            .with_status(200)
771            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
772            .create();
773
774        let mock_request = server
775            .mock("POST", "/api_jsonrpc.php")
776            .with_status(200)
777            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"BlahBlahBlah"},"id":1}"#)
778            .create();
779
780        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
781        let result = builder.login(AuthType::Token("test_token".to_string()));
782        let host_get = result.unwrap().zabbix_request(
783            "host.get",
784            serde_json::json!({"output": ["host", "name"], "limit": 1}),
785        );
786
787        assert!(host_get.is_err());
788        mock_version.assert();
789        mock_auth.assert();
790        mock_request.assert();
791    }
792
793    #[test]
794    fn test_request_string_success() {
795        let mut server = Server::new();
796        let url = server.url();
797
798        let mock_version = server
799            .mock("POST", "/api_jsonrpc.php")
800            .with_status(200)
801            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
802            .create();
803
804        let mock_auth = server
805            .mock("POST", "/api_jsonrpc.php")
806            .with_status(200)
807            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
808            .create();
809
810        let mock_request = server
811            .mock("POST", "/api_jsonrpc.php")
812            .with_status(200)
813            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_result","id":1}"#)
814            .create();
815
816        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
817        let result = builder.login(AuthType::Token("test_token".to_string()));
818        let host_get = result
819            .unwrap()
820            .zabbix_request("host.get", r#"{"output": ["host", "name"], "limit": 1}"#);
821
822        assert!(host_get.is_ok());
823        mock_version.assert();
824        mock_auth.assert();
825        mock_request.assert();
826    }
827
828    #[test]
829    fn test_request_string_failure() {
830        let mut server = Server::new();
831        let url = server.url();
832
833        let mock_version = server
834            .mock("POST", "/api_jsonrpc.php")
835            .with_status(200)
836            .with_body(r#"{"jsonrpc":"2.0","result":"7.0.0","id":1}"#)
837            .create();
838
839        let mock_auth = server
840            .mock("POST", "/api_jsonrpc.php")
841            .with_status(200)
842            .with_body(r#"{"jsonrpc":"2.0","result":"dummy_token","id":1}"#)
843            .create();
844
845        let mock_request = server
846            .mock("POST", "/api_jsonrpc.php")
847            .with_status(200)
848            .with_body(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"BlahBlahBlah"},"id":1}"#)
849            .create();
850
851        let builder = ZabbixInstanceBuilder::new(&url).build().unwrap();
852        let result = builder.login(AuthType::Token("test_token".to_string()));
853        let host_get = result
854            .unwrap()
855            .zabbix_request("host.get", r#"{"output": ["host", "name"], "limit": 1}"#);
856
857        assert!(host_get.is_err());
858        mock_version.assert();
859        mock_auth.assert();
860        mock_request.assert();
861    }
862}