Skip to main content

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}