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