TLA Line data Source code
1 : use serde::Deserialize;
2 : use std::fmt;
3 :
4 : /// Generic error response with human-readable description.
5 : /// Note that we can't always present it to user as is.
6 UBC 0 : #[derive(Debug, Deserialize)]
7 : pub struct ConsoleError {
8 : pub error: Box<str>,
9 : }
10 :
11 : /// Response which holds client's auth secret, e.g. [`crate::scram::ServerSecret`].
12 : /// Returned by the `/proxy_get_role_secret` API method.
13 0 : #[derive(Deserialize)]
14 : pub struct GetRoleSecret {
15 : pub role_secret: Box<str>,
16 : }
17 :
18 : // Manually implement debug to omit sensitive info.
19 : impl fmt::Debug for GetRoleSecret {
20 0 : fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 0 : f.debug_struct("GetRoleSecret").finish_non_exhaustive()
22 0 : }
23 : }
24 :
25 : /// Response which holds compute node's `host:port` pair.
26 : /// Returned by the `/proxy_wake_compute` API method.
27 0 : #[derive(Debug, Deserialize)]
28 : pub struct WakeCompute {
29 : pub address: Box<str>,
30 : pub aux: MetricsAuxInfo,
31 : }
32 :
33 : /// Async response which concludes the link auth flow.
34 : /// Also known as `kickResponse` in the console.
35 CBC 24 : #[derive(Debug, Deserialize)]
36 : pub struct KickSession<'a> {
37 : /// Session ID is assigned by the proxy.
38 : pub session_id: &'a str,
39 :
40 : /// Compute node connection params.
41 : #[serde(deserialize_with = "KickSession::parse_db_info")]
42 : pub result: DatabaseInfo,
43 : }
44 :
45 : impl KickSession<'_> {
46 4 : fn parse_db_info<'de, D>(des: D) -> Result<DatabaseInfo, D::Error>
47 4 : where
48 4 : D: serde::Deserializer<'de>,
49 4 : {
50 8 : #[derive(Deserialize)]
51 4 : enum Wrapper {
52 4 : // Currently, console only reports `Success`.
53 4 : // `Failure(String)` used to be here... RIP.
54 4 : Success(DatabaseInfo),
55 4 : }
56 4 :
57 4 : Wrapper::deserialize(des).map(|x| match x {
58 4 : Wrapper::Success(info) => info,
59 4 : })
60 4 : }
61 : }
62 :
63 : /// Compute node connection params.
64 83 : #[derive(Deserialize)]
65 : pub struct DatabaseInfo {
66 : pub host: Box<str>,
67 : pub port: u16,
68 : pub dbname: Box<str>,
69 : pub user: Box<str>,
70 : /// Console always provides a password, but it might
71 : /// be inconvenient for debug with local PG instance.
72 : pub password: Option<Box<str>>,
73 : pub aux: MetricsAuxInfo,
74 : }
75 :
76 : // Manually implement debug to omit sensitive info.
77 : impl fmt::Debug for DatabaseInfo {
78 6 : fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 6 : f.debug_struct("DatabaseInfo")
80 6 : .field("host", &self.host)
81 6 : .field("port", &self.port)
82 6 : .field("dbname", &self.dbname)
83 6 : .field("user", &self.user)
84 6 : .finish_non_exhaustive()
85 6 : }
86 : }
87 :
88 : /// Various labels for prometheus metrics.
89 : /// Also known as `ProxyMetricsAuxInfo` in the console.
90 68 : #[derive(Debug, Deserialize, Default)]
91 : pub struct MetricsAuxInfo {
92 : pub endpoint_id: Box<str>,
93 : pub project_id: Box<str>,
94 : pub branch_id: Box<str>,
95 : }
96 :
97 : impl MetricsAuxInfo {
98 : /// Definitions of labels for traffic metric.
99 : pub const TRAFFIC_LABELS: &'static [&'static str] = &[
100 : // Received (rx) / sent (tx).
101 : "direction",
102 : // ID of a project.
103 : "project_id",
104 : // ID of an endpoint within a project.
105 : "endpoint_id",
106 : // ID of a branch within a project (snapshot).
107 : "branch_id",
108 : ];
109 :
110 : /// Values of labels for traffic metric.
111 : // TODO: add more type safety (validate arity & positions).
112 60 : pub fn traffic_labels(&self, direction: &'static str) -> [&str; 4] {
113 60 : [
114 60 : direction,
115 60 : &self.project_id,
116 60 : &self.endpoint_id,
117 60 : &self.branch_id,
118 60 : ]
119 60 : }
120 : }
121 :
122 : #[cfg(test)]
123 : mod tests {
124 : use super::*;
125 : use serde_json::json;
126 :
127 4 : fn dummy_aux() -> serde_json::Value {
128 4 : json!({
129 4 : "endpoint_id": "endpoint",
130 4 : "project_id": "project",
131 4 : "branch_id": "branch",
132 4 : })
133 4 : }
134 :
135 1 : #[test]
136 1 : fn parse_kick_session() -> anyhow::Result<()> {
137 1 : // This is what the console's kickResponse looks like.
138 1 : let json = json!({
139 1 : "session_id": "deadbeef",
140 1 : "result": {
141 1 : "Success": {
142 1 : "host": "localhost",
143 1 : "port": 5432,
144 1 : "dbname": "postgres",
145 1 : "user": "john_doe",
146 1 : "password": "password",
147 1 : "aux": dummy_aux(),
148 1 : }
149 1 : }
150 1 : });
151 1 : let _: KickSession = serde_json::from_str(&json.to_string())?;
152 :
153 1 : Ok(())
154 1 : }
155 :
156 1 : #[test]
157 1 : fn parse_db_info() -> anyhow::Result<()> {
158 : // with password
159 1 : let _: DatabaseInfo = serde_json::from_value(json!({
160 1 : "host": "localhost",
161 1 : "port": 5432,
162 1 : "dbname": "postgres",
163 1 : "user": "john_doe",
164 1 : "password": "password",
165 1 : "aux": dummy_aux(),
166 1 : }))?;
167 :
168 : // without password
169 1 : let _: DatabaseInfo = serde_json::from_value(json!({
170 1 : "host": "localhost",
171 1 : "port": 5432,
172 1 : "dbname": "postgres",
173 1 : "user": "john_doe",
174 1 : "aux": dummy_aux(),
175 1 : }))?;
176 :
177 : // new field (forward compatibility)
178 1 : let _: DatabaseInfo = serde_json::from_value(json!({
179 1 : "host": "localhost",
180 1 : "port": 5432,
181 1 : "dbname": "postgres",
182 1 : "user": "john_doe",
183 1 : "project": "hello_world",
184 1 : "N.E.W": "forward compatibility check",
185 1 : "aux": dummy_aux(),
186 1 : }))?;
187 :
188 1 : Ok(())
189 1 : }
190 : }
|