robius_open/lib.rs
1//! This crate provides easy Rust interfaces to open URIs across multiple platforms.
2//!
3//! Supports:
4//! - macOS (via `NSWorkspace`)
5//! - Android (via `android/content/Intent`)
6//! - Linux (via `xdg-open`)
7//! - Windows (via `start`)
8//! - iOS (via `UIApplication`)
9//!
10//! URIs take many different forms: URLs (`http://`), `tel:`, `mailto:`, `file://`,
11//! and more (see the [official list of schemes](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml)).
12//!
13//! ## Examples
14//!
15//! ```rust
16//! use robius_open::Uri;
17//! Uri::new("tel:+61 123 456 789")
18//! .open()
19//! .expect("failed to open telephone URI");
20//! ```
21//!
22//! ```rust
23//! use robius_open::Uri;
24//! Uri::new("http://www.google.com")
25//! .open_with_completion(|success| {
26//! println!("Opened URI? {success}");
27//! })
28//! .expect("failed to open URL");
29//! ```
30
31#![allow(clippy::result_unit_err)]
32
33mod error;
34mod sys;
35
36pub use error::{Error, Result};
37
38/// A uniform resource identifier.
39pub struct Uri<'a, 'b> {
40 inner: sys::Uri<'a, 'b>,
41}
42
43impl<'a, 'b> Uri<'a, 'b> {
44 /// Constructs a new URI.
45 pub fn new(s: &'a str) -> Self {
46 Self {
47 inner: sys::Uri::new(s),
48 }
49 }
50
51 /// Sets the action to perform with this URI.
52 ///
53 /// This only has an effect on Android, and corresponds to an [action
54 /// activity][aa]. By default, it is set to `"ACTION_VIEW"`.
55 ///
56 /// # Examples
57 ///
58 /// ```
59 /// # use robius_open::Uri;
60 /// Uri::new("tel:+61 123 456 789")
61 /// .action("ACTION_DIAL")
62 /// .open()
63 /// .expect("failed to open telephone URI");
64 /// ```
65 ///
66 /// [aa]: https://developer.android.com/reference/android/content/Intent#standard-activity-actions
67 pub fn action(self, action: &'b str) -> Self {
68 Self {
69 inner: self.inner.action(action),
70 }
71 }
72
73 /// Opens this URI.
74 ///
75 /// This must be called on the main UI thread, or it will return [Error::NotMainThread].
76 ///
77 /// Note that the returned `Result` does not necessarily indicate whether
78 /// the URI was successfully opened by the system.
79 /// For that purpose, you should use [`Self::open_with_completion()`].
80 pub fn open(self) -> Result<()> {
81 self.open_with_completion(|_success| {
82 #[cfg(feature = "log")]
83 log::debug!("Uri::open(): called on_completion closure, success: {}", _success);
84 })
85 }
86
87 /// Opens this URI, with a callback for determining if the URI was successfully opened.
88 ///
89 /// This must be called on the main UI thread, or it will return [Error::NotMainThread].
90 ///
91 /// Note that the returned `Result` does not *necessarily* indicate whether
92 /// the URI was successfully opened by the system.
93 /// For that purpose, the given `on_completion` callback will be called
94 /// with a boolean indicating whether the URI was successfully opened.
95 /// Note that the callback may be not be called at all,
96 /// but should typically be called upon success.
97 ///
98 /// Thus, the URI was *not* successfully opened if this function returns an error,
99 /// **OR** if the `on_completion` callback is invoked with `false`.
100 pub fn open_with_completion<F>(self, on_completion: F) -> Result<()>
101 where
102 F: Fn(bool) + Send + 'static,
103 {
104 // Passing an empty URI can cause your app to be killed on certain platforms.
105 if self.inner.is_empty() {
106 #[cfg(feature = "log")]
107 log::error!("Error: cannot open an empty URI.");
108
109 return Err(Error::MalformedUri);
110 }
111 self.inner.open(on_completion)
112 }
113}