oracledb_protocol/net/
mod.rs1#![forbid(unsafe_code)]
2
3pub mod connectstring;
4
5#[cfg(feature = "cassette")]
8pub mod cassette;
9
10use crate::{ProtocolError, Result};
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
14pub enum Protocol {
15 #[default]
17 Tcp,
18 Tcps,
20}
21
22impl Protocol {
23 #[must_use]
25 pub fn default_port(self) -> u16 {
26 match self {
27 Self::Tcp => 1521,
28 Self::Tcps => 2484,
29 }
30 }
31
32 #[must_use]
34 pub fn is_tls(self) -> bool {
35 matches!(self, Self::Tcps)
36 }
37}
38
39#[derive(Clone, Debug, Eq, PartialEq)]
40pub struct EasyConnect {
41 pub host: String,
42 pub port: u16,
43 pub service_name: String,
44 pub protocol: Protocol,
47}
48
49impl From<connectstring::Protocol> for Protocol {
50 fn from(value: connectstring::Protocol) -> Self {
51 match value {
52 connectstring::Protocol::Tcp => Self::Tcp,
53 connectstring::Protocol::Tcps => Self::Tcps,
54 }
55 }
56}
57
58impl EasyConnect {
59 pub fn parse(input: &str) -> Result<Self> {
68 let descriptor = connectstring::parse(input)?.ok_or_else(|| {
69 ProtocolError::InvalidConnectDescriptor(format!(
70 "\"{input}\" is not a connect descriptor or EZConnect string \
71 (it may be a tnsnames.ora alias requiring a config directory)"
72 ))
73 })?;
74
75 let address = descriptor.first_address().ok_or_else(|| {
76 ProtocolError::InvalidConnectDescriptor(
77 "connect descriptor defines no usable address (host is required)".to_string(),
78 )
79 })?;
80 let host = address.host.clone().ok_or_else(|| {
81 ProtocolError::InvalidConnectDescriptor("host is required".to_string())
82 })?;
83 let service_name = descriptor
84 .first_description()
85 .connect_data
86 .service_name
87 .clone()
88 .ok_or_else(|| {
89 ProtocolError::InvalidConnectDescriptor("service name is required".to_string())
90 })?;
91
92 Ok(Self {
93 host,
94 port: address.port,
95 service_name,
96 protocol: address.protocol.into(),
97 })
98 }
99
100 pub fn parse_descriptor(input: &str) -> Result<connectstring::Descriptor> {
104 connectstring::parse(input)?.ok_or_else(|| {
105 ProtocolError::InvalidConnectDescriptor(format!(
106 "\"{input}\" is not a connect descriptor or EZConnect string"
107 ))
108 })
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn parses_easy_connect_with_default_port() {
118 let parsed = EasyConnect::parse("localhost/FREEPDB1")
119 .expect("default-port EZConnect descriptor should parse");
120 assert_eq!(parsed.host, "localhost");
121 assert_eq!(parsed.port, 1521);
122 assert_eq!(parsed.service_name, "FREEPDB1");
123 }
124
125 #[test]
126 fn parses_easy_connect_with_explicit_port() {
127 let parsed = EasyConnect::parse("db.example.test:1522/service")
128 .expect("explicit-port EZConnect descriptor should parse");
129 assert_eq!(parsed.host, "db.example.test");
130 assert_eq!(parsed.port, 1522);
131 assert_eq!(parsed.service_name, "service");
132 assert_eq!(parsed.protocol, Protocol::Tcp);
133 }
134
135 #[test]
136 fn parses_tcps_prefix_defaults_to_2484() {
137 let parsed = EasyConnect::parse("tcps://db.example.test/FREEPDB1")
138 .expect("tcps EZConnect descriptor should parse");
139 assert_eq!(parsed.host, "db.example.test");
140 assert_eq!(parsed.port, 2484);
141 assert_eq!(parsed.service_name, "FREEPDB1");
142 assert_eq!(parsed.protocol, Protocol::Tcps);
143 assert!(parsed.protocol.is_tls());
144 }
145
146 #[test]
147 fn parses_tcps_prefix_with_explicit_port() {
148 let parsed = EasyConnect::parse("tcps://host:2484/svc").expect("should parse");
149 assert_eq!(parsed.port, 2484);
150 assert_eq!(parsed.protocol, Protocol::Tcps);
151 }
152
153 #[test]
154 fn parses_tcp_prefix_explicitly() {
155 let parsed = EasyConnect::parse("tcp://host/svc").expect("should parse");
156 assert_eq!(parsed.port, 1521);
157 assert_eq!(parsed.protocol, Protocol::Tcp);
158 }
159
160 #[test]
161 fn parses_full_tns_descriptor_via_easy_connect() {
162 let parsed = EasyConnect::parse(
165 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=db.example.test)(PORT=2484))\
166 (CONNECT_DATA=(SERVICE_NAME=FREEPDB1)))",
167 )
168 .expect("full TNS descriptor should parse via EasyConnect");
169 assert_eq!(parsed.host, "db.example.test");
170 assert_eq!(parsed.port, 2484);
171 assert_eq!(parsed.service_name, "FREEPDB1");
172 assert_eq!(parsed.protocol, Protocol::Tcps);
173 }
174
175 #[test]
176 fn picks_first_address_of_address_list() {
177 let parsed = EasyConnect::parse(
178 "(DESCRIPTION=(ADDRESS_LIST=\
179 (ADDRESS=(PROTOCOL=tcp)(HOST=primary)(PORT=1521))\
180 (ADDRESS=(PROTOCOL=tcp)(HOST=standby)(PORT=1522)))\
181 (CONNECT_DATA=(SERVICE_NAME=svc)))",
182 )
183 .expect("address-list descriptor should parse");
184 assert_eq!(parsed.host, "primary");
185 assert_eq!(parsed.port, 1521);
186 }
187}