Much more work on duck-typing.
This commit is contained in:
parent
032dcb7536
commit
b2ff133e4a
24
README.md
24
README.md
|
@ -12,7 +12,11 @@ Good.
|
|||
|
||||
Let us proceed.
|
||||
|
||||
**The Old Dog and Duck** is intended to be a set of libraries to enable people to build stuff which interacts with ActivityPub. It isn't intended to be a replacement for, or clone of, Mastodon. I do think I might implement my own ActivityPub server on top of The Old Dog and Duck, that specifically might allow for user-pluggable feed-sorting algorithms and with my own user interface/user experience take, but that project is not this project.
|
||||
**The Old Dog and Duck** is intended to be a set of libraries to enable people to build stuff which interacts with ActivityPub. It isn't intended to be a replacement for, or clone of, Mastodon. I do think I might implement my own ActivityPub server on top of The Old Dog and Duck, that specifically might allow for user-pluggable feed-sorting algorithms and with my own user interface/user experience take, but that project is not (yet, at any rate) this project.
|
||||
|
||||
## Status
|
||||
|
||||
This is a long way pre-alpha. Everything will change. Feel free to play, but do so at your own risk. Contributions welcome.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
@ -47,17 +51,31 @@ Where deliveries are ordered and arrive; and from where deliveries onwards are d
|
|||
|
||||
Duck-typing for ActivityStreams objects.
|
||||
|
||||
As of version 0.1.0, this is substantially the only part that is yet at all useful, and it is still a long way from finished or robust.
|
||||
|
||||
### Scratch
|
||||
|
||||
What the dog does when bored. Essentially, a place where I can learn how to make this stuff work, but perhaps eventually an ActivityPub server in its own right.
|
||||
|
||||
## Usage
|
||||
|
||||
FIXME
|
||||
At present, only the duck-typing functions work. To play with them, use
|
||||
|
||||
```clojure
|
||||
(require '[dog-and-duck.quack.quack :as q])
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Prior to testing, you should clone [activitystreams-test-documents](https://github.com/w3c-social/activitystreams-test-documents) into the `resources` directory. You can then test with
|
||||
|
||||
```bash
|
||||
lein test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright © Simon Brooke, 2022
|
||||
Copyright © Simon Brooke, 2022.
|
||||
|
||||
This program and the accompanying materials are made available under the
|
||||
terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
(defn object?
|
||||
"Return `true` iff `x` is recognisably an ActivityStreams object.
|
||||
"Returns `true` iff `x` is recognisably an ActivityStreams object.
|
||||
|
||||
**NOTE THAT** The ActivityStreams spec
|
||||
[says](https://www.w3.org/TR/activitystreams-core/#object):
|
||||
|
@ -34,9 +34,6 @@
|
|||
[x]
|
||||
(and (map? x) (:type x) true))
|
||||
|
||||
(object? nil)
|
||||
(object? {:type "test"})
|
||||
|
||||
(defn persistent-object?
|
||||
"`true` iff `x` is a persistent object.
|
||||
|
||||
|
@ -49,31 +46,142 @@
|
|||
|
||||
(persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"})
|
||||
|
||||
(defn actor?
|
||||
"TODO!"
|
||||
[x]
|
||||
true)
|
||||
(def ^:const actor-types
|
||||
"The set of types we will accept as actors.
|
||||
|
||||
There's an [explicit set of allowed actor types]
|
||||
(https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)."
|
||||
#{"Application"
|
||||
"Group"
|
||||
"Organization"
|
||||
"Person"
|
||||
"Service"})
|
||||
|
||||
(def verb?
|
||||
(defn actor-type?
|
||||
;; TODO: better as a macro
|
||||
[x]
|
||||
(if (actor-types x) true false))
|
||||
|
||||
(def ^:const verb-types
|
||||
"The set of types we will accept as verbs.
|
||||
|
||||
There's an [explicit set of allowed verbs]
|
||||
There's an [explicit set of allowed verb types]
|
||||
(https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
|
||||
#{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
|
||||
"Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
|
||||
"Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
|
||||
"TentativeReject" "Travel" "Undo" "Update" "View"})
|
||||
|
||||
(defn activity?
|
||||
"`true` iff `x` is an activity, else false.
|
||||
(defn verb-type?
|
||||
;; TODO: better as a macro
|
||||
[x]
|
||||
(if (verb-types x) true false))
|
||||
|
||||
see "
|
||||
(def ^:const activitystreams-context-uri
|
||||
"The URI of the context of an ActivityStreams object is expected to be this
|
||||
literal string."
|
||||
"https://www.w3.org/ns/activitystreams")
|
||||
|
||||
(defn context?
|
||||
"Returns `true` iff `x` quacks like an ActivityStreams context, else false.
|
||||
|
||||
A context is either
|
||||
1. the URI (actually an IRI) `activitystreams-context-uri`, or
|
||||
2. a collection comprising that URI and a map."
|
||||
[x]
|
||||
(cond
|
||||
(nil? x) false
|
||||
(string? x) (and (= x activitystreams-context-uri) true)
|
||||
(coll? x) (and (context? (first (remove map? x)))
|
||||
(= (count x) 2)
|
||||
true)
|
||||
:else false))
|
||||
|
||||
(defmacro has-context? [x]
|
||||
`(context? ((keyword "@context") ~x)))
|
||||
|
||||
(defn actor?
|
||||
"Returns `true` if `x` quacks like an actor, else false."
|
||||
[x]
|
||||
(and
|
||||
(object? x)
|
||||
(has-context? x)
|
||||
(uri? (URI. (:inbox x)))
|
||||
(uri? (URI. (:outbox x)))
|
||||
(actor-type? (:type x))
|
||||
true))
|
||||
|
||||
(defn activity?
|
||||
"`true` iff `x` quacks like an activity, else false."
|
||||
[x]
|
||||
(try
|
||||
(and (object? x)
|
||||
(uri? (URI. ((keyword "@context") x)))
|
||||
(has-context? x)
|
||||
(string? (:summary x))
|
||||
(actor? (:actor x))
|
||||
(verb? (:type x))
|
||||
(or (object? (:object x)) (uri? (URI. x))))
|
||||
(catch URISyntaxException _ false)))
|
||||
(verb-type? (:type x))
|
||||
(or (object? (:object x)) (uri? (URI. (:object x))))
|
||||
true)
|
||||
(catch URISyntaxException _ false)))
|
||||
|
||||
(defn link?
|
||||
"`true` iff `x` quacks like a link, else false."
|
||||
[x]
|
||||
(and (object? x)
|
||||
(= (:type x) "Link")
|
||||
(uri? (URI. (:href x)))
|
||||
true))
|
||||
|
||||
(defn link-or-uri?
|
||||
"`true` iff `x` is either a URI or a link, else false.
|
||||
|
||||
There are several points in the specification where e.g. the `:image`
|
||||
property (if present) may be either a link or a URI."
|
||||
[x]
|
||||
(and
|
||||
(cond (string? x) (uri? (URI. x))
|
||||
:else (link? x))
|
||||
true))
|
||||
|
||||
(defn collection?
|
||||
"`true` iff `x` quacks like a collection of type `type`, else `false`.
|
||||
|
||||
With one argument, will recognise plain collections and ordered collections,
|
||||
but (currently) not collection pages."
|
||||
([x type]
|
||||
(let [items (or (:items x) (:orderedItems x))]
|
||||
(and
|
||||
(cond
|
||||
(:items x) (nil? (:orderedItems x))
|
||||
(:orderedItems x) (nil? (:items x))) ;; can't have both properties
|
||||
(object? x)
|
||||
(= (:type x) type)
|
||||
(coll? items)
|
||||
(every? object? items)
|
||||
(integer? (:totalItems x))
|
||||
true)))
|
||||
([x]
|
||||
(or (collection? x "Collection")
|
||||
(collection? x "OrderedCollection"))))
|
||||
|
||||
(defn unordered-collection?
|
||||
"`true` iff `x` quacks like an unordered collection, else `false`."
|
||||
[x]
|
||||
(collection? x "Collection"))
|
||||
|
||||
(defn ordered-collection?
|
||||
"`true` iff `x` quacks like an ordered collection, else `false`."
|
||||
[x]
|
||||
(collection? x "OrderedCollection"))
|
||||
|
||||
(defn collection-page?
|
||||
"`true` iff `x` quacks like a page in a paged collection, else `false`."
|
||||
[x]
|
||||
(collection? x "CollectionPage"))
|
||||
|
||||
(defn ordered-collection-page?
|
||||
"`true` iff `x` quacks like a page in an ordered paged collection, else `false`."
|
||||
[x]
|
||||
(collection? x "OrderedCollectionPage"))
|
||||
|
||||
|
||||
|
|
129
test/dog_and_duck/quack/quack_test.clj
Normal file
129
test/dog_and_duck/quack/quack_test.clj
Normal file
|
@ -0,0 +1,129 @@
|
|||
(ns dog-and-duck.quack.quack-test
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[dog-and-duck.quack.quack :refer [activitystreams-context-uri
|
||||
actor? actor-type? context?
|
||||
object? persistent-object?
|
||||
verb-type?]]
|
||||
[dog-and-duck.scratch.parser :refer [clean]]))
|
||||
|
||||
;;; Copyright (C) Simon Brooke, 2022
|
||||
|
||||
;;; This program is free software; you can redistribute it and/or
|
||||
;;; modify it under the terms of the GNU General Public License
|
||||
;;; as published by the Free Software Foundation; either version 2
|
||||
;;; of the License, or (at your option) any later version.
|
||||
|
||||
;;; This program is distributed in the hope that it will be useful,
|
||||
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;;; GNU General Public License for more details.
|
||||
|
||||
;;; You should have received a copy of the GNU General Public License
|
||||
;;; along with this program; if not, write to the Free Software
|
||||
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
(deftest object-test
|
||||
(testing "object recognition"
|
||||
(let [expected false
|
||||
actual (object? nil)]
|
||||
(is (= actual expected)))
|
||||
(let [expected true
|
||||
actual (object? {:type "Test"})]
|
||||
(is (= actual expected)))
|
||||
(let [expected false
|
||||
actual (object?
|
||||
(first
|
||||
(clean
|
||||
(slurp "resources/activitystreams-test-documents/empty.json"))))]
|
||||
(is (= actual expected)))
|
||||
(let [expected true
|
||||
actual (object?
|
||||
(first
|
||||
(clean
|
||||
(slurp "resources/activitystreams-test-documents/core-ex1-jsonld.json"))))]
|
||||
(is (= actual expected)))))
|
||||
|
||||
(deftest persistent-object-test
|
||||
(testing "persistent object recognition"
|
||||
(let [expected false
|
||||
actual (persistent-object? nil)]
|
||||
(is (= actual expected) "Not persistent: not an object."))
|
||||
(let [expected true
|
||||
actual (persistent-object? {:type "Test" :id "https://foo.bar/@ban"})]
|
||||
(is (= actual expected) "Is persistent: has both id and type."))
|
||||
(let [expected false
|
||||
actual (persistent-object?
|
||||
(first
|
||||
(clean
|
||||
(slurp "resources/activitystreams-test-documents/simple0001.json"))))]
|
||||
(is (= actual expected) "Not persistent: has no id."))
|
||||
(let [expected true
|
||||
actual (persistent-object?
|
||||
(first
|
||||
(clean
|
||||
(slurp "resources/activitystreams-test-documents/simple0008.json"))))]
|
||||
(is (= actual expected) "Is persistent: has both id and type."))))
|
||||
|
||||
(deftest actor-type-test
|
||||
(testing "identification of actor types"
|
||||
(let [expected false
|
||||
actual (actor-type? nil)]
|
||||
(is (= actual expected) "nil is not an actor"))
|
||||
(let [expected false
|
||||
actual (actor-type? "Duck")]
|
||||
(is (= actual expected) "A duck is not an actor"))
|
||||
(let [expected true
|
||||
actual (actor-type? "Person")]
|
||||
(is (= actual expected) "A person is an actor"))
|
||||
(let [expected true
|
||||
actual (actor-type? "Service")]
|
||||
(is (= actual expected) "A service is an actor"))))
|
||||
|
||||
(deftest verb-type-test
|
||||
(testing "identification of verb types"
|
||||
(let [expected false
|
||||
actual (verb-type? nil)]
|
||||
(is (= actual expected) "nil is not a verb"))
|
||||
(let [expected false
|
||||
actual (verb-type? "Quack")]
|
||||
(is (= actual expected) "Quack is not a verb"))
|
||||
(let [expected true
|
||||
actual (verb-type? "Create")]
|
||||
(is (= actual expected) "Create is a verb"))
|
||||
(let [expected true
|
||||
actual (verb-type? "Reject")]
|
||||
(is (= actual expected) "Reject is a verb"))))
|
||||
|
||||
(deftest context-test
|
||||
(testing "identification of valid contexts"
|
||||
(let [expected false
|
||||
actual (context? "https://foo.bar/ban/")]
|
||||
(is (= actual expected)
|
||||
"Only `activitystreams-context-uri` is valid as a context on its own"))
|
||||
(let [expected true
|
||||
actual (context? activitystreams-context-uri)]
|
||||
(is (= actual expected)
|
||||
"`activitystreams-context-uri` is valid as a context on its own"))
|
||||
(let [expected false
|
||||
actual (context? [{:foo "bar"} "https://foo.bar/ban/"])]
|
||||
(is (= actual expected)
|
||||
"Only `activitystreams-context-uri` is valid as a context uri"))
|
||||
(let [expected true
|
||||
actual (context? [{:foo "bar"} activitystreams-context-uri])]
|
||||
(is (= actual expected)
|
||||
"`activitystreams-context-uri` is valid as a context uri"))
|
||||
(let [expected true
|
||||
actual (context? [activitystreams-context-uri {:foo "bar"}])]
|
||||
(is (= actual expected)
|
||||
"order of elements within a context should not matter"))
|
||||
))
|
||||
|
||||
(deftest actor-test
|
||||
(testing "identification of actors"
|
||||
(let [expected false
|
||||
actual (actor? (-> "resources/activitystreams-test-documents/simple0008.json" slurp clean first))]
|
||||
(is (= actual expected) "A Note is not an actor"))
|
||||
(let [expected true
|
||||
actual (actor? (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor))]
|
||||
(is (= actual expected) "A Person is an actor"))
|
||||
))
|
Loading…
Reference in a new issue