firebase_rs_sdk/data_connect/
config.rs1use serde::{Deserialize, Serialize};
2use std::fmt::{Display, Formatter};
3
4use crate::data_connect::error::{invalid_argument, DataConnectResult};
5
6pub const DEFAULT_DATA_CONNECT_HOST: &str = "firebasedataconnect.googleapis.com";
8
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
11pub struct ConnectorConfig {
12 pub location: String,
13 pub connector: String,
14 pub service: String,
15}
16
17impl ConnectorConfig {
18 pub fn new(
20 location: impl Into<String>,
21 connector: impl Into<String>,
22 service: impl Into<String>,
23 ) -> DataConnectResult<Self> {
24 let config = Self {
25 location: location.into(),
26 connector: connector.into(),
27 service: service.into(),
28 };
29 config.validate()?;
30 Ok(config)
31 }
32
33 pub fn validate(&self) -> DataConnectResult<()> {
35 if self.location.trim().is_empty() {
36 return Err(invalid_argument("location is required"));
37 }
38 if self.connector.trim().is_empty() {
39 return Err(invalid_argument("connector is required"));
40 }
41 if self.service.trim().is_empty() {
42 return Err(invalid_argument("service is required"));
43 }
44 Ok(())
45 }
46
47 pub fn identifier(&self) -> String {
49 serde_json::to_string(self).expect("connector config serialization")
50 }
51}
52
53impl Display for ConnectorConfig {
54 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
55 write!(f, "{}/{}/{}", self.location, self.service, self.connector)
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
61pub struct DataConnectOptions {
62 pub connector: ConnectorConfig,
63 pub project_id: String,
64}
65
66impl DataConnectOptions {
67 pub fn new(
68 connector: ConnectorConfig,
69 project_id: impl Into<String>,
70 ) -> DataConnectResult<Self> {
71 let options = Self {
72 connector,
73 project_id: project_id.into(),
74 };
75 options.connector.validate()?;
76 if options.project_id.trim().is_empty() {
77 return Err(invalid_argument("project_id is required"));
78 }
79 Ok(options)
80 }
81
82 pub fn resource_path(&self) -> String {
84 format!(
85 "projects/{}/locations/{}/services/{}/connectors/{}",
86 self.project_id,
87 self.connector.location,
88 self.connector.service,
89 self.connector.connector
90 )
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
96pub struct TransportOptions {
97 pub host: String,
98 pub port: Option<u16>,
99 pub ssl_enabled: bool,
100}
101
102impl TransportOptions {
103 pub fn new(host: impl Into<String>, port: Option<u16>, ssl_enabled: bool) -> Self {
104 Self {
105 host: host.into(),
106 port,
107 ssl_enabled,
108 }
109 }
110
111 pub fn base_url(&self) -> String {
113 let scheme = if self.ssl_enabled { "https" } else { "http" };
114 match self.port {
115 Some(port) => format!("{scheme}://{}:{port}", self.host),
116 None => format!("{scheme}://{}", self.host),
117 }
118 }
119}
120
121impl Default for TransportOptions {
122 fn default() -> Self {
123 Self {
124 host: DEFAULT_DATA_CONNECT_HOST.to_string(),
125 port: None,
126 ssl_enabled: true,
127 }
128 }
129}
130
131pub fn parse_transport_options(spec: &str) -> DataConnectResult<TransportOptions> {
133 let (protocol, rest) = spec.split_once("://").unwrap_or(("https", spec));
134 let ssl_enabled = match protocol {
135 "http" => false,
136 "https" => true,
137 other => {
138 return Err(invalid_argument(format!(
139 "Unsupported protocol '{other}' in emulator host"
140 )))
141 }
142 };
143
144 let (host, port) = if let Some((host, port)) = rest.split_once(':') {
145 let port = port
146 .parse::<u16>()
147 .map_err(|_| invalid_argument("Port must be a number in emulator host declaration"))?;
148 (host.to_string(), Some(port))
149 } else {
150 (rest.to_string(), None)
151 };
152
153 if host.trim().is_empty() {
154 return Err(invalid_argument(
155 "Host is required for emulator connections",
156 ));
157 }
158
159 Ok(TransportOptions::new(host, port, ssl_enabled))
160}