mirror of
https://git.asonix.dog/asonix/activitystreams.git
synced 2024-11-22 03:40:59 +00:00
Add 'closed' to Question
This commit is contained in:
parent
2f9532ad65
commit
f3d394d5d6
5 changed files with 383 additions and 9 deletions
|
@ -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
|
||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -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"
|
||||
|
|
199
src/activity.rs
199
src/activity.rs
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
|
169
src/primitives/xsd_boolean.rs
Normal file
169
src/primitives/xsd_boolean.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue