Start checking again

This commit is contained in:
Aode (Lion) 2022-01-17 14:02:18 -06:00
parent 71fae0bbd4
commit aef1bdf588
10 changed files with 460 additions and 43 deletions

View file

@ -39,7 +39,7 @@ trigger:
kind: pipeline
type: docker
name: tests
name: tests-amd64
platform:
arch: amd64
@ -70,6 +70,39 @@ trigger:
---
kind: pipeline
type: docker
name: tests-arm64v8
platform:
arch: arm64
clone:
disable: true
steps:
- name: clone
image: alpine/git:latest
user: root
commands:
- git clone $DRONE_GIT_HTTP_URL .
- git checkout $DRONE_COMMIT
- chown -R 991:991 .
- name: tests
image: asonix/rust-builder:latest-linux-amd64
pull: always
commands:
- cargo test
trigger:
event:
- tag
- push
- pull_request
---
kind: pipeline
type: docker
name: check-amd64
@ -210,6 +243,11 @@ trigger:
event:
- tag
depends_on:
- clippy
- tests-amd64
- tests-arm64v8
---
kind: pipeline
@ -242,6 +280,11 @@ trigger:
event:
- tag
depends_on:
- clippy
- tests-amd64
- tests-arm64v8
---
kind: pipeline
@ -274,6 +317,11 @@ trigger:
event:
- tag
depends_on:
- clippy
- tests-amd64
- tests-arm64v8
---
kind: pipeline
@ -328,8 +376,6 @@ steps:
- cargo publish -p activitystreams-ext --token $CRATES_IO_TOKEN
depends_on:
- clippy
- tests
- build-amd64
- build-arm64v8
- build-arm32v7

View file

@ -1,4 +1,10 @@
# Unreleased
- implement `IntoIterator` for `&OneOrMany<T>` and `&mut OneOrMany<T>`
- add `check` function for verifying an IRI's authority
- add `BaseExt::check_authority` for verifying an IRI's authority against an object's ID
- add back checked `Base::id`, `Activity::actor`, `Activity::object`, `Actor::inbox`,
`Actor::outbox`, `Actor::following`, `Actor::followers`, `Actor::liked`, `Actor::streams`,
`Actor::endpoints`
# 0.7.0-alpha.14
- switch to iri-string from url

View file

