Add 'closed' to Question

This commit is contained in:
Aode (lion) 2022-01-28 12:24:08 -06:00
parent 2f9532ad65
commit f3d394d5d6
5 changed files with 383 additions and 9 deletions

View file

@ -1,5 +1,10 @@
# Unreleased
# 0.7.0-alpha.17
- add `XsdBoolean` for `xsd:boolean` serde compatibility
- add `closed` field for `Question` (range Object | Link | xsd:datetime | xsd:boolean), and related
getters and setters
# 0.7.0-alpha.16
- implement `IntoIterator` for `&OneOrMany<T>` and `&mut OneOrMany<T>`
- add `check` function for verifying an IRI's authority

View file

@ -1,7 +1,7 @@
[package]
name = "activitystreams"
description = "A set of core types and traits for activitystreams data"
version = "0.7.0-alpha.16"
version = "0.7.0-alpha.17"
license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/Aardwolf/activitystreams"
@ -13,14 +13,17 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = [
"activitystreams-ext",
"activitystreams-kinds"
]
members = ["activitystreams-ext", "activitystreams-kinds"]
[dependencies]
activitystreams-kinds = { version = "0.2.0", path = "./activitystreams-kinds/", default-features = false, features = ["iri-string"] }
iri-string = { version = "0.5.0-beta.1", features = ["serde", "serde-std", "std"] }
activitystreams-kinds = { version = "0.2.0", path = "./activitystreams-kinds/", default-features = false, features = [
"iri-string",
] }
iri-string = { version = "0.5.0-beta.1", features = [
"serde",
"serde-std",
"std",
] }
mime = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View file

