Skip to content

Commit 1501955

Browse files
authored
More FFI APIs (#178)
Unfortunately the `temporal_core` stuff breaks all the duration APIs, and I don't understand what it's doing. I wrote this PR before that change, help updating it to work with Duration appreciated. In general I don't think it's a good idea to have two separate Duration types with differing APIs. Opting in to features might enable new APIs, but it's not kosher to disable APIs that way, which seems to be the case here.
1 parent 68690c6 commit 1501955

File tree

9 files changed

+1084
-4
lines changed

9 files changed

+1084
-4
lines changed

temporal_capi/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ exclude.workspace = true
1212
[dependencies]
1313
diplomat = "0.9.0"
1414
diplomat-runtime = "0.9.0"
15-
temporal_rs = { version = "0.0.4", path = ".." }
15+
temporal_rs = { version = "0.0.4", path = "..", default-features = false }
1616
icu_calendar = { version = "2.0.0-beta1", default-features = false}

temporal_capi/src/duration.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
use crate::error::ffi::TemporalError;
2+
3+
#[diplomat::bridge]
4+
#[diplomat::abi_rename = "temporal_rs_{0}"]
5+
#[diplomat::attr(auto, namespace = "temporal_rs")]
6+
pub mod ffi {
7+
use crate::error::ffi::TemporalError;
8+
use diplomat_runtime::DiplomatOption;
9+
10+
#[diplomat::opaque]
11+
pub struct Duration(pub(crate) temporal_rs::Duration);
12+
13+
#[diplomat::opaque]
14+
#[diplomat::transparent_convert]
15+
pub struct TimeDuration(pub(crate) temporal_rs::TimeDuration);
16+
17+
#[diplomat::opaque]
18+
#[diplomat::transparent_convert]
19+
pub struct DateDuration(pub(crate) temporal_rs::DateDuration);
20+
21+
pub struct PartialDuration {
22+
pub years: DiplomatOption<f64>,
23+
pub months: DiplomatOption<f64>,
24+
pub weeks: DiplomatOption<f64>,
25+
pub days: DiplomatOption<f64>,
26+
pub hours: DiplomatOption<f64>,
27+
pub minutes: DiplomatOption<f64>,
28+
pub seconds: DiplomatOption<f64>,
29+
pub milliseconds: DiplomatOption<f64>,
30+
pub microseconds: DiplomatOption<f64>,
31+
pub nanoseconds: DiplomatOption<f64>,
32+
}
33+
34+
#[diplomat::enum_convert(temporal_rs::Sign)]
35+
pub enum Sign {
36+
Positive = 1,
37+
Zero = 0,
38+
Negative = -1,
39+
}
40+
41+
impl PartialDuration {
42+
pub fn is_empty(self) -> bool {
43+
temporal_rs::partial::PartialDuration::try_from(self)
44+
.map(|p| p.is_empty())
45+
.unwrap_or(false)
46+
}
47+
}
48+
49+
impl TimeDuration {
50+
pub fn new(
51+
hours: f64,
52+
minutes: f64,
53+
seconds: f64,
54+
milliseconds: f64,
55+
microseconds: f64,
56+
nanoseconds: f64,
57+
) -> Result<Box<Self>, TemporalError> {
58+
temporal_rs::TimeDuration::new(
59+
hours.try_into()?,
60+
minutes.try_into()?,
61+
seconds.try_into()?,
62+
milliseconds.try_into()?,
63+
microseconds.try_into()?,
64+
nanoseconds.try_into()?,
65+
)
66+
.map(|x| Box::new(TimeDuration(x)))
67+
.map_err(Into::into)
68+
}
69+
70+
pub fn abs(&self) -> Box<Self> {
71+
Box::new(Self(self.0.abs()))
72+
}
73+
pub fn negated(&self) -> Box<Self> {
74+
Box::new(Self(self.0.negated()))
75+
}
76+
77+
pub fn is_within_range(&self) -> bool {
78+
self.0.is_within_range()
79+
}
80+
pub fn sign(&self) -> Sign {
81+
self.0.sign().into()
82+
}
83+
}
84+
85+
impl DateDuration {
86+
pub fn new(
87+
years: f64,
88+
months: f64,
89+
weeks: f64,
90+
days: f64,
91+
) -> Result<Box<Self>, TemporalError> {
92+
temporal_rs::DateDuration::new(
93+
years.try_into()?,
94+
months.try_into()?,
95+
weeks.try_into()?,
96+
days.try_into()?,
97+
)
98+
.map(|x| Box::new(DateDuration(x)))
99+
.map_err(Into::into)
100+
}
101+
102+
pub fn abs(&self) -> Box<Self> {
103+
Box::new(Self(self.0.abs()))
104+
}
105+
pub fn negated(&self) -> Box<Self> {
106+
Box::new(Self(self.0.negated()))
107+
}
108+
109+
pub fn sign(&self) -> Sign {
110+
self.0.sign().into()
111+
}
112+
}
113+
impl Duration {
114+
pub fn create(
115+
years: f64,
116+
months: f64,
117+
weeks: f64,
118+
days: f64,
119+
hours: f64,
120+
minutes: f64,
121+
seconds: f64,
122+
milliseconds: f64,
123+
microseconds: f64,
124+
nanoseconds: f64,
125+
) -> Result<Box<Self>, TemporalError> {
126+
temporal_rs::Duration::new(
127+
years.try_into()?,
128+
months.try_into()?,
129+
weeks.try_into()?,
130+
days.try_into()?,
131+
hours.try_into()?,
132+
minutes.try_into()?,
133+
seconds.try_into()?,
134+
milliseconds.try_into()?,
135+
microseconds.try_into()?,
136+
nanoseconds.try_into()?,
137+
)
138+
.map(|x| Box::new(Duration(x)))
139+
.map_err(Into::into)
140+
}
141+
142+
pub fn from_day_and_time(
143+
day: f64,
144+
time: &TimeDuration,
145+
) -> Result<Box<Self>, TemporalError> {
146+
Ok(Box::new(Duration(
147+
temporal_rs::Duration::from_day_and_time(day.try_into()?, &time.0),
148+
)))
149+
}
150+
pub fn from_partial_duration(partial: PartialDuration) -> Result<Box<Self>, TemporalError> {
151+
temporal_rs::Duration::from_partial_duration(partial.try_into()?)
152+
.map(|x| Box::new(Duration(x)))
153+
.map_err(Into::into)
154+
}
155+
pub fn is_time_within_range(&self) -> bool {
156+
self.0.is_time_within_range()
157+
}
158+
159+
pub fn time<'a>(&'a self) -> &'a TimeDuration {
160+
TimeDuration::transparent_convert(self.0.time())
161+
}
162+
pub fn date<'a>(&'a self) -> &'a DateDuration {
163+
DateDuration::transparent_convert(self.0.date())
164+
}
165+
166+
// set_time_duration is NOT safe to expose over FFI if the date()/time() methods are available
167+
// Diplomat plans to make this a hard error.
168+
// If needed, implement it as with_time_duration(&self, TimeDuration) -> Self
169+
170+
pub fn years(&self) -> f64 {
171+
self.0.years().as_inner()
172+
}
173+
pub fn months(&self) -> f64 {
174+
self.0.months().as_inner()
175+
}
176+
pub fn weeks(&self) -> f64 {
177+
self.0.weeks().as_inner()
178+
}
179+
pub fn days(&self) -> f64 {
180+
self.0.days().as_inner()
181+
}
182+
pub fn hours(&self) -> f64 {
183+
self.0.hours().as_inner()
184+
}
185+
pub fn minutes(&self) -> f64 {
186+
self.0.minutes().as_inner()
187+
}
188+
pub fn seconds(&self) -> f64 {
189+
self.0.seconds().as_inner()
190+
}
191+
pub fn milliseconds(&self) -> f64 {
192+
self.0.milliseconds().as_inner()
193+
}
194+
pub fn microseconds(&self) -> f64 {
195+
self.0.microseconds().as_inner()
196+
}
197+
pub fn nanoseconds(&self) -> f64 {
198+
self.0.nanoseconds().as_inner()
199+
}
200+
201+
pub fn sign(&self) -> Sign {
202+
self.0.sign().into()
203+
}
204+
205+
pub fn is_zero(&self) -> bool {
206+
self.0.is_zero()
207+
}
208+
209+
pub fn abs(&self) -> Box<Self> {
210+
Box::new(Self(self.0.abs()))
211+
}
212+
pub fn negated(&self) -> Box<Self> {
213+
Box::new(Self(self.0.negated()))
214+
}
215+
216+
pub fn add(&self, other: &Self) -> Result<Box<Self>, TemporalError> {
217+
self.0
218+
.add(&other.0)
219+
.map(|x| Box::new(Duration(x)))
220+
.map_err(Into::into)
221+
}
222+
223+
pub fn subtract(&self, other: &Self) -> Result<Box<Self>, TemporalError> {
224+
self.0
225+
.subtract(&other.0)
226+
.map(|x| Box::new(Duration(x)))
227+
.map_err(Into::into)
228+
}
229+
230+
// TODO round_with_provider (needs time zone stuff)
231+
}
232+
}
233+
234+
impl TryFrom<ffi::PartialDuration> for temporal_rs::partial::PartialDuration {
235+
type Error = TemporalError;
236+
fn try_from(other: ffi::PartialDuration) -> Result<Self, TemporalError> {
237+
Ok(Self {
238+
years: other
239+
.years
240+
.into_option()
241+
.map(TryFrom::try_from)
242+
.transpose()?,
243+
months: other
244+
.months
245+
.into_option()
246+
.map(TryFrom::try_from)
247+
.transpose()?,
248+
weeks: other
249+
.weeks
250+
.into_option()
251+
.map(TryFrom::try_from)
252+
.transpose()?,
253+
days: other
254+
.days
255+
.into_option()
256+
.map(TryFrom::try_from)
257+
.transpose()?,
258+
hours: other
259+
.hours
260+
.into_option()
261+
.map(TryFrom::try_from)
262+
.transpose()?,
263+
minutes: other
264+
.minutes
265+
.into_option()
266+
.map(TryFrom::try_from)
267+
.transpose()?,
268+
seconds: other
269+
.seconds
270+
.into_option()
271+
.map(TryFrom::try_from)
272+
.transpose()?,
273+
milliseconds: other
274+
.milliseconds
275+
.into_option()
276+
.map(TryFrom::try_from)
277+
.transpose()?,
278+
microseconds: other
279+
.microseconds
280+
.into_option()
281+
.map(TryFrom::try_from)
282+
.transpose()?,
283+
nanoseconds: other
284+
.nanoseconds
285+
.into_option()
286+
.map(TryFrom::try_from)
287+
.transpose()?,
288+
})
289+
}
290+
}

temporal_capi/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
#![allow(unused)] // Until we add all the APIs
2+
#![warn(unused_imports)] // But we want to clean up imports
23
#![allow(clippy::needless_lifetimes)] // Diplomat requires explicit lifetimes at times
4+
#![allow(clippy::too_many_arguments)] // We're mapping APIs with the same argument size
5+
#![allow(clippy::wrong_self_convention)] // Diplomat forces self conventions that may not always be ideal
36

47
mod calendar;
8+
mod duration;
59
mod error;
610
mod options;
711

812
mod plain_date;
13+
mod plain_date_time;
14+
mod plain_month_day;
15+
mod plain_time;
16+
mod plain_year_month;

0 commit comments

Comments
 (0)