sqlx_core_oldapi/mssql/options/
parse.rs1use crate::error::Error;
2use crate::mssql::protocol::pre_login::Encrypt;
3use crate::mssql::MssqlConnectOptions;
4use percent_encoding::percent_decode_str;
5use std::str::FromStr;
6use url::Url;
7
8impl FromStr for MssqlConnectOptions {
9 type Err = Error;
10
11 fn from_str(s: &str) -> Result<Self, Self::Err> {
49 let url: Url = s.parse().map_err(Error::config)?;
50 let mut options = Self::new();
51
52 if let Some(host) = url.host_str() {
53 options = options.host(host);
54 }
55
56 if let Some(port) = url.port() {
57 options = options.port(port);
58 }
59
60 let username = url.username();
61 if !username.is_empty() {
62 options = options.username(
63 &*percent_decode_str(username)
64 .decode_utf8()
65 .map_err(Error::config)?,
66 );
67 }
68
69 if let Some(password) = url.password() {
70 options = options.password(
71 &*percent_decode_str(password)
72 .decode_utf8()
73 .map_err(Error::config)?,
74 );
75 }
76
77 let path = url.path().trim_start_matches('/');
78 if !path.is_empty() {
79 options = options.database(path);
80 }
81
82 for (key, value) in url.query_pairs() {
83 match key.as_ref() {
84 "instance" => {
85 options = options.instance(&*value);
86 }
87 "encrypt" => {
88 match value.to_lowercase().as_str() {
89 "strict" => options = options.encrypt(Encrypt::Required),
90 "mandatory" | "true" | "yes" => options = options.encrypt(Encrypt::On),
91 "optional" | "false" | "no" => options = options.encrypt(Encrypt::Off),
92 "not_supported" => options = options.encrypt(Encrypt::NotSupported),
93 _ => return Err(Error::config(MssqlInvalidOption(format!(
94 "encrypt={} is not a valid value for encrypt. Valid values are: strict, mandatory, optional, true, false, yes, no",
95 value
96 )))),
97 }
98 }
99 "sslrootcert" | "ssl-root-cert" | "ssl-ca" => {
100 options = options.ssl_root_cert(&*value);
101 }
102 "trust_server_certificate" => {
103 let trust = value.parse::<bool>().map_err(Error::config)?;
104 options = options.trust_server_certificate(trust);
105 }
106 "hostname_in_certificate" => {
107 options = options.hostname_in_certificate(&*value);
108 }
109 "packet_size" => {
110 let size = value.parse().map_err(Error::config)?;
111 options = options.requested_packet_size(size).map_err(|_| {
112 Error::config(MssqlInvalidOption(format!("packet_size={}", size)))
113 })?;
114 }
115 "client_program_version" => {
116 options = options.client_program_version(value.parse().map_err(Error::config)?)
117 }
118 "client_pid" => options = options.client_pid(value.parse().map_err(Error::config)?),
119 "hostname" => options = options.hostname(&*value),
120 "app_name" => options = options.app_name(&*value),
121 "server_name" => options = options.server_name(&*value),
122 "client_interface_name" => options = options.client_interface_name(&*value),
123 "language" => options = options.language(&*value),
124 _ => {
125 return Err(Error::config(MssqlInvalidOption(key.into())));
126 }
127 }
128 }
129 Ok(options)
130 }
131}
132
133#[derive(Debug)]
134struct MssqlInvalidOption(String);
135
136impl std::fmt::Display for MssqlInvalidOption {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 write!(f, "`{}` is not a valid mssql connection option", self.0)
139 }
140}
141
142impl std::error::Error for MssqlInvalidOption {}
143
144#[test]
145fn it_parses_username_with_at_sign_correctly() {
146 let url = "mssql://user@hostname:password@hostname:5432/database";
147 let opts = MssqlConnectOptions::from_str(url).unwrap();
148
149 assert_eq!("user@hostname", &opts.username);
150}
151
152#[test]
153fn it_parses_password_with_non_ascii_chars_correctly() {
154 let url = "mssql://username:p@ssw0rd@hostname:5432/database";
155 let opts = MssqlConnectOptions::from_str(url).unwrap();
156
157 assert_eq!(Some("p@ssw0rd".into()), opts.password);
158}