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