use serde::ser::{SerializeMap, SerializeStruct};
use serde::{Serialize, Serializer};
use serde_repr::Serialize_repr;
use std::fmt;
#[cfg(feature = "utf16")]
use widestring::Utf16Str;
use crate::intl::date_time_format_options::JsIntlDateTimeFormatOptions;
use crate::intl::number_format_options::JsIntlNumberFormatOptions;
#[derive(Clone, Debug, Eq, PartialEq, Serialize_repr)]
#[repr(u8)]
pub enum ErrorKind {
ExpectArgumentClosingBrace = 1,
EmptyArgument = 2,
MalformedArgument = 3,
ExpectArgumentType = 4,
InvalidArgumentType = 5,
ExpectArgumentStyle = 6,
InvalidNumberSkeleton = 7,
InvalidDateTimeSkeleton = 8,
ExpectNumberSkeleton = 9,
ExpectDateTimeSkeleton = 10,
UnclosedQuoteInArgumentStyle = 11,
ExpectSelectArgumentOptions = 12,
ExpectPluralArgumentOffsetValue = 13,
InvalidPluralArgumentOffsetValue = 14,
ExpectSelectArgumentSelector = 15,
ExpectPluralArgumentSelector = 16,
ExpectSelectArgumentSelectorFragment = 17,
ExpectPluralArgumentSelectorFragment = 18,
InvalidPluralArgumentSelector = 19,
DuplicatePluralArgumentSelector = 20,
DuplicateSelectArgumentSelector = 21,
MissingOtherClause = 22,
InvalidTag = 23,
InvalidTagName = 25,
UnmatchedClosingTag = 26,
UnclosedTag = 27,
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErrorKind::ExpectArgumentClosingBrace => write!(f, "EXPECT_ARGUMENT_CLOSING_BRACE"),
ErrorKind::EmptyArgument => write!(f, "EMPTY_ARGUMENT"),
ErrorKind::MalformedArgument => write!(f, "MALFORMED_ARGUMENT"),
ErrorKind::ExpectArgumentType => write!(f, "EXPECT_ARGUMENT_TYPE"),
ErrorKind::InvalidArgumentType => write!(f, "INVALID_ARGUMENT_TYPE"),
ErrorKind::ExpectArgumentStyle => write!(f, "EXPECT_ARGUMENT_STYLE"),
ErrorKind::InvalidNumberSkeleton => write!(f, "INVALID_NUMBER_SKELETON"),
ErrorKind::InvalidDateTimeSkeleton => write!(f, "INVALID_DATE_TIME_SKELETON"),
ErrorKind::ExpectNumberSkeleton => write!(f, "EXPECT_NUMBER_SKELETON"),
ErrorKind::ExpectDateTimeSkeleton => write!(f, "EXPECT_DATE_TIME_SKELETON"),
ErrorKind::UnclosedQuoteInArgumentStyle => {
write!(f, "UNCLOSED_QUOTE_IN_ARGUMENT_STYLE")
}
ErrorKind::ExpectSelectArgumentOptions => write!(f, "EXPECT_SELECT_ARGUMENT_OPTIONS"),
ErrorKind::ExpectPluralArgumentOffsetValue => {
write!(f, "EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE")
}
ErrorKind::InvalidPluralArgumentOffsetValue => {
write!(f, "INVALID_PLURAL_ARGUMENT_OFFSET_VALUE")
}
ErrorKind::ExpectSelectArgumentSelector => write!(f, "EXPECT_SELECT_ARGUMENT_SELECTOR"),
ErrorKind::ExpectPluralArgumentSelector => write!(f, "EXPECT_PLURAL_ARGUMENT_SELECTOR"),
ErrorKind::ExpectSelectArgumentSelectorFragment => {
write!(f, "EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT")
}
ErrorKind::ExpectPluralArgumentSelectorFragment => {
write!(f, "EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT")
}
ErrorKind::InvalidPluralArgumentSelector => {
write!(f, "INVALID_PLURAL_ARGUMENT_SELECTOR")
}
ErrorKind::DuplicatePluralArgumentSelector => {
write!(f, "DUPLICATE_PLURAL_ARGUMENT_SELECTOR")
}
ErrorKind::DuplicateSelectArgumentSelector => {
write!(f, "DUPLICATE_SELECT_ARGUMENT_SELECTOR")
}
ErrorKind::MissingOtherClause => write!(f, "MISSING_OTHER_CLAUSE"),
ErrorKind::InvalidTag => write!(f, "INVALID_TAG"),
ErrorKind::InvalidTagName => write!(f, "INVALID_TAG_NAME"),
ErrorKind::UnmatchedClosingTag => write!(f, "UNMATCHED_CLOSING_TAG"),
ErrorKind::UnclosedTag => write!(f, "UNCLOSED_TAG"),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Position {
pub offset: usize,
pub line: usize,
pub column: usize,
}
impl Position {
pub fn new(offset: usize, line: usize, column: usize) -> Position {
Position {
offset,
line,
column,
}
}
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Position::new({:?}, {:?}, {:?})",
self.offset, self.line, self.column
)
}
}
#[derive(Clone, Copy, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Span {
pub start: Position,
pub end: Position,
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Span::new({:?}, {:?})", self.start, self.end)
}
}
impl Span {
pub fn new(start: Position, end: Position) -> Span {
Span { start, end }
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Error {
pub kind: ErrorKind,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Span>,
}
pub type Ast<'s> = Vec<AstElement<'s>>;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum PluralType {
Cardinal,
Ordinal,
}
#[derive(Clone, Debug, PartialEq)]
pub enum AstElement<'s> {
Literal {
value: String,
span: Option<Span>,
},
Argument { value: String, span: Option<Span> },
Number {
value: String,
span: Option<Span>,
style: Option<NumberArgStyle<'s>>,
},
Date {
value: String,
span: Option<Span>,
style: Option<DateTimeArgStyle<'s>>,
},
Time {
value: String,
span: Option<Span>,
style: Option<DateTimeArgStyle<'s>>,
},
Select {
value: String,
span: Option<Span>,
options: PluralOrSelectOptions<'s>,
},
Plural {
value: String,
plural_type: PluralType,
span: Option<Span>,
offset: i64,
options: PluralOrSelectOptions<'s>,
},
Pound(Span),
Tag {
value: &'s str,
span: Option<Span>,
children: Box<Ast<'s>>,
},
}
impl<'s> Serialize for AstElement<'s> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
AstElement::Literal {
ref value,
ref span,
} => {
let mut state = serializer.serialize_struct("Literal", 3)?;
state.serialize_field("type", &0)?;
state.serialize_field("value", value)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
state.end()
}
AstElement::Argument {
ref value,
ref span,
} => {
let mut state = serializer.serialize_struct("Argument", 3)?;
state.serialize_field("type", &1)?;
state.serialize_field("value", value)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
state.end()
}
AstElement::Number {
ref value,
ref span,
ref style,
} => {
let mut state = serializer.serialize_struct("Number", 4)?;
state.serialize_field("type", &2)?;
state.serialize_field("value", value)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
if style.is_some() {
state.serialize_field("style", style)?;
}
state.end()
}
AstElement::Date {
ref value,
ref span,
ref style,
} => {
let mut state = serializer.serialize_struct("Date", 4)?;
state.serialize_field("type", &3)?;
state.serialize_field("value", value)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
if style.is_some() {
state.serialize_field("style", style)?;
}
state.end()
}
AstElement::Time {
ref value,
ref span,
ref style,
} => {
let mut state = serializer.serialize_struct("Time", 4)?;
state.serialize_field("type", &4)?;
state.serialize_field("value", value)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
if style.is_some() {
state.serialize_field("style", style)?;
}
state.end()
}
AstElement::Select {
ref value,
ref span,
ref options,
} => {
let mut state = serializer.serialize_struct("Select", 4)?;
state.serialize_field("type", &5)?;
state.serialize_field("value", value)?;
state.serialize_field("options", options)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
state.end()
}
AstElement::Plural {
ref value,
ref span,
ref plural_type,
ref offset,
ref options,
} => {
let mut state = serializer.serialize_struct("Plural", 6)?;
state.serialize_field("type", &6)?;
state.serialize_field("value", value)?;
state.serialize_field("options", options)?;
state.serialize_field("offset", offset)?;
state.serialize_field("pluralType", plural_type)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
state.end()
}
AstElement::Pound(ref span) => {
let mut state = serializer.serialize_struct("Pound", 2)?;
state.serialize_field("type", &7)?;
state.serialize_field("location", span)?;
state.end()
}
AstElement::Tag {
ref value,
ref span,
ref children,
} => {
let mut state = serializer.serialize_struct("Pound", 2)?;
state.serialize_field("type", &8)?;
state.serialize_field("value", value)?;
state.serialize_field("children", children)?;
if span.is_some() {
state.serialize_field("location", span)?;
}
state.end()
}
}
}
}
#[cfg(feature = "utf16")]
#[derive(Clone, Debug, PartialEq)]
pub struct PluralOrSelectOptions<'s>(pub Vec<(&'s Utf16Str, PluralOrSelectOption<'s>)>);
#[cfg(not(feature = "utf16"))]
#[derive(Clone, Debug, PartialEq)]
pub struct PluralOrSelectOptions<'s>(pub Vec<(&'s str, PluralOrSelectOption<'s>)>);
impl<'s> Serialize for PluralOrSelectOptions<'s> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let options = &self.0;
let mut state = serializer.serialize_map(Some(options.len()))?;
for (selector, fragment) in options {
#[cfg(feature = "utf16")]
let s = selector.to_string();
#[cfg(feature = "utf16")]
let s = s.as_str();
#[cfg(not(feature = "utf16"))]
let s = selector;
state.serialize_entry(s, fragment)?;
}
state.end()
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(untagged)]
pub enum NumberArgStyle<'s> {
Style(&'s str),
Skeleton(NumberSkeleton<'s>),
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NumberSkeleton<'s> {
#[serde(rename = "type")]
pub skeleton_type: SkeletonType,
pub tokens: Vec<NumberSkeletonToken<'s>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Span>,
pub parsed_options: JsIntlNumberFormatOptions,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NumberSkeletonToken<'s> {
pub stem: &'s str,
pub options: Vec<&'s str>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum DateTimeArgStyle<'s> {
Style(&'s str),
Skeleton(DateTimeSkeleton),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize_repr)]
#[repr(u8)]
pub enum SkeletonType {
Number,
DateTime,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DateTimeSkeleton {
#[serde(rename = "type")]
pub skeleton_type: SkeletonType,
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Span>,
pub parsed_options: JsIntlDateTimeFormatOptions,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PluralOrSelectOption<'s> {
pub value: Ast<'s>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Span>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intl::number_format_options::JsIntlNumberFormatOptions;
use serde_json::json;
#[test]
fn serialize_number_arg_style_with_skeleton() {
similar_asserts::assert_eq!(
serde_json::to_value(NumberArgStyle::Skeleton(NumberSkeleton {
skeleton_type: SkeletonType::Number,
tokens: vec![NumberSkeletonToken {
stem: "foo",
options: vec!["bar", "baz"]
}],
location: Some(Span::new(Position::new(0, 1, 1), Position::new(11, 1, 12))),
parsed_options: JsIntlNumberFormatOptions::default(),
}))
.unwrap(),
json!({
"tokens": [{
"stem": "foo",
"options": [
"bar",
"baz"
]
}],
"location": {
"start": {
"offset": 0,
"line": 1,
"column": 1,
},
"end": {
"offset": 11,
"line": 1,
"column": 12,
}
},
"type": 0,
"parsedOptions": {},
})
);
}
#[test]
fn serialize_number_arg_style_string() {
similar_asserts::assert_eq!(
serde_json::to_value(NumberArgStyle::Style("percent")).unwrap(),
json!("percent")
)
}
#[test]
fn serialize_plural_type() {
similar_asserts::assert_eq!(
serde_json::to_value(PluralType::Cardinal).unwrap(),
json!("cardinal")
)
}
}