sqlx_firebird/options/
parse.rs

1//
2// Copyright © 2023, RedSoft
3// License: MIT
4//
5
6use std::str::FromStr;
7
8use rsfbclient::charset::Charset;
9use rsfbclient::{Dialect, FbError};
10use sqlx_core::error::Error;
11use sqlx_core::percent_encoding::percent_decode_str;
12use sqlx_core::Url;
13
14use super::FbConnectOptions;
15
16impl FbConnectOptions {
17    pub(crate) fn parse_from_url(url: &Url) -> Result<Self, Error> {
18        let settings = parse(url).map_err(Error::config)?;
19
20        let mut options = FbConnectOptions::new();
21
22        if let Some(user) = settings.user {
23            options = options.username(user);
24        }
25
26        if let Some(pass) = settings.pass {
27            options = options.password(pass);
28        }
29
30        if let Some(host) = settings.host {
31            options = options.host(host);
32        }
33
34        if let Some(port) = settings.port {
35            options = options.port(port);
36        }
37
38        options = options.database(settings.db_name);
39
40        if let Some(charset) = settings.charset {
41            options = options.charset(charset);
42        }
43
44        if let Some(dialect) = settings.dialect {
45            options = options.dialect(dialect);
46        }
47
48        if let Some(stmt_cache_size) = settings.stmt_cache_size {
49            options = options.stmt_cache_size(stmt_cache_size);
50        }
51
52        if let Some(role_name) = settings.role_name {
53            options = options.role_name(role_name);
54        }
55
56        Ok(options)
57    }
58}
59
60pub struct ConnStringSettings {
61    pub user: Option<String>,
62    pub pass: Option<String>,
63    pub host: Option<String>,
64    pub port: Option<u16>,
65    pub db_name: String,
66    pub charset: Option<Charset>,
67    pub dialect: Option<Dialect>,
68    pub stmt_cache_size: Option<usize>,
69    pub role_name: Option<String>,
70}
71
72fn parse(url: &Url) -> Result<ConnStringSettings, FbError> {
73    if url.scheme().to_lowercase() != "firebird" {
74        return Err(FbError::from(
75            "The string must start with the prefix 'firebird://'",
76        ));
77    }
78
79    let user = match url.username() {
80        "" => None,
81        u => Some(percent_decode_str(u).decode_utf8()?.into_owned()),
82    };
83
84    let pass = match url.password() {
85        Some(p) => Some(percent_decode_str(p).decode_utf8()?.into_owned()),
86        _ => None,
87    };
88
89    let mut host = match url.host_str() {
90        Some(h) => Some(percent_decode_str(h).decode_utf8()?.into_owned()),
91        _ => None,
92    };
93
94    let port = url.port();
95
96    let mut db_name = match url.path() {
97        "" => None,
98        db => {
99            let db = percent_decode_str(db).decode_utf8()?.into_owned();
100            if db.starts_with('/') && url.has_host() {
101                Some(db.replacen('/', "", 1))
102            } else {
103                Some(db)
104            }
105        },
106    };
107
108    match (&host, &db_name) {
109        // In the embedded case with a windows path,
110        // the lib will return the drive in the host,
111        // because of ':' char.
112        //
113        // Example: firebird://c:/a/b/c.fdb
114        // We get:
115        //  - host: c
116        //  - port: None
117        //  - db_name: a/b/c.fdb
118        (Some(h), Some(db)) => {
119            if h.len() == 1 && user.is_none() && pass.is_none() && port.is_none() {
120                db_name = Some(format!("{}:/{}", h, db));
121                host = None;
122            }
123        },
124        // When we have an embedded path, but only
125        // with the filename. In this cases, the lib
126        // will return the db path in the host.
127        //
128        // Example: firebird://abc.fdb
129        // We get:
130        //  - host: abc.fdb
131        //  - db_name: None
132        (Some(h), None) => {
133            if user.is_none() && pass.is_none() && port.is_none() {
134                db_name = Some(h.to_string());
135                host = None;
136            }
137        },
138        _ => {},
139    }
140
141    let db_name = db_name.ok_or_else(|| FbError::from("The database name/path is required"))?;
142
143    let mut dialect = None;
144    let mut charset = None;
145    let mut stmt_cache_size = None;
146    let mut role_name = None;
147
148    for (param, val) in url.query_pairs() {
149        match param.to_string().as_str() {
150            "dialect" => {
151                dialect = match Dialect::from_str(&val) {
152                    Ok(d) => Some(d),
153                    _ => None,
154                };
155            },
156            "charset" => {
157                charset = match Charset::from_str(&val) {
158                    Ok(d) => Some(d),
159                    _ => None,
160                };
161            },
162            "stmt_cache_size" => {
163                stmt_cache_size = match val.parse::<usize>() {
164                    Ok(v) => Some(v),
165                    _ => None,
166                };
167            },
168            "role_name" => {
169                if val != "" {
170                    role_name = Some(val.to_string());
171                }
172            },
173            _ => {},
174        }
175    }
176
177    Ok(ConnStringSettings {
178        user,
179        pass,
180        host,
181        port,
182        db_name,
183        charset,
184        dialect,
185        stmt_cache_size,
186        role_name,
187    })
188}
189
190impl FromStr for FbConnectOptions {
191    type Err = Error;
192
193    fn from_str(url: &str) -> Result<Self, Self::Err> {
194        let url = Url::parse(url).map_err(Error::config)?;
195        Self::parse_from_url(&url)
196    }
197}