Skip to main content

mtl_foundation/
url.rs

1//! URL type for Foundation.
2//!
3//! Corresponds to `Foundation/NSURL.hpp`.
4//!
5//! # C++ Equivalent
6//!
7//! ```cpp
8//! namespace NS {
9//! class URL : public Copying<URL> {
10//! public:
11//!     static URL* fileURLWithPath(const class String* pPath);
12//!     static URL* alloc();
13//!     URL*        init();
14//!     URL*        init(const class String* pString);
15//!     URL*        initFileURLWithPath(const class String* pPath);
16//!     const char* fileSystemRepresentation() const;
17//! };
18//! }
19//! ```
20
21use std::ffi::{c_char, c_void};
22use std::ptr::NonNull;
23
24use mtl_sys::{class, msg_send_0, msg_send_1, sel};
25
26use crate::object::{Copying, Referencing};
27use crate::string::String;
28
29/// An Objective-C URL object.
30///
31/// C++ equivalent: `NS::URL`
32#[repr(transparent)]
33#[derive(Clone)]
34pub struct Url(NonNull<c_void>);
35
36impl Url {
37    /// Create a file URL with the specified path.
38    ///
39    /// C++ equivalent: `static URL* fileURLWithPath(const class String* pPath)`
40    #[inline]
41    pub fn file_url_with_path(path: &String) -> Option<Self> {
42        unsafe {
43            let ptr: *mut c_void = msg_send_1(
44                class!(NSURL).as_ptr(),
45                sel!(fileURLWithPath:),
46                path.as_ptr(),
47            );
48            Self::from_ptr(ptr)
49        }
50    }
51
52    /// Allocate a new URL.
53    ///
54    /// C++ equivalent: `static URL* alloc()`
55    #[inline]
56    pub fn alloc() -> Option<Self> {
57        unsafe {
58            let ptr: *mut c_void = msg_send_0(class!(NSURL).as_ptr(), sel!(alloc));
59            Self::from_ptr(ptr)
60        }
61    }
62
63    /// Initialize an allocated URL.
64    ///
65    /// C++ equivalent: `URL* init()`
66    #[inline]
67    pub fn init(&self) -> Option<Self> {
68        unsafe {
69            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(init));
70            Self::from_ptr(ptr)
71        }
72    }
73
74    /// Initialize with a string.
75    ///
76    /// C++ equivalent: `URL* init(const class String* pString)`
77    #[inline]
78    pub fn init_with_string(&self, string: &String) -> Option<Self> {
79        unsafe {
80            let ptr: *mut c_void =
81                msg_send_1(self.as_ptr(), sel!(initWithString:), string.as_ptr());
82            Self::from_ptr(ptr)
83        }
84    }
85
86    /// Initialize as a file URL with the specified path.
87    ///
88    /// C++ equivalent: `URL* initFileURLWithPath(const class String* pPath)`
89    #[inline]
90    pub fn init_file_url_with_path(&self, path: &String) -> Option<Self> {
91        unsafe {
92            let ptr: *mut c_void =
93                msg_send_1(self.as_ptr(), sel!(initFileURLWithPath:), path.as_ptr());
94            Self::from_ptr(ptr)
95        }
96    }
97
98    /// Get the file system representation.
99    ///
100    /// C++ equivalent: `const char* fileSystemRepresentation() const`
101    #[inline]
102    pub fn file_system_representation(&self) -> *const c_char {
103        unsafe { msg_send_0(self.as_ptr(), sel!(fileSystemRepresentation)) }
104    }
105
106    /// Create a URL from a raw pointer.
107    ///
108    /// # Safety
109    ///
110    /// The pointer must be a valid Objective-C NSURL object.
111    #[inline]
112    pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
113        NonNull::new(ptr).map(Self)
114    }
115
116    // Convenience methods for Rust interop
117
118    /// Create a file URL from a Rust path.
119    ///
120    /// This is a convenience method for Rust users.
121    #[inline]
122    pub fn from_path(path: &std::path::Path) -> Option<Self> {
123        let path_str = path.to_str()?;
124        let ns_string = String::from_str(path_str)?;
125        Self::file_url_with_path(&ns_string)
126    }
127}
128
129impl Referencing for Url {
130    #[inline]
131    fn as_ptr(&self) -> *const c_void {
132        self.0.as_ptr()
133    }
134}
135
136impl Copying for Url {
137    #[inline]
138    fn copy(&self) -> Option<Self> {
139        unsafe {
140            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(copy));
141            Self::from_ptr(ptr)
142        }
143    }
144}
145
146// SAFETY: NSURL is thread-safe for reference counting
147unsafe impl Send for Url {}
148unsafe impl Sync for Url {}
149
150impl std::fmt::Debug for Url {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        let repr = self.file_system_representation();
153        if repr.is_null() {
154            f.debug_struct("Url").field("ptr", &self.0).finish()
155        } else {
156            let s = unsafe { std::ffi::CStr::from_ptr(repr) };
157            f.debug_struct("Url")
158                .field("path", &s.to_string_lossy())
159                .finish()
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_url_size() {
170        // URL should be pointer-sized
171        assert_eq!(
172            std::mem::size_of::<Url>(),
173            std::mem::size_of::<*mut c_void>()
174        );
175    }
176}