Skip to main content

security_client_rs/
client.rs

1use std::fmt;
2
3use serde_json::Value;
4
5use crate::{
6    Error, Request, Result,
7    cases::Cases,
8    exceptions::Exceptions,
9    fleet::Fleet,
10    http::{Method, Transport},
11    request::endpoint,
12    roles::Roles,
13    security::Security,
14    spaces::Spaces,
15};
16
17/// Whether a route is resolved inside the client's selected space.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum Scope {
20    /// Always `/api/...`, even on a space-scoped client.
21    Global,
22    /// `/s/{space}/api/...` when a space is selected, otherwise `/api/...`.
23    Space,
24}
25
26/// Result ordering for search endpoints.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum SortOrder {
29    Asc,
30    Desc,
31}
32
33impl SortOrder {
34    pub(crate) fn as_str(self) -> &'static str {
35        match self {
36            Self::Asc => "asc",
37            Self::Desc => "desc",
38        }
39    }
40}
41
42/// The Kibana client. API groups are reached through namespace methods such as
43/// [`security`](Self::security), each returning request builders.
44///
45/// Cloning a client or selecting a space shares the transport and its connection pool.
46#[derive(Clone)]
47pub struct Kibana {
48    transport: Transport,
49    space: Option<String>,
50}
51
52impl Kibana {
53    pub fn new(transport: Transport) -> Self {
54        Self {
55            transport,
56            space: None,
57        }
58    }
59
60    pub fn transport(&self) -> &Transport {
61        &self.transport
62    }
63
64    /// A client whose space-scoped routes use `/s/{id}`. Global routes are unchanged.
65    /// This selects a space without contacting Kibana or creating the space.
66    /// Empty IDs, dot segments and IDs containing `/` return [`Error::Configuration`].
67    pub fn space(&self, id: impl Into<String>) -> Result<Self> {
68        let id = id.into();
69        if id.is_empty() || id == "." || id == ".." || id.contains('/') {
70            return Err(Error::Configuration("invalid space ID".into()));
71        }
72        Ok(Self {
73            transport: self.transport.clone(),
74            space: Some(id),
75        })
76    }
77
78    pub fn default_space(&self) -> Self {
79        Self {
80            transport: self.transport.clone(),
81            space: None,
82        }
83    }
84
85    pub fn space_id(&self) -> Option<&str> {
86        self.space.as_deref()
87    }
88
89    pub fn security(&self) -> Security<'_> {
90        Security(self)
91    }
92
93    pub fn exceptions(&self) -> Exceptions<'_> {
94        Exceptions(self)
95    }
96
97    pub fn cases(&self) -> Cases<'_> {
98        Cases(self)
99    }
100
101    pub fn fleet(&self) -> Fleet<'_> {
102        Fleet(self)
103    }
104
105    pub fn spaces(&self) -> Spaces<'_> {
106        Spaces(self)
107    }
108
109    pub fn roles(&self) -> Roles<'_> {
110        Roles(self)
111    }
112
113    /// `GET /api/status`
114    pub fn status(&self) -> Status<'_> {
115        Status(self.request(Method::GET, Scope::Global, &["api", "status"]))
116    }
117
118    /// A request to a route without a named builder. Each segment is percent-encoded
119    /// separately, so identifiers cannot change the route.
120    pub fn request(&self, method: Method, scope: Scope, segments: &[&str]) -> Request<'_> {
121        Request::new(self, method, scope, segments)
122    }
123}
124
125impl fmt::Debug for Kibana {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.debug_struct("Kibana")
128            .field("transport", &self.transport)
129            .field("space", &self.space)
130            .finish()
131    }
132}
133
134endpoint! {
135    /// `GET /api/status`
136    Status => Value
137}