1
0
mirror of https://codeberg.org/fediverse/fep.git synced 2026-08-05 11:46:04 +00:00

(FEP-5bf0): Rework API to use SHACL (#93)

This updates the proposal again, this time leveraging SHACL in order to represent more complex possibilities.

Co-authored-by: Michael Puckett <michaelcpuckett@gmail.com>
Reviewed-on: https://codeberg.org/fediverse/fep/pulls/93
Co-authored-by: michaelcpuckett <michaelcpuckett@noreply.codeberg.org>
Co-committed-by: michaelcpuckett <michaelcpuckett@noreply.codeberg.org>
This commit is contained in:
michaelcpuckett
2023-04-21 20:48:58 +00:00
committed by David Sterry
parent fbff3b8cbc
commit eaae974fbf
+207 -83
View File
@@ -8,19 +8,9 @@ dateReceived: 2023-04-10
## Summary
This proposal would allow Collections to have a `streams` property, as Actors do. The streams would be additional Collections that are sorted or filtered versions of the original Collection.
This proposal would allow Collections to have a `streams` property, as Actors do. The streams would be of the type CollectionView, a proposed vocabulary extension that represents a sorted and/or filtered version of a Collection. ActivityPub clients could then render CollectionViews without having to perform such filtering or sorting operations themselves.
Additionally, metadata about the sorting/filtering could be indicated using new proposed additions to the ActivityStreams vocabulary: `orderedBy`, `reversed`, `filteredProperty`, and `filteredValue`.
## Implementation
Sorted Collections would always be of the type `Collection` so as not to violate the purpose of OrderedCollections, but would use `orderedItems` instead of `items`. The `reversed` property would indicate that a sorted Collection is in the reverse order.
The `filteredProperty` and `filteredValue` attributes would indicate the specific property being filtered and its corresponding value. These attributes can be represented as strings that reference properties from the Activity Vocabulary (e.g., `type` and `Person`).
When multiple filters are applied, both `filteredProperty` and `filteredValue` would be arrays of strings.
When applying filters to properties within nested objects, dot notation would identify the targeted property for filtering (e.g., `object.type`).
Metadata about how the sorting or filtering has been applied would be applied using new proposed vocabulary extensions that leverage SHACL for describing constraints.
## Motivations
@@ -30,104 +20,238 @@ Currently, in order to support filtering or sorting, these clients need to retri
This proposal would allow servers to perform these kinds of operations, either at runtime or ahead of time, to ease the burden on clients.
Regarding S2S protocols: other servers should be free to explore the Collections, but they can be easily ignored, along with the new properties.
Other servers should be free to explore the Collections, but they can be easily ignored, along with the new properties.
## Implementation
A CollectionView extends from OrderedCollection and represents a filtered and/or sorted version of a Collection. Similarly, a CollectionViewPage extends from OrderedCollectionPage.
The method of filtering applied to the CollectionView can be indicated via its "filter" property, which maps to one or more SHACL Shapes.
The vocabulary would also provide a SHACL Shape for indicating that a property's value is among the items in a particular ActivityStreams Collection.
The method of sorting applied to the CollectionView can be indicated via its "sort" property, which maps to a SortShape. A SortShape extends SHACL's PropertyShape and adds the "order" property which can be mapped to "Ascending" or "Descending".
If there is no "sort" property, the order is the same as that of the original Collection.
## Examples
### Sorting
Here, an Actor's Inbox returns all Activities posted by the Actor, and the server also provides filtered versions as streams for client consumption.
Hashtags will typically be entered into a database according to the order of creation, however it is more useful for them to be displayed alphabetically.
The first CollectionView returns only the Like Activities.
The second CollectionView returns only created Articles with replies, demonstrating multiple filters and filtering on nested properties.
The third CollectionView demonstrates how to indicates that a given property's value is in a partiular ActivityStreams Collection. In the example, the CollectionView is returning Activities by Alyssa's Co-workers. (Alyssa has a custom stream of mutual followers who she has labeled as Co-workers.)
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.social/hashtags",
"url": "https://example.social/hashtags",
"name": "Hashtags",
"type": "Collection",
"totalItems": 100,
"first": "https://example.social/hashtags/page/1",
"last": "https://example.social/hashtags/page/2",
"streams": [
{
"id": "https://example.social/hashtags/ordered/name",
"url": "https://example.social/hashtags/ordered/name",
"name": "Hashtags",
"summary": "Hashtags, Ordered by Name (A -> Z)",
"type": "Collection",
"totalItems": 100,
"first": "https://example.social/hashtags/ordered/name/page/1",
"last": "https://example.social/hashtags/ordered/name/page/2",
"orderedBy": "name"
},
{
"id": "https://example.social/hashtags/ordered/name/reversed",
"url": "https://example.social/hashtags/ordered/name/reversed",
"name": "Hashtags",
"summary": "Hashtags, Ordered by Name (Z -> A)",
"type": "Collection",
"totalItems": 100,
"first": "https://example.social/hashtags/ordered/name/reversed/page/1",
"last": "https://example.social/hashtags/ordered/name/reversed/page/2",
"orderedBy": "name",
"reversed": true
}
]
}
```
### Filtering
Here, an Actor's Inbox returns all Activities posted by the Actor, and the server also provides filtered versions for client consumption.
The first filtered stream returns only the Like Activities.
The second filtered stream filters Activities by their type and their nested Object's type to return only created Notes.
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"@context": {
"@vocab": "https://www.w3.org/ns/activitystreams#",
"fep": "https://w3id.org/fep#",
"CollectionView": "fep:CollectionView",
"viewOf": "fep:viewOf",
"filter": "fep:filter",
"sort": "fep:sort",
"inCollection": "fep:inCollection",
"SortShape": "fep:SortShape",
"order": "fep:order",
"sh": "http://www.w3.org/ns/shacl#",
"PropertyShape": "sh:PropertyShape",
"path": "sh:path",
"hasValue": "sh:hasValue",
"minCount": "sh:minCount"
},
"id": "https://example.social/@alyssa/inbox",
"url": "https://example.social/@alyssa/inbox",
"name": "Inbox",
"type": "OrderedCollection",
"name": "Inbox",
"totalItems": 1000,
"first": "https://example.social/@alyssa/inbox/page/1",
"last": "https://example.social/@alyssa/inbox/page/2",
"streams": [
{
"id": "https://example.social/@alyssa/inbox/likes",
"url": "https://example.social/@alyssa/inbox/likes",
"type": "CollectionView",
"name": "Likes",
"summary": "Incoming Likes to Alyssa's Inbox",
"type": "OrderedCollection",
"totalItems": 100,
"filter": {
"type": "PropertyShape",
"path": "type",
"hasValue": "Like"
},
"sort": {
"type": "SortShape",
"path": "published",
"order": "Descending"
},
"totalItems": 10,
"first": "https://example.social/@alyssa/inbox/likes/page/1",
"last": "https://example.social/@alyssa/inbox/likes/page/2",
"filteredProperty": "type",
"filteredValue": "Like"
"last": "https://example.social/@alyssa/inbox/likes/page/1"
},
{
"id": "https://example.social/@alyssa/inbox/notes",
"url": "https://example.social/@alyssa/inbox/notes",
"name": "Notes",
"summary": "Incoming Notes to Alyssa's Inbox",
"type": "OrderedCollection",
"totalItems": 100,
"first": "https://example.social/@alyssa/inbox/notes/page/1",
"last": "https://example.social/@alyssa/inbox/notes/page/2",
"filteredProperty": ["type", "object.type"],
"filteredValue": ["Create", "Note"]
"id": "https://example.social/@alyssa/inbox/posts-with-replies",
"type": "CollectionView",
"name": "Posts with Replies",
"filter": [
{
"type": "PropertyShape",
"path": "type",
"hasValue": "Create"
},
{
"type": "PropertyShape",
"path": ["object", "inReplyTo"],
"minCount": 1
}
],
"sort": {
"type": "SortShape",
"path": "published",
"order": "Descending"
},
"totalItems": 10,
"first": "https://example.social/@alyssa/inbox/blog-posts/page/1",
"last": "https://example.social/@alyssa/inbox/blog-posts/page/1",
"viewOf": "https://example.social/@alyssa/inbox"
},
{
"id": "https://example.social/@alyssa/inbox/notes-by-coworkers",
"type": "CollectionView",
"name": "Posts by Co-Workers",
"filter": {
"type": "InCollectionShape",
"path": "actor",
"inCollection": "https://example.social/@alyssa/friends/coworkers"
},
"sort": {
"type": "SortShape",
"path": "published",
"order": "Descending"
},
"totalItems": 10,
"first": "https://example.social/@alyssa/inbox/notes-by-coworkers/page/1",
"last": "https://example.social/@alyssa/inbox/notes-by-coworkers/page/1",
"viewOf": "https://example.social/@alyssa/inbox"
}
]
}
```
Note that this example using an Actor's Inbox may be more suitably linked via an Actor's `streams` instead of on the Inbox itself but is designed to show how filters might work.
## Vocabulary Extensions
Here are the terms that would needed to be added to the FEP vocabulary:
```json
{
"@context": {
"fep": "https://w3id.org/fep#",
"as": "https://www.w3.org/ns/activitystreams#",
"sh": "http://www.w3.org/ns/shacl#",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#"
},
"fep:CollectionView": {
"@id": "fep:CollectionView",
"@type": "rdfs:Class",
"rdfs:subClassOf": "as:OrderedCollection",
"rdfs:label": "Collection View",
"rdfs:comment": "Represents a sorted and/or filtered version of a Collection"
},
"fep:CollectionViewPage": {
"@id": "fep:CollectionViewPage",
"@type": "rdfs:Class",
"rdfs:subClassOf": "as:OrderedCollectionPage",
"rdfs:label": "Collection View Page",
"rdfs:comment": "Represents a sorted and/or filtered version of a CollectionPage"
},
"fep:SortShape": {
"@id": "fep:SortShape",
"@type": "rdfs:Class",
"rdfs:subClassOf": "sh:PropertyShape",
"rdfs:label": "Sort Shape",
"rdfs:comment": "A PropertyShape with an 'order' property"
},
"fep:filter": {
"@id": "fep:filter",
"@type": "rdf:Property",
"rdfs:domain": "fep:CollectionView",
"rdfs:range": "sh:Shape",
"rdfs:label": "Filter",
"rdfs:comment": "The method of filtering applied to the CollectionView"
},
"fep:sort": {
"@id": "fep:sort",
"@type": "rdf:Property",
"rdfs:domain": "fep:CollectionView",
"rdfs:range": "fep:SortShape",
"rdfs:label": "Sort",
"rdfs:comment": "The method of sorting applied to the CollectionView"
},
"fep:order": {
"@id": "fep:order",
"@type": "rdf:Property",
"rdfs:domain": "fep:SortShape",
"rdfs:range": "fep:SortOrderType",
"rdfs:label": "Order",
"rdfs:comment": "Indicates whether the sort order is 'Ascending' or 'Descending'"
},
"fep:SortOrderType": {
"@id": "fep:SortOrderType",
"@type": "rdfs:Class",
"rdfs:label": "Sort Order Type",
"rdfs:comment": "For indicating the sort order"
},
"fep:Ascending": {
"@id": "fep:Ascending",
"@type": "fep:SortOrderType",
"rdfs:label": "Ascending",
"rdfs:comment": "Indicates ascending sort order"
},
"fep:Descending": {
"@id": "fep:Descending",
"@type": "fep:SortOrderType",
"rdfs:label": "Descending",
"rdfs:comment": "Indicates descending sort order"
},
"fep:InCollectionShape": {
"@id": "fep:InCollectionShape",
"@type": "rdfs:Class",
"rdfs:subClassOf": "sh:PropertyShape",
"rdfs:label": "In Collection Shape",
"rdfs:comment": "For filtering on whether a property's value is in a Collection.",
"sh:js": "fep:inCollectionFunction"
},
"fep:inCollection": {
"@id": "fep:inCollection",
"@type": "rdf:Property",
"rdfs:domain": "fep:InCollectionShape",
"rdfs:range": "sh:IRI",
"rdfs:label": "In Collection",
"rdfs:comment": "Maps a Collection URL to fep:inCollectionFunction"
},
"fep:inCollectionFunction": {
"@id": "fep:inCollectionFunction",
"@type": "sh:JSFunction",
"sh:jsFunction": "inCollection",
"sh:jsLibrary": "https://w3id.org/fep/functions.js",
"sh:parameter": {
"@type": "sh:Parameter",
"sh:path": "fep:inCollection"
}
},
"fep:viewOf": {
"@id": "fep:viewOf",
"@type": "rdf:Property",
"rdfs:label": "View of",
"rdfs:domain": "fep:CollectionView",
"rdfs:range": "as:Collection",
"rdfs:comment": "A reference back to the original Collection"
}
}
```
## Security
Servers could in theory make available a templated URL endpoint that allows for arbitrary sorting or filtering. This should be discouraged, as it could lead to database injections. Instead, only predetermined sorted/filtered Collections should be made available via the `streams` property.
Servers could in theory make available a templated URL endpoint that allows for arbitrary sorting or filtering. This should be discouraged, as it could lead to database injections. Instead, only predetermined sorted/filtered CollectionViews should be made available via the `streams` property.
## References