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