cxx_qt_lib/core/
qurl.rs

1// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5use cxx::{type_id, ExternType};
6use std::fmt;
7use std::mem::MaybeUninit;
8
9#[cxx::bridge]
10mod ffi {
11    unsafe extern "C++" {
12        include!("cxx-qt-lib/qbytearray.h");
13        type QByteArray = crate::QByteArray;
14        include!("cxx-qt-lib/qstring.h");
15        type QString = crate::QString;
16        include!("cxx-qt-lib/qstringlist.h");
17        type QStringList = crate::QStringList;
18        include!("cxx-qt-lib/qurl.h");
19        type QUrl = super::QUrl;
20
21        /// Resets the content of the QUrl. After calling this function,
22        /// the QUrl is equal to one that has been constructed with the default empty constructor.
23        fn clear(self: &mut QUrl);
24
25        /// Returns an error message if the last operation that modified this QUrl object ran into a parsing error.
26        /// If no error was detected, this function returns an empty string and isValid() returns true.
27        #[rust_name = "error_string"]
28        fn errorString(self: &QUrl) -> QString;
29
30        /// Returns true if this URL contains a fragment (i.e., if # was seen on it).
31        #[rust_name = "has_fragment"]
32        fn hasFragment(self: &QUrl) -> bool;
33
34        /// Returns true if this URL contains a Query (i.e., if ? was seen on it).
35        #[rust_name = "has_query"]
36        fn hasQuery(self: &QUrl) -> bool;
37
38        /// Returns true if the URL has no data; otherwise returns false.
39        #[rust_name = "is_empty"]
40        fn isEmpty(self: &QUrl) -> bool;
41
42        /// Returns true if this URL is pointing to a local file path. A URL is a local file path if the scheme is "file".
43        #[rust_name = "is_local_file"]
44        fn isLocalFile(self: &QUrl) -> bool;
45
46        /// Returns true if this URL is a parent of child_url.
47        /// childUrl is a child of this URL if the two URLs share the same scheme and authority,
48        /// and this URL's path is a parent of the path of child_url.
49        #[rust_name = "is_parent_of"]
50        fn isParentOf(self: &QUrl, child_url: &QUrl) -> bool;
51
52        /// Returns true if the URL is relative; otherwise returns false.
53        /// A URL is relative reference if its scheme is undefined;
54        /// this function is therefore equivalent to calling scheme().is_empty().
55        #[rust_name = "is_relative"]
56        fn isRelative(self: &QUrl) -> bool;
57
58        /// Returns true if the URL is non-empty and valid; otherwise returns false.
59        #[rust_name = "is_valid"]
60        fn isValid(self: &QUrl) -> bool;
61
62        /// Returns the port of the URL, or defaultPort if the port is unspecified.
63        #[rust_name = "port_or"]
64        fn port(self: &QUrl, port: i32) -> i32;
65
66        /// Returns the result of the merge of this URL with relative. This URL is used as a base to convert relative to an absolute URL.
67        fn resolved(self: &QUrl, relative: &QUrl) -> QUrl;
68
69        /// Returns the scheme of the URL. If an empty string is returned,
70        /// this means the scheme is undefined and the URL is then relative.
71        ///
72        /// The scheme can only contain US-ASCII letters or digits,
73        /// which means it cannot contain any character that would otherwise require encoding
74        /// Additionally, schemes are always returned in lowercase form.
75        #[rust_name = "scheme_or_default"]
76        fn scheme(self: &QUrl) -> QString;
77
78        /// Sets the port of the URL to port.
79        ///
80        /// port must be between 0 and 65535 inclusive. Setting the port to -1 indicates that the port is unspecified.
81        #[rust_name = "set_port"]
82        fn setPort(self: &mut QUrl, port: i32);
83
84        /// Returns the path of this URL formatted as a local file path.
85        /// The path returned will use forward slashes, even if it was originally created from one with backslashes.
86        #[rust_name = "to_local_file_or_default"]
87        fn toLocalFile(self: &QUrl) -> QString;
88    }
89
90    // Bitwise enums don't work well with Rust and CXX, so lets just use the defaults for now
91    #[namespace = "rust::cxxqtlib1"]
92    unsafe extern "C++" {
93        #[doc(hidden)]
94        #[rust_name = "qurl_init_from_string"]
95        fn qurlInitFromString(string: &str) -> QUrl;
96        #[doc(hidden)]
97        #[rust_name = "qurl_to_rust_string"]
98        fn qurlToRustString(url: &QUrl) -> String;
99
100        #[rust_name = "qurl_authority"]
101        fn qurlAuthority(url: &QUrl) -> QString;
102        #[rust_name = "qurl_file_name"]
103        fn qurlFileName(url: &QUrl) -> QString;
104        #[rust_name = "qurl_fragment"]
105        fn qurlFragment(url: &QUrl) -> QString;
106        #[rust_name = "qurl_from_encoded"]
107        fn qurlFromEncoded(input: &QByteArray) -> QUrl;
108        #[rust_name = "qurl_from_local_file"]
109        fn qurlFromLocalFile(local_file: &QString) -> QUrl;
110        #[rust_name = "qurl_from_percent_encoding"]
111        fn qurlFromPercentEncoding(input: &QByteArray) -> QString;
112        #[rust_name = "qurl_from_user_input"]
113        fn qurlFromUserInput(user_input: &QString, working_directory: &QString) -> QUrl;
114        #[rust_name = "qurl_host"]
115        fn qurlHost(url: &QUrl) -> QString;
116        #[rust_name = "qurl_idn_whitelist"]
117        fn qurlIdnWhitelist() -> QStringList;
118        #[rust_name = "qurl_path"]
119        fn qurlPath(url: &QUrl) -> QString;
120        #[rust_name = "qurl_password"]
121        fn qurlPassword(url: &QUrl) -> QString;
122        #[rust_name = "qurl_query"]
123        fn qurlQuery(url: &QUrl) -> QString;
124        #[rust_name = "qurl_set_authority"]
125        fn qurlSetAuthority(url: &mut QUrl, authority: &QString);
126        #[rust_name = "qurl_set_fragment"]
127        fn qurlSetFragment(url: &mut QUrl, fragment: &QString);
128        #[rust_name = "qurl_set_host"]
129        fn qurlSetHost(url: &mut QUrl, host: &QString);
130        #[rust_name = "qurl_set_idn_whitelist"]
131        fn qurlSetIdnWhitelist(list: &QStringList);
132        #[rust_name = "qurl_set_password"]
133        fn qurlSetPassword(url: &mut QUrl, password: &QString);
134        #[rust_name = "qurl_set_path"]
135        fn qurlSetPath(url: &mut QUrl, path: &QString);
136        #[rust_name = "qurl_set_query"]
137        fn qurlSetQuery(url: &mut QUrl, query: &QString);
138        #[rust_name = "qurl_set_scheme"]
139        fn qurlSetScheme(url: &mut QUrl, scheme: &QString);
140        #[rust_name = "qurl_set_url"]
141        fn qurlSetUrl(url: &mut QUrl, new_url: &QString);
142        #[rust_name = "qurl_set_user_info"]
143        fn qurlSetUserInfo(url: &mut QUrl, user_info: &QString);
144        #[rust_name = "qurl_set_user_name"]
145        fn qurlSetUserName(url: &mut QUrl, user_name: &QString);
146        #[rust_name = "qurl_to_display_string"]
147        fn qurlToDisplayString(url: &QUrl) -> QString;
148        #[rust_name = "qurl_to_encoded"]
149        fn qurlToEncoded(url: &QUrl) -> QByteArray;
150        #[rust_name = "qurl_to_percent_encoding"]
151        fn qurlToPercentEncoding(
152            input: &QString,
153            exclude: &QByteArray,
154            include: &QByteArray,
155        ) -> QByteArray;
156        #[doc(hidden)]
157        #[rust_name = "qurl_to_qstring"]
158        fn qurlToQString(url: &QUrl) -> QString;
159        #[rust_name = "qurl_user_info"]
160        fn qurlUserInfo(url: &QUrl) -> QString;
161        #[rust_name = "qurl_user_name"]
162        fn qurlUserName(url: &QUrl) -> QString;
163    }
164
165    #[namespace = "rust::cxxqtlib1"]
166    unsafe extern "C++" {
167        include!("cxx-qt-lib/common.h");
168
169        #[doc(hidden)]
170        #[rust_name = "qurl_drop"]
171        fn drop(url: &mut QUrl);
172
173        #[doc(hidden)]
174        #[rust_name = "qurl_init_default"]
175        fn construct() -> QUrl;
176        #[doc(hidden)]
177        #[rust_name = "qurl_init_from_qstring"]
178        fn construct(string: &QString) -> QUrl;
179        #[doc(hidden)]
180        #[rust_name = "qurl_init_from_qurl"]
181        fn construct(url: &QUrl) -> QUrl;
182
183        #[doc(hidden)]
184        #[rust_name = "qurl_eq"]
185        fn operatorEq(a: &QUrl, b: &QUrl) -> bool;
186
187        #[doc(hidden)]
188        #[rust_name = "qurl_debug"]
189        fn toQString(url: &QUrl) -> QString;
190    }
191}
192
193/// The QUrl class provides a convenient interface for working with URLs.
194#[repr(C)]
195pub struct QUrl {
196    _space: MaybeUninit<usize>,
197}
198
199impl QUrl {
200    /// Returns the authority of the URL if it is defined; otherwise an empty string is returned.
201    pub fn authority_or_default(&self) -> ffi::QString {
202        ffi::qurl_authority(self)
203    }
204
205    /// Returns the name of the file, excluding the directory path.
206    ///
207    /// Note that, if this QUrl object is given a path ending in a slash, the name of the file is considered empty.
208    ///
209    /// If the path doesn't contain any slash, it is fully returned as the fileName.
210    pub fn file_name(&self) -> ffi::QString {
211        ffi::qurl_file_name(self)
212    }
213
214    /// Returns the fragment of the URL.
215    pub fn fragment(&self) -> Option<ffi::QString> {
216        if self.has_fragment() {
217            Some(self.fragment_or_default())
218        } else {
219            None
220        }
221    }
222
223    /// Returns the fragment of the URL if it is defined; otherwise an empty string is returned.
224    pub fn fragment_or_default(&self) -> ffi::QString {
225        ffi::qurl_fragment(self)
226    }
227
228    /// Parses input and returns the corresponding QUrl. input is assumed to be in encoded form, containing only ASCII characters.
229    pub fn from_encoded(input: &ffi::QByteArray) -> Self {
230        ffi::qurl_from_encoded(input)
231    }
232
233    /// Returns a QUrl representation of localFile, interpreted as a local file.
234    /// This function accepts paths separated by slashes as well as the native separator for this platform.
235    pub fn from_local_file(local_file: &ffi::QString) -> Self {
236        ffi::qurl_from_local_file(local_file)
237    }
238
239    /// Returns a decoded copy of input. input is first decoded from percent encoding,
240    /// then converted from UTF-8 to unicode.
241    pub fn from_percent_encoding(input: &ffi::QByteArray) -> ffi::QString {
242        ffi::qurl_from_percent_encoding(input)
243    }
244
245    /// Returns a valid URL from a user supplied userInput string if one can be deduced.
246    /// In the case that is not possible, an invalid QUrl() is returned.
247    pub fn from_user_input(user_input: &ffi::QString, working_directory: &ffi::QString) -> Self {
248        ffi::qurl_from_user_input(user_input, working_directory)
249    }
250
251    /// Returns the host of the URL if it is defined; otherwise an empty string is returned.
252    pub fn host_or_default(&self) -> ffi::QString {
253        ffi::qurl_host(self)
254    }
255
256    /// Returns the current whitelist of top-level domains that are allowed to have non-ASCII characters in their compositions.
257    pub fn idn_whitelist() -> ffi::QStringList {
258        ffi::qurl_idn_whitelist()
259    }
260
261    /// Returns the password of the URL if it is defined; otherwise an empty string is returned.
262    pub fn password_or_default(&self) -> ffi::QString {
263        ffi::qurl_password(self)
264    }
265
266    /// Returns the path of the URL.
267    pub fn path(&self) -> ffi::QString {
268        ffi::qurl_path(self)
269    }
270
271    /// Returns the query string of the URL if there's a query string
272    pub fn query(&self) -> Option<ffi::QString> {
273        if self.has_query() {
274            Some(self.query_or_default())
275        } else {
276            None
277        }
278    }
279
280    /// Returns the query string of the URL if it is defined; otherwise an empty string is returned.
281    pub fn query_or_default(&self) -> ffi::QString {
282        ffi::qurl_query(self)
283    }
284
285    /// Returns the scheme of the URL. If the Option is None,
286    /// this means the scheme is undefined and the URL is then relative.
287    ///
288    /// The scheme can only contain US-ASCII letters or digits,
289    /// which means it cannot contain any character that would otherwise require encoding
290    /// Additionally, schemes are always returned in lowercase form.
291    pub fn scheme(&self) -> Option<ffi::QString> {
292        let scheme = self.scheme_or_default();
293        if scheme.is_empty() {
294            None
295        } else {
296            Some(scheme)
297        }
298    }
299
300    /// Sets the authority of the URL to authority.
301    pub fn set_authority(&mut self, authority: &ffi::QString) {
302        ffi::qurl_set_authority(self, authority)
303    }
304
305    /// Sets the fragment of the URL to fragment.
306    /// The fragment is the last part of the URL, represented by a '#' followed by a string of characters.
307    pub fn set_fragment(&mut self, fragment: &ffi::QString) {
308        ffi::qurl_set_fragment(self, fragment)
309    }
310
311    /// Sets the host of the URL to host. The host is part of the authority.
312    pub fn set_host(&mut self, host: &ffi::QString) {
313        ffi::qurl_set_host(self, host)
314    }
315
316    /// Sets the whitelist of Top-Level Domains (TLDs) that are allowed to have non-ASCII characters in domains to the value of list.
317    pub fn set_idn_whitelist(list: &ffi::QStringList) {
318        ffi::qurl_set_idn_whitelist(list)
319    }
320
321    /// Sets the URL's password to password.
322    pub fn set_password(&mut self, password: &ffi::QString) {
323        ffi::qurl_set_password(self, password)
324    }
325
326    /// Sets the path of the URL to path.
327    /// The path is the part of the URL that comes after the authority but before the query string.
328    pub fn set_path(&mut self, path: &ffi::QString) {
329        ffi::qurl_set_path(self, path)
330    }
331
332    /// Sets the query string of the URL to query.
333    pub fn set_query(&mut self, query: &ffi::QString) {
334        ffi::qurl_set_query(self, query)
335    }
336
337    /// Sets the scheme of the URL to scheme. As a scheme can only contain ASCII characters,
338    /// no conversion or decoding is done on the input. It must also start with an ASCII letter.
339    pub fn set_scheme(&mut self, scheme: &ffi::QString) {
340        ffi::qurl_set_scheme(self, scheme)
341    }
342
343    /// Parses url and sets this object to that value.
344    /// QUrl will automatically percent encode all characters that are not allowed in a URL
345    /// and decode the percent-encoded sequences that represent an unreserved character
346    /// (letters, digits, hyphens, underscores, dots and tildes).
347    /// All other characters are left in their original forms.
348    pub fn set_url(&mut self, url: &ffi::QString) {
349        ffi::qurl_set_url(self, url)
350    }
351
352    /// Sets the user info of the URL to userInfo.
353    pub fn set_user_info(&mut self, user_info: &ffi::QString) {
354        ffi::qurl_set_user_info(self, user_info)
355    }
356
357    /// Sets the URL's user name to userName.
358    pub fn set_user_name(&mut self, user_name: &ffi::QString) {
359        ffi::qurl_set_user_name(self, user_name)
360    }
361
362    /// Returns a human-displayable string representation of the URL.
363    /// The option RemovePassword is always enabled, since passwords should never be shown back to users.
364    pub fn to_display_string(&self) -> ffi::QString {
365        ffi::qurl_to_display_string(self)
366    }
367
368    /// Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returned.
369    pub fn to_encoded(&self) -> ffi::QByteArray {
370        ffi::qurl_to_encoded(self)
371    }
372
373    /// Returns the path of this URL formatted as a local file path.
374    /// The path returned will use forward slashes, even if it was originally created from one with backslashes.
375    pub fn to_local_file(&self) -> Option<ffi::QString> {
376        if self.is_local_file() {
377            Some(self.to_local_file_or_default())
378        } else {
379            None
380        }
381    }
382
383    /// Returns an encoded copy of input. input is first converted to UTF-8,
384    /// and all ASCII-characters that are not in the unreserved group are percent encoded.
385    /// To prevent characters from being percent encoded pass them to exclude.
386    /// To force characters to be percent encoded pass them to include.
387    pub fn to_percent_encoding(
388        input: &ffi::QString,
389        exclude: &ffi::QByteArray,
390        include: &ffi::QByteArray,
391    ) -> ffi::QByteArray {
392        ffi::qurl_to_percent_encoding(input, exclude, include)
393    }
394
395    /// Returns a QString representation of the URL.
396    pub fn to_qstring(&self) -> ffi::QString {
397        ffi::qurl_to_qstring(self)
398    }
399
400    /// Returns the user info of the URL, or an empty string if the user info is undefined.
401    pub fn user_info_or_default(&self) -> ffi::QString {
402        ffi::qurl_user_info(self)
403    }
404
405    /// Returns the user name of the URL if it is defined; otherwise an empty string is returned.
406    pub fn user_name_or_default(&self) -> ffi::QString {
407        ffi::qurl_user_name(self)
408    }
409}
410
411impl Clone for QUrl {
412    /// Constructs a copy of other.
413    fn clone(&self) -> Self {
414        ffi::qurl_init_from_qurl(self)
415    }
416}
417
418impl Default for QUrl {
419    /// Constructs an empty QUrl object.
420    fn default() -> Self {
421        ffi::qurl_init_default()
422    }
423}
424
425impl std::cmp::PartialEq for QUrl {
426    fn eq(&self, other: &Self) -> bool {
427        ffi::qurl_eq(self, other)
428    }
429}
430
431impl std::cmp::Eq for QUrl {}
432
433impl fmt::Display for QUrl {
434    /// Convert the QUrl to a Rust string
435    ///
436    /// Note that this converts from UTF-16 to UTF-8
437    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
438        write!(f, "{}", ffi::qurl_to_rust_string(self))
439    }
440}
441
442impl fmt::Debug for QUrl {
443    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
444        write!(f, "{}", ffi::qurl_debug(self))
445    }
446}
447
448impl Drop for QUrl {
449    /// Destructor; called immediately before the object is deleted.
450    fn drop(&mut self) {
451        ffi::qurl_drop(self)
452    }
453}
454
455impl From<&ffi::QString> for QUrl {
456    /// Constructs a QUrl from a QString
457    fn from(str: &ffi::QString) -> Self {
458        ffi::qurl_init_from_qstring(str)
459    }
460}
461
462impl From<&str> for QUrl {
463    /// Constructs a QUrl from a Rust string
464    ///
465    /// Note that this converts from UTF-8 to UTF-16
466    fn from(str: &str) -> Self {
467        ffi::qurl_init_from_string(str)
468    }
469}
470
471impl From<&String> for QUrl {
472    /// Constructs a QUrl from a Rust string
473    ///
474    /// Note that this converts from UTF-8 to UTF-16
475    fn from(str: &String) -> Self {
476        ffi::qurl_init_from_string(str)
477    }
478}
479
480#[cfg(feature = "http")]
481impl From<&http::Uri> for QUrl {
482    fn from(value: &http::Uri) -> Self {
483        QUrl::from(&value.to_string())
484    }
485}
486
487#[cfg(feature = "http")]
488impl TryFrom<&QUrl> for http::Uri {
489    type Error = http::uri::InvalidUri;
490
491    fn try_from(value: &QUrl) -> Result<Self, Self::Error> {
492        value.to_string().parse::<http::Uri>()
493    }
494}
495
496#[cfg(feature = "url")]
497impl From<&url::Url> for QUrl {
498    fn from(value: &url::Url) -> Self {
499        QUrl::from(&value.to_string())
500    }
501}
502
503#[cfg(feature = "url")]
504impl TryFrom<&QUrl> for url::Url {
505    type Error = url::ParseError;
506
507    fn try_from(value: &QUrl) -> Result<Self, Self::Error> {
508        url::Url::parse(value.to_string().as_str())
509    }
510}
511
512// Safety:
513//
514// Static checks on the C++ side to ensure the size is the same.
515unsafe impl ExternType for QUrl {
516    type Id = type_id!("QUrl");
517    type Kind = cxx::kind::Trivial;
518}
519
520#[cfg(test)]
521mod tests {
522    #[cfg(any(feature = "http", feature = "url"))]
523    use super::*;
524
525    #[cfg(feature = "http")]
526    #[test]
527    fn test_http() {
528        let uri = "https://github.com/kdab/cxx-qt"
529            .parse::<http::Uri>()
530            .unwrap();
531        let qurl = QUrl::from(&uri);
532        assert_eq!(uri.to_string(), qurl.to_string());
533
534        let http_uri = http::Uri::try_from(&qurl).unwrap();
535        assert_eq!(http_uri, uri);
536    }
537
538    #[cfg(feature = "url")]
539    #[test]
540    fn test_url() {
541        let url = url::Url::parse("https://github.com/kdab/cxx-qt").unwrap();
542        let qurl = QUrl::from(&url);
543        assert_eq!(url.to_string(), qurl.to_string());
544
545        let url_url = url::Url::try_from(&qurl).unwrap();
546        assert_eq!(url_url, url);
547    }
548}