mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-07-12 22:21:53 +00:00
Add support for XEP-0122: Data Forms Validation
This commit is contained in:
parent
d4d520e1f6
commit
ffd0c3c719
5 changed files with 556 additions and 2 deletions
|
@ -4,6 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
use crate::data_forms::Validate;
|
||||||
use crate::media_element::MediaElement;
|
use crate::media_element::MediaElement;
|
||||||
use crate::ns;
|
use crate::ns;
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
@ -93,6 +94,9 @@ pub struct Field {
|
||||||
|
|
||||||
/// A list of media related to this field.
|
/// A list of media related to this field.
|
||||||
pub media: Vec<MediaElement>,
|
pub media: Vec<MediaElement>,
|
||||||
|
|
||||||
|
/// Validation rules for this field.
|
||||||
|
pub validate: Option<Validate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Field {
|
impl Field {
|
||||||
|
@ -107,6 +111,7 @@ impl Field {
|
||||||
options: Vec::new(),
|
options: Vec::new(),
|
||||||
media: Vec::new(),
|
media: Vec::new(),
|
||||||
values: Vec::new(),
|
values: Vec::new(),
|
||||||
|
validate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +187,7 @@ impl TryFrom<Element> for Field {
|
||||||
options: vec![],
|
options: vec![],
|
||||||
values: vec![],
|
values: vec![],
|
||||||
media: vec![],
|
media: vec![],
|
||||||
|
validate: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if field.type_ != FieldType::Fixed && field.var.is_none() {
|
if field.type_ != FieldType::Fixed && field.var.is_none() {
|
||||||
|
@ -213,6 +219,13 @@ impl TryFrom<Element> for Field {
|
||||||
check_no_children!(element, "desc");
|
check_no_children!(element, "desc");
|
||||||
check_no_attributes!(element, "desc");
|
check_no_attributes!(element, "desc");
|
||||||
field.desc = Some(element.text());
|
field.desc = Some(element.text());
|
||||||
|
} else if element.is("validate", ns::XDATA_VALIDATE) {
|
||||||
|
if field.validate.is_some() {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"More than one validate element in field.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
field.validate = Some(Validate::try_from(element.clone())?);
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
return Err(
|
||||||
Error::Other("Field child isn’t a value, option or media element.").into(),
|
Error::Other("Field child isn’t a value, option or media element.").into(),
|
||||||
|
@ -242,6 +255,7 @@ impl From<Field> for Element {
|
||||||
.map(|value| Element::builder("value", ns::DATA_FORMS).append(value)),
|
.map(|value| Element::builder("value", ns::DATA_FORMS).append(value)),
|
||||||
)
|
)
|
||||||
.append_all(field.media.iter().cloned().map(Element::from))
|
.append_all(field.media.iter().cloned().map(Element::from))
|
||||||
|
.append_all(field.validate)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,13 +389,14 @@ impl From<DataForm> for Element {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::data_forms::{Datatype, Validate};
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(Option_, 24);
|
assert_size!(Option_, 24);
|
||||||
assert_size!(FieldType, 1);
|
assert_size!(FieldType, 1);
|
||||||
assert_size!(Field, 76);
|
assert_size!(Field, 132);
|
||||||
assert_size!(DataFormType, 1);
|
assert_size!(DataFormType, 1);
|
||||||
assert_size!(DataForm, 52);
|
assert_size!(DataForm, 52);
|
||||||
}
|
}
|
||||||
|
@ -391,7 +406,7 @@ mod tests {
|
||||||
fn test_size() {
|
fn test_size() {
|
||||||
assert_size!(Option_, 48);
|
assert_size!(Option_, 48);
|
||||||
assert_size!(FieldType, 1);
|
assert_size!(FieldType, 1);
|
||||||
assert_size!(Field, 152);
|
assert_size!(Field, 264);
|
||||||
assert_size!(DataFormType, 1);
|
assert_size!(DataFormType, 1);
|
||||||
assert_size!(DataForm, 104);
|
assert_size!(DataForm, 104);
|
||||||
}
|
}
|
||||||
|
@ -439,6 +454,7 @@ mod tests {
|
||||||
options: vec![],
|
options: vec![],
|
||||||
values: vec!["Section 1: Bot Info".to_string()],
|
values: vec!["Section 1: Bot Info".to_string()],
|
||||||
media: vec![],
|
media: vec![],
|
||||||
|
validate: None,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -463,6 +479,40 @@ mod tests {
|
||||||
options: vec![],
|
options: vec![],
|
||||||
values: vec![],
|
values: vec![],
|
||||||
media: vec![],
|
media: vec![],
|
||||||
|
validate: None,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate() {
|
||||||
|
let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
|
||||||
|
<field var='evt.date' type='text-single' label='Event Date/Time'>
|
||||||
|
<validate xmlns='http://jabber.org/protocol/xdata-validate' datatype='xs:dateTime'/>
|
||||||
|
<value>2003-10-06T11:22:00-07:00</value>
|
||||||
|
</field>
|
||||||
|
</x>"#
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
let form = DataForm::try_from(elem).unwrap();
|
||||||
|
assert_eq!(form.type_, DataFormType::Form);
|
||||||
|
assert!(form.form_type.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
form.fields,
|
||||||
|
vec![Field {
|
||||||
|
var: Some("evt.date".to_string()),
|
||||||
|
type_: FieldType::TextSingle,
|
||||||
|
label: Some("Event Date/Time".to_string()),
|
||||||
|
required: false,
|
||||||
|
desc: None,
|
||||||
|
options: vec![],
|
||||||
|
values: vec!["2003-10-06T11:22:00-07:00".to_string()],
|
||||||
|
media: vec![],
|
||||||
|
validate: Some(Validate {
|
||||||
|
datatype: Some(Datatype::DateTime),
|
||||||
|
method: None,
|
||||||
|
list_range: None,
|
||||||
|
}),
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
14
parsers/src/data_forms/mod.rs
Normal file
14
parsers/src/data_forms/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) 2024 xmpp-rs contributors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
pub use data_form::*;
|
||||||
|
pub use validate::*;
|
||||||
|
|
||||||
|
/// XEP-0004: Data Forms
|
||||||
|
pub mod data_form;
|
||||||
|
|
||||||
|
/// XEP-0122: Data Forms Validation
|
||||||
|
pub mod validate;
|
486
parsers/src/data_forms/validate.rs
Normal file
486
parsers/src/data_forms/validate.rs
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
// Copyright (c) 2024 xmpp-rs contributors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// XEP-0122: Data Forms Validation
|
||||||
|
// https://xmpp.org/extensions/xep-0122.html#usecases-datatypes
|
||||||
|
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use minidom::{Element, IntoAttributeValue};
|
||||||
|
|
||||||
|
use crate::ns::XDATA_VALIDATE;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// Validation Method
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Method {
|
||||||
|
/// … to indicate that the value(s) should simply match the field type and datatype constraints,
|
||||||
|
/// the <validate/> element shall contain a <basic/> child element. Using <basic/> validation,
|
||||||
|
/// the form interpreter MUST follow the validation rules of the datatype (if understood) and
|
||||||
|
/// the field type.
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/extensions/xep-0122.html#usercases-validation.basic
|
||||||
|
Basic,
|
||||||
|
|
||||||
|
/// For "list-single" or "list-multi", to indicate that the user may enter a custom value
|
||||||
|
/// (matching the datatype constraints) or choose from the predefined values, the <validate/>
|
||||||
|
/// element shall contain an <open/> child element. The <open/> validation method applies to
|
||||||
|
/// "text-multi" differently; it hints that each value for a "text-multi" field shall be
|
||||||
|
/// validated separately. This effectively turns "text-multi" fields into an open-ended
|
||||||
|
/// "list-multi", with no options and all values automatically selected.
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/extensions/xep-0122.html#usercases-validation.open
|
||||||
|
Open,
|
||||||
|
|
||||||
|
/// To indicate that the value should fall within a certain range, the <validate/> element shall
|
||||||
|
/// contain a <range/> child element. The 'min' and 'max' attributes of the <range/> element
|
||||||
|
/// specify the minimum and maximum values allowed, respectively.
|
||||||
|
///
|
||||||
|
/// The 'max' attribute specifies the maximum allowable value. This attribute is OPTIONAL.
|
||||||
|
/// The value depends on the datatype in use.
|
||||||
|
///
|
||||||
|
/// The 'min' attribute specifies the minimum allowable value. This attribute is OPTIONAL.
|
||||||
|
/// The value depends on the datatype in use.
|
||||||
|
///
|
||||||
|
/// The <range/> element SHOULD possess either a 'min' or 'max' attribute, and MAY possess both.
|
||||||
|
/// If neither attribute is included, the processor MUST assume that there are no range
|
||||||
|
/// constraints.
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/extensions/xep-0122.html#usercases-validation.range
|
||||||
|
Range {
|
||||||
|
/// The 'min' attribute specifies the minimum allowable value.
|
||||||
|
min: Option<String>,
|
||||||
|
/// The 'max' attribute specifies the maximum allowable value.
|
||||||
|
max: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// To indicate that the value should be restricted to a regular expression, the <validate/>
|
||||||
|
/// element shall contain a <regex/> child element. The XML character data of this element is
|
||||||
|
/// the pattern to apply. The syntax of this content MUST be that defined for POSIX extended
|
||||||
|
/// regular expressions, including support for Unicode. The <regex/> element MUST contain
|
||||||
|
/// character data only.
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/extensions/xep-0122.html#usercases-validatoin.regex
|
||||||
|
Regex(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_element!(
|
||||||
|
/// Selection Ranges in "list-multi"
|
||||||
|
ListRange, "list-range", XDATA_VALIDATE,
|
||||||
|
attributes: [
|
||||||
|
/// The 'min' attribute specifies the minimum allowable number of selected/entered values.
|
||||||
|
min: Option<u32> = "min",
|
||||||
|
/// The 'max' attribute specifies the maximum allowable number of selected/entered values.
|
||||||
|
max: Option<u32> = "max",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Data Forms Validation Datatypes
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/registrar/xdv-datatypes.html
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Datatype {
|
||||||
|
/// A Uniform Resource Identifier Reference (URI)
|
||||||
|
AnyUri,
|
||||||
|
|
||||||
|
/// An integer with the specified min/max
|
||||||
|
/// Min: -128, Max: 127
|
||||||
|
Byte,
|
||||||
|
|
||||||
|
/// A calendar date
|
||||||
|
Date,
|
||||||
|
|
||||||
|
/// A specific instant of time
|
||||||
|
DateTime,
|
||||||
|
|
||||||
|
/// An arbitrary-precision decimal number
|
||||||
|
Decimal,
|
||||||
|
|
||||||
|
/// An IEEE double-precision 64-bit floating point type
|
||||||
|
Double,
|
||||||
|
|
||||||
|
/// An integer with the specified min/max
|
||||||
|
/// Min: -2147483648, Max: 2147483647
|
||||||
|
Int,
|
||||||
|
|
||||||
|
/// A decimal number with no fraction digits
|
||||||
|
Integer,
|
||||||
|
|
||||||
|
/// A language identifier as defined by RFC 1766
|
||||||
|
Language,
|
||||||
|
|
||||||
|
/// An integer with the specified min/max
|
||||||
|
/// Min: -9223372036854775808, Max: 9223372036854775807
|
||||||
|
Long,
|
||||||
|
|
||||||
|
/// An integer with the specified min/max
|
||||||
|
/// Min: -32768, Max: 32767
|
||||||
|
Short,
|
||||||
|
|
||||||
|
/// A character strings in XML
|
||||||
|
String,
|
||||||
|
|
||||||
|
/// An instant of time that recurs every day
|
||||||
|
Time,
|
||||||
|
|
||||||
|
/// A user-defined datatype
|
||||||
|
UserDefined(String),
|
||||||
|
|
||||||
|
/// A non-standard datatype
|
||||||
|
Other {
|
||||||
|
/// The prefix of the specified datatype. Should be registered with the XMPP Registrar.
|
||||||
|
prefix: String,
|
||||||
|
/// The actual value of the specified datatype. E.g. "lat" in the case of "geo:lat".
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validation rules for a DataForms Field.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Validate {
|
||||||
|
/// The 'datatype' attribute specifies the datatype. This attribute is OPTIONAL, and defaults
|
||||||
|
/// to "xs:string". It MUST meet one of the following conditions:
|
||||||
|
///
|
||||||
|
/// - Start with "xs:", and be one of the "built-in" datatypes defined in XML Schema Part 2
|
||||||
|
/// - Start with a prefix registered with the XMPP Registrar
|
||||||
|
/// - Start with "x:", and specify a user-defined datatype.
|
||||||
|
///
|
||||||
|
/// Note that while "x:" allows for ad-hoc definitions, its use is NOT RECOMMENDED.
|
||||||
|
pub datatype: Option<Datatype>,
|
||||||
|
|
||||||
|
/// The validation method. If no validation method is specified, form processors MUST
|
||||||
|
/// assume <basic/> validation. The <validate/> element SHOULD include one of the above
|
||||||
|
/// validation method elements, and MUST NOT include more than one.
|
||||||
|
///
|
||||||
|
/// Any validation method applied to a field of type "list-multi", "list-single", or "text-multi"
|
||||||
|
/// (other than <basic/>) MUST imply the same behavior as <open/>, with the additional constraints
|
||||||
|
/// defined by that method.
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/extensions/xep-0122.html#usecases-validation
|
||||||
|
pub method: Option<Method>,
|
||||||
|
|
||||||
|
/// For "list-multi", validation can indicate (via the <list-range/> element) that a minimum
|
||||||
|
/// and maximum number of options should be selected and/or entered. This selection range
|
||||||
|
/// MAY be combined with the other methods to provide more flexibility.
|
||||||
|
/// The <list-range/> element SHOULD be included only when the <field/> is of type "list-multi"
|
||||||
|
/// and SHOULD be ignored otherwise.
|
||||||
|
///
|
||||||
|
/// The <list-range/> element SHOULD possess either a 'min' or 'max' attribute, and MAY possess
|
||||||
|
/// both. If neither attribute is included, the processor MUST assume that there are no
|
||||||
|
/// selection constraints.
|
||||||
|
///
|
||||||
|
/// https://xmpp.org/extensions/xep-0122.html#usecases-ranges
|
||||||
|
pub list_range: Option<ListRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Element> for Validate {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(elem: Element) -> Result<Self, Self::Error> {
|
||||||
|
check_self!(elem, "validate", XDATA_VALIDATE);
|
||||||
|
check_no_unknown_attributes!(elem, "item", ["datatype"]);
|
||||||
|
|
||||||
|
let mut validate = Validate {
|
||||||
|
datatype: elem.attr("datatype").map(Datatype::from_str).transpose()?,
|
||||||
|
method: None,
|
||||||
|
list_range: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for child in elem.children() {
|
||||||
|
match child {
|
||||||
|
_ if child.is("list-range", XDATA_VALIDATE) => {
|
||||||
|
let list_range = ListRange::try_from(child.clone())?;
|
||||||
|
if validate.list_range.is_some() {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"Encountered unsupported number (n > 1) of list-range in validate element.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
validate.list_range = Some(list_range);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let method = Method::try_from(child.clone())?;
|
||||||
|
if validate.method.is_some() {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"Encountered unsupported number (n > 1) of validation methods in validate element.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
validate.method = Some(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(validate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Validate> for Element {
|
||||||
|
fn from(value: Validate) -> Self {
|
||||||
|
Element::builder("validate", XDATA_VALIDATE)
|
||||||
|
.attr("datatype", value.datatype)
|
||||||
|
.append_all(value.method)
|
||||||
|
.append_all(value.list_range)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Element> for Method {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(elem: Element) -> Result<Self, Self::Error> {
|
||||||
|
let method = match elem {
|
||||||
|
_ if elem.is("basic", XDATA_VALIDATE) => {
|
||||||
|
check_no_attributes!(elem, "basic");
|
||||||
|
Method::Basic
|
||||||
|
}
|
||||||
|
_ if elem.is("open", XDATA_VALIDATE) => {
|
||||||
|
check_no_attributes!(elem, "open");
|
||||||
|
Method::Open
|
||||||
|
}
|
||||||
|
_ if elem.is("range", XDATA_VALIDATE) => {
|
||||||
|
check_no_unknown_attributes!(elem, "range", ["min", "max"]);
|
||||||
|
Method::Range {
|
||||||
|
min: elem.attr("min").map(ToString::to_string),
|
||||||
|
max: elem.attr("max").map(ToString::to_string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ if elem.is("regex", XDATA_VALIDATE) => {
|
||||||
|
check_no_attributes!(elem, "regex");
|
||||||
|
check_no_children!(elem, "regex");
|
||||||
|
Method::Regex(elem.text())
|
||||||
|
}
|
||||||
|
_ => return Err(Error::ParseError("Encountered invalid validation method.")),
|
||||||
|
};
|
||||||
|
Ok(method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Method> for Element {
|
||||||
|
fn from(value: Method) -> Self {
|
||||||
|
match value {
|
||||||
|
Method::Basic => Element::builder("basic", XDATA_VALIDATE),
|
||||||
|
Method::Open => Element::builder("open", XDATA_VALIDATE),
|
||||||
|
Method::Range { min, max } => Element::builder("range", XDATA_VALIDATE)
|
||||||
|
.attr("min", min)
|
||||||
|
.attr("max", max),
|
||||||
|
Method::Regex(regex) => Element::builder("regex", XDATA_VALIDATE).append(regex),
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Datatype {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = s.splitn(2, ":");
|
||||||
|
|
||||||
|
let Some(prefix) = parts.next() else {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"Encountered invalid validation datatype which is missing a prefix.",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
match prefix {
|
||||||
|
"xs" => (),
|
||||||
|
"x" => {
|
||||||
|
return Ok(Datatype::UserDefined(
|
||||||
|
parts.next().unwrap_or_default().to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Ok(Datatype::Other {
|
||||||
|
prefix: prefix.to_string(),
|
||||||
|
value: parts.next().unwrap_or_default().to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(datatype) = parts.next() else {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"Encountered invalid validation datatype.",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsed_datatype = match datatype.to_ascii_lowercase().as_str() {
|
||||||
|
"anyuri" => Datatype::AnyUri,
|
||||||
|
"byte" => Datatype::Byte,
|
||||||
|
"date" => Datatype::Date,
|
||||||
|
"datetime" => Datatype::DateTime,
|
||||||
|
"decimal" => Datatype::Decimal,
|
||||||
|
"double" => Datatype::Double,
|
||||||
|
"int" => Datatype::Int,
|
||||||
|
"integer" => Datatype::Integer,
|
||||||
|
"language" => Datatype::Language,
|
||||||
|
"long" => Datatype::Long,
|
||||||
|
"short" => Datatype::Short,
|
||||||
|
"string" => Datatype::String,
|
||||||
|
"time" => Datatype::Time,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::ParseError(
|
||||||
|
"Encountered invalid validation datatype.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(parsed_datatype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Datatype {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let value = match self {
|
||||||
|
Datatype::AnyUri => "xs:anyURI",
|
||||||
|
Datatype::Byte => "xs:byte",
|
||||||
|
Datatype::Date => "xs:date",
|
||||||
|
Datatype::DateTime => "xs:dateTime",
|
||||||
|
Datatype::Decimal => "xs:decimal",
|
||||||
|
Datatype::Double => "xs:double",
|
||||||
|
Datatype::Int => "xs:int",
|
||||||
|
Datatype::Integer => "xs:integer",
|
||||||
|
Datatype::Language => "xs:language",
|
||||||
|
Datatype::Long => "xs:long",
|
||||||
|
Datatype::Short => "xs:short",
|
||||||
|
Datatype::String => "xs:string",
|
||||||
|
Datatype::Time => "xs:time",
|
||||||
|
Datatype::UserDefined(value) => &format!("x:{value}"),
|
||||||
|
Datatype::Other { prefix, value } => &format!("{prefix}:{value}"),
|
||||||
|
};
|
||||||
|
write!(f, "{value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoAttributeValue for Datatype {
|
||||||
|
fn into_attribute_value(self) -> Option<String> {
|
||||||
|
Some(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_datatype() -> Result<(), Error> {
|
||||||
|
assert_eq!(Datatype::AnyUri, "xs:anyURI".parse()?);
|
||||||
|
assert_eq!(Datatype::AnyUri, "xs:anyuri".parse()?);
|
||||||
|
assert!("xs:".parse::<Datatype>().is_err());
|
||||||
|
assert_eq!(
|
||||||
|
Datatype::AnyUri.into_attribute_value(),
|
||||||
|
Some("xs:anyURI".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Datatype::UserDefined("id".to_string()), "x:id".parse()?);
|
||||||
|
assert_eq!(Datatype::UserDefined("".to_string()), "x:".parse()?);
|
||||||
|
assert_eq!(
|
||||||
|
Datatype::UserDefined("id".to_string()).into_attribute_value(),
|
||||||
|
Some("x:id".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Datatype::Other {
|
||||||
|
prefix: "geo".to_string(),
|
||||||
|
value: "lat".to_string()
|
||||||
|
},
|
||||||
|
"geo:lat".parse()?
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Datatype::Other {
|
||||||
|
prefix: "geo".to_string(),
|
||||||
|
value: "".to_string()
|
||||||
|
},
|
||||||
|
"geo:".parse()?
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Datatype::Other {
|
||||||
|
prefix: "geo".to_string(),
|
||||||
|
value: "lat".to_string()
|
||||||
|
}
|
||||||
|
.into_attribute_value(),
|
||||||
|
Some("geo:lat".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_validate_element() -> Result<(), Error> {
|
||||||
|
let cases = [
|
||||||
|
(
|
||||||
|
r#"<validate xmlns='http://jabber.org/protocol/xdata-validate'/>"#,
|
||||||
|
Validate {
|
||||||
|
datatype: None,
|
||||||
|
method: None,
|
||||||
|
list_range: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"<validate xmlns='http://jabber.org/protocol/xdata-validate' datatype="xs:string"><basic/><list-range max="3" min="1"/></validate>"#,
|
||||||
|
Validate {
|
||||||
|
datatype: Some(Datatype::String),
|
||||||
|
method: Some(Method::Basic),
|
||||||
|
list_range: Some(ListRange {
|
||||||
|
min: Some(1),
|
||||||
|
max: Some(3),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"<validate xmlns='http://jabber.org/protocol/xdata-validate' datatype="xs:string"><regex>([0-9]{3})-([0-9]{2})-([0-9]{4})</regex></validate>"#,
|
||||||
|
Validate {
|
||||||
|
datatype: Some(Datatype::String),
|
||||||
|
method: Some(Method::Regex(
|
||||||
|
"([0-9]{3})-([0-9]{2})-([0-9]{4})".to_string(),
|
||||||
|
)),
|
||||||
|
list_range: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r#"<validate xmlns='http://jabber.org/protocol/xdata-validate' datatype="xs:dateTime"><range max="2003-10-24T23:59:59-07:00" min="2003-10-05T00:00:00-07:00"/></validate>"#,
|
||||||
|
Validate {
|
||||||
|
datatype: Some(Datatype::DateTime),
|
||||||
|
method: Some(Method::Range {
|
||||||
|
min: Some("2003-10-05T00:00:00-07:00".to_string()),
|
||||||
|
max: Some("2003-10-24T23:59:59-07:00".to_string()),
|
||||||
|
}),
|
||||||
|
list_range: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for case in cases {
|
||||||
|
let parsed_element: Validate = case
|
||||||
|
.0
|
||||||
|
.parse::<Element>()
|
||||||
|
.expect(&format!("Failed to parse {}", case.0))
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
assert_eq!(parsed_element, case.1);
|
||||||
|
|
||||||
|
let xml = String::from(&Element::from(parsed_element));
|
||||||
|
assert_eq!(xml, case.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fails_with_invalid_children() -> Result<(), Error> {
|
||||||
|
let cases = [
|
||||||
|
r#"<validate xmlns='http://jabber.org/protocol/xdata-validate'><basic /><open /></validate>"#,
|
||||||
|
r#"<validate xmlns='http://jabber.org/protocol/xdata-validate'><unknown /></validate>"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
for case in cases {
|
||||||
|
let element = case
|
||||||
|
.parse::<Element>()
|
||||||
|
.expect(&format!("Failed to parse {}", case));
|
||||||
|
assert!(Validate::try_from(element).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -290,6 +290,9 @@ pub const OID: &str = "urn:xmpp:occupant-id:0";
|
||||||
/// XEP-0444: Message Reactions
|
/// XEP-0444: Message Reactions
|
||||||
pub const REACTIONS: &str = "urn:xmpp:reactions:0";
|
pub const REACTIONS: &str = "urn:xmpp:reactions:0";
|
||||||
|
|
||||||
|
/// XEP-0122: Data Forms Validation
|
||||||
|
pub const XDATA_VALIDATE: &str = "http://jabber.org/protocol/xdata-validate";
|
||||||
|
|
||||||
/// Alias for the main namespace of the stream, that is "jabber:client" when
|
/// Alias for the main namespace of the stream, that is "jabber:client" when
|
||||||
/// the component feature isn’t enabled.
|
/// the component feature isn’t enabled.
|
||||||
#[cfg(not(feature = "component"))]
|
#[cfg(not(feature = "component"))]
|
||||||
|
|
|
@ -95,6 +95,7 @@ pub fn generate_address_field<S: Into<String>>(var: S, values: Vec<String>) -> F
|
||||||
options: vec![],
|
options: vec![],
|
||||||
values,
|
values,
|
||||||
media: vec![],
|
media: vec![],
|
||||||
|
validate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue