xmpp-parsers: Convert DataForms validation to xso

This commit is contained in:
Emmanuel Gil Peyrot 2024-08-04 20:53:44 +02:00 committed by Jonas Schäfer
parent 95d08e3c1e
commit ce0f3bec0e

View file

@ -4,19 +4,18 @@
// 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 std::borrow::Cow;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use minidom::{Element, IntoAttributeValue}; use minidom::IntoAttributeValue;
use xso::{ use xso::{error::Error, AsXml, AsXmlText, FromXml, FromXmlText};
error::{Error, FromElementError},
AsXml, FromXml,
};
use crate::ns::{self, XDATA_VALIDATE}; use crate::ns;
/// Validation Method /// Validation Method
#[derive(Debug, Clone, PartialEq)] #[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::XDATA_VALIDATE)]
pub enum Method { pub enum Method {
/// … to indicate that the value(s) should simply match the field type and datatype constraints, /// … 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 `<validate/>` element shall contain a `<basic/>` child element. Using `<basic/>` validation,
@ -24,6 +23,7 @@ pub enum Method {
/// the field type. /// the field type.
/// ///
/// <https://xmpp.org/extensions/xep-0122.html#usercases-validation.basic> /// <https://xmpp.org/extensions/xep-0122.html#usercases-validation.basic>
#[xml(name = "basic")]
Basic, Basic,
/// For "list-single" or "list-multi", to indicate that the user may enter a custom value /// For "list-single" or "list-multi", to indicate that the user may enter a custom value
@ -34,6 +34,7 @@ pub enum Method {
/// "list-multi", with no options and all values automatically selected. /// "list-multi", with no options and all values automatically selected.
/// ///
/// <https://xmpp.org/extensions/xep-0122.html#usercases-validation.open> /// <https://xmpp.org/extensions/xep-0122.html#usercases-validation.open>
#[xml(name = "open")]
Open, Open,
/// To indicate that the value should fall within a certain range, the `<validate/>` element shall /// To indicate that the value should fall within a certain range, the `<validate/>` element shall
@ -51,10 +52,14 @@ pub enum Method {
/// constraints. /// constraints.
/// ///
/// <https://xmpp.org/extensions/xep-0122.html#usercases-validation.range> /// <https://xmpp.org/extensions/xep-0122.html#usercases-validation.range>
#[xml(name = "range")]
Range { Range {
/// The 'min' attribute specifies the minimum allowable value. /// The 'min' attribute specifies the minimum allowable value.
#[xml(attribute(default))]
min: Option<String>, min: Option<String>,
/// The 'max' attribute specifies the maximum allowable value. /// The 'max' attribute specifies the maximum allowable value.
#[xml(attribute(default))]
max: Option<String>, max: Option<String>,
}, },
@ -65,7 +70,8 @@ pub enum Method {
/// character data only. /// character data only.
/// ///
/// <https://xmpp.org/extensions/xep-0122.html#usercases-validatoin.regex> /// <https://xmpp.org/extensions/xep-0122.html#usercases-validatoin.regex>
Regex(String), #[xml(name = "regex")]
Regex(#[xml(text)] String),
} }
/// Selection Ranges in "list-multi" /// Selection Ranges in "list-multi"
@ -75,6 +81,7 @@ pub struct ListRange {
/// The 'min' attribute specifies the minimum allowable number of selected/entered values. /// The 'min' attribute specifies the minimum allowable number of selected/entered values.
#[xml(attribute(default))] #[xml(attribute(default))]
pub min: Option<u32>, pub min: Option<u32>,
/// The 'max' attribute specifies the maximum allowable number of selected/entered values. /// The 'max' attribute specifies the maximum allowable number of selected/entered values.
#[xml(attribute(default))] #[xml(attribute(default))]
pub max: Option<u32>, pub max: Option<u32>,
@ -181,7 +188,8 @@ pub enum Datatype {
} }
/// Validation rules for a DataForms Field. /// Validation rules for a DataForms Field.
#[derive(Debug, Clone, PartialEq)] #[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::XDATA_VALIDATE, name = "validate")]
pub struct Validate { pub struct Validate {
/// The 'datatype' attribute specifies the datatype. This attribute is OPTIONAL, and defaults /// The 'datatype' attribute specifies the datatype. This attribute is OPTIONAL, and defaults
/// to "xs:string". It MUST meet one of the following conditions: /// to "xs:string". It MUST meet one of the following conditions:
@ -191,6 +199,7 @@ pub struct Validate {
/// - Start with "x:", and specify a user-defined datatype. /// - Start with "x:", and specify a user-defined datatype.
/// ///
/// Note that while "x:" allows for ad-hoc definitions, its use is NOT RECOMMENDED. /// Note that while "x:" allows for ad-hoc definitions, its use is NOT RECOMMENDED.
#[xml(attribute(default))]
pub datatype: Option<Datatype>, pub datatype: Option<Datatype>,
/// The validation method. If no validation method is specified, form processors MUST /// The validation method. If no validation method is specified, form processors MUST
@ -202,6 +211,7 @@ pub struct Validate {
/// defined by that method. /// defined by that method.
/// ///
/// <https://xmpp.org/extensions/xep-0122.html#usecases-validation> /// <https://xmpp.org/extensions/xep-0122.html#usecases-validation>
#[xml(child(default))]
pub method: Option<Method>, pub method: Option<Method>,
/// For "list-multi", validation can indicate (via the `<list-range/>` element) that a minimum /// For "list-multi", validation can indicate (via the `<list-range/>` element) that a minimum
@ -215,108 +225,10 @@ pub struct Validate {
/// selection constraints. /// selection constraints.
/// ///
/// <https://xmpp.org/extensions/xep-0122.html#usecases-ranges> /// <https://xmpp.org/extensions/xep-0122.html#usecases-ranges>
#[xml(child(default))]
pub list_range: Option<ListRange>, pub list_range: Option<ListRange>,
} }
impl TryFrom<Element> for Validate {
type Error = FromElementError;
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()
.map_err(|err| FromElementError::Invalid(Error::TextParseError(err.into())))?,
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::Other(
"Encountered unsupported number (n > 1) of list-range in validate element.",
).into());
}
validate.list_range = Some(list_range);
}
_ => {
let method = Method::try_from(child.clone())?;
if validate.method.is_some() {
return Err(Error::Other(
"Encountered unsupported number (n > 1) of validation methods in validate element.",
).into());
}
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::Other("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 { impl FromStr for Datatype {
type Err = DatatypeError; type Err = DatatypeError;
@ -404,9 +316,22 @@ impl IntoAttributeValue for Datatype {
} }
} }
impl FromXmlText for Datatype {
fn from_xml_text(s: String) -> Result<Datatype, Error> {
s.parse().map_err(Error::text_parse_error)
}
}
impl AsXmlText for Datatype {
fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
Ok(Cow::Owned(self.to_string()))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use minidom::Element;
#[test] #[test]
fn test_parse_datatype() -> Result<(), DatatypeError> { fn test_parse_datatype() -> Result<(), DatatypeError> {