1use crate::ion_extension::ElementExtensions;
2use crate::isl::ranges::{NumberRange, TimestampRange};
3use crate::isl::IslVersion;
4use crate::isl_require;
5use crate::result::{invalid_schema_error, IonSchemaError, IonSchemaResult};
6use ion_rs::TimestampPrecision as Precision;
7use ion_rs::{Element, IonResult, Value, ValueWriter, WriteAsIon};
8use ion_rs::{IonType, Timestamp};
9use num_traits::abs;
10use std::cmp::Ordering;
11use std::fmt;
12use std::fmt::{Display, Formatter};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Annotation {
23 isl_version: IslVersion,
24 value: String,
25 is_required: bool, }
27
28impl Annotation {
29 pub fn new(value: String, is_required: bool, isl_version: IslVersion) -> Self {
30 Self {
31 value,
32 is_required,
33 isl_version,
34 }
35 }
36
37 pub fn value(&self) -> &String {
38 &self.value
39 }
40
41 pub fn is_required(&self) -> bool {
42 self.is_required
43 }
44
45 pub(crate) fn is_annotation_required(value: &Element, list_level_required: bool) -> bool {
47 if value.annotations().contains("required") {
48 true
49 } else if list_level_required {
50 !value.annotations().contains("optional")
52 } else {
53 false
55 }
56 }
57}
58
59impl WriteAsIon for Annotation {
60 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
61 if self.isl_version == IslVersion::V1_0 {
62 if self.is_required {
63 writer
64 .with_annotations(["required"])?
65 .write_symbol(self.value.as_str())
66 } else {
67 writer
68 .with_annotations(["optional"])?
69 .write_symbol(self.value.as_str())
70 }
71 } else {
72 writer.write_symbol(self.value.as_str())
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum TimestampPrecision {
79 Year,
80 Month,
81 Day,
82 Minute,
83 Second,
84 Millisecond,
85 Microsecond,
86 Nanosecond,
87 OtherFractionalSeconds(i64),
88}
89
90impl TimestampPrecision {
91 pub fn from_timestamp(timestamp_value: &Timestamp) -> TimestampPrecision {
92 use TimestampPrecision::*;
93 let precision_value = timestamp_value.precision();
94 match precision_value {
95 Precision::Year => Year,
96 Precision::Month => Month,
97 Precision::Day => Day,
98 Precision::HourAndMinute => Minute,
99 Precision::Second => match timestamp_value.fractional_seconds_scale().unwrap_or(0) {
103 0 => Second,
104 3 => Millisecond,
105 6 => Microsecond,
106 9 => Nanosecond,
107 scale => OtherFractionalSeconds(scale),
108 },
109 }
110 }
111
112 fn string_value(&self) -> String {
113 match self {
114 TimestampPrecision::Year => "year".to_string(),
115 TimestampPrecision::Month => "month".to_string(),
116 TimestampPrecision::Day => "day".to_string(),
117 TimestampPrecision::Minute => "minute".to_string(),
118 TimestampPrecision::Second => "second".to_string(),
119 TimestampPrecision::Millisecond => "millisecond".to_string(),
120 TimestampPrecision::Microsecond => "microsecond".to_string(),
121 TimestampPrecision::Nanosecond => "nanosecond".to_string(),
122 TimestampPrecision::OtherFractionalSeconds(i) => format!("fractional second (10e{i})"),
123 }
124 }
125
126 pub(crate) fn int_value(&self) -> i64 {
127 use TimestampPrecision::*;
128 match self {
129 Year => -4,
130 Month => -3,
131 Day => -2,
132 Minute => -1,
133 Second => 0,
134 Millisecond => 3,
135 Microsecond => 6,
136 Nanosecond => 9,
137 OtherFractionalSeconds(scale) => *scale,
138 }
139 }
140}
141
142impl TryFrom<&str> for TimestampPrecision {
143 type Error = IonSchemaError;
144
145 fn try_from(value: &str) -> Result<Self, Self::Error> {
146 Ok(match value {
147 "year" => TimestampPrecision::Year,
148 "month" => TimestampPrecision::Month,
149 "day" => TimestampPrecision::Day,
150 "minute" => TimestampPrecision::Minute,
151 "second" => TimestampPrecision::Second,
152 "millisecond" => TimestampPrecision::Millisecond,
153 "microsecond" => TimestampPrecision::Microsecond,
154 "nanosecond" => TimestampPrecision::Nanosecond,
155 _ => {
156 return invalid_schema_error(format!(
157 "Invalid timestamp precision specified {value}"
158 ))
159 }
160 })
161 }
162}
163
164impl PartialOrd for TimestampPrecision {
165 fn partial_cmp(&self, other: &TimestampPrecision) -> Option<Ordering> {
166 let self_value = self.int_value();
167 let other_value = other.int_value();
168
169 Some(self_value.cmp(&other_value))
170 }
171}
172
173impl WriteAsIon for TimestampPrecision {
174 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
175 writer.write_symbol(self.string_value().as_str())
176 }
177}
178
179impl From<TimestampPrecision> for Element {
180 fn from(value: TimestampPrecision) -> Self {
181 Element::symbol(value.string_value())
182 }
183}
184
185impl Display for TimestampPrecision {
186 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
187 f.write_str(&self.string_value())
188 }
189}
190
191#[derive(Debug, Clone, PartialEq)]
200pub enum ValidValue {
201 NumberRange(NumberRange),
202 TimestampRange(TimestampRange),
203 Element(Value),
204}
205
206impl ValidValue {
207 pub fn from_ion_element(element: &Element, isl_version: IslVersion) -> IonSchemaResult<Self> {
208 let annotation = element.annotations();
209 if element.annotations().contains("range") {
210 isl_require!(annotation.len() == 1 => "Unexpected annotation(s) on valid values argument: {element}")?;
211 let has_timestamp = element.as_sequence().map_or(false, |s| {
213 s.elements().any(|it| it.ion_type() == IonType::Timestamp)
214 });
215 let range = if has_timestamp {
216 ValidValue::TimestampRange(TimestampRange::from_ion_element(element, |e| {
217 e.as_timestamp()
218 .filter(|t| isl_version != IslVersion::V1_0 || t.offset().is_some())
219 })?)
220 } else {
221 ValidValue::NumberRange(NumberRange::from_ion_element(
222 element,
223 Element::any_number_as_decimal,
224 )?)
225 };
226 Ok(range)
227 } else {
228 isl_require!(annotation.is_empty() => "Unexpected annotation(s) on valid values argument: {element}")?;
229 Ok(ValidValue::Element(element.value().to_owned()))
230 }
231 }
232}
233
234impl Display for ValidValue {
235 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
236 match self {
237 ValidValue::Element(element) => write!(f, "{element}"),
238 ValidValue::NumberRange(r) => write!(f, "{r}"),
239 ValidValue::TimestampRange(r) => write!(f, "{r}"),
240 }
241 }
242}
243
244impl WriteAsIon for ValidValue {
245 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
246 match self {
247 ValidValue::Element(value) => writer.write(value),
248 ValidValue::NumberRange(r) => writer.write(r),
249 ValidValue::TimestampRange(r) => writer.write(r),
250 }
251 }
252}
253
254impl From<NumberRange> for ValidValue {
255 fn from(number_range: NumberRange) -> Self {
256 ValidValue::NumberRange(number_range)
257 }
258}
259
260impl From<TimestampRange> for ValidValue {
261 fn from(timestamp_range: TimestampRange) -> Self {
262 ValidValue::TimestampRange(timestamp_range)
263 }
264}
265
266impl<T: Into<Value>> From<T> for ValidValue {
267 fn from(value: T) -> Self {
268 ValidValue::Element(value.into())
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum TimestampOffset {
277 Known(i32), Unknown, }
280
281impl TryFrom<&str> for TimestampOffset {
282 type Error = IonSchemaError;
283
284 fn try_from(string_value: &str) -> Result<Self, Self::Error> {
285 if string_value == "-00:00" {
287 Ok(TimestampOffset::Unknown)
288 } else {
289 if string_value.len() != 6 || string_value.chars().nth(3).unwrap() != ':' {
290 return invalid_schema_error(
291 "`timestamp_offset` values must be of the form \"[+|-]hh:mm\"",
292 );
293 }
294 let h = &string_value[1..3];
296 let m = &string_value[4..6];
297 let sign = match &string_value[..1] {
298 "-" => -1,
299 "+" => 1,
300 _ => {
301 return invalid_schema_error(format!(
302 "unrecognized `timestamp_offset` sign '{}'",
303 &string_value[..1]
304 ))
305 }
306 };
307 match (h.parse::<i32>(), m.parse::<i32>()) {
308 (Ok(hours), Ok(minutes))
309 if (0..24).contains(&hours) && (0..60).contains(&minutes) =>
310 {
311 Ok(TimestampOffset::Known(sign * (hours * 60 + minutes)))
312 }
313 _ => invalid_schema_error(format!("invalid timestamp offset {string_value}")),
314 }
315 }
316 }
317}
318
319impl From<Option<i32>> for TimestampOffset {
320 fn from(value: Option<i32>) -> Self {
321 use TimestampOffset::*;
322 match value {
323 None => Unknown,
324 Some(offset_in_minutes) => Known(offset_in_minutes),
325 }
326 }
327}
328
329impl Display for TimestampOffset {
330 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
331 use TimestampOffset::*;
332 match &self {
333 Unknown => write!(f, "-00:00"),
334 Known(offset) => {
335 let sign = if offset < &0 { "-" } else { "+" };
336 let hours = abs(*offset) / 60;
337 let minutes = abs(*offset) - hours * 60;
338 write!(f, "{sign}{hours:02}:{minutes:02}")
339 }
340 }
341 }
342}
343
344impl WriteAsIon for TimestampOffset {
345 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
346 match &self {
347 TimestampOffset::Known(offset) => {
348 let sign = if offset < &0 { "-" } else { "+" };
349 let hours = abs(*offset) / 60;
350 let minutes = abs(*offset) - hours * 60;
351 writer.write_string(format!("{sign}{hours:02}:{minutes:02}"))
352 }
353 TimestampOffset::Unknown => writer.write_string("-00:00"),
354 }
355 }
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub enum Ieee754InterchangeFormat {
360 Binary16,
361 Binary32,
362 Binary64,
363}
364
365impl TryFrom<&str> for Ieee754InterchangeFormat {
366 type Error = IonSchemaError;
367 fn try_from(string_value: &str) -> Result<Self, Self::Error> {
368 use Ieee754InterchangeFormat::*;
369 match string_value {
370 "binary16" => Ok(Binary16),
371 "binary32" => Ok(Binary32),
372 "binary64" => Ok(Binary64),
373 _ => invalid_schema_error(format!(
374 "unrecognized `ieee754_float` value {}",
375 &string_value
376 )),
377 }
378 }
379}
380
381impl Display for Ieee754InterchangeFormat {
382 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
383 write!(
384 f,
385 "{}",
386 match self {
387 Ieee754InterchangeFormat::Binary16 => "binary16",
388 Ieee754InterchangeFormat::Binary32 => "binary32",
389 Ieee754InterchangeFormat::Binary64 => "binary64",
390 }
391 )
392 }
393}
394
395impl WriteAsIon for Ieee754InterchangeFormat {
396 fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
397 writer.write_symbol(match self {
398 Ieee754InterchangeFormat::Binary16 => "binary16",
399 Ieee754InterchangeFormat::Binary32 => "binary32",
400 Ieee754InterchangeFormat::Binary64 => "binary64",
401 })
402 }
403}