@ -25,14 +25,16 @@
use crate::{
base::{AnyBase, AsBase, Base, Extends},
checked::CheckError,
either::Either,
markers,
object::{ApObject, AsObject, Object},
prelude::BaseExt,
primitives::OneOrMany,
primitives::{OneOrMany, XsdBoolean, XsdDateTime},
unparsed::{Unparsed, UnparsedMut, UnparsedMutExt},
};
use iri_string::types::IriString;
use std::convert::TryFrom;
use time::OffsetDateTime;
pub use activitystreams_kinds::activity as kind;
@ -1418,6 +1420,185 @@ pub trait QuestionExt: AsQuestion {
self.question_mut().any_of = None;
self
}
/// Fetch the closed field for the current activity
///
/// ```rust
/// # use activitystreams::activity::Question;
/// # let mut question = Question::new();
/// #
/// use activitystreams::prelude::*;
///
/// if let Some(closed) = question.closed() {
/// println!("{:?}", closed);
/// }
/// ```
fn closed(&self) -> Option<Either<&OneOrMany<AnyBase>, Either<OffsetDateTime, bool>>> {
self.question_ref().closed.as_ref().map(|either| {
either
.as_ref()
.map(|l| l, |r| r.as_ref().map(|l| *l.as_datetime(), |r| r.0))
})
}
/// Set the closed field for the current activity
///
/// This overwrites the contents of any_of
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// use activitystreams::prelude::*;
/// # use activitystreams::{activity::Question, iri};
/// # let mut question = Question::new();
///
/// question.set_closed_base(iri!("https://example.com/one"));
/// # Ok(())
/// # }
/// ```
fn set_closed_base<T>(&mut self, closed: T) -> &mut Self
where
T: Into<AnyBase>,
{
self.question_mut().closed = Some(Either::Left(OneOrMany::from_one(closed.into())));
self
}
/// Set many closed items for the current activity
///
/// This overwrites the contents of any_of
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// use activitystreams::prelude::*;
/// # use activitystreams::{activity::Question, iri};
/// # let mut question = Question::new();
///
/// question.set_many_closed_bases(vec![
/// iri!("https://example.com/one"),
/// iri!("https://example.com/two"),
/// ]);
/// # Ok(())
/// # }
/// ```
fn set_many_closed_bases<I, T>(&mut self, closed: I) -> &mut Self
where
I: IntoIterator<Item = T>,
T: Into<AnyBase>,
{
let many = OneOrMany::from_many(closed.into_iter().map(|t| t.into()).collect());
self.question_mut().closed = Some(Either::Left(many));
self
}
/// Set the closed field as a date
///
/// This overwrites the contents of any_of
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// use activitystreams::prelude::*;
/// # use activitystreams::{activity::Question, iri};
/// # let mut question = Question::new();
///
/// question.set_closed_date(time::OffsetDateTime::now_utc());
/// # Ok(())
/// # }
/// ```
fn set_closed_date(&mut self, closed: OffsetDateTime) -> &mut Self {
self.question_mut().closed = Some(Either::Right(Either::Left(closed.into())));
self
}
/// Set the closed field as a boolean
///
/// This overwrites the contents of any_of
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// use activitystreams::prelude::*;
/// # use activitystreams::{activity::Question, iri};
/// # let mut question = Question::new();
///
/// question.set_closed_bool(true);
/// # Ok(())
/// # }
/// ```
fn set_closed_bool(&mut self, closed: bool) -> &mut Self {
self.question_mut().closed = Some(Either::Right(Either::Right(closed.into())));
self
}
/// Add an object or link to the closed field
///
/// This overwrites the contents of any_of
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// use activitystreams::prelude::*;
/// # use activitystreams::{activity::Question, iri};
/// # let mut question = Question::new();
///
/// question
/// .add_closed_base(iri!("https://example.com/one"))
/// .add_closed_base(iri!("https://example.com/two"));
/// # Ok(())
/// # }
/// ```
fn add_closed_base<T>(&mut self, closed: T) -> &mut Self
where
T: Into<AnyBase>,
{
let one_or_many = match self.question_mut().closed.take() {
Some(Either::Left(mut one_or_many)) => {
one_or_many.add(closed.into());
one_or_many
}
_ => OneOrMany::from_one(closed.into()),
};
self.question_mut().closed = Some(Either::Left(one_or_many));
self
}
/// Take the closed field from the current activity
///
/// ```rust
/// # use activitystreams::activity::Question;
/// # let mut question = Question::new();
/// #
/// use activitystreams::prelude::*;
///
/// if let Some(closed) = question.take_closed() {
/// println!("{:?}", closed);
/// }
/// ```
fn take_closed(&mut self) -> Option<Either<OneOrMany<AnyBase>, Either<OffsetDateTime, bool>>> {
self.question_mut()
.closed
.take()
.map(|either| either.map(|l| l, |r| r.map(|date| date.into(), |b| b.into())))
}
/// Remove the closed field from the current activity
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// # use activitystreams::{activity::Question, iri};
/// # let mut question = Question::new();
/// # question.set_closed_bool(true);
/// #
/// use activitystreams::prelude::*;
///
/// assert!(question.closed().is_some());
/// question.delete_closed();
/// assert!(question.closed().is_none());
/// # Ok(())
/// # }
/// ```
fn delete_closed(&mut self) -> &mut Self {
self.question_mut().closed = None;
self
}
}
/// Indicates that the actor accepts the object.
@ -1940,6 +2121,13 @@ pub struct Question {
#[serde(skip_serializing_if = "Option::is_none")]
any_of: Option<OneOrMany<AnyBase>>,
/// Indicates that a question has been closed, and answers are no longer accepted.
///
/// - Range: Object | Link | xsd:datetime | xsd:boolean
/// - Functional: false
#[serde(skip_serializing_if = "Option::is_none")]
closed: Option<Either<OneOrMany<AnyBase>, Either<XsdDateTime, XsdBoolean>>>,
/// base fields and unparsed json ends up here
#[serde(flatten)]
inner: Activity<QuestionType>,
@ -2619,6 +2807,7 @@ impl Question {
Question {
one_of: None,
any_of: None,
closed: None,
inner: Activity::new(),
}
}
@ -2647,10 +2836,12 @@ impl Question {
let one_of = inner.remove("oneOf")?;
let any_of = inner.remove("anyOf")?;
let closed = inner.remove("closed")?;
Ok(Question {
one_of,
any_of,
closed,
inner,
})
}
@ -2659,10 +2850,14 @@ impl Question {
let Question {
one_of,
any_of,
closed,
mut inner,
} = self;
inner.insert("oneOf", one_of)?.insert("anyOf", any_of)?;
inner
.insert("oneOf", one_of)?
.insert("anyOf", any_of)?
.insert("closed", closed)?;
inner.retracting()
}

View file

@ -18,6 +18,7 @@ mod one_or_many;
mod rdf_lang_string;
mod serde_parse;
mod unit;
mod xsd_boolean;
mod xsd_datetime;
mod xsd_duration;
@ -26,6 +27,7 @@ pub use self::{
one_or_many::OneOrMany,
rdf_lang_string::RdfLangString,
unit::Unit,
xsd_boolean::XsdBoolean,
xsd_datetime::XsdDateTime,
xsd_duration::{XsdDuration, XsdDurationError},
};

View file

@ -0,0 +1,169 @@
use crate::either::Either;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
/// The type xsd:boolean represents logical yes/no values. The valid values for xsd:boolean are
/// true, false, 0, and 1. Values that are capitalized (e.g. TRUE) or abbreviated (e.g. T) are not
/// valid.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct XsdBoolean(pub bool);
impl XsdBoolean {
/// Construct a new XsdBoolean
pub fn new(b: bool) -> Self {
Self(b)
}
/// Retreive the inner bool
pub fn into_inner(self) -> bool {
self.0
}
}
impl PartialEq<bool> for XsdBoolean {
fn eq(&self, other: &bool) -> bool {
self.0 == *other
}
}
impl PartialEq<XsdBoolean> for bool {
fn eq(&self, other: &XsdBoolean) -> bool {
*self == other.0
}
}
impl Deref for XsdBoolean {
type Target = bool;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for XsdBoolean {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl AsRef<bool> for XsdBoolean {
fn as_ref(&self) -> &bool {
&self.0
}
}
impl AsMut<bool> for XsdBoolean {
fn as_mut(&mut self) -> &mut bool {
&mut self.0
}
}
impl From<bool> for XsdBoolean {
fn from(b: bool) -> Self {
Self(b)
}
}
impl From<XsdBoolean> for bool {
fn from(b: XsdBoolean) -> Self {
b.0
}
}
impl<'de> Deserialize<'de> for XsdBoolean {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let helper: Either<u8, bool> = Deserialize::<'de>::deserialize(deserializer)?;
match helper {
Either::Left(u @ 0 | u @ 1) => Ok(XsdBoolean(u == 1)),
Either::Right(b) => Ok(XsdBoolean(b)),
_ => Err(serde::de::Error::custom("Invalid boolean")),
}
}
}
impl Serialize for XsdBoolean {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use super::XsdBoolean;
#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
struct MyStruct {
field: XsdBoolean,
}
#[test]
fn deserialize_bool() {
let json = r#"[{"field":true},{"field":false}]"#;
let structs: Vec<MyStruct> = serde_json::from_str(json).unwrap();
assert_eq!(structs[0].field, true);
assert_eq!(structs[1].field, false);
}
#[test]
fn deserialize_number() {
let json = r#"[{"field":1},{"field":0}]"#;
let structs: Vec<MyStruct> = serde_json::from_str(json).unwrap();
assert_eq!(structs[0].field, true);
assert_eq!(structs[1].field, false);
}
#[test]
fn dont_deserialize_invalid_number() {
let invalids = [
r#"{"field":2}"#,
r#"{"field":3}"#,
r#"{"field":4}"#,
r#"{"field":-1}"#,
];
for case in invalids {
assert!(serde_json::from_str::<MyStruct>(case).is_err());
}
}
#[test]
fn dont_deserialize_strings() {
let invalids = [
r#"{"field":"1"}"#,
r#"{"field":"0"}"#,
r#"{"field":"true"}"#,
r#"{"field":"false"}"#,
];
for case in invalids {
assert!(serde_json::from_str::<MyStruct>(case).is_err());
}
}
#[test]
fn round_trip() {
let structs = vec![
MyStruct {
field: XsdBoolean(false),
},
MyStruct {
field: XsdBoolean(true),
},
];
let string = serde_json::to_string(&structs).unwrap();
let new_structs: Vec<MyStruct> = serde_json::from_str(&string).unwrap();
assert_eq!(structs, new_structs);
}
}