@ -19,8 +19,8 @@ members = [
]
[dependencies]
activitystreams-kinds = { version = "0.1.0", path = "./activitystreams-kinds/", default-features = false, features = ["iri-string"] }
iri-string = { version = "0.4.1", features = ["serde", "serde-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 = ["alloc", "serde", "serde-alloc", "serde-std", "std"] }
mime = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View file

@ -1,7 +1,7 @@
[package]
name = "activitystreams-kinds"
description = "Type-safe activitystreams 'type' values"
version = "0.1.3"
version = "0.2.0"
license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/asonix/activitystreams"
@ -17,7 +17,7 @@ default = ["url"]
[dependencies]
serde = { version = "1", features = ["derive"] }
url = { version = "2", optional = true }
iri-string = { version = "0.4.1", optional = true }
iri-string = { version = "0.5.0-beta.1", optional = true }
[dev-dependencies]
anyhow = "1"

View file

@ -24,8 +24,10 @@
//! ```
use crate::{
base::{AnyBase, AsBase, Base, Extends},
checked::CheckError,
markers,
object::{ApObject, AsObject, Object},
prelude::BaseExt,
primitives::OneOrMany,
unparsed::{Unparsed, UnparsedMut, UnparsedMutExt},
};
@ -420,7 +422,32 @@ pub trait ActorAndObjectRefExt: ActorAndObjectRef {
/// let actor_ref = create.actor();
/// println!("{:?}", actor_ref);
/// ```
fn actor(&self) -> &OneOrMany<AnyBase> {
fn actor<Kind>(&self) -> Result<&OneOrMany<AnyBase>, CheckError>
where
Self: BaseExt<Kind>,
{
let actor = self.actor_unchecked();
for any_base in actor {
let id = any_base.id().ok_or(CheckError)?;
self.check_authority(id)?;
}
Ok(actor)
}
/// Fetch the actor for the current activity
///
/// ```rust
/// # use activitystreams::{context, activity::Create};
/// # let mut create = Create::new(context(), context());
/// #
/// use activitystreams::prelude::*;
///
/// let actor_ref = create.actor_unchecked();
/// println!("{:?}", actor_ref);
/// ```
fn actor_unchecked(&self) -> &OneOrMany<AnyBase> {
self.actor_field_ref()
}
@ -439,7 +466,7 @@ pub trait ActorAndObjectRefExt: ActorAndObjectRef {
/// # }
/// ```
fn actor_is(&self, id: &IriString) -> bool {
self.actor().is_single_id(id)
self.actor_unchecked().is_single_id(id)
}
/// Set the actor for the current activity
@ -526,7 +553,32 @@ pub trait ActorAndObjectRefExt: ActorAndObjectRef {
/// let object_ref = create.object();
/// println!("{:?}", object_ref);
/// ```
fn object(&self) -> &OneOrMany<AnyBase> {
fn object<Kind>(&self) -> Result<&OneOrMany<AnyBase>, CheckError>
where
Self: BaseExt<Kind>,
{
let object = self.object_unchecked();
for any_base in object {
let id = any_base.id().ok_or(CheckError)?;
self.check_authority(id)?;
}
Ok(object)
}
/// Fetch the object for the current activity
///
/// ```rust
/// # use activitystreams::{context, activity::Create};
/// # let mut create = Create::new(context(), context());
/// #
/// use activitystreams::prelude::*;
///
/// let object_ref = create.object_unchecked();
/// println!("{:?}", object_ref);
/// ```
fn object_unchecked(&self) -> &OneOrMany<AnyBase> {
self.object_field_ref()
}
@ -545,7 +597,7 @@ pub trait ActorAndObjectRefExt: ActorAndObjectRef {
/// # }
/// ```
fn object_is(&self, id: &IriString) -> bool {
self.object().is_single_id(id)
self.object_unchecked().is_single_id(id)
}
/// Set the object for the current activity

View file

@ -23,8 +23,10 @@
//! ```
use crate::{
base::{AsBase, Base, Extends},
checked::CheckError,
markers,
object::{ApObject, AsApObject, AsObject, Object},
prelude::BaseExt,
primitives::OneOrMany,
unparsed::{Unparsed, UnparsedMut, UnparsedMutExt},
};
@ -60,7 +62,25 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
///
/// let inbox_ref = person.inbox();
/// ```
fn inbox<'a>(&'a self) -> &'a IriString
fn inbox<'a, Kind>(&'a self) -> Result<&'a IriString, CheckError>
where
Inner: 'a,
Self: BaseExt<Kind>,
{
let inbox = self.inbox_unchecked();
self.check_authority(inbox)
}
/// Fetch the inbox for the current actor
///
/// ```rust
/// # use activitystreams::{actor::{ApActor, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// let inbox_ref = person.inbox_unchecked();
/// ```
fn inbox_unchecked<'a>(&'a self) -> &'a IriString
where
Inner: 'a,
{
@ -109,7 +129,26 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
///
/// let outbox_ref = person.outbox();
/// ```
fn outbox<'a>(&'a self) -> Option<&'a IriString>
fn outbox<'a, Kind>(&'a self) -> Result<Option<&'a IriString>, CheckError>
where
Inner: 'a,
Self: BaseExt<Kind>,
{
self.outbox_unchecked()
.map(|outbox| self.check_authority(outbox))
.transpose()
}
/// Fetch the outbox for the current actor
///
/// ```rust
/// # use activitystreams::{actor::{ApActor, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// let outbox_ref = person.outbox_unchecked();
/// ```
fn outbox_unchecked<'a>(&'a self) -> Option<&'a IriString>
where
Inner: 'a,
{
@ -175,9 +214,9 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # person.set_outbox(iri!("https://example.com/outbox"));
/// use activitystreams::prelude::*;
///
/// assert!(person.outbox().is_some());
/// assert!(person.outbox_unchecked().is_some());
/// person.delete_outbox();
/// assert!(person.outbox().is_none());
/// assert!(person.outbox_unchecked().is_none());
/// # Ok(())
/// # }
/// ```
@ -193,11 +232,32 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(following) = person.following() {
/// if let Ok(following) = person.following() {
/// println!("{:?}", following);
/// }
/// ```
fn following<'a>(&'a self) -> Option<&'a IriString>
fn following<'a, Kind>(&'a self) -> Result<Option<&'a IriString>, CheckError>
where
Inner: 'a,
Self: BaseExt<Kind>,
{
self.following_unchecked()
.map(|following| self.check_authority(following))
.transpose()
}
/// Fetch the following link for the current actor
///
/// ```rust
/// # use activitystreams::{actor::{ApActor, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(following) = person.following_unchecked() {
/// println!("{:?}", following);
/// }
/// ```
fn following_unchecked<'a>(&'a self) -> Option<&'a IriString>
where
Inner: 'a,
{
@ -263,9 +323,9 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # person.set_following(iri!("https://example.com/following"));
/// use activitystreams::prelude::*;
///
/// assert!(person.following().is_some());
/// assert!(person.following_unchecked().is_some());
/// person.delete_following();
/// assert!(person.following().is_none());
/// assert!(person.following_unchecked().is_none());
/// # Ok(())
/// # }
/// ```
@ -281,11 +341,32 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(followers) = person.followers() {
/// if let Ok(followers) = person.followers() {
/// println!("{:?}", followers);
/// }
/// ```
fn followers<'a>(&'a self) -> Option<&'a IriString>
fn followers<'a, Kind>(&'a self) -> Result<Option<&'a IriString>, CheckError>
where
Inner: 'a,
Self: BaseExt<Kind>,
{
self.followers_unchecked()
.map(|followers| self.check_authority(followers))
.transpose()
}
/// Fetch the followers link for the current actor
///
/// ```rust
/// # use activitystreams::{actor::{ApActor, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(followers) = person.followers_unchecked() {
/// println!("{:?}", followers);
/// }
/// ```
fn followers_unchecked<'a>(&'a self) -> Option<&'a IriString>
where
Inner: 'a,
{
@ -351,9 +432,9 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # person.set_followers(iri!("https://example.com/followers"));
/// use activitystreams::prelude::*;
///
/// assert!(person.followers().is_some());
/// assert!(person.followers_unchecked().is_some());
/// person.delete_followers();
/// assert!(person.followers().is_none());
/// assert!(person.followers_unchecked().is_none());
/// # Ok(())
/// # }
/// ```
@ -369,11 +450,32 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(liked) = person.liked() {
/// if let Ok(liked) = person.liked() {
/// println!("{:?}", liked);
/// }
/// ```
fn liked<'a>(&'a self) -> Option<&'a IriString>
fn liked<'a, Kind>(&'a self) -> Result<Option<&'a IriString>, CheckError>
where
Inner: 'a,
Self: BaseExt<Kind>,
{
self.liked_unchecked()
.map(|liked| self.check_authority(liked))
.transpose()
}
/// Fetch the liked link for the current actor
///
/// ```rust
/// # use activitystreams::{actor::{ApActor, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(liked) = person.liked_unchecked() {
/// println!("{:?}", liked);
/// }
/// ```
fn liked_unchecked<'a>(&'a self) -> Option<&'a IriString>
where
Inner: 'a,
{
@ -439,9 +541,9 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # person.set_liked(iri!("https://example.com/liked"));
/// use activitystreams::prelude::*;
///
/// assert!(person.liked().is_some());
/// assert!(person.liked_unchecked().is_some());
/// person.delete_liked();
/// assert!(person.liked().is_none());
/// assert!(person.liked_unchecked().is_none());
/// # Ok(())
/// # }
/// ```
@ -457,11 +559,38 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(streams) = person.streams() {
/// if let Ok(streams) = person.streams() {
/// println!("{:?}", streams);
/// }
/// ```
fn streams<'a>(&'a self) -> Option<OneOrMany<&'a IriString>>
fn streams<'a, Kind>(&'a self) -> Result<Option<OneOrMany<&'a IriString>>, CheckError>
where
Inner: 'a,
Self: BaseExt<Kind>,
{
if let Some(streams) = self.streams_unchecked() {
for id in &streams {
self.check_authority(id)?;
}
Ok(Some(streams))
} else {
Ok(None)
}
}
/// Fetch the streams links for the current actor
///
/// ```rust
/// # use activitystreams::{actor::{ApActor, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(streams) = person.streams_unchecked() {
/// println!("{:?}", streams);
/// }
/// ```
fn streams_unchecked<'a>(&'a self) -> Option<OneOrMany<&'a IriString>>
where
Inner: 'a,
{
@ -581,9 +710,9 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # person.set_stream(iri!("https://example.com/streams"));
/// use activitystreams::prelude::*;
///
/// assert!(person.streams().is_some());
/// assert!(person.streams_unchecked().is_some());
/// person.delete_streams();
/// assert!(person.streams().is_none());
/// assert!(person.streams_unchecked().is_none());
/// # Ok(())
/// # }
/// ```
@ -662,6 +791,71 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
self
}
/// Fetch the endpoints for the current actor, erroring if the Endpoints' domains do not
/// match the ID's domain
///
/// ```rust
/// # fn main() -> Result<(), anyhow::Error> {
/// # use activitystreams::{actor::{ApActor, Endpoints, Person}, context};
/// # let mut person = ApActor::new(context(), Person::new());
/// # person.set_id(context()).set_endpoints(Endpoints {
/// # shared_inbox: Some(context()),
/// # ..Default::default()
/// # });
/// use activitystreams::prelude::*;
///
/// if let Some(endpoints) = person.endpoints()? {
/// println!("{:?}", endpoints);
/// }
/// # Ok(())
/// # }
/// ```
fn endpoints<'a, Kind>(&'a self) -> Result<Option<Endpoints<&'a IriString>>, CheckError>
where
Self: BaseExt<Kind>,
Inner: 'a,
Kind: 'a,
{
if let Some(endpoints) = self.endpoints_unchecked() {
let authority_opt = self.id_unchecked().and_then(|id| id.authority_components());
let mut any_failed = false;
any_failed |= endpoints
.proxy_url
.map(|u| u.authority_components() != authority_opt)
.unwrap_or(false);
any_failed |= endpoints
.oauth_authorization_endpoint
.map(|u| u.authority_components() != authority_opt)
.unwrap_or(false);
any_failed |= endpoints
.oauth_token_endpoint
.map(|u| u.authority_components() != authority_opt)
.unwrap_or(false);
any_failed |= endpoints
.provide_client_key
.map(|u| u.authority_components() != authority_opt)
.unwrap_or(false);
any_failed |= endpoints
.sign_client_key
.map(|u| u.authority_components() != authority_opt)
.unwrap_or(false);
any_failed |= endpoints
.shared_inbox
.map(|u| u.authority_components() != authority_opt)
.unwrap_or(false);
if any_failed {
return Err(CheckError);
}
return Ok(Some(endpoints));
}
Ok(None)
}
/// Fetch the endpoints for the current actor
///
/// ```rust
@ -669,11 +863,11 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # let mut person = ApActor::new(context(), Person::new());
/// use activitystreams::prelude::*;
///
/// if let Some(endpoints) = person.endpoints() {
/// if let Some(endpoints) = person.endpoints_unchecked() {
/// println!("{:?}", endpoints);
/// }
/// ```
fn endpoints<'a>(&'a self) -> Option<Endpoints<&'a IriString>>
fn endpoints_unchecked<'a>(&'a self) -> Option<Endpoints<&'a IriString>>
where
Inner: 'a,
{
@ -744,9 +938,9 @@ pub trait ApActorExt<Inner>: AsApActor<Inner> {
/// # person.set_endpoints(Default::default());
/// use activitystreams::prelude::*;
///
/// assert!(person.endpoints().is_some());
/// assert!(person.endpoints_unchecked().is_some());
/// person.delete_endpoints();
/// assert!(person.endpoints().is_none());
/// assert!(person.endpoints_unchecked().is_none());
/// ```
fn delete_endpoints(&mut self) -> &mut Self {
self.ap_actor_mut().endpoints = None;

View file

@ -28,12 +28,13 @@
//! # }
//! ```
use crate::{
checked::{check, CheckError},
either::Either,
markers,
primitives::{AnyString, MimeMediaType, OneOrMany},
unparsed::{Unparsed, UnparsedMut},
};
use iri_string::types::IriString;
use iri_string::types::{IriStr, IriString};
use mime::Mime;
/// Implements conversion between `Base<Kind>` and other ActivityStreams objects defined in this
@ -245,6 +246,24 @@ pub trait BaseExt<Kind>: AsBase<Kind> {
self
}
/// Check the authority of a given IRI matches this object's ID
///
/// ```rust
/// # use activitystreams::{base::BaseExt, object::Video, iri};
/// # fn main() -> anyhow::Result<()> {
/// # let video = Video::new();
/// let res = video.check_authority(&iri!("https://example.com"));
/// # Ok(())
/// # }
/// ```
fn check_authority<'a, T: AsRef<IriStr>>(&self, iri: T) -> Result<T, CheckError> {
let authority = self
.id_unchecked()
.and_then(|id| id.authority_components())
.ok_or(CheckError)?;
check(iri, authority.host(), authority.port())
}
/// Fetch the id for the current object
///
/// ```rust
@ -253,11 +272,40 @@ pub trait BaseExt<Kind>: AsBase<Kind> {
/// #
/// use activitystreams::prelude::*;
///
/// if let Some(id) = video.id() {
/// if let Ok(Some(id)) = video.id("exmaple.com", Some("443")) {
/// println!("{:?}", id);
/// }
/// ```
fn id<'a>(&'a self) -> Option<&'a IriString>
fn id<'a>(&'a self, host: &str, port: Option<&str>) -> Result<Option<&'a IriString>, CheckError>
where
Kind: 'a,
{
self.id_unchecked()
.and_then(|id| {
let authority = id.authority_components()?;
if authority.host() == host && authority.port() == port {
Some(Ok(id))
} else {
Some(Err(CheckError))
}
})
.transpose()
}
/// Fetch the id for the current object
///
/// ```rust
/// # use activitystreams::object::Video;
/// # let mut video = Video::new();
/// #
/// use activitystreams::prelude::*;
///
/// if let Some(id) = video.id_unchecked() {
/// println!("{:?}", id);
/// }
/// ```
fn id_unchecked<'a>(&'a self) -> Option<&'a IriString>
where
Kind: 'a,
{
@ -272,11 +320,44 @@ pub trait BaseExt<Kind>: AsBase<Kind> {
/// #
/// use activitystreams::prelude::*;
///
/// if let Some(id) = video.id_mut() {
/// if let Ok(Some(id)) = video.id_mut("example.com", Some("443")) {
/// println!("{:?}", id);
/// }
/// ```
fn id_mut<'a>(&'a mut self) -> Option<&'a mut IriString>
fn id_mut<'a>(
&'a mut self,
host: &str,
port: Option<&str>,
) -> Result<Option<&'a mut IriString>, CheckError>
where
Kind: 'a,
{
self.id_mut_unchecked()
.and_then(|id| {
let authority = id.authority_components()?;
if authority.host() == host && authority.port() == port {
Some(Ok(id))
} else {
Some(Err(CheckError))
}
})
.transpose()
}
/// Mutably borrow the ID from the current object
///
/// ```rust
/// # use activitystreams::object::Video;
/// # let mut video = Video::new();
/// #
/// use activitystreams::prelude::*;
///
/// if let Some(id) = video.id_mut_unchecked() {
/// println!("{:?}", id);
/// }
/// ```
fn id_mut_unchecked<'a>(&'a mut self) -> Option<&'a mut IriString>
where
Kind: 'a,
{
@ -296,7 +377,7 @@ pub trait BaseExt<Kind>: AsBase<Kind> {
/// # }
/// ```
fn is_id(&self, id: &IriString) -> bool {
self.id() == Some(id)
self.id_unchecked() == Some(id)
}
/// Set the id for the current object
@ -344,9 +425,9 @@ pub trait BaseExt<Kind>: AsBase<Kind> {
/// #
/// use activitystreams::prelude::*;
///
/// assert!(video.id().is_some());
/// assert!(video.id_unchecked().is_some());
/// video.delete_id();
/// assert!(video.id().is_none());
/// assert!(video.id_unchecked().is_none());
/// ```
fn delete_id(&mut self) -> &mut Self {
self.base_mut().id = None;

19
src/checked.rs Normal file
View file

@ -0,0 +1,19 @@
use iri_string::types::IriStr;
#[derive(Debug, thiserror::Error)]
#[error("IRI failed host and port check")]
pub struct CheckError;
pub(crate) fn check<'a, T: AsRef<IriStr>>(
iri: T,
host: &str,
port: Option<&str>,
) -> Result<T, CheckError> {
let authority = iri.as_ref().authority_components().ok_or(CheckError)?;
if authority.host() != host || authority.port() != port {
return Err(CheckError);
}
Ok(iri)
}

View file

@ -298,6 +298,7 @@
pub mod activity;
pub mod actor;
pub mod base;
pub mod checked;
pub mod collection;
mod either;
pub mod link;

View file

@ -362,6 +362,24 @@ impl<T> IntoIterator for OneOrMany<T> {
}
}
impl<'a, T> IntoIterator for &'a OneOrMany<T> {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T> IntoIterator for &'a mut OneOrMany<T> {
type Item = &'a mut T;
type IntoIter = IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;