ap: add base object type
This commit is contained in:
parent
6a4e753069
commit
055a97a8e1
2 changed files with 618 additions and 0 deletions
|
@ -1 +1,2 @@
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod object;
|
||||||
|
|
617
src/ap/object.rs
Normal file
617
src/ap/object.rs
Normal file
|
@ -0,0 +1,617 @@
|
||||||
|
use serde::de::{self, IgnoredAny, MapAccess, SeqAccess, Visitor};
|
||||||
|
use serde::ser::{SerializeMap, SerializeSeq};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ap::context::{uri, Context};
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
/// Wrapper for [`BaseObject`] because ActivityPub allows
|
||||||
|
/// any object to be referenced indirectly through a URL.
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Object {
|
||||||
|
Direct(BaseObject),
|
||||||
|
Indirect(String),
|
||||||
|
Multi(Vec<Object>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intermediate representation of actual AP objects.
|
||||||
|
/// Contains all possible fields for any type of object.
|
||||||
|
/// We "simplify" AP processing by treating anything as an object.
|
||||||
|
/// This struct is only used internally as a helper for (de)serializing objects
|
||||||
|
/// because doing that all in one step would, frankly, be insane.
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BaseObject {
|
||||||
|
pub context: Option<Context>,
|
||||||
|
pub typ: Option<ObjectType>,
|
||||||
|
pub id: Option<ObjectId>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub subject: Option<Box<Object>>,
|
||||||
|
pub actor: Option<Box<Object>>,
|
||||||
|
pub object: Option<Box<Object>>,
|
||||||
|
pub attributed_to: Option<Box<Object>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ObjectType {
|
||||||
|
Unknown(String),
|
||||||
|
// Object
|
||||||
|
Object,
|
||||||
|
Article,
|
||||||
|
Audio,
|
||||||
|
Document,
|
||||||
|
Event,
|
||||||
|
Image,
|
||||||
|
Note,
|
||||||
|
Page,
|
||||||
|
Place,
|
||||||
|
Profile,
|
||||||
|
Relationship,
|
||||||
|
Tombstone,
|
||||||
|
Video,
|
||||||
|
// Link
|
||||||
|
Link,
|
||||||
|
Mention,
|
||||||
|
// Activity
|
||||||
|
Activity,
|
||||||
|
Accept,
|
||||||
|
Add,
|
||||||
|
Announce,
|
||||||
|
Arrive,
|
||||||
|
Block,
|
||||||
|
Create,
|
||||||
|
Delete,
|
||||||
|
Dislike,
|
||||||
|
EmojiReact, // extension
|
||||||
|
Flag,
|
||||||
|
Follow,
|
||||||
|
Ignore,
|
||||||
|
Invite,
|
||||||
|
Join,
|
||||||
|
Leave,
|
||||||
|
Like,
|
||||||
|
Listen,
|
||||||
|
Move,
|
||||||
|
Offer,
|
||||||
|
Question,
|
||||||
|
Reject,
|
||||||
|
Read,
|
||||||
|
Remove,
|
||||||
|
TentativeReject,
|
||||||
|
TentativeAccept,
|
||||||
|
Travel,
|
||||||
|
Undo,
|
||||||
|
Update,
|
||||||
|
View,
|
||||||
|
// Actor
|
||||||
|
Actor,
|
||||||
|
Application,
|
||||||
|
Group,
|
||||||
|
Organization,
|
||||||
|
Person,
|
||||||
|
Service,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Globally unique object identifier.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct ObjectId(String);
|
||||||
|
|
||||||
|
pub trait GetObjectId {
|
||||||
|
/// Get this object's globally unique (ActivityPub) id.
|
||||||
|
/// The `base_url` parameter is for constructing a URI and takes
|
||||||
|
/// the form `https://domain.tld` (without a trailing slash).
|
||||||
|
/// Do not rely on the URL starting with `https` though, a future
|
||||||
|
/// version might support `http` URLs for Tor and such.
|
||||||
|
fn get_object_id(&self, base_url: &str) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for ObjectId
|
||||||
|
where
|
||||||
|
T: GetObjectId,
|
||||||
|
{
|
||||||
|
fn from(t: T) -> ObjectId {
|
||||||
|
// TODO: there has to be a more elegant way for this
|
||||||
|
let base_domain = format!("https://{}", std::env::var("LOCAL_DOMAIN").unwrap());
|
||||||
|
ObjectId(t.get_object_id(&base_domain))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Object {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Object::Direct(direct) => direct.serialize(serializer),
|
||||||
|
Object::Indirect(url) => serializer.serialize_str(url),
|
||||||
|
Object::Multi(objs) => {
|
||||||
|
let mut seq = serializer.serialize_seq(Some(objs.len()))?;
|
||||||
|
for obj in objs {
|
||||||
|
seq.serialize_element(obj)?;
|
||||||
|
}
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for BaseObject {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
|
||||||
|
if let Some(context) = &self.context {
|
||||||
|
map.serialize_entry("@context", context)?;
|
||||||
|
}
|
||||||
|
if let Some(typ) = &self.typ {
|
||||||
|
map.serialize_entry("type", typ)?;
|
||||||
|
}
|
||||||
|
if let Some(id) = &self.id {
|
||||||
|
map.serialize_entry("id", &id.0)?;
|
||||||
|
}
|
||||||
|
if let Some(name) = &self.name {
|
||||||
|
map.serialize_entry("name", name)?;
|
||||||
|
}
|
||||||
|
if let Some(content) = &self.content {
|
||||||
|
map.serialize_entry("content", content)?;
|
||||||
|
}
|
||||||
|
if let Some(subject) = &self.subject {
|
||||||
|
map.serialize_entry("subject", subject.as_ref())?;
|
||||||
|
}
|
||||||
|
if let Some(actor) = &self.actor {
|
||||||
|
map.serialize_entry("actor", actor.as_ref())?;
|
||||||
|
}
|
||||||
|
if let Some(object) = &self.object {
|
||||||
|
map.serialize_entry("object", object.as_ref())?;
|
||||||
|
}
|
||||||
|
if let Some(attributed_to) = &self.attributed_to {
|
||||||
|
map.serialize_entry("attributedTo", attributed_to.as_ref())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for ObjectType {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Object {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(ObjectVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for ObjectVisitor {
|
||||||
|
type Value = Object;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str("an Activity Streams 2.0 compliant JSON-LD object")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, s: String) -> std::result::Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(Object::Indirect(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut objs = Vec::new();
|
||||||
|
|
||||||
|
while let Some(obj) = seq.next_element()? {
|
||||||
|
objs.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Object::Multi(objs))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut obj = BaseObject::default();
|
||||||
|
|
||||||
|
while let Some(k) = map.next_key::<String>()? {
|
||||||
|
match k.as_str() {
|
||||||
|
"@context" => obj.context = Some(map.next_value()?),
|
||||||
|
"type" => obj.typ = Some(map.next_value()?),
|
||||||
|
"id" => obj.id = Some(ObjectId(map.next_value()?)),
|
||||||
|
"name" => obj.name = Some(map.next_value()?),
|
||||||
|
"content" => obj.content = Some(map.next_value()?),
|
||||||
|
"subject" => obj.subject = Some(map.next_value()?),
|
||||||
|
"actor" => obj.actor = Some(Box::new(map.next_value()?)),
|
||||||
|
"object" => obj.object = Some(Box::new(map.next_value()?)),
|
||||||
|
"attributedTo" => obj.attributed_to = Some(Box::new(map.next_value()?)),
|
||||||
|
_ => {
|
||||||
|
map.next_value::<IgnoredAny>()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Object::Direct(obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for ObjectType {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(ObjectTypeVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectTypeVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for ObjectTypeVisitor {
|
||||||
|
type Value = ObjectType;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str("an Activity Streams 2.0 compliant object type name")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(ObjectType::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BaseObject {
|
||||||
|
fn default() -> Self {
|
||||||
|
BaseObject {
|
||||||
|
context: None,
|
||||||
|
typ: None,
|
||||||
|
id: None,
|
||||||
|
name: None,
|
||||||
|
content: None,
|
||||||
|
subject: None,
|
||||||
|
actor: None,
|
||||||
|
object: None,
|
||||||
|
attributed_to: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////// the rest is just boring ObjectType impls and tests //////////
|
||||||
|
|
||||||
|
impl ObjectType {
|
||||||
|
pub fn is_object(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ObjectType::Object => true,
|
||||||
|
ObjectType::Article => true,
|
||||||
|
ObjectType::Audio => true,
|
||||||
|
ObjectType::Document => true,
|
||||||
|
ObjectType::Event => true,
|
||||||
|
ObjectType::Image => true,
|
||||||
|
ObjectType::Note => true,
|
||||||
|
ObjectType::Page => true,
|
||||||
|
ObjectType::Place => true,
|
||||||
|
ObjectType::Profile => true,
|
||||||
|
ObjectType::Relationship => true,
|
||||||
|
ObjectType::Tombstone => true,
|
||||||
|
ObjectType::Video => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_link(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ObjectType::Link => true,
|
||||||
|
ObjectType::Mention => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_activity(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ObjectType::Activity => true,
|
||||||
|
ObjectType::Accept => true,
|
||||||
|
ObjectType::Add => true,
|
||||||
|
ObjectType::Announce => true,
|
||||||
|
ObjectType::Arrive => true,
|
||||||
|
ObjectType::Block => true,
|
||||||
|
ObjectType::Create => true,
|
||||||
|
ObjectType::Delete => true,
|
||||||
|
ObjectType::Dislike => true,
|
||||||
|
ObjectType::EmojiReact => true,
|
||||||
|
ObjectType::Flag => true,
|
||||||
|
ObjectType::Follow => true,
|
||||||
|
ObjectType::Ignore => true,
|
||||||
|
ObjectType::Invite => true,
|
||||||
|
ObjectType::Join => true,
|
||||||
|
ObjectType::Leave => true,
|
||||||
|
ObjectType::Like => true,
|
||||||
|
ObjectType::Listen => true,
|
||||||
|
ObjectType::Move => true,
|
||||||
|
ObjectType::Offer => true,
|
||||||
|
ObjectType::Question => true,
|
||||||
|
ObjectType::Reject => true,
|
||||||
|
ObjectType::Read => true,
|
||||||
|
ObjectType::Remove => true,
|
||||||
|
ObjectType::TentativeReject => true,
|
||||||
|
ObjectType::TentativeAccept => true,
|
||||||
|
ObjectType::Travel => true,
|
||||||
|
ObjectType::Undo => true,
|
||||||
|
ObjectType::Update => true,
|
||||||
|
ObjectType::View => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_actor(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ObjectType::Actor => true,
|
||||||
|
ObjectType::Application => true,
|
||||||
|
ObjectType::Group => true,
|
||||||
|
ObjectType::Organization => true,
|
||||||
|
ObjectType::Person => true,
|
||||||
|
ObjectType::Service => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ObjectType::Unknown(s) => s.as_str(),
|
||||||
|
// Object
|
||||||
|
ObjectType::Object => "Object",
|
||||||
|
ObjectType::Article => "Article",
|
||||||
|
ObjectType::Audio => "Audio",
|
||||||
|
ObjectType::Document => "Document",
|
||||||
|
ObjectType::Event => "Event",
|
||||||
|
ObjectType::Image => "Image",
|
||||||
|
ObjectType::Note => "Note",
|
||||||
|
ObjectType::Page => "Page",
|
||||||
|
ObjectType::Place => "Place",
|
||||||
|
ObjectType::Profile => "Profile",
|
||||||
|
ObjectType::Relationship => "Relationship",
|
||||||
|
ObjectType::Tombstone => "Tombstone",
|
||||||
|
ObjectType::Video => "Video",
|
||||||
|
// Link
|
||||||
|
ObjectType::Link => "Link",
|
||||||
|
ObjectType::Mention => "Mention",
|
||||||
|
// Activity
|
||||||
|
ObjectType::Activity => "Activity",
|
||||||
|
ObjectType::Accept => "Accept",
|
||||||
|
ObjectType::Add => "Add",
|
||||||
|
ObjectType::Announce => "Announce",
|
||||||
|
ObjectType::Arrive => "Arrive",
|
||||||
|
ObjectType::Block => "Block",
|
||||||
|
ObjectType::Create => "Create",
|
||||||
|
ObjectType::Delete => "Delete",
|
||||||
|
ObjectType::Dislike => "Dislike",
|
||||||
|
ObjectType::EmojiReact => "EmojiReact",
|
||||||
|
ObjectType::Flag => "Flag",
|
||||||
|
ObjectType::Follow => "Follow",
|
||||||
|
ObjectType::Ignore => "Ignore",
|
||||||
|
ObjectType::Invite => "Invite",
|
||||||
|
ObjectType::Join => "Join",
|
||||||
|
ObjectType::Leave => "Leave",
|
||||||
|
ObjectType::Like => "Like",
|
||||||
|
ObjectType::Listen => "Listen",
|
||||||
|
ObjectType::Move => "Move",
|
||||||
|
ObjectType::Offer => "Offer",
|
||||||
|
ObjectType::Question => "Question",
|
||||||
|
ObjectType::Reject => "Reject",
|
||||||
|
ObjectType::Read => "Read",
|
||||||
|
ObjectType::Remove => "Remove",
|
||||||
|
ObjectType::TentativeReject => "TentativeReject",
|
||||||
|
ObjectType::TentativeAccept => "TentativeAccept",
|
||||||
|
ObjectType::Travel => "Travel",
|
||||||
|
ObjectType::Undo => "Undo",
|
||||||
|
ObjectType::Update => "Update",
|
||||||
|
ObjectType::View => "View",
|
||||||
|
// Actor
|
||||||
|
ObjectType::Actor => "Actor",
|
||||||
|
ObjectType::Application => "Application",
|
||||||
|
ObjectType::Group => "Group",
|
||||||
|
ObjectType::Organization => "Organization",
|
||||||
|
ObjectType::Person => "Person",
|
||||||
|
ObjectType::Service => "Service",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for ObjectType {
|
||||||
|
fn from(s: &str) -> ObjectType {
|
||||||
|
match s {
|
||||||
|
// Object
|
||||||
|
"Object" => ObjectType::Object,
|
||||||
|
"Article" => ObjectType::Article,
|
||||||
|
"Audio" => ObjectType::Audio,
|
||||||
|
"Document" => ObjectType::Document,
|
||||||
|
"Event" => ObjectType::Event,
|
||||||
|
"Image" => ObjectType::Image,
|
||||||
|
"Note" => ObjectType::Note,
|
||||||
|
"Page" => ObjectType::Page,
|
||||||
|
"Place" => ObjectType::Place,
|
||||||
|
"Profile" => ObjectType::Profile,
|
||||||
|
"Relationship" => ObjectType::Relationship,
|
||||||
|
"Tombstone" => ObjectType::Tombstone,
|
||||||
|
"Video" => ObjectType::Video,
|
||||||
|
// Link
|
||||||
|
"Link" => ObjectType::Link,
|
||||||
|
"Mention" => ObjectType::Mention,
|
||||||
|
// Activity
|
||||||
|
"Activity" => ObjectType::Activity,
|
||||||
|
"Accept" => ObjectType::Accept,
|
||||||
|
"Add" => ObjectType::Add,
|
||||||
|
"Announce" => ObjectType::Announce,
|
||||||
|
"Arrive" => ObjectType::Arrive,
|
||||||
|
"Block" => ObjectType::Block,
|
||||||
|
"Create" => ObjectType::Create,
|
||||||
|
"Delete" => ObjectType::Delete,
|
||||||
|
"Dislike" => ObjectType::Dislike,
|
||||||
|
"EmojiReact" => ObjectType::EmojiReact,
|
||||||
|
"Flag" => ObjectType::Flag,
|
||||||
|
"Follow" => ObjectType::Follow,
|
||||||
|
"Ignore" => ObjectType::Ignore,
|
||||||
|
"Invite" => ObjectType::Invite,
|
||||||
|
"Join" => ObjectType::Join,
|
||||||
|
"Leave" => ObjectType::Leave,
|
||||||
|
"Like" => ObjectType::Like,
|
||||||
|
"Listen" => ObjectType::Listen,
|
||||||
|
"Move" => ObjectType::Move,
|
||||||
|
"Offer" => ObjectType::Offer,
|
||||||
|
"Question" => ObjectType::Question,
|
||||||
|
"Reject" => ObjectType::Reject,
|
||||||
|
"Read" => ObjectType::Read,
|
||||||
|
"Remove" => ObjectType::Remove,
|
||||||
|
"TentativeReject" => ObjectType::TentativeReject,
|
||||||
|
"TentativeAccept" => ObjectType::TentativeAccept,
|
||||||
|
"Travel" => ObjectType::Travel,
|
||||||
|
"Undo" => ObjectType::Undo,
|
||||||
|
"Update" => ObjectType::Update,
|
||||||
|
"View" => ObjectType::View,
|
||||||
|
// Actor
|
||||||
|
"Actor" => ObjectType::Actor,
|
||||||
|
"Application" => ObjectType::Application,
|
||||||
|
"Group" => ObjectType::Group,
|
||||||
|
"Organization" => ObjectType::Organization,
|
||||||
|
"Person" => ObjectType::Person,
|
||||||
|
"Service" => ObjectType::Service,
|
||||||
|
// Unknown
|
||||||
|
_ => ObjectType::Unknown(String::from(s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_test::{assert_tokens, Token};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn indirect() {
|
||||||
|
let obj = Object::Indirect(String::from("https://example.org/notes/1"));
|
||||||
|
|
||||||
|
assert_tokens(&obj, &[Token::String("https://example.org/notes/1")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
let obj = Object::Direct(BaseObject::default());
|
||||||
|
|
||||||
|
assert_tokens(&obj, &[Token::Map { len: None }, Token::MapEnd]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
let obj = Object::Direct(BaseObject {
|
||||||
|
context: Some(Context::default()),
|
||||||
|
typ: Some(ObjectType::Object),
|
||||||
|
id: Some(ObjectId(String::from("https://www.example.com/object/1"))),
|
||||||
|
name: Some(String::from("A Simple, non-specific object")),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_tokens(
|
||||||
|
&obj,
|
||||||
|
&[
|
||||||
|
Token::Map { len: None },
|
||||||
|
Token::String("@context"),
|
||||||
|
Token::String(uri::ACTIVITY_STREAMS),
|
||||||
|
Token::String("type"),
|
||||||
|
Token::String("Object"),
|
||||||
|
Token::String("id"),
|
||||||
|
Token::String("https://www.example.com/object/1"),
|
||||||
|
Token::String("name"),
|
||||||
|
Token::String("A Simple, non-specific object"),
|
||||||
|
Token::MapEnd,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full() {
|
||||||
|
let obj = Object::Direct(BaseObject {
|
||||||
|
context: Some(Context::default()),
|
||||||
|
typ: Some(ObjectType::Create),
|
||||||
|
id: Some(ObjectId(String::from("https://www.example.org/object/2"))),
|
||||||
|
name: None,
|
||||||
|
content: None,
|
||||||
|
subject: Some(Box::new(Object::Indirect(String::from(
|
||||||
|
"https://www.example.org/actor/1",
|
||||||
|
)))),
|
||||||
|
actor: Some(Box::new(Object::Direct(BaseObject {
|
||||||
|
typ: Some(ObjectType::Person),
|
||||||
|
name: Some(String::from("Alice")),
|
||||||
|
..Default::default()
|
||||||
|
}))),
|
||||||
|
object: Some(Box::new(Object::Direct(BaseObject {
|
||||||
|
typ: Some(ObjectType::Note),
|
||||||
|
content: Some(String::from("hi i'm gay uwu")),
|
||||||
|
..Default::default()
|
||||||
|
}))),
|
||||||
|
attributed_to: Some(Box::new(Object::Multi(vec![
|
||||||
|
Object::Indirect(String::from("https://www.example.org/actor/2")),
|
||||||
|
Object::Direct(BaseObject {
|
||||||
|
typ: Some(ObjectType::Person),
|
||||||
|
name: Some(String::from("Bob")),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
]))),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_tokens(
|
||||||
|
&obj,
|
||||||
|
&[
|
||||||
|
Token::Map { len: None },
|
||||||
|
Token::String("@context"),
|
||||||
|
Token::String(uri::ACTIVITY_STREAMS),
|
||||||
|
Token::String("type"),
|
||||||
|
Token::String("Create"),
|
||||||
|
Token::String("id"),
|
||||||
|
Token::String("https://www.example.org/object/2"),
|
||||||
|
Token::String("subject"),
|
||||||
|
Token::String("https://www.example.org/actor/1"),
|
||||||
|
Token::String("actor"),
|
||||||
|
Token::Map { len: None },
|
||||||
|
Token::String("type"),
|
||||||
|
Token::String("Person"),
|
||||||
|
Token::String("name"),
|
||||||
|
Token::String("Alice"),
|
||||||
|
Token::MapEnd,
|
||||||
|
Token::String("object"),
|
||||||
|
Token::Map { len: None },
|
||||||
|
Token::String("type"),
|
||||||
|
Token::String("Note"),
|
||||||
|
Token::String("content"),
|
||||||
|
Token::String("hi i'm gay uwu"),
|
||||||
|
Token::MapEnd,
|
||||||
|
Token::String("attributedTo"),
|
||||||
|
Token::Seq { len: Some(2) },
|
||||||
|
Token::String("https://www.example.org/actor/2"),
|
||||||
|
Token::Map { len: None },
|
||||||
|
Token::String("type"),
|
||||||
|
Token::String("Person"),
|
||||||
|
Token::String("name"),
|
||||||
|
Token::String("Bob"),
|
||||||
|
Token::MapEnd,
|
||||||
|
Token::SeqEnd,
|
||||||
|
Token::MapEnd,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue