diff --git a/README.md b/README.md index 97d9ad0..1782064 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,10 @@ At present, only the duck-typing functions work. To play with them, use (require '[dog-and-duck.quack.quack :as q]) ``` +## Documentation + +Full documentation is [here](https://simon-brooke.github.io/dog-and-duck/). + ## 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 diff --git a/doc/Using_ActivityPub.md b/doc/Using_ActivityPub.md index b306543..821b0fc 100644 --- a/doc/Using_ActivityPub.md +++ b/doc/Using_ActivityPub.md @@ -1,112 +1,5 @@ # Using ActivityPub -```clojure -user=> (require '[clj-activitypub.core :as activitypub]) -nil -user=> (require '[clj-activitypub.webfinger :as webfinger]) -nil -user=> (require '[clojure.walk :refer [keywordize-keys]]) -nil -user=> (require '[clojure.pprint :refer [pprint]]) -nil -user=> (def base-domain "mastodon.scot") -#'user/base-domain -user=> (def account-handle "@simon_brooke@mastodon.scot") -#'user/account-handle -user=> (in-ns 'user) -#object[clojure.lang.Namespace 0x525575 "user"] -user=> (activitypub/parse-account account-handle ) -{:domain "mastodon.scot", :username "simon_brooke"} -user=> (map *1 [:domain :username]) -("mastodon.scot" "simon_brooke") -user=> (apply webfinger/fetch-user-id *1) -"https://mastodon.scot/users/simon_brooke" -user=> (activitypub/fetch-user *1) -{"followers" "https://mastodon.scot/users/simon_brooke/followers", "inbox" "https://mastodon.scot/users/simon_brooke/inbox", "url" "https://mastodon.scot/@simon_brooke", "@context" ["https://www.w3.org/ns/activitystreams" "https://w3id.org/security/v1" {"identityKey" {"@type" "@id", "@id" "toot:identityKey"}, "EncryptedMessage" "toot:EncryptedMessage", "Ed25519Key" "toot:Ed25519Key", "devices" {"@type" "@id", "@id" "toot:devices"}, "manuallyApprovesFollowers" "as:manuallyApprovesFollowers", "schema" "http://schema.org#", "PropertyValue" "schema:PropertyValue", "Curve25519Key" "toot:Curve25519Key", "claim" {"@type" "@id", "@id" "toot:claim"}, "value" "schema:value", "Hashtag" "as:Hashtag", "movedTo" {"@id" "as:movedTo", "@type" "@id"}, "discoverable" "toot:discoverable", "messageType" "toot:messageType", "messageFranking" "toot:messageFranking", "cipherText" "toot:cipherText", "toot" "http://joinmastodon.org/ns#", "alsoKnownAs" {"@id" "as:alsoKnownAs", "@type" "@id"}, "featured" {"@id" "toot:featured", "@type" "@id"}, "featuredTags" {"@id" "toot:featuredTags", "@type" "@id"}, "Ed25519Signature" "toot:Ed25519Signature", "focalPoint" {"@container" "@list", "@id" "toot:focalPoint"}, "fingerprintKey" {"@type" "@id", "@id" "toot:fingerprintKey"}, "Device" "toot:Device", "publicKeyBase64" "toot:publicKeyBase64", "deviceId" "toot:deviceId", "suspended" "toot:suspended"}], "devices" "https://mastodon.scot/users/simon_brooke/collections/devices", "manuallyApprovesFollowers" false, "image" {"type" "Image", "mediaType" "image/jpeg", "url" "https://media.mastodon.scot/mastodon-scot-public/accounts/headers/109/252/274/874/045/781/original/e1f1823c4361fa27.jpg"}, "endpoints" {"sharedInbox" "https://mastodon.scot/inbox"}, "id" "https://mastodon.scot/users/simon_brooke", "publicKey" {"id" "https://mastodon.scot/users/simon_brooke#main-key", "owner" "https://mastodon.scot/users/simon_brooke", "publicKeyPem" "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/6GgLJgJlPhhqFm1tUQ\noSLnWxhDwq4HlZIHrBsVjkSvUAnHKqq42Q/hta+fkWB8rmTFpmjLXDj/Fi0uejvT\nBc+KrLwfX/yR8+G87afGCRS3CaumoLJ7zkBIlsFzIKMoIke1D3QuHX95yGGXs+hp\nmyxt/+CXRyZjK7u9NG7SMRUlpwvOlpD12Aei35Nb8NSr03JvY8/WVMIbWrecyI0b\nAlwj6axxHx7J15Yo+aEtKzZ2OFKXf+sh0QF9BEnYcmVKYlR6kiOglLFHKdCBUSYi\ni9Flv00TydqlGvR5fpShBqORiy0M/FVtNXlz2sNBEsGB2meipkjh+cRLzTbYo4KL\nJwIDAQAB\n-----END PUBLIC KEY-----\n"}, "summary" "

Anarcho-syndicalist, autistic, crofter, cyclist, depressive, entrepreneur, geek, Zapatista. Politics & environment, especially #LandReform. he/him.

Twitter: @simon_brooke
GitHub: simon-brooke
FetLife: Simon_Brooke

Credo: Life is harsh. What we can do - and what we should do - is strive to make it less harsh for the people around us.

", "attachment" [{"type" "PropertyValue", "name" "Home Page", "value" "https://www.journeyman.cc/~simon/"}], "name" "Simon Brooke", "tag" [{"type" "Hashtag", "href" "https://mastodon.scot/tags/landreform", "name" "#landreform"}], "published" "2022-10-29T00:00:00Z", "preferredUsername" "simon_brooke", "discoverable" true, "alsoKnownAs" ["https://mastodon.social/users/simon_brooke"], "featured" "https://mastodon.scot/users/simon_brooke/collections/featured", "featuredTags" "https://mastodon.scot/users/simon_brooke/collections/tags", "type" "Person", "outbox" "https://mastodon.scot/users/simon_brooke/outbox", "following" "https://mastodon.scot/users/simon_brooke/following", "icon" {"type" "Image", "mediaType" "image/png", "url" "https://media.mastodon.scot/mastodon-scot-public/accounts/avatars/109/252/274/874/045/781/original/172e8f7530627e87.png"}} -user=> (def sb (keywordize-keys *1)) -#'user/sb -user=> (:outbox sb) -"https://mastodon.scot/users/simon_brooke/outbox" -user=> (require '[clojure.data.json :as json]) -nil -user=> (slurp (:outbox sb)) -Execution error (IOException) at sun.net.www.protocol.http.HttpURLConnection/getInputStream0 (HttpURLConnection.java:1894). -Server returned HTTP response code: 403 for URL: https://mastodon.scot/users/simon_brooke/outbox -user=> (pprint sb) -{:inbox "https://mastodon.scot/users/simon_brooke/inbox", - :name "Simon Brooke", - :@context - ["https://www.w3.org/ns/activitystreams" - "https://w3id.org/security/v1" - {:schema "http://schema.org#", - :messageType "toot:messageType", - :messageFranking "toot:messageFranking", - :identityKey {:@type "@id", :@id "toot:identityKey"}, - :Hashtag "as:Hashtag", - :deviceId "toot:deviceId", - :publicKeyBase64 "toot:publicKeyBase64", - :value "schema:value", - :Ed25519Key "toot:Ed25519Key", - :featured {:@id "toot:featured", :@type "@id"}, - :Curve25519Key "toot:Curve25519Key", - :discoverable "toot:discoverable", - :focalPoint {:@container "@list", :@id "toot:focalPoint"}, - :suspended "toot:suspended", - :fingerprintKey {:@type "@id", :@id "toot:fingerprintKey"}, - :Ed25519Signature "toot:Ed25519Signature", - :cipherText "toot:cipherText", - :EncryptedMessage "toot:EncryptedMessage", - :alsoKnownAs {:@id "as:alsoKnownAs", :@type "@id"}, - :featuredTags {:@id "toot:featuredTags", :@type "@id"}, - :devices {:@type "@id", :@id "toot:devices"}, - :toot "http://joinmastodon.org/ns#", - :movedTo {:@id "as:movedTo", :@type "@id"}, - :Device "toot:Device", - :PropertyValue "schema:PropertyValue", - :manuallyApprovesFollowers "as:manuallyApprovesFollowers", - :claim {:@type "@id", :@id "toot:claim"}}], - :featured - "https://mastodon.scot/users/simon_brooke/collections/featured", - :type "Person", - :discoverable true, - :icon - {:type "Image", - :mediaType "image/png", - :url - "https://media.mastodon.scot/mastodon-scot-public/accounts/avatars/109/252/274/874/045/781/original/172e8f7530627e87.png"}, - :following "https://mastodon.scot/users/simon_brooke/following", - :summary - "

Anarcho-syndicalist, autistic, crofter, cyclist, depressive, entrepreneur, geek, Zapatista. Politics & environment, especially #LandReform. he/him.

Twitter: @simon_brooke
GitHub: simon-brooke
FetLife: Simon_Brooke

Credo: Life is harsh. What we can do - and what we should do - is strive to make it less harsh for the people around us.

", - :publicKey - {:id "https://mastodon.scot/users/simon_brooke#main-key", - :owner "https://mastodon.scot/users/simon_brooke", - :publicKeyPem - "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/6GgLJgJlPhhqFm1tUQ\noSLnWxhDwq4HlZIHrBsVjkSvUAnHKqq42Q/hta+fkWB8rmTFpmjLXDj/Fi0uejvT\nBc+KrLwfX/yR8+G87afGCRS3CaumoLJ7zkBIlsFzIKMoIke1D3QuHX95yGGXs+hp\nmyxt/+CXRyZjK7u9NG7SMRUlpwvOlpD12Aei35Nb8NSr03JvY8/WVMIbWrecyI0b\nAlwj6axxHx7J15Yo+aEtKzZ2OFKXf+sh0QF9BEnYcmVKYlR6kiOglLFHKdCBUSYi\ni9Flv00TydqlGvR5fpShBqORiy0M/FVtNXlz2sNBEsGB2meipkjh+cRLzTbYo4KL\nJwIDAQAB\n-----END PUBLIC KEY-----\n"}, - :endpoints {:sharedInbox "https://mastodon.scot/inbox"}, - :preferredUsername "simon_brooke", - :id "https://mastodon.scot/users/simon_brooke", - :alsoKnownAs ["https://mastodon.social/users/simon_brooke"], - :outbox "https://mastodon.scot/users/simon_brooke/outbox", - :url "https://mastodon.scot/@simon_brooke", - :featuredTags - "https://mastodon.scot/users/simon_brooke/collections/tags", - :devices - "https://mastodon.scot/users/simon_brooke/collections/devices", - :image - {:type "Image", - :mediaType "image/jpeg", - :url - "https://media.mastodon.scot/mastodon-scot-public/accounts/headers/109/252/274/874/045/781/original/e1f1823c4361fa27.jpg"}, - :tag - [{:type "Hashtag", - :href "https://mastodon.scot/tags/landreform", - :name "#landreform"}], - :followers "https://mastodon.scot/users/simon_brooke/followers", - :published "2022-10-29T00:00:00Z", - :manuallyApprovesFollowers false, - :attachment - [{:type "PropertyValue", - :name "Home Page", - :value - "https://www.journeyman.cc/~simon/"}]} -``` +## Introduction + +I do not know what I am doing; I am learning, and playing. Nothing in this document should be treated as good advice; it simply relates to the current state of my knowledge. \ No newline at end of file diff --git a/doc/Validation_Faults.md b/doc/Validation_Faults.md new file mode 100644 index 0000000..9f6a8f6 --- /dev/null +++ b/doc/Validation_Faults.md @@ -0,0 +1,61 @@ +# Validation Faults in ActivityPub documents + +## Motivation + +This document is intended to provide an extension vocabulary for [ActivityStreams](https://www.w3.org/TR/activitystreams-core/) documents, which provides vocabulary for categorising and describing faults in [ActivityPub](https://www.w3.org/TR/activitypub/) documents. + +The motivation is to be able to serialise a validation report on an ActivityPub document as an ActivityStreams document. + +## Intepretation + +### Conformance + +As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative. + +The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119]. + +### 'the spec' + +Where the phrase 'the spec' is used in this document, it refers to a concatenation of the ActivityStreams specification and the ActivityPub specification. + +## The `Fault` object type + +The `Fault` object type is a novel object type introduced by this document to describe validation faults. Objects with the `Fault` object type MUST have at least the following fields (additional fields are not required but are optional): + +1. `:@context` whose value shall be the URL of a document specifying this vocabulary; +2. `:type` whose value shall be `Fault`; +3. `:severity` whose value shall be one of `minor`, `should`, `must` or `critical`; +4. `:fault` whose value shall be a unique token representing the particular fault type; +5. `:narrative` whose value shall be a natural language description of the fault type. + +### The Fields + +#### Context + +The value of the `@context` field of a fault report object shall be the URL of this +document, currently `https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html`. + +#### Type + +The value of the `type` field of a fault report object MUST be `Fault`. + +#### Severity + +Each fault report object MUST have a `severity` field whose value MUST be one of + + 1. `:minor` things which I consider to be faults, but which + don't actually breach the spec; + 2. `:should` instances where the spec says something SHOULD + be done, which isn't; + 3. `:must` instances where the spec says something MUST + be done, which isn't; + 4. `:critical` instances where I believe the fault means that + the object cannot be meaningfully processed. + +#### Fault + +Unique codes shall be assigned to each fault type, and shall be documented in this section. + +It is intended that there should ultimately be a well known site at which the fault codes can be resolved to natural language explanations in as many natural languages as possible of the nature of the particular fault. + + diff --git a/docs/cloverage/clj_activitypub/core.clj.html b/docs/cloverage/clj_activitypub/core.clj.html index d0e28ea..f00e04b 100644 --- a/docs/cloverage/clj_activitypub/core.clj.html +++ b/docs/cloverage/clj_activitypub/core.clj.html @@ -8,442 +8,451 @@ 001  (ns clj-activitypub.core
- 002    (:require [clj-activitypub.internal.crypto :as crypto] + 002    "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). 
- 003              [clj-activitypub.internal.thread-cache :as thread-cache] + 003     If and when Jahfer issues a release of that library, this directory will be deleted and a 
- 004              [clj-activitypub.internal.http-util :as http] + 004     dependency on that library will be added to the project."
- 005              [clj-http.client :as client] + 005    (:require [clj-activitypub.internal.crypto :as crypto]
- 006              [clojure.string :as str])) + 006              [clj-activitypub.internal.thread-cache :as thread-cache] +
+ + 007              [clj-activitypub.internal.http-util :as http] +
+ + 008              [clj-http.client :as client] +
+ + 009              [clojure.string :as str]))
- 007   + 010  
- 008  (defn config + 011  (defn config
- 009    "Creates hash of computed data relevant for most ActivityPub utilities." + 012    "Creates hash of computed data relevant for most ActivityPub utilities."
- 010    [{:keys [domain username username-route public-key private-key] + 013    [{:keys [domain username username-route public-key private-key]
- 011      :or {username-route "/users/" + 014      :or {username-route "/users/"
- 012           public-key nil + 015           public-key nil
- 013           private-key nil}}] + 016           private-key nil}}]
- 014    (let [base-url (str "https://" domain)] + 017    (let [base-url (str "https://" domain)]
- 015      {:domain domain + 018      {:domain domain
- 016       :base-url base-url + 019       :base-url base-url
- 017       :username username + 020       :username username
- 018       :user-id (str base-url username-route username) + 021       :user-id (str base-url username-route username)
- 019       :public-key public-key + 022       :public-key public-key
- 020       :private-key (when private-key + 023       :private-key (when private-key
- 021                      (crypto/private-key private-key))})) + 024                      (crypto/private-key private-key))}))
- 022   + 025  
- 023  (defn parse-account + 026  (defn parse-account
- 024    "Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces + 027    "Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces
- 025     a map containing {:domain ... :username ...}." + 028     a map containing {:domain ... :username ...}."
- 026    [handle] + 029    [handle]
- 027    (let [[username domain] (filter #(not (str/blank? %)) + 030    (let [[username domain] (filter #(not (str/blank? %))
- 028                                    (str/split handle #"@"))] + 031                                    (str/split handle #"@"))]
- 029      {:domain domain :username username})) + 032      {:domain domain :username username}))
- 030   + 033  
- 031  (def ^:private user-cache (thread-cache/make)) + 034  (def ^:private user-cache (thread-cache/make))
- 032  (defn fetch-user + 035  (defn fetch-user
- 033    "Fetches the customer account details located at user-id from a remote + 036    "Fetches the customer account details located at user-id from a remote
- 034     server. Will return cached results if they exist in memory." + 037     server. Will return cached results if they exist in memory."
- 035    [user-id] + 038    [user-id]
- 036    ((:get-v user-cache) + 039    ((:get-v user-cache)
- 037     user-id + 040     user-id
- 038     #(:body + 041     #(:body
- 039       (client/get user-id {:as :json-string-keys + 042       (client/get user-id {:as :json-string-keys
- 040                            :throw-exceptions false + 043                            :throw-exceptions false
- 041                            :ignore-unknown-host? true + 044                            :ignore-unknown-host? true
- 042                            :headers {"Accept" "application/activity+json"}})))) + 045                            :headers {"Accept" "application/activity+json"}}))))
- 043   + 046  
- 044  (defn actor + 047  (defn actor
- 045    "Accepts a config, and returns a map in the form expected by the ActivityPub + 048    "Accepts a config, and returns a map in the form expected by the ActivityPub
- 046     spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference." + 049     spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference."
- 047    [{:keys [user-id username public-key]}] + 050    [{:keys [user-id username public-key]}]
- 048    {"@context" ["https://www.w3.org/ns/activitystreams" + 051    {"@context" ["https://www.w3.org/ns/activitystreams"
- 049                 "https://w3id.org/security/v1"] + 052                 "https://w3id.org/security/v1"]
- 050     :id user-id + 053     :id user-id
- 051     :type "Person" + 054     :type "Person"
- 052     :preferredUsername username + 055     :preferredUsername username
- 053     :inbox (str user-id "/inbox") + 056     :inbox (str user-id "/inbox")
- 054     :outbox (str user-id "/outbox") + 057     :outbox (str user-id "/outbox")
- 055     :publicKey {:id (str user-id "#main-key") + 058     :publicKey {:id (str user-id "#main-key")
- 056                 :owner user-id + 059                 :owner user-id
- 057                 :publicKeyPem (or public-key "")}}) + 060                 :publicKeyPem (or public-key "")}})
- 058   + 061  
- 059  (def signature-headers ["(request-target)" "host" "date" "digest"]) + 062  (def signature-headers ["(request-target)" "host" "date" "digest"])
- 060   + 063  
- 061  (defn- str-for-signature [headers] + 064  (defn- str-for-signature [headers]
- 062    (let [headers-xf (reduce-kv + 065    (let [headers-xf (reduce-kv
- 063                      (fn [m k v] + 066                      (fn [m k v]
- 064                        (assoc m (str/lower-case k) v)) {} headers)] + 067                        (assoc m (str/lower-case k) v)) {} headers)]
- 065      (->> signature-headers + 068      (->> signature-headers
- 066           (select-keys headers-xf) + 069           (select-keys headers-xf)
- 067           (reduce-kv (fn [coll k v] (conj coll (str k ": " v))) []) + 070           (reduce-kv (fn [coll k v] (conj coll (str k ": " v))) [])
- 068           (interpose "\n") + 071           (interpose "\n")
- 069           (apply str)))) + 072           (apply str))))
- 070   + 073  
- 071  (defn gen-signature-header + 074  (defn gen-signature-header
- 072    "Generates a HTTP Signature string based on the provided map of headers." + 075    "Generates a HTTP Signature string based on the provided map of headers."
- 073    [config headers] + 076    [config headers]
- 074    (let [{:keys [user-id private-key]} config + 077    (let [{:keys [user-id private-key]} config
- 075          string-to-sign (str-for-signature headers) + 078          string-to-sign (str-for-signature headers)
- 076          signature (crypto/base64-encode (crypto/sign string-to-sign private-key)) + 079          signature (crypto/base64-encode (crypto/sign string-to-sign private-key))
- 077          sig-header-keys {"keyId" user-id + 080          sig-header-keys {"keyId" user-id
- 078                           "headers" (str/join " " signature-headers) + 081                           "headers" (str/join " " signature-headers)
- 079                           "signature" signature}] + 082                           "signature" signature}]
- 080      (->> sig-header-keys + 083      (->> sig-header-keys
- 081           (reduce-kv (fn [m k v] + 084           (reduce-kv (fn [m k v]
- 082                        (conj m (str k "=" "\"" v "\""))) []) + 085                        (conj m (str k "=" "\"" v "\""))) [])
- 083           (interpose ",") + 086           (interpose ",")
- 084           (apply str)))) + 087           (apply str))))
- 085   + 088  
- 086  (defn auth-headers + 089  (defn auth-headers
- 087    "Given a config and request map of {:body ... :headers ...}, returns the + 090    "Given a config and request map of {:body ... :headers ...}, returns the
- 088     original set of headers with Signature and Digest attributes appended." + 091     original set of headers with Signature and Digest attributes appended."
- 089    [config {:keys [body headers]}] + 092    [config {:keys [body headers]}]
- 090    (let [digest (http/digest body) + 093    (let [digest (http/digest body)
- 091          h (-> headers + 094          h (-> headers
- 092                (assoc "Digest" digest) + 095                (assoc "Digest" digest)
- 093                (assoc "(request-target)" "post /inbox"))] + 096                (assoc "(request-target)" "post /inbox"))]
- 094      (assoc headers + 097      (assoc headers
- 095             "Signature" (gen-signature-header config h) + 098             "Signature" (gen-signature-header config h)
- 096             "Digest" digest))) + 099             "Digest" digest)))
- 097   + 100  
- 098  (defmulti obj + 101  (defmulti obj
- 099    "Produces a map representing an ActivityPub object which can be serialized + 102    "Produces a map representing an ActivityPub object which can be serialized
- 100     directly to JSON in the form expected by the ActivityStreams 2.0 spec. + 103     directly to JSON in the form expected by the ActivityStreams 2.0 spec.
- 101     See https://www.w3.org/TR/activitystreams-vocabulary/ for reference." + 104     See https://www.w3.org/TR/activitystreams-vocabulary/ for reference."
- 102    (fn [_config object-data] (:type object-data))) + 105    (fn [_config object-data] (:type object-data)))
- 103   + 106  
- 104  (defmethod obj :note + 107  (defmethod obj :note
- 105    [{:keys [user-id]} + 108    [{:keys [user-id]}
- 106     {:keys [id published inReplyTo content to] + 109     {:keys [id published inReplyTo content to]
- 107      :or {published (http/date) + 110      :or {published (http/date)
- 108           inReplyTo "" + 111           inReplyTo ""
- 109           to "https://www.w3.org/ns/activitystreams#Public"}}] + 112           to "https://www.w3.org/ns/activitystreams#Public"}}]
- 110    {"id" (str user-id "/notes/" id) + 113    {"id" (str user-id "/notes/" id)
- 111     "type" "Note" + 114     "type" "Note"
- 112     "published" published + 115     "published" published
- 113     "attributedTo" user-id + 116     "attributedTo" user-id
- 114     "inReplyTo" inReplyTo + 117     "inReplyTo" inReplyTo
- 115     "content" content + 118     "content" content
- 116     "to" to}) + 119     "to" to})
- 117   + 120  
- 118  (defmulti activity + 121  (defmulti activity
- 119    "Produces a map representing an ActivityPub activity which can be serialized + 122    "Produces a map representing an ActivityPub activity which can be serialized
- 120     directly to JSON in the form expected by the ActivityStreams 2.0 spec. + 123     directly to JSON in the form expected by the ActivityStreams 2.0 spec.
- 121     See https://www.w3.org/TR/activitystreams-vocabulary/ for reference." + 124     See https://www.w3.org/TR/activitystreams-vocabulary/ for reference."
- 122    (fn [_config activity-type _data] activity-type)) + 125    (fn [_config activity-type _data] activity-type))
- 123   + 126  
- 124  (defmethod activity :create [{:keys [user-id]} _ data] + 127  (defmethod activity :create [{:keys [user-id]} _ data]
- 125    {"@context" ["https://www.w3.org/ns/activitystreams" + 128    {"@context" ["https://www.w3.org/ns/activitystreams"
- 126                 "https://w3id.org/security/v1"] + 129                 "https://w3id.org/security/v1"]
- 127     "type" "Create" + 130     "type" "Create"
- 128     "actor" user-id + 131     "actor" user-id
- 129     "object" data}) + 132     "object" data})
- 130   + 133  
- 131  (defmethod activity :delete [{:keys [user-id]} _ data] + 134  (defmethod activity :delete [{:keys [user-id]} _ data]
- 132    {"@context" ["https://www.w3.org/ns/activitystreams" + 135    {"@context" ["https://www.w3.org/ns/activitystreams"
- 133                 "https://w3id.org/security/v1"] + 136                 "https://w3id.org/security/v1"]
- 134     "type" "Delete" + 137     "type" "Delete"
- 135     "actor" user-id + 138     "actor" user-id
- 136     "object" data}) + 139     "object" data})
- 137   + 140  
- 138  (defn with-config + 141  (defn with-config
- 139    "Returns curried forms of the #activity and #obj multimethods in the form + 142    "Returns curried forms of the #activity and #obj multimethods in the form
- 140     {:activity ... :obj ...}, with the initial parameter set to config." + 143     {:activity ... :obj ...}, with the initial parameter set to config."
- 141    [config] + 144    [config]
- 142    (let [f (juxt + 145    (let [f (juxt
- 143             #(partial activity %) + 146             #(partial activity %)
- 144             #(partial obj %)) + 147             #(partial obj %))
- 145          [activity-fn obj-fn] (f config)] + 148          [activity-fn obj-fn] (f config)]
- 146      {:activity activity-fn + 149      {:activity activity-fn
- 147       :obj obj-fn})) + 150       :obj obj-fn}))
diff --git a/docs/cloverage/clj_activitypub/internal/crypto.clj.html b/docs/cloverage/clj_activitypub/internal/crypto.clj.html index dc72564..40f7251 100644 --- a/docs/cloverage/clj_activitypub/internal/crypto.clj.html +++ b/docs/cloverage/clj_activitypub/internal/crypto.clj.html @@ -8,109 +8,118 @@ 001  (ns clj-activitypub.internal.crypto
- 002    (:require [clojure.java.io :as io]) + 002    "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). 
- 003    (:import (java.util Base64) + 003     If and when Jahfer issues a release of that library, this directory will be deleted and a 
- 004             (java.security MessageDigest SecureRandom Signature))) + 004     dependency on that library will be added to the project."
- - 005   + + 005    (:require [clojure.java.io :as io])
- - 006  (java.security.Security/addProvider + + 006    (:import (java.util Base64)
- - 007   (org.bouncycastle.jce.provider.BouncyCastleProvider.)) + + 007             (java.security MessageDigest SecureRandom Signature)))
008  
- 009  (defn- keydata [reader] + 009  (java.security.Security/addProvider +
+ + 010   (org.bouncycastle.jce.provider.BouncyCastleProvider.)) +
+ + 011   +
+ + 012  (defn- keydata [reader]
- 010    (->> reader + 013    (->> reader
- 011         (org.bouncycastle.openssl.PEMParser.) + 014         (org.bouncycastle.openssl.PEMParser.)
- 012         (.readObject))) + 015         (.readObject)))
- 013   + 016  
- 014  (defn- pem-string->key-pair [string] + 017  (defn- pem-string->key-pair [string]
- 015    (let [kd (keydata (io/reader (.getBytes string)))] + 018    (let [kd (keydata (io/reader (.getBytes string)))]
- 016      (.getKeyPair (org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.) kd))) + 019      (.getKeyPair (org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.) kd)))
- 017   + 020  
- 018  (defn private-key [private-pem-str] + 021  (defn private-key [private-pem-str]
- 019    (-> private-pem-str + 022    (-> private-pem-str
- 020        (pem-string->key-pair) + 023        (pem-string->key-pair)
- 021        (.getPrivate))) -
- - 022   -
- - 023  (defn base64-encode [bytes] -
- - 024    (.encodeToString (Base64/getEncoder) bytes)) + 024        (.getPrivate)))
025  
- 026  (defn sha256-base64 [data] -
- - 027    (let [digest (.digest (MessageDigest/getInstance "SHA-256") (.getBytes data))] + 026  (defn base64-encode [bytes]
- 028      (base64-encode digest))) + 027    (.encodeToString (Base64/getEncoder) bytes))
- 029   + 028  
- 030  (defn sign [data private-key]  + 029  (defn sha256-base64 [data]
- - 031    (let [bytes (.getBytes data) -
- - 032          signer (doto (Signature/getInstance "SHA256withRSA") + + 030    (let [digest (.digest (MessageDigest/getInstance "SHA-256") (.getBytes data))]
- 033                (.initSign private-key (SecureRandom.)) -
- - 034                (.update bytes))] -
- - 035      (.sign signer))) + 031      (base64-encode digest)))
- 036   + 032   +
+ + 033  (defn sign [data private-key] +
+ + 034    (let [bytes (.getBytes data) +
+ + 035          signer (doto (Signature/getInstance "SHA256withRSA") +
+ + 036                   (.initSign private-key (SecureRandom.)) +
+ + 037                   (.update bytes))] +
+ + 038      (.sign signer))) +
+ + 039  
diff --git a/docs/cloverage/clj_activitypub/internal/http_util.clj.html b/docs/cloverage/clj_activitypub/internal/http_util.clj.html index 16768c7..02f7169 100644 --- a/docs/cloverage/clj_activitypub/internal/http_util.clj.html +++ b/docs/cloverage/clj_activitypub/internal/http_util.clj.html @@ -8,76 +8,85 @@ 001  (ns clj-activitypub.internal.http-util

- 002    (:require [clj-activitypub.internal.crypto :as crypto]) + 002    "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). 
- 003    (:import (java.net URLEncoder) + 003     If and when Jahfer issues a release of that library, this directory will be deleted and a 
- 004             (java.time OffsetDateTime ZoneOffset) + 004     dependency on that library will be added to the project."
- 005             (java.time.format DateTimeFormatter))) + 005    (:require [clj-activitypub.internal.crypto :as crypto]) +
+ + 006    (:import (java.net URLEncoder) +
+ + 007             (java.time OffsetDateTime ZoneOffset) +
+ + 008             (java.time.format DateTimeFormatter)))
- 006   + 009  
- 007  (defn encode-url-params [params] + 010  (defn encode-url-params [params]
- 008    (->> params + 011    (->> params
- 009         (reduce-kv + 012         (reduce-kv
- 010          (fn [coll k v] + 013          (fn [coll k v]
- 011            (conj coll + 014            (conj coll
- 012                  (str (URLEncoder/encode (name k)) "=" (URLEncoder/encode (str v))))) + 015                  (str (URLEncoder/encode (name k)) "=" (URLEncoder/encode (str v)))))
- 013          []) + 016          [])
- 014         (interpose "&") + 017         (interpose "&")
- 015         (apply str))) + 018         (apply str)))
- 016   + 019  
- 017  (defn date [] + 020  (defn date []
- 018    (-> (OffsetDateTime/now (ZoneOffset/UTC)) + 021    (-> (OffsetDateTime/now (ZoneOffset/UTC))
- 019        (.format DateTimeFormatter/RFC_1123_DATE_TIME))) + 022        (.format DateTimeFormatter/RFC_1123_DATE_TIME)))
- 020   + 023  
- 021  (defn digest + 024  (defn digest
- 022    "Accepts body from HTTP request and generates string + 025    "Accepts body from HTTP request and generates string
- 023     for use in HTTP `Digest` request header." + 026     for use in HTTP `Digest` request header."
- 024    [body] + 027    [body]
- 025    (str "sha-256=" (crypto/sha256-base64 body))) + 028    (str "sha-256=" (crypto/sha256-base64 body)))
diff --git a/docs/cloverage/clj_activitypub/internal/thread_cache.clj.html b/docs/cloverage/clj_activitypub/internal/thread_cache.clj.html index 5082b7b..5a8809f 100644 --- a/docs/cloverage/clj_activitypub/internal/thread_cache.clj.html +++ b/docs/cloverage/clj_activitypub/internal/thread_cache.clj.html @@ -5,136 +5,145 @@ - 001  (ns clj-activitypub.internal.thread-cache) -
- - 002   -
- - 003  (defn- current-time  + 001  (ns clj-activitypub.internal.thread-cache
- 004    "Returns current time using UNIX epoch." + 002    "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). 
- 005    [] + 003     If and when Jahfer issues a release of that library, this directory will be deleted and a 
- - 006    (System/currentTimeMillis)) + + 004     dependency on that library will be added to the project.")
- 007   + 005  
- 008  (defn- update-read-at [store k v] + 006  (defn- current-time +
+ + 007    "Returns current time using UNIX epoch." +
+ + 008    [] +
+ + 009    (System/currentTimeMillis)) +
+ + 010   +
+ + 011  (defn- update-read-at [store k v]
- 009    (dosync + 012    (dosync
- 010     (commute store assoc k + 013     (commute store assoc k
- 011              (merge v {:read-at (current-time)})))) + 014              (merge v {:read-at (current-time)}))))
- 012   + 015  
- 013  (defn make  + 016  (defn make
- 014    "Creates a thread-local cache." + 017    "Creates a thread-local cache."
- 015    ([] (make false)) + 018    ([] (make false))
- 016    ([cache-if-nil] + 019    ([cache-if-nil]
- 017     (let [store (ref {})] + 020     (let [store (ref {})]
- 018       (letfn [(cache-kv ([k v] + 021       (letfn [(cache-kv ([k v]
- 019                          (dosync + 022                          (dosync
- 020                           (commute store assoc k + 023                           (commute store assoc k
- 021                                    {:write-at (current-time)  + 024                                    {:write-at (current-time)
- 022                                     :read-at (current-time)  + 025                                     :read-at (current-time)
- 023                                     :value v}) + 026                                     :value v})
- 024                           v))) + 027                           v)))
- 025               (get-v ([k] + 028               (get-v ([k]
- 026                       (when-let [data (get @store k)] + 029                       (when-let [data (get @store k)]
- 027                         (update-read-at store k data) + 030                         (update-read-at store k data)
- 028                         (:value data))) + 031                         (:value data)))
- 029                      ([k compute-fn] + 032                 ([k compute-fn]
- 030                       (let [storage @store] + 033                  (let [storage @store]
- 031                         (if (contains? storage k) + 034                    (if (contains? storage k)
- 032                           (get-v k) + 035                      (get-v k)
- 033                           (let [v (compute-fn)] + 036                      (let [v (compute-fn)]
- 034                             (when (or (not (nil? v)) cache-if-nil) + 037                        (when (or (not (nil? v)) cache-if-nil)
- 035                               (cache-kv k v) + 038                          (cache-kv k v)
- 036                               (get-v k))))))) + 039                          (get-v k)))))))
- 037               (lru ([] + 040               (lru ([]
- 038                     (mapv + 041                     (mapv
- 039                      (fn [[k v]] [k (:value v)]) + 042                      (fn [[k v]] [k (:value v)])
- 040                      (sort-by #(-> % val :read-at) < @store))))] + 043                      (sort-by #(-> % val :read-at) < @store))))]
- 041         {:cache-kv cache-kv  + 044         {:cache-kv cache-kv
- 042          :get-v get-v + 045          :get-v get-v
- 043          :cache-if-nil cache-if-nil + 046          :cache-if-nil cache-if-nil
- 044          :lru lru})))) + 047          :lru lru}))))
diff --git a/docs/cloverage/clj_activitypub/webfinger.clj.html b/docs/cloverage/clj_activitypub/webfinger.clj.html index a834d7a..2303e26 100644 --- a/docs/cloverage/clj_activitypub/webfinger.clj.html +++ b/docs/cloverage/clj_activitypub/webfinger.clj.html @@ -8,97 +8,106 @@ 001  (ns clj-activitypub.webfinger

- 002    (:require [clj-http.client :as client] + 002    "copied from [Jahfer's clj-activitypub library](https://github.com/jahfer/clj-activitypub). 
- 003              [clj-activitypub.internal.http-util :as http] + 003     If and when Jahfer issues a release of that library, this directory will be deleted and a 
- 004              [clj-activitypub.internal.thread-cache :as thread-cache])) + 004     dependency on that library will be added to the project." +
+ + 005    (:require [clj-http.client :as client] +
+ + 006              [clj-activitypub.internal.http-util :as http] +
+ + 007              [clj-activitypub.internal.thread-cache :as thread-cache]))
- 005   + 008  
- 006  (def remote-uri-path "/.well-known/webfinger") -
- - 007   -
- - 008  (defn- resource-str [domain username] -
- - 009    (str "acct:" username "@" domain)) + 009  (def remote-uri-path "/.well-known/webfinger")
010  
- 011  (defn resource-url + 011  (defn- resource-str [domain username]
- - 012    "Builds a URL pointing to the user's account on the remote server." -
- - 013    [domain username & [params]] -
- - 014    (let [resource (resource-str domain username) -
- - 015          query-str (http/encode-url-params (merge params {:resource resource}))] -
- - 016      (str "https://" domain remote-uri-path "?" query-str))) + + 012    (str "acct:" username "@" domain))
- 017   + 013  
- 018  (def ^:private user-id-cache + 014  (defn resource-url
- - 019    (thread-cache/make)) + + 015    "Builds a URL pointing to the user's account on the remote server." +
+ + 016    [domain username & [params]] +
+ + 017    (let [resource (resource-str domain username) +
+ + 018          query-str (http/encode-url-params (merge params {:resource resource}))] +
+ + 019      (str "https://" domain remote-uri-path "?" query-str)))
020  
- 021  (defn fetch-user-id + 021  (def ^:private user-id-cache
- - 022    "Follows the webfinger request to a remote domain, retrieving the ID of the requested + + 022    (thread-cache/make))
- - 023     account. Typically returns a string in the form of a URL." -
- - 024    [domain username] -
- - 025    ((:get-v user-id-cache) -
- - 026     (str domain "@" username) ;; cache key + + 023  
- 027     (fn [] -
- - 028       (let [response (some-> (resource-url domain username {:rel "self"}) -
- - 029                              (client/get {:as :json :throw-exceptions false :ignore-unknown-host? true}))] -
- - 030         (some->> response :body :links -
- - 031                  (some #(when (= (:type %) "application/activity+json") %)) + 024  (defn fetch-user-id
- 032                  :href))))) + 025    "Follows the webfinger request to a remote domain, retrieving the ID of the requested +
+ + 026     account. Typically returns a string in the form of a URL." +
+ + 027    [domain username] +
+ + 028    ((:get-v user-id-cache) +
+ + 029     (str domain "@" username) ;; cache key +
+ + 030     (fn [] +
+ + 031       (let [response (some-> (resource-url domain username {:rel "self"}) +
+ + 032                              (client/get {:as :json :throw-exceptions false :ignore-unknown-host? true}))] +
+ + 033         (some->> response :body :links +
+ + 034                  (some #(when (= (:type %) "application/activity+json") %)) +
+ + 035                  :href)))))
diff --git a/docs/cloverage/codecov.json b/docs/cloverage/codecov.json index 766b60c..f90d8b1 100644 --- a/docs/cloverage/codecov.json +++ b/docs/cloverage/codecov.json @@ -1,54 +1,73 @@ {"coverage": {"dog_and_duck/quack/quack.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null, null, null, null, null, null, - 17, null, 1, null, null, null, null, null, true, 5, null, null, 1, - null, 1, null, null, null, null, 1, null, null, null, null, null, 1, - null, null, 4, null, 1, null, null, null, null, 1, null, null, null, - null, 1, null, null, 4, null, 1, null, null, null, null, 1, null, - null, null, null, null, null, true, 10, 9, true, 2, null, null, - null, true, 2, null, 1, null, null, true, 2, 2, 0, 0, 0, null, null, - 1, null, null, 0, 0, 0, 0, 0, 0, 0, null, null, null, 1, null, null, - 0, 0, 0, null, null, 1, null, null, null, null, null, 0, 0, 0, null, - null, 1, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - null, null, 0, 0, null, 1, null, null, 0, null, 1, null, null, 0, - null, 1, null, null, 0, null, 1, null, null, 0, null, null], + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, 1, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + 40, null, 0, null, 1, null, null, null, null, null, true, 4, null, + null, null, null, 1, null, null, null, null, 1, null, null, null, + null, null, true, null, null, 5, null, true, null, 1, null, null, + null, null, 1, null, null, null, null, true, null, null, 5, null, + null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, true, 2, 2, 0, 0, 0, null, null, 1, null, + null, null, null, null, null, null, 0, 0, 0, null, null, 1, null, + null, 0, 0, 0, 0, 0, 0, 0, null, null, null, 1, null, null, 0, 0, 0, + null, null, 1, null, null, null, null, null, 0, 0, 0, null, null, 1, + null, null, null, null, null, true, true, true, true, 2, 1, null, 1, + 1, true, true, 1, null, null, null, null, null, null, null, null, 0, + 0, 0, 0, null, null, 1, null, null, 0, null, 1, null, null, 0, null, + 1, null, null, 0, null, 1, null, null, 2, null, null], + "dog_and_duck/utils/process.clj": + [null, 1, null, null, 1, null, null, null, null, 1, 1, 1, null, true, + null, null, null, null, null, null, null, null, null, 1], "clj_activitypub/internal/http_util.clj": - [null, 1, null, null, null, null, null, 1, 1, 1, 1, 2, 2, 1, 1, 1, - null, 1, 0, 0, null, 1, null, null, null, 0], + [null, 1, null, null, null, null, null, null, null, null, 1, 1, 1, 1, + 2, 2, 1, 1, 1, null, 1, 0, 0, null, 1, null, null, null, 0], "clj_activitypub/internal/thread_cache.clj": - [null, 1, null, 1, null, null, 8, null, 1, 4, 4, 4, null, 1, null, 2, - null, 2, 2, 2, 2, 2, 2, 2, 2, null, 4, 4, 4, null, 4, 4, 2, 2, true, - 2, 2, null, 0, 0, 0, 2, 2, 2, 2], + [null, 1, null, null, null, null, 1, null, null, 8, null, 1, 4, 4, 4, + null, 1, null, 2, null, 2, 2, 2, 2, 2, 2, 2, 2, null, 4, 4, 4, null, + 4, 4, 2, 2, true, 2, 2, null, 0, 0, 0, 2, 2, 2, 2], "dog_and_duck/scratch/parser.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, 1, null, null, null, 7, 7, 7, 7, 7, true, 0, null, 1, null, 1, - 0, 0, 0, 0, 0, 0, 1], + null, 1, null, null, null, 10, 10, 10, 10, 10, true, 0, null, 1, + null, 1, 0, 0, 0, 0, 0, 0, 1, null, 1], "clj_activitypub/core.clj": - [null, 1, null, null, null, null, null, null, 1, null, null, null, - null, null, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, null, null, 6, 2, - 2, null, 1, 1, null, null, null, 1, 1, 1, 1, null, null, 1, null, 1, - null, null, null, 0, null, 0, null, 0, 0, 0, 0, 0, 0, null, 1, null, - 1, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, null, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, null, 1, null, null, null, 0, 0, 0, 0, 0, 0, 0, null, 1, - null, null, null, true, null, 1, null, null, null, null, null, 0, - null, 0, 0, 0, 0, 0, null, 1, null, null, null, true, null, 1, 0, - null, null, 0, 0, null, 1, 0, null, null, 0, 0, null, 1, null, null, - null, 0, 0, 0, 0, 0, 0], + [null, 1, null, null, null, null, null, null, null, null, null, 1, + null, null, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, + null, null, 6, 2, 2, null, 1, 1, null, null, null, 1, 1, 1, 1, null, + null, 1, null, 1, null, null, null, 0, null, 0, null, 0, 0, 0, 0, 0, + 0, null, 1, null, 1, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, null, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, null, null, 0, 0, 0, 0, + 0, 0, 0, null, 1, null, null, null, true, null, 1, null, null, null, + null, null, 0, null, 0, 0, 0, 0, 0, null, 1, null, null, null, true, + null, 1, 0, null, null, 0, 0, null, 1, 0, null, null, 0, 0, null, 1, + null, null, null, 0, 0, 0, 0, 0, 0], "clj_activitypub/internal/crypto.clj": - [null, 1, null, null, null, null, 1, 1, null, 1, 0, 0, null, null, 1, - 0, 0, null, 1, 0, 0, null, null, 1, 0, null, 1, 0, 0, null, 1, 0, 0, - 0, 0, 0, null], + [null, 1, null, null, null, null, null, null, null, 1, 1, null, 1, 0, + 0, null, null, 1, 0, 0, null, 1, 0, 0, null, null, 1, 0, null, 1, 0, + 0, null, 1, 0, 0, 0, 0, 0, null], + "dog_and_duck/quack/picky.clj": + [null, 1, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, 1, null, null, null, null, + null, null, null, null, null, null, 1, null, 1, null, 1, 1, 1, 1, 1, + null, 1, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null, + null, null, null, 1, null, null, null, null, 1, null, null, null, + null, null, null, true, 10, 9, true, 2, null, null, null, true, + null, null, 3, null, null, null, 1, null, null, null, null, null, + null, 0, 0, 0, 0, null, 0, null, 0, null, 0, 0, 0, null, 1, null, 0, + 0, 0, 0, 0, 0, null, null, null, null, null, 0, 0, null, null, null, + null, 0, 0, null, null, null, null, null, null, null], "dog_and_duck/scratch/core.clj":[null, 1, null, 1, null, null, 0], "clj_activitypub/webfinger.clj": - [null, 1, null, null, null, null, 1, null, 1, 1, null, 1, null, null, - 1, 1, 1, null, 1, 1, null, 1, null, null, null, 3, 3, 3, true, 1, - true, 2, null], + [null, 1, null, null, null, null, null, null, null, 1, null, 1, 1, + null, 1, null, null, 1, 1, 1, null, 1, 1, null, 1, null, null, null, + 3, 3, 3, true, 1, true, 2, null], "dog_and_duck/scratch/scratch.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1, null, 1, 1, 1, null, null, null, null, 1, null, 1, 1, 1, 1, null, null, 1, null, null, 1, 1, null, - null, null, null, 1, 1, 1, 1, 1, 1, null]}} + null, null, null, 1, 1, 1, 1, 1, 1, null, null]}} diff --git a/docs/cloverage/coverage.xml b/docs/cloverage/coverage.xml index f5390a2..2594ab7 100644 --- a/docs/cloverage/coverage.xml +++ b/docs/cloverage/coverage.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/cloverage/dog_and_duck/quack/picky.clj.html b/docs/cloverage/dog_and_duck/quack/picky.clj.html new file mode 100644 index 0000000..82aa1dd --- /dev/null +++ b/docs/cloverage/dog_and_duck/quack/picky.clj.html @@ -0,0 +1,437 @@ + + + + dog_and_duck/quack/picky.clj + + + + 001  (ns dog-and-duck.quack.picky "Fault-finder for ActivityPub documents.  +
+ + 002                                 +
+ + 003                                Generally, each `-faults` function will return: +
+ + 004                                1. `nil` if no faults were found; +
+ + 005                                2. a sequence of fault objects if faults were found. +
+ + 006                                 +
+ + 007                                Each fault object shall have the properties: +
+ + 008                                1. `:@context` whose value shall be the URL of a  +
+ + 009                                   document specifying this vocabulary; +
+ + 010                                2. `:type` whose value shall be `Fault`; +
+ + 011                                3. `:severity` whose value shall be one of  +
+ + 012                                   `minor`, `should`, `must` or `critical`; +
+ + 013                                4. `:fault` whose value shall be a unique token +
+ + 014                                   representing the particular fault type; +
+ + 015                                5. `:narrative` whose value shall be a natural +
+ + 016                                   language description of the fault type. +
+ + 017                                 +
+ + 018                                Note that the reason for the `:fault` property is +
+ + 019                                to be able to have a well known place, linked to +
+ + 020                                from the @context URL, which allows narratives  +
+ + 021                                for each fault type to be served in as many +
+ + 022                                natural languages as possible. +
+ + 023                                 +
+ + 024                                The idea further is that it should ultimately be +
+ + 025                                possible to serialise a fault report as a  +
+ + 026                                document which in its own right conforms to the +
+ + 027                                ActivityStreams spec." +
+ + 028   (:require [dog-and-duck.utils.process :refer [pid]])) +
+ + 029   +
+ + 030  (def ^:const severity +
+ + 031    "Severity of faults found, as follows: +
+ + 032      +
+ + 033     1. `:minor` things which I consider to be faults, but which  +
+ + 034        don't actually breach the spec; +
+ + 035     2. `:should` instances where the spec says something SHOULD +
+ + 036        be done, which isn't; +
+ + 037     3. `:must` instances where the spec says something MUST +
+ + 038        be done, which isn't; +
+ + 039     4. `:critical` instances where I believe the fault means that +
+ + 040        the object cannot be meaningfully processed." +
+ + 041    #{:minor :should :must :critical}) +
+ + 042   +
+ + 043  (def ^:const severity-filters +
+ + 044    "Hack for implementing a severity hierarchy" +
+ + 045    {:all #{} +
+ + 046     :minor #{:minor} +
+ + 047     :should #{:minor :should} +
+ + 048     :must #{:minor :should :must} +
+ + 049     :critical severity}) +
+ + 050   +
+ + 051  (defn filter-severity +
+ + 052    "Return a list of reports taken from these `reports` where the severity +
+ + 053     of the report is greater than this `severity`." +
+ + 054    [reports severity] +
+ + 055    (assert  +
+ + 056     (and  +
+ + 057      (coll? reports)  +
+ + 058      (every? map? reports)  +
+ + 059      (every? :severity reports))) +
+ + 060    (remove  +
+ + 061     #((severity-filters severity) (:severity %)) +
+ + 062     reports)) +
+ + 063   +
+ + 064  (def ^:const activitystreams-context-uri +
+ + 065    "The URI of the context of an ActivityStreams object is expected to be this +
+ + 066     literal string." +
+ + 067    "https://www.w3.org/ns/activitystreams") +
+ + 068   +
+ + 069  (def ^:const validation-fault-context-uri +
+ + 070    "The URI of the context of a validation fault report object shall be this +
+ + 071     literal string." +
+ + 072    "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html") +
+ + 073   +
+ + 074  (defn context? +
+ + 075    "Returns `true` iff `x` quacks like an ActivityStreams context, else false. +
+ + 076      +
+ + 077     A context is either +
+ + 078     1. the URI (actually an IRI) `activitystreams-context-uri`, or +
+ + 079     2. a collection comprising that URI and a map." +
+ + 080    [x] +
+ + 081    (cond +
+ + 082      (nil? x) false +
+ + 083      (string? x) (and (= x activitystreams-context-uri) true) +
+ + 084      (coll? x) (and (context? (first (remove map? x))) +
+ + 085                     (= (count x) 2) +
+ + 086                     true) +
+ + 087      :else false)) +
+ + 088   +
+ + 089  (defmacro has-context? +
+ + 090    "True if `x` is an ActivityStreams object with a valid context, else `false`." +
+ + 091    [x] +
+ + 092    `(context? ((keyword "@context") ~x))) +
+ + 093   +
+ + 094   +
+ + 095   +
+ + 096  (defn make-fault-object +
+ + 097    "Return a fault object with these `severity`, `fault` and `narrative` values. +
+ + 098      +
+ + 099     An ActivityPub object MUST have a globally unique ID. Whether this is  +
+ + 100     meaningful depends on whether we persist fault report objects and serve +
+ + 101     them, which at present I have no plans to do." +
+ + 102    [severity fault narrative] +
+ + 103    (assoc {} +
+ + 104           (keyword "@context") validation-fault-context-uri +
+ + 105           :id (str "https://" +
+ + 106                    (.. java.net.InetAddress getLocalHost getHostName) +
+ + 107                    "/fault/" +
+ + 108                    pid +
+ + 109                    ":" +
+ + 110                    (inst-ms (java.util.Date.))) +
+ + 111           :type "Fault" +
+ + 112           :severity severity +
+ + 113           :fault fault +
+ + 114           :narrative narrative)) +
+ + 115   +
+ + 116  (defn object-faults +
+ + 117    [x] +
+ + 118    (remove  +
+ + 119     empty? +
+ + 120     (list +
+ + 121      (when-not +
+ + 122       (has-context? x) +
+ + 123        (make-fault-object  +
+ + 124         :should  +
+ + 125         :no-context  +
+ + 126         "Section 3 of the ActivityPub specification states  +
+ + 127          `Implementers SHOULD include the ActivityPub context in  +
+ + 128          their object definitions`.") +
+ + 129      (when-not (:type x)  +
+ + 130        (make-fault-object  +
+ + 131         :minor  +
+ + 132         :no-type +
+ + 133         "The ActivityPub specification states that the `type` field is +
+ + 134          optional, but it is hard to process objects with no known type.")) +
+ + 135        (when-not (contains? x :id) +
+ + 136          (make-fault-object +
+ + 137           :minor +
+ + 138           :no-id-transient +
+ + 139           "The ActivityPub specification allows objects without `id` fields +
+ + 140            only if they are intentionally transient; even so it is preferred +
+ + 141            that the object should have an explicit null id." +
+ + 142           )) +
+ + 143      )))) +
+ + diff --git a/docs/cloverage/dog_and_duck/quack/quack.clj.html b/docs/cloverage/dog_and_duck/quack/quack.clj.html index 30f6484..e8bef6e 100644 --- a/docs/cloverage/dog_and_duck/quack/quack.clj.html +++ b/docs/cloverage/dog_and_duck/quack/quack.clj.html @@ -8,562 +8,682 @@ 001  (ns dog-and-duck.quack.quack

- 002    "Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck..." + 002    "Validator for ActivityPub objects: if it walks like a duck, and it quacks 
- 003    ;;(:require [clojure.spec.alpha as s]) + 003     like a duck...
- 004    (:import [java.net URI URISyntaxException])) + 004      +
+ + 005     **NOTE THAT the ActivityPub spec  +
+ + 006     [says](https://www.w3.org/TR/activitypub/#obj) +
+ + 007      +
+ + 008     > Servers SHOULD validate the content they receive to avoid content  +
+ + 009     > spoofing attacks +
+ + 010      +
+ + 011     but in practice ActivityPub content collected in the wild bears only  +
+ + 012     a hazy relationship to the spec, so this is difficult. I suspect that +
+ + 013     I may have to implement a `*strict*` dynamic variable, so that users can  +
+ + 014     toggle some checks off." +
+ + 015     +
+ + 016    ;;(:require [clojure.spec.alpha as s]) +
+ + 017    (:require [dog-and-duck.quack.picky :refer [filter-severity has-context?  +
+ + 018                                                object-faults]]) +
+ + 019    (:import [java.net URI URISyntaxException]))
- 005   + 020  
- 006  ;;;     Copyright (C) Simon Brooke, 2022 + 021  ;;;     Copyright (C) Simon Brooke, 2022
- 007   + 022  
- 008  ;;;     This program is free software; you can redistribute it and/or + 023  ;;;     This program is free software; you can redistribute it and/or
- 009  ;;;     modify it under the terms of the GNU General Public License + 024  ;;;     modify it under the terms of the GNU General Public License
- 010  ;;;     as published by the Free Software Foundation; either version 2 + 025  ;;;     as published by the Free Software Foundation; either version 2
- 011  ;;;     of the License, or (at your option) any later version. + 026  ;;;     of the License, or (at your option) any later version.
- 012   + 027  
- 013  ;;;     This program is distributed in the hope that it will be useful, + 028  ;;;     This program is distributed in the hope that it will be useful,
- 014  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of + 029  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
- 015  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + 030  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- 016  ;;;     GNU General Public License for more details. + 031  ;;;     GNU General Public License for more details.
- 017   + 032  
- 018  ;;;     You should have received a copy of the GNU General Public License + 033  ;;;     You should have received a copy of the GNU General Public License
- 019  ;;;     along with this program; if not, write to the Free Software + 034  ;;;     along with this program; if not, write to the Free Software
- 020  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. -
- - 021   -
- - 022  (defn object? -
- - 023    "Returns `true` iff `x` is recognisably an ActivityStreams object. -
- - 024      -
- - 025     **NOTE THAT** The ActivityStreams spec  -
- - 026     [says](https://www.w3.org/TR/activitystreams-core/#object): -
- - 027      -
- - 028     > All properties are optional (including the id and type) -
- - 029      -
- - 030     But we are *just not having that*, because otherwise we're flying blind. -
- - 031     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are -
- - 032     tolerable because they represent transient objects, which we expect to  -
- - 033     handle." -
- - 034    [x] -
- - 035    (and (map? x) (:type x) true)) + 035  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
036  
- 037  (defn persistent-object? + 037  (defn object?
- 038    "`true` iff `x` is a persistent object. -
- - 039   + 038    "Returns `true` iff `x` is recognisably an ActivityStreams object.
- 040     Transient objects in ActivityPub are not required to have an `id` key, but persistent + 039     
- 041     ones must have a key, and it must be an IRI (but normally a URI)." + 040     **NOTE THAT** The ActivityStreams spec 
- 042    [x] -
- - 043    (try -
- - 044      (and (object? x) (uri? (URI. (:id x)))) + 041     [says](https://www.w3.org/TR/activitystreams-core/#object):
- 045      (catch URISyntaxException _ false))) -
- - 046   -
- - 047  (persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"}) -
- - 048   -
- - 049  (def ^:const actor-types + 042     
- 050    "The set of types we will accept as actors. + 043     > All properties are optional (including the id and type) +
+ + 044      +
+ + 045     But we are *just not having that*, because otherwise we're flying blind. +
+ + 046     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are +
+ + 047     tolerable because they represent transient objects, which we expect to  +
+ + 048     handle. +
+ + 049      +
+ + 050     **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj)
051     
- 052     There's an [explicit set of allowed actor types] + 052     > Implementers SHOULD include the ActivityPub context in their object 
- 053     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)." -
- - 054    #{"Application" + 053     > definitions
- 055      "Group" + 054     
- 056      "Organization" + 055     but in samples found in the wild they typically don't."
- 057      "Person" + 056    ([x] +
+ + 057    (and (map? x) (:type x) true))
- 058      "Service"}) + 058    ([x severity] +
+ + 059     (empty? (filter-severity (object-faults x) severity))))
- 059   + 060  
- 060  (defn actor-type? + 061  (defn persistent-object?
- 061    ;; TODO: better as a macro -
- - 062    [x] -
- - 063    (if (actor-types x) true false)) + 062    "`true` iff `x` is a persistent object.
- 064   + 063   +
+ + 064     Transient objects in ActivityPub are not required to have an `id` key, but persistent +
+ + 065     ones must have a key, and it must be an IRI (but normally a URI)." +
+ + 066    [x] +
+ + 067    (try +
+ + 068      (and (object? x) (uri? (URI. (:id x)))) +
+ + 069      (catch URISyntaxException _ false))) +
+ + 070   +
+ + 071  ;; (persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"}) +
+ + 072  
- 065  (def ^:const verb-types + 073  (def ^:const actor-types
- 066    "The set of types we will accept as verbs. + 074    "The set of types we will accept as actors.
- 067      + 075     
- 068     There's an [explicit set of allowed verb types] + 076     There's an [explicit set of allowed actor types]
- 069     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)." + 077     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)." +
+ + 078    #{"Application" +
+ + 079      "Group" +
+ + 080      "Organization" +
+ + 081      "Person" +
+ + 082      "Service"}) +
+ + 083   +
+ + 084  (defmacro actor-type? +
+ + 085    "Return `true` iff the `x` is a recognised actor type, else `false`." +
+ + 086    [^String x] +
+ + 087    `(if (actor-types ~x) true false)) +
+ + 088   +
+ + 089  ;; (actor-type? "Group") +
+ + 090   +
+ + 091  (def ^:const verb-types +
+ + 092    "The set of types we will accept as verbs. +
+ + 093      +
+ + 094     There's an [explicit set of allowed verb types] +
+ + 095     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
- 070    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike" + 096    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
- 071      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move" + 097      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
- 072      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept" + 098      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
- 073      "TentativeReject" "Travel" "Undo" "Update" "View"}) + 099      "TentativeReject" "Travel" "Undo" "Update" "View"})
- 074   + 100   +
+ + 101  (defmacro verb-type? +
+ + 102    ;; TODO: better as a macro +
+ + 103    [^String x]
- 075  (defn verb-type? -
- - 076    ;; TODO: better as a macro -
- - 077    [x] -
- - 078    (if (verb-types x) true false)) + 104    `(if (verb-types ~x) true false))
- 079   -
- - 080  (def ^:const activitystreams-context-uri -
- - 081    "The URI of the context of an ActivityStreams object is expected to be this -
- - 082     literal string." -
- - 083    "https://www.w3.org/ns/activitystreams") + 105  
- 084   + 106  
- 085  (defn context? + 107  (defn actor?
- 086    "Returns `true` iff `x` quacks like an ActivityStreams context, else false. + 108    "Returns `true` if `x` quacks like an actor, else false.
- 087      + 109     
- 088     A context is either + 110     **NOTE THAT** [Section 4.1 of the spec]
- 089     1. the URI (actually an IRI) `activitystreams-context-uri`, or + 111     (https://www.w3.org/TR/activitypub/#actor-objects) says explicitly that
- 090     2. a collection comprising that URI and a map." + 112     
- 091    [x] -
- - 092    (cond -
- - 093      (nil? x) false -
- - 094      (string? x) (and (= x activitystreams-context-uri) true) -
- - 095      (coll? x) (and (context? (first (remove map? x)))  -
- - 096                     (= (count x) 2) + 113     >  Actor objects MUST have, in addition to the properties mandated by 3.1 Object Identifiers, the following properties:
- 097                     true) + 114     >
- 098      :else false)) -
- - 099   -
- - 100  (defmacro has-context? [x] -
- - 101    `(context? ((keyword "@context") ~x))) -
- - 102   -
- - 103  (defn actor? + 115     >  inbox
- 104    "Returns `true` if `x` quacks like an actor, else false." + 116     >    A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor; see 5.2 Inbox. 
- 105    [x] + 117     > outbox +
+ + 118     >    An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox.  +
+ + 119      +
+ + 120     However, none of the provided examples in the [activitystreams-test-documents repository]() does in fact have these properties" +
+ + 121    [x]
- 106    (and + 122    (and
- 107     (object? x) + 123     (object? x)
- 108     (has-context? x) + 124     (has-context? x)
- 109     (uri? (URI. (:inbox x))) + 125     (uri? (URI. (:inbox x)))
- 110     (uri? (URI. (:outbox x))) + 126     (uri? (URI. (:outbox x)))
- - 111     (actor-type? (:type x)) + + 127     (actor-type? (:type x))
- 112     true)) + 128     true))
- 113   + 129  
- 114  (defn activity? + 130  (defn actor-or-uri?
- 115    "`true` iff `x` quacks like an activity, else false." + 131    "`true` if `x` is either a URI or an actor.
- 116    [x] + 132     
- - 117    (try + + 133     **TODO**: I need to decide about whether to reify referenced objects
- - 118      (and (object? x) + + 134     before validation or after. After reification, every reference to an actor +
+ + 135     *must be* to an actor object, but before, may only be to a URI pointing to  +
+ + 136     one." +
+ + 137    [x]
- 119           (has-context? x) -
- - 120           (string? (:summary x)) -
- - 121           (actor? (:actor x)) -
- - 122           (verb-type? (:type x)) -
- - 123           (or (object? (:object x)) (uri? (URI. (:object x)))) -
- - 124           true) -
- - 125      (catch URISyntaxException _ false))) -
- - 126   -
- - 127  (defn link? -
- - 128    "`true` iff `x` quacks like a link, else false." -
- - 129    [x] -
- - 130    (and (object? x) -
- - 131         (= (:type x) "Link") -
- - 132         (uri? (URI. (:href x))) -
- - 133         true)) -
- - 134   -
- - 135  (defn link-or-uri? -
- - 136    "`true` iff `x` is either a URI or a link, else false. -
- - 137      -
- - 138     There are several points in the specification where e.g. the `:image` -
- - 139     property (if present) may be either a link or a URI." -
- - 140    [x] -
- - 141    (and + 138    (and 
- 142     (cond (string? x) (uri? (URI. x)) + 139     (cond (string? x) (uri? (URI. x))
- 143           :else (link? x)) + 140          :else (actor? x)) 
- 144     true)) + 141         true))
- 145   + 142  
- 146  (defn collection? + 143  (defn activity?
- 147    "`true` iff `x` quacks like a collection of type `type`, else `false`. + 144    "`true` iff `x` quacks like an activity, else false."
- 148      -
- - 149     With one argument, will recognise plain collections and ordered collections, -
- - 150     but (currently) not collection pages." -
- - 151    ([x type] -
- - 152     (let [items (or (:items x) (:orderedItems x))] -
- - 153       (and -
- - 154        (cond -
- - 155          (:items x) (nil? (:orderedItems x)) -
- - 156          (:orderedItems x) (nil? (:items x))) ;; can't have both properties -
- - 157        (object? x) -
- - 158        (= (:type x) type) -
- - 159        (coll? items) -
- - 160        (every? object? items) -
- - 161        (integer? (:totalItems x)) -
- - 162        true))) -
- - 163    ([x] -
- - 164     (or (collection? x "Collection") + 145    [x]
- 165         (collection? x "OrderedCollection")))) + 146    (try
- - 166   + + 147      (and (object? x)
- - 167  (defn unordered-collection? + + 148           (has-context? x) +
+ + 149           (string? (:summary x)) +
+ + 150           (actor-or-uri? (:actor x)) +
+ + 151           (verb-type? (:type x)) +
+ + 152           (or (object? (:object x)) (uri? (URI. (:object x))))
- 168    "`true` iff `x` quacks like an unordered collection, else `false`." + 153           true) +
+ + 154      (catch URISyntaxException _ false))) +
+ + 155   +
+ + 156  (defn link? +
+ + 157    "`true` iff `x` quacks like a link, else false." +
+ + 158    [x] +
+ + 159    (and (object? x) +
+ + 160         (= (:type x) "Link") +
+ + 161         (uri? (URI. (:href x))) +
+ + 162         true)) +
+ + 163   +
+ + 164  (defn link-or-uri? +
+ + 165    "`true` iff `x` is either a URI or a link, else false. +
+ + 166      +
+ + 167     There are several points in the specification where e.g. the `:image` +
+ + 168     property (if present) may be either a link or a URI."
169    [x]
- - 170    (collection? x "Collection")) + + 170    (and +
+ + 171     (cond (string? x) (uri? (URI. x)) +
+ + 172           :else (link? x)) +
+ + 173     true))
- 171   + 174  
- 172  (defn ordered-collection? + 175  (defn collection?
- 173    "`true` iff `x` quacks like an ordered collection, else `false`." + 176    "`true` iff `x` quacks like a collection of type `object-type`, else `false`.
- 174    [x] + 177     
- - 175    (collection? x "OrderedCollection")) + + 178     With one argument, will recognise plain collections and ordered collections, +
+ + 179     but (currently) not collection pages." +
+ + 180    ([x ^String object-type] +
+ + 181     (let [items (or (:items x) (:orderedItems x))] +
+ + 182       (and +
+ + 183        (cond +
+ + 184          (:items x) (nil? (:orderedItems x)) +
+ + 185          (:orderedItems x) (nil? (:items x)) ;; can't have both properties +
+ + 186          (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems. +
+ + 187          :else false)  +
+ + 188        (object? x) +
+ + 189        (= (:type x) object-type) +
+ + 190        (if items +
+ + 191          (and (coll? items) +
+ + 192               (every? object? items) ;; if there are items, they must form a +
+ + 193                                      ;; collection of objects. +
+ + 194               true) +
+ + 195          true) ;; but it's OK if there aren't. +
+ + 196        true) +
+ + 197       ;; test for totalItems not done here, because collection pages don't +
+ + 198       ;; have it. +
+ + 199       )) +
+ + 200    ([x] +
+ + 201     (and +
+ + 202      (or (collection? x "Collection") +
+ + 203          (collection? x "OrderedCollection")) +
+ + 204      (integer? (:totalItems x)) +
+ + 205      true)))
- 176   + 206  
- 177  (defn collection-page? + 207  (defn unordered-collection?
- 178    "`true` iff `x` quacks like a page in a paged collection, else `false`." + 208    "`true` iff `x` quacks like an unordered collection, else `false`."
- 179    [x] + 209    [x]
- - 180    (collection? x "CollectionPage")) + + 210    (and (collection? x "Collection") (integer? (:totalItems x)) true))
- 181   + 211  
- 182  (defn ordered-collection-page? + 212  (defn ordered-collection?
- 183    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`." + 213    "`true` iff `x` quacks like an ordered collection, else `false`."
- 184    [x] + 214    [x] +
+ + 215    (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true)) +
+ + 216   +
+ + 217  (defn collection-page? +
+ + 218    "`true` iff `x` quacks like a page in a paged collection, else `false`." +
+ + 219    [x]
- 185    (collection? x "OrderedCollectionPage")) + 220    (collection? x "CollectionPage"))
- 186   + 221   +
+ + 222  (defn ordered-collection-page? +
+ + 223    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`." +
+ + 224    [x] +
+ + 225    (collection? x "OrderedCollectionPage"))
- 187   + 226   +
+ + 227  
diff --git a/docs/cloverage/dog_and_duck/scratch/parser.clj.html b/docs/cloverage/dog_and_duck/scratch/parser.clj.html index 7f217ac..1f2b0a5 100644 --- a/docs/cloverage/dog_and_duck/scratch/parser.clj.html +++ b/docs/cloverage/dog_and_duck/scratch/parser.clj.html @@ -139,5 +139,11 @@ 045   (file-seq (file "resources/activitystreams-test-documents")))
+ + 046   +
+ + 047  (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor) +
diff --git a/docs/cloverage/dog_and_duck/scratch/scratch.clj.html b/docs/cloverage/dog_and_duck/scratch/scratch.clj.html index 2991e30..6ed1ed5 100644 --- a/docs/cloverage/dog_and_duck/scratch/scratch.clj.html +++ b/docs/cloverage/dog_and_duck/scratch/scratch.clj.html @@ -137,7 +137,7 @@ 044  ;;; examine what you got back!

- 045  (:outbox account) + 045  (:inbox account)
046   @@ -184,5 +184,8 @@ 060    )
+ + 061   +
diff --git a/docs/cloverage/dog_and_duck/utils/process.clj.html b/docs/cloverage/dog_and_duck/utils/process.clj.html new file mode 100644 index 0000000..51feb91 --- /dev/null +++ b/docs/cloverage/dog_and_duck/utils/process.clj.html @@ -0,0 +1,77 @@ + + + + dog_and_duck/utils/process.clj + + + + 001  (ns dog-and-duck.utils.process +
+ + 002    (:require [clojure.string :refer [split]])) +
+ + 003   +
+ + 004  (def pid +
+ + 005    "OK, this is hacky as fuck, but I hope it works. The problem is that the +
+ + 006     way to get the process id has changed several times during the history +
+ + 007     of Java development, and the code for one version of Java won't even compile +
+ + 008     in a different version." +
+ + 009    (let [java-version (read-string (apply str (take 2 +
+ + 010                                                     (split +
+ + 011                                                      (System/getProperty "java.version") +
+ + 012                                                      #"[_\.]")))) +
+ + 013          cmd (case java-version +
+ + 014                18 "(let [[_ pid hostname] +
+ + 015                      (re-find +
+ + 016                        #\"^(\\d+)@(.*)\" +
+ + 017                        (.getName +
+ + 018                          (java.lang.management.ManagementFactory/getRuntimeMXBean)))] +
+ + 019                      pid)" +
+ + 020                (19 110) "(.pid (java.lang.ProcessHandle/current))" +
+ + 021                111 "(.getPid (java.lang.management.ManagementFactory/getRuntimeMXBean))" +
+ + 022                ":default")] +
+ + 023      (eval (read-string cmd)))) +
+ + diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index a11f17a..6d990c7 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -29,7 +29,7 @@ style="width:69.76744186046511%; float:left;"> 60 30.23 % -1471486 +1501486 clj-activitypub.internal.crypto
14
39.13 % -36823 +39823 clj-activitypub.internal.http-util
3
80.00 % -25315 +28315 clj-activitypub.internal.thread-cache
3
91.18 % -44334 +47334 clj-activitypub.webfinger
2
100.00 % -32518 +35518 + + + dog-and-duck.quack.picky
113
114
+49.78 % +
19
3
27
+44.90 % +1431149 dog-and-duck.quack.quack
199
249
-44.42 % + style="width:48.36734693877551%; + float:left;"> 237
253
+48.37 %
31
5
32
-52.94 % -1872668 + style="width:43.28358208955224%; + float:left;"> 29
11
27
+59.70 % +2272667 dog-and-duck.scratch.core
dog-and-duck.scratch.parser
34
43
34
-50.00 % +55.84 %
10
11
1
7
-61.11 % -45718 +63.16 % +47819 dog-and-duck.scratch.scratch
19
100.00 % -60919 +611019 + + + dog-and-duck.utils.process
25
4
+86.21 % +
6
1
+100.00 % +2317 Totals: -51.59 % +53.33 % -57.75 % +58.24 % diff --git a/docs/codox/Using_ActivityPub.html b/docs/codox/Using_ActivityPub.html index 208f2cb..f1fe215 100644 --- a/docs/codox/Using_ActivityPub.html +++ b/docs/codox/Using_ActivityPub.html @@ -1,112 +1,5 @@ -Using ActivityPub

Using ActivityPub

-
user=> (require '[clj-activitypub.core :as activitypub])
-nil
-user=> (require '[clj-activitypub.webfinger :as webfinger])
-nil
-user=> (require '[clojure.walk :refer [keywordize-keys]])
-nil
-user=> (require '[clojure.pprint :refer [pprint]])
-nil
-user=> (def base-domain "mastodon.scot")
-#'user/base-domain
-user=> (def account-handle "@simon_brooke@mastodon.scot")
-#'user/account-handle
-user=> (in-ns 'user)
-#object[clojure.lang.Namespace 0x525575 "user"]
-user=> (activitypub/parse-account account-handle )
-{:domain "mastodon.scot", :username "simon_brooke"}
-user=> (map *1 [:domain :username])
-("mastodon.scot" "simon_brooke")
-user=> (apply webfinger/fetch-user-id *1)
-"https://mastodon.scot/users/simon_brooke"
-user=> (activitypub/fetch-user *1)
-{"followers" "https://mastodon.scot/users/simon_brooke/followers", "inbox" "https://mastodon.scot/users/simon_brooke/inbox", "url" "https://mastodon.scot/@simon_brooke", "@context" ["https://www.w3.org/ns/activitystreams" "https://w3id.org/security/v1" {"identityKey" {"@type" "@id", "@id" "toot:identityKey"}, "EncryptedMessage" "toot:EncryptedMessage", "Ed25519Key" "toot:Ed25519Key", "devices" {"@type" "@id", "@id" "toot:devices"}, "manuallyApprovesFollowers" "as:manuallyApprovesFollowers", "schema" "http://schema.org#", "PropertyValue" "schema:PropertyValue", "Curve25519Key" "toot:Curve25519Key", "claim" {"@type" "@id", "@id" "toot:claim"}, "value" "schema:value", "Hashtag" "as:Hashtag", "movedTo" {"@id" "as:movedTo", "@type" "@id"}, "discoverable" "toot:discoverable", "messageType" "toot:messageType", "messageFranking" "toot:messageFranking", "cipherText" "toot:cipherText", "toot" "http://joinmastodon.org/ns#", "alsoKnownAs" {"@id" "as:alsoKnownAs", "@type" "@id"}, "featured" {"@id" "toot:featured", "@type" "@id"}, "featuredTags" {"@id" "toot:featuredTags", "@type" "@id"}, "Ed25519Signature" "toot:Ed25519Signature", "focalPoint" {"@container" "@list", "@id" "toot:focalPoint"}, "fingerprintKey" {"@type" "@id", "@id" "toot:fingerprintKey"}, "Device" "toot:Device", "publicKeyBase64" "toot:publicKeyBase64", "deviceId" "toot:deviceId", "suspended" "toot:suspended"}], "devices" "https://mastodon.scot/users/simon_brooke/collections/devices", "manuallyApprovesFollowers" false, "image" {"type" "Image", "mediaType" "image/jpeg", "url" "https://media.mastodon.scot/mastodon-scot-public/accounts/headers/109/252/274/874/045/781/original/e1f1823c4361fa27.jpg"}, "endpoints" {"sharedInbox" "https://mastodon.scot/inbox"}, "id" "https://mastodon.scot/users/simon_brooke", "publicKey" {"id" "https://mastodon.scot/users/simon_brooke#main-key", "owner" "https://mastodon.scot/users/simon_brooke", "publicKeyPem" "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/6GgLJgJlPhhqFm1tUQ\noSLnWxhDwq4HlZIHrBsVjkSvUAnHKqq42Q/hta+fkWB8rmTFpmjLXDj/Fi0uejvT\nBc+KrLwfX/yR8+G87afGCRS3CaumoLJ7zkBIlsFzIKMoIke1D3QuHX95yGGXs+hp\nmyxt/+CXRyZjK7u9NG7SMRUlpwvOlpD12Aei35Nb8NSr03JvY8/WVMIbWrecyI0b\nAlwj6axxHx7J15Yo+aEtKzZ2OFKXf+sh0QF9BEnYcmVKYlR6kiOglLFHKdCBUSYi\ni9Flv00TydqlGvR5fpShBqORiy0M/FVtNXlz2sNBEsGB2meipkjh+cRLzTbYo4KL\nJwIDAQAB\n-----END PUBLIC KEY-----\n"}, "summary" "<p>Anarcho-syndicalist, autistic, crofter, cyclist, depressive, entrepreneur, geek, Zapatista. Politics &amp; environment, especially <a href=\"https://mastodon.scot/tags/LandReform\" class=\"mention hashtag\" rel=\"tag\">#<span>LandReform</span></a>. he/him.</p><p>Twitter: <span class=\"h-card\"><a href=\"https://mastodon.scot/@simon_brooke\" class=\"u-url mention\">@<span>simon_brooke</span></a></span><br />GitHub: simon-brooke<br />FetLife: Simon_Brooke</p><p>Credo: Life is harsh. What we can do - and what we should do - is strive to make it less harsh for the people around us.</p>", "attachment" [{"type" "PropertyValue", "name" "Home Page", "value" "<a href=\"https://www.journeyman.cc/~simon/\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"><span class=\"invisible\">https://www.</span><span class=\"\">journeyman.cc/~simon/</span><span class=\"invisible\"></span></a>"}], "name" "Simon Brooke", "tag" [{"type" "Hashtag", "href" "https://mastodon.scot/tags/landreform", "name" "#landreform"}], "published" "2022-10-29T00:00:00Z", "preferredUsername" "simon_brooke", "discoverable" true, "alsoKnownAs" ["https://mastodon.social/users/simon_brooke"], "featured" "https://mastodon.scot/users/simon_brooke/collections/featured", "featuredTags" "https://mastodon.scot/users/simon_brooke/collections/tags", "type" "Person", "outbox" "https://mastodon.scot/users/simon_brooke/outbox", "following" "https://mastodon.scot/users/simon_brooke/following", "icon" {"type" "Image", "mediaType" "image/png", "url" "https://media.mastodon.scot/mastodon-scot-public/accounts/avatars/109/252/274/874/045/781/original/172e8f7530627e87.png"}}
-user=> (def sb (keywordize-keys *1))
-#'user/sb
-user=> (:outbox sb)
-"https://mastodon.scot/users/simon_brooke/outbox"
-user=> (require '[clojure.data.json :as json])
-nil
-user=> (slurp (:outbox sb))
-Execution error (IOException) at sun.net.www.protocol.http.HttpURLConnection/getInputStream0 (HttpURLConnection.java:1894).
-Server returned HTTP response code: 403 for URL: https://mastodon.scot/users/simon_brooke/outbox
-user=> (pprint sb)
-{:inbox "https://mastodon.scot/users/simon_brooke/inbox",
- :name "Simon Brooke",
- :@context
- ["https://www.w3.org/ns/activitystreams"
-  "https://w3id.org/security/v1"
-  {:schema "http://schema.org#",
-   :messageType "toot:messageType",
-   :messageFranking "toot:messageFranking",
-   :identityKey {:@type "@id", :@id "toot:identityKey"},
-   :Hashtag "as:Hashtag",
-   :deviceId "toot:deviceId",
-   :publicKeyBase64 "toot:publicKeyBase64",
-   :value "schema:value",
-   :Ed25519Key "toot:Ed25519Key",
-   :featured {:@id "toot:featured", :@type "@id"},
-   :Curve25519Key "toot:Curve25519Key",
-   :discoverable "toot:discoverable",
-   :focalPoint {:@container "@list", :@id "toot:focalPoint"},
-   :suspended "toot:suspended",
-   :fingerprintKey {:@type "@id", :@id "toot:fingerprintKey"},
-   :Ed25519Signature "toot:Ed25519Signature",
-   :cipherText "toot:cipherText",
-   :EncryptedMessage "toot:EncryptedMessage",
-   :alsoKnownAs {:@id "as:alsoKnownAs", :@type "@id"},
-   :featuredTags {:@id "toot:featuredTags", :@type "@id"},
-   :devices {:@type "@id", :@id "toot:devices"},
-   :toot "http://joinmastodon.org/ns#",
-   :movedTo {:@id "as:movedTo", :@type "@id"},
-   :Device "toot:Device",
-   :PropertyValue "schema:PropertyValue",
-   :manuallyApprovesFollowers "as:manuallyApprovesFollowers",
-   :claim {:@type "@id", :@id "toot:claim"}}],
- :featured
- "https://mastodon.scot/users/simon_brooke/collections/featured",
- :type "Person",
- :discoverable true,
- :icon
- {:type "Image",
-  :mediaType "image/png",
-  :url
-  "https://media.mastodon.scot/mastodon-scot-public/accounts/avatars/109/252/274/874/045/781/original/172e8f7530627e87.png"},
- :following "https://mastodon.scot/users/simon_brooke/following",
- :summary
- "<p>Anarcho-syndicalist, autistic, crofter, cyclist, depressive, entrepreneur, geek, Zapatista. Politics &amp; environment, especially <a href=\"https://mastodon.scot/tags/LandReform\" class=\"mention hashtag\" rel=\"tag\">#<span>LandReform</span></a>. he/him.</p><p>Twitter: <span class=\"h-card\"><a href=\"https://mastodon.scot/@simon_brooke\" class=\"u-url mention\">@<span>simon_brooke</span></a></span><br />GitHub: simon-brooke<br />FetLife: Simon_Brooke</p><p>Credo: Life is harsh. What we can do - and what we should do - is strive to make it less harsh for the people around us.</p>",
- :publicKey
- {:id "https://mastodon.scot/users/simon_brooke#main-key",
-  :owner "https://mastodon.scot/users/simon_brooke",
-  :publicKeyPem
-  "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/6GgLJgJlPhhqFm1tUQ\noSLnWxhDwq4HlZIHrBsVjkSvUAnHKqq42Q/hta+fkWB8rmTFpmjLXDj/Fi0uejvT\nBc+KrLwfX/yR8+G87afGCRS3CaumoLJ7zkBIlsFzIKMoIke1D3QuHX95yGGXs+hp\nmyxt/+CXRyZjK7u9NG7SMRUlpwvOlpD12Aei35Nb8NSr03JvY8/WVMIbWrecyI0b\nAlwj6axxHx7J15Yo+aEtKzZ2OFKXf+sh0QF9BEnYcmVKYlR6kiOglLFHKdCBUSYi\ni9Flv00TydqlGvR5fpShBqORiy0M/FVtNXlz2sNBEsGB2meipkjh+cRLzTbYo4KL\nJwIDAQAB\n-----END PUBLIC KEY-----\n"},
- :endpoints {:sharedInbox "https://mastodon.scot/inbox"},
- :preferredUsername "simon_brooke",
- :id "https://mastodon.scot/users/simon_brooke",
- :alsoKnownAs ["https://mastodon.social/users/simon_brooke"],
- :outbox "https://mastodon.scot/users/simon_brooke/outbox",
- :url "https://mastodon.scot/@simon_brooke",
- :featuredTags
- "https://mastodon.scot/users/simon_brooke/collections/tags",
- :devices
- "https://mastodon.scot/users/simon_brooke/collections/devices",
- :image
- {:type "Image",
-  :mediaType "image/jpeg",
-  :url
-  "https://media.mastodon.scot/mastodon-scot-public/accounts/headers/109/252/274/874/045/781/original/e1f1823c4361fa27.jpg"},
- :tag
- [{:type "Hashtag",
-   :href "https://mastodon.scot/tags/landreform",
-   :name "#landreform"}],
- :followers "https://mastodon.scot/users/simon_brooke/followers",
- :published "2022-10-29T00:00:00Z",
- :manuallyApprovesFollowers false,
- :attachment
- [{:type "PropertyValue",
-   :name "Home Page",
-   :value
-   "<a href=\"https://www.journeyman.cc/~simon/\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"><span class=\"invisible\">https://www.</span><span class=\"\">journeyman.cc/~simon/</span><span class=\"invisible\"></span></a>"}]}
-
\ No newline at end of file +Using ActivityPub

Using ActivityPub

+

Introduction

+

I do not know what I am doing; I am learning, and playing. Nothing in this document should be treated as good advice; it simply relates to the current state of my knowledge.

\ No newline at end of file diff --git a/docs/codox/Validation_Faults.html b/docs/codox/Validation_Faults.html new file mode 100644 index 0000000..5e33709 --- /dev/null +++ b/docs/codox/Validation_Faults.html @@ -0,0 +1,37 @@ + +Validation Faults in ActivityPub documents

Validation Faults in ActivityPub documents

+

Motivation

+

This document is intended to provide an extension vocabulary for ActivityStreams documents, which provides vocabulary for categorising and describing faults in ActivityPub documents.

+

The motivation is to be able to serialise a validation report on an ActivityPub document as an ActivityStreams document.

+

Intepretation

+

Conformance

+

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

+

The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119].

+

‘the spec’

+

Where the phrase ‘the spec’ is used in this document, it refers to a concatenation of the ActivityStreams specification and the ActivityPub specification.

+

The Fault object type

+

The Fault object type is a novel object type introduced by this document to describe validation faults. Objects with the Fault object type MUST have at least the following fields (additional fields are not required but are optional):

+
    +
  1. :@context whose value shall be the URL of a document specifying this vocabulary;
  2. +
  3. :type whose value shall be Fault;
  4. +
  5. :severity whose value shall be one of minor, should, must or critical;
  6. +
  7. :fault whose value shall be a unique token representing the particular fault type;
  8. +
  9. :narrative whose value shall be a natural language description of the fault type.
  10. +
+

The Fields

+

Context

+

The value of the @context field of a fault report object shall be the URL of this document, currently https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html.

+

Type

+

The value of the type field of a fault report object MUST be Fault.

+

Severity

+

Each fault report object MUST have a severity field whose value MUST be one of

+
    +
  1. :minor things which I consider to be faults, but which don’t actually breach the spec;
  2. +
  3. :should instances where the spec says something SHOULD be done, which isn’t;
  4. +
  5. :must instances where the spec says something MUST be done, which isn’t;
  6. +
  7. :critical instances where I believe the fault means that the object cannot be meaningfully processed.
  8. +
+

Fault

+

Unique codes shall be assigned to each fault type, and shall be documented in this section.

+

It is intended that there should ultimately be a well known site at which the fault codes can be resolved to natural language explanations in as many natural languages as possible of the nature of the particular fault.

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.core.html b/docs/codox/clj-activitypub.core.html index de2f1d6..8c06e24 100644 --- a/docs/codox/clj-activitypub.core.html +++ b/docs/codox/clj-activitypub.core.html @@ -1,3 +1,3 @@ -clj-activitypub.core documentation

clj-activitypub.core

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

activity

multimethod

Produces a map representing an ActivityPub activity which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

actor

(actor {:keys [user-id username public-key]})

Accepts a config, and returns a map in the form expected by the ActivityPub spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference.

auth-headers

(auth-headers config {:keys [body headers]})

Given a config and request map of {:body … :headers …}, returns the original set of headers with Signature and Digest attributes appended.

config

(config {:keys [domain username username-route public-key private-key], :or {username-route "/users/", public-key nil, private-key nil}})

Creates hash of computed data relevant for most ActivityPub utilities.

fetch-user

(fetch-user user-id)

Fetches the customer account details located at user-id from a remote server. Will return cached results if they exist in memory.

gen-signature-header

(gen-signature-header config headers)

Generates a HTTP Signature string based on the provided map of headers.

obj

multimethod

Produces a map representing an ActivityPub object which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

parse-account

(parse-account handle)

Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces a map containing {:domain … :username …}.

signature-headers

TODO: write docs

with-config

(with-config config)

Returns curried forms of the #activity and #obj multimethods in the form {:activity … :obj …}, with the initial parameter set to config.

\ No newline at end of file +clj-activitypub.core documentation

clj-activitypub.core

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

activity

multimethod

Produces a map representing an ActivityPub activity which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

actor

(actor {:keys [user-id username public-key]})

Accepts a config, and returns a map in the form expected by the ActivityPub spec. See https://www.w3.org/TR/activitypub/#actor-objects for reference.

auth-headers

(auth-headers config {:keys [body headers]})

Given a config and request map of {:body … :headers …}, returns the original set of headers with Signature and Digest attributes appended.

config

(config {:keys [domain username username-route public-key private-key], :or {username-route "/users/", public-key nil, private-key nil}})

Creates hash of computed data relevant for most ActivityPub utilities.

fetch-user

(fetch-user user-id)

Fetches the customer account details located at user-id from a remote server. Will return cached results if they exist in memory.

gen-signature-header

(gen-signature-header config headers)

Generates a HTTP Signature string based on the provided map of headers.

obj

multimethod

Produces a map representing an ActivityPub object which can be serialized directly to JSON in the form expected by the ActivityStreams 2.0 spec. See https://www.w3.org/TR/activitystreams-vocabulary/ for reference.

parse-account

(parse-account handle)

Given an ActivityPub handle (e.g. @jahfer@mastodon.social), produces a map containing {:domain … :username …}.

signature-headers

TODO: write docs

with-config

(with-config config)

Returns curried forms of the #activity and #obj multimethods in the form {:activity … :obj …}, with the initial parameter set to config.

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.internal.crypto.html b/docs/codox/clj-activitypub.internal.crypto.html index ca50280..6852d12 100644 --- a/docs/codox/clj-activitypub.internal.crypto.html +++ b/docs/codox/clj-activitypub.internal.crypto.html @@ -1,3 +1,3 @@ -clj-activitypub.internal.crypto documentation

clj-activitypub.internal.crypto

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

base64-encode

(base64-encode bytes)

TODO: write docs

private-key

(private-key private-pem-str)

TODO: write docs

sha256-base64

(sha256-base64 data)

TODO: write docs

sign

(sign data private-key)

TODO: write docs

\ No newline at end of file +clj-activitypub.internal.crypto documentation

clj-activitypub.internal.crypto

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

base64-encode

(base64-encode bytes)

TODO: write docs

private-key

(private-key private-pem-str)

TODO: write docs

sha256-base64

(sha256-base64 data)

TODO: write docs

sign

(sign data private-key)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.internal.http-util.html b/docs/codox/clj-activitypub.internal.http-util.html index b6afb96..1df08cf 100644 --- a/docs/codox/clj-activitypub.internal.http-util.html +++ b/docs/codox/clj-activitypub.internal.http-util.html @@ -1,3 +1,3 @@ -clj-activitypub.internal.http-util documentation

clj-activitypub.internal.http-util

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

date

(date)

TODO: write docs

digest

(digest body)

Accepts body from HTTP request and generates string for use in HTTP Digest request header.

encode-url-params

(encode-url-params params)

TODO: write docs

\ No newline at end of file +clj-activitypub.internal.http-util documentation

clj-activitypub.internal.http-util

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

date

(date)

TODO: write docs

digest

(digest body)

Accepts body from HTTP request and generates string for use in HTTP Digest request header.

encode-url-params

(encode-url-params params)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.internal.thread-cache.html b/docs/codox/clj-activitypub.internal.thread-cache.html index ad0a146..fe63b03 100644 --- a/docs/codox/clj-activitypub.internal.thread-cache.html +++ b/docs/codox/clj-activitypub.internal.thread-cache.html @@ -1,3 +1,3 @@ -clj-activitypub.internal.thread-cache documentation

clj-activitypub.internal.thread-cache

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

make

(make)(make cache-if-nil)

Creates a thread-local cache.

\ No newline at end of file +clj-activitypub.internal.thread-cache documentation

clj-activitypub.internal.thread-cache

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

make

(make)(make cache-if-nil)

Creates a thread-local cache.

\ No newline at end of file diff --git a/docs/codox/clj-activitypub.webfinger.html b/docs/codox/clj-activitypub.webfinger.html index 68fb97c..8ea3cad 100644 --- a/docs/codox/clj-activitypub.webfinger.html +++ b/docs/codox/clj-activitypub.webfinger.html @@ -1,3 +1,3 @@ -clj-activitypub.webfinger documentation

clj-activitypub.webfinger

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

fetch-user-id

(fetch-user-id domain username)

Follows the webfinger request to a remote domain, retrieving the ID of the requested account. Typically returns a string in the form of a URL.

remote-uri-path

TODO: write docs

resource-url

(resource-url domain username & [params])

Builds a URL pointing to the user’s account on the remote server.

\ No newline at end of file +clj-activitypub.webfinger documentation

clj-activitypub.webfinger

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

fetch-user-id

(fetch-user-id domain username)

Follows the webfinger request to a remote domain, retrieving the ID of the requested account. Typically returns a string in the form of a URL.

remote-uri-path

TODO: write docs

resource-url

(resource-url domain username & [params])

Builds a URL pointing to the user’s account on the remote server.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.quack.picky.html b/docs/codox/dog-and-duck.quack.picky.html new file mode 100644 index 0000000..02260e7 --- /dev/null +++ b/docs/codox/dog-and-duck.quack.picky.html @@ -0,0 +1,15 @@ + +dog-and-duck.quack.picky documentation

dog-and-duck.quack.picky

Fault-finder for ActivityPub documents.

+

Generally, each -faults function will return: 1. nil if no faults were found; 2. a sequence of fault objects if faults were found.

+

Each fault object shall have the properties: 1. :@context whose value shall be the URL of a document specifying this vocabulary; 2. :type whose value shall be Fault; 3. :severity whose value shall be one of minor, should, must or critical; 4. :fault whose value shall be a unique token representing the particular fault type; 5. :narrative whose value shall be a natural language description of the fault type.

+

Note that the reason for the :fault property is to be able to have a well known place, linked to from the @context URL, which allows narratives for each fault type to be served in as many natural languages as possible.

+

The idea further is that it should ultimately be possible to serialise a fault report as a document which in its own right conforms to the ActivityStreams spec.

activitystreams-context-uri

The URI of the context of an ActivityStreams object is expected to be this literal string.

context?

(context? x)

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.

filter-severity

(filter-severity reports severity)

Return a list of reports taken from these reports where the severity of the report is greater than this severity.

has-context?

macro

(has-context? x)

True if x is an ActivityStreams object with a valid context, else false.

make-fault-object

(make-fault-object severity fault narrative)

Return a fault object with these severity, fault and narrative values.

+

An ActivityPub object MUST have a globally unique ID. Whether this is meaningful depends on whether we persist fault report objects and serve them, which at present I have no plans to do.

object-faults

(object-faults x)

TODO: write docs

severity

Severity of faults found, as follows:

+
    +
  1. :minor things which I consider to be faults, but which don’t actually breach the spec;
  2. +
  3. :should instances where the spec says something SHOULD be done, which isn’t;
  4. +
  5. :must instances where the spec says something MUST be done, which isn’t;
  6. +
  7. :critical instances where I believe the fault means that the object cannot be meaningfully processed.
  8. +

severity-filters

Hack for implementing a severity hierarchy

validation-fault-context-uri

The URI of the context of a validation fault report object shall be this literal string.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.quack.quack.html b/docs/codox/dog-and-duck.quack.quack.html index 602dabd..878a31d 100644 --- a/docs/codox/dog-and-duck.quack.quack.html +++ b/docs/codox/dog-and-duck.quack.quack.html @@ -1,20 +1,30 @@ -dog-and-duck.quack.quack documentation

dog-and-duck.quack.quack

Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck…

activity?

(activity? x)

true iff x quacks like an activity, else false.

+dog-and-duck.quack.quack documentation

dog-and-duck.quack.quack

Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck…

+

**NOTE THAT the ActivityPub spec says

+
+

Servers SHOULD validate the content they receive to avoid content spoofing attacks

+
+

but in practice ActivityPub content collected in the wild bears only a hazy relationship to the spec, so this is difficult. I suspect that I may have to implement a *strict* dynamic variable, so that users can toggle some checks off.

activity?

(activity? x)

true iff x quacks like an activity, else false.

actor-or-uri?

(actor-or-uri? x)

true if x is either a URI or an actor.

+

TODO: I need to decide about whether to reify referenced objects before validation or after. After reification, every reference to an actor must be to an actor object, but before, may only be to a URI pointing to one.

actor-type?

macro

(actor-type? x)

Return true iff the x is a recognised actor type, else false.

actor-types

The set of types we will accept as actors.

+

There’s an explicit set of allowed actor types.

actor?

(actor? x)

Returns true if x quacks like an actor, else false.

NOTE THAT Section 4.1 of the spec says explicitly that

Actor objects MUST have, in addition to the properties mandated by 3.1 Object Identifiers, the following properties:

inbox A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor; see 5.2 Inbox. outbox An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox.

-

However, none of the provided examples in the activitystreams-test-documents repository does in fact have these properties

activitystreams-context-uri

The URI of the context of an ActivityStreams object is expected to be this literal string.

actor-type?

(actor-type? x)

TODO: write docs

actor-types

The set of types we will accept as actors.

-

There’s an explicit set of allowed actor types.

actor?

(actor? x)

Returns true if x quacks like an actor, else false.

collection-page?

(collection-page? x)

true iff x quacks like a page in a paged collection, else false.

collection?

(collection? x type)(collection? x)

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.

context?

(context? x)

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.

has-context?

macro

(has-context? x)

TODO: write docs

link?

(link? x)

true iff x quacks like a link, else false.

object?

(object? x)

Returns true iff x is recognisably an ActivityStreams object.

+

However, none of the provided examples in the activitystreams-test-documents repository does in fact have these properties

collection-page?

(collection-page? x)

true iff x quacks like a page in a paged collection, else false.

collection?

(collection? x object-type)(collection? x)

true iff x quacks like a collection of type object-type, else false.

+

With one argument, will recognise plain collections and ordered collections, but (currently) not collection pages.

link?

(link? x)

true iff x quacks like a link, else false.

object?

(object? x)(object? x severity)

Returns true iff x is recognisably an ActivityStreams object.

NOTE THAT The ActivityStreams spec says:

All properties are optional (including the id and type)

-

But we are just not having that, because otherwise we’re flying blind. We shall reject objects lacking at least :type. Missing :id keys are tolerable because they represent transient objects, which we expect to handle.

ordered-collection-page?

(ordered-collection-page? x)

true iff x quacks like a page in an ordered paged collection, else false.

ordered-collection?

(ordered-collection? x)

true iff x quacks like an ordered collection, else false.

persistent-object?

(persistent-object? x)

true iff x is a persistent object.

-

Transient objects in ActivityPub are not required to have an id key, but persistent ones must have a key, and it must be an IRI (but normally a URI).

unordered-collection?

(unordered-collection? x)

true iff x quacks like an unordered collection, else false.

verb-type?

(verb-type? x)

TODO: write docs

verb-types

The set of types we will accept as verbs.

-

There’s an explicit set of allowed verb types.

\ No newline at end of file +

But we are just not having that, because otherwise we’re flying blind. We shall reject objects lacking at least :type. Missing :id keys are tolerable because they represent transient objects, which we expect to handle.

+

NOTE THAT The ActivityPub spec says

+
+

Implementers SHOULD include the ActivityPub context in their object definitions

+
+

but in samples found in the wild they typically don’t.

ordered-collection-page?

(ordered-collection-page? x)

true iff x quacks like a page in an ordered paged collection, else false.

ordered-collection?

(ordered-collection? x)

true iff x quacks like an ordered collection, else false.

persistent-object?

(persistent-object? x)

true iff x is a persistent object.

+

Transient objects in ActivityPub are not required to have an id key, but persistent ones must have a key, and it must be an IRI (but normally a URI).

unordered-collection?

(unordered-collection? x)

true iff x quacks like an unordered collection, else false.

verb-type?

macro

(verb-type? x)

TODO: write docs

verb-types

The set of types we will accept as verbs.

+

There’s an explicit set of allowed verb types.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.scratch.core.html b/docs/codox/dog-and-duck.scratch.core.html index 30b1865..15b09e5 100644 --- a/docs/codox/dog-and-duck.scratch.core.html +++ b/docs/codox/dog-and-duck.scratch.core.html @@ -1,3 +1,3 @@ -dog-and-duck.scratch.core documentation

dog-and-duck.scratch.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file +dog-and-duck.scratch.core documentation

dog-and-duck.scratch.core

TODO: write docs

foo

(foo x)

I don’t do a whole lot.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.scratch.parser.html b/docs/codox/dog-and-duck.scratch.parser.html index 8f8968c..147af45 100644 --- a/docs/codox/dog-and-duck.scratch.parser.html +++ b/docs/codox/dog-and-duck.scratch.parser.html @@ -1,3 +1,3 @@ -dog-and-duck.scratch.parser documentation

dog-and-duck.scratch.parser

TODO: write docs

clean

(clean json)

Take this json input, and return a sequence of ActivityPub objects represented by it.

\ No newline at end of file +dog-and-duck.scratch.parser documentation

dog-and-duck.scratch.parser

TODO: write docs

clean

(clean json)

Take this json input, and return a sequence of ActivityPub objects represented by it.

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.scratch.scratch.html b/docs/codox/dog-and-duck.scratch.scratch.html index be30ecf..f74e1c0 100644 --- a/docs/codox/dog-and-duck.scratch.scratch.html +++ b/docs/codox/dog-and-duck.scratch.scratch.html @@ -1,3 +1,3 @@ -dog-and-duck.scratch.scratch documentation

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

account

Fetch my account to mess with

account-handle

TODO: write docs

handle

TODO: write docs

kp

TODO: write docs

rsa

TODO: write docs

\ No newline at end of file +dog-and-duck.scratch.scratch documentation

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

account

Fetch my account to mess with

account-handle

TODO: write docs

handle

TODO: write docs

kp

TODO: write docs

rsa

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/dog-and-duck.utils.process.html b/docs/codox/dog-and-duck.utils.process.html new file mode 100644 index 0000000..2cd7848 --- /dev/null +++ b/docs/codox/dog-and-duck.utils.process.html @@ -0,0 +1,3 @@ + +dog-and-duck.utils.process documentation

dog-and-duck.utils.process

TODO: write docs

pid

OK, this is hacky as fuck, but I hope it works. The problem is that the way to get the process id has changed several times during the history of Java development, and the code for one version of Java won’t even compile in a different version.

\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html index b31eb48..de6430f 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Dog-and-duck 0.1.0-SNAPSHOT

Dog-and-duck 0.1.0-SNAPSHOT

Released under the GPL-2.0-or-later

A Clojure library designed to implement the ActivityPub protocol.

Installation

To install, add the following dependency to your project or build file:

[dog-and-duck "0.1.0-SNAPSHOT"]

Topics

Namespaces

clj-activitypub.core

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

clj-activitypub.internal.crypto

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

clj-activitypub.internal.http-util

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

clj-activitypub.internal.thread-cache

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

clj-activitypub.webfinger

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

dog-and-duck.scratch.core

TODO: write docs

Public variables and functions:

dog-and-duck.scratch.parser

TODO: write docs

Public variables and functions:

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

Public variables and functions:

\ No newline at end of file +Dog-and-duck 0.1.0-SNAPSHOT

Dog-and-duck 0.1.0-SNAPSHOT

Released under the GPL-2.0-or-later

A playground for hacking ActivityPub stuff.

Installation

To install, add the following dependency to your project or build file:

[dog-and-duck "0.1.0-SNAPSHOT"]

Topics

Namespaces

clj-activitypub.core

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

clj-activitypub.internal.crypto

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

clj-activitypub.internal.http-util

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

clj-activitypub.internal.thread-cache

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

clj-activitypub.webfinger

copied from Jahfer’s clj-activitypub library. If and when Jahfer issues a release of that library, this directory will be deleted and a dependency on that library will be added to the project.

Public variables and functions:

dog-and-duck.scratch.core

TODO: write docs

Public variables and functions:

dog-and-duck.scratch.parser

TODO: write docs

Public variables and functions:

dog-and-duck.scratch.scratch

Scratchpad where I try to understand how to do this stuff.

Public variables and functions:

dog-and-duck.utils.process

TODO: write docs

Public variables and functions:

\ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 6a5fb84..a753b8d 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -The Old Dog and Duck

The Old Dog and Duck

+The Old Dog and Duck

The Old Dog and Duck

A Clojure library designed to implement the ActivityPub protocol, obviously.

Introduction

The Old Dog and Duck is clearly a pub, and it’s a pub related to an activity; to whit, hunting ducks with dogs. Yes, of course one could also hunt dogs with ducks, but in practice that doesn’t work so well. The point isn’t whether or not I approve of hunting ducks with dogs (or vice versa); to be clear, I don’t. The point is that it’s a pub related to an activity, and is therefore an ActivityPub.

diff --git a/docs/images/Dog_and_Duck_tavern.jpg b/docs/images/Dog_and_Duck_tavern.jpg new file mode 100644 index 0000000..cf15ce7 Binary files /dev/null and b/docs/images/Dog_and_Duck_tavern.jpg differ diff --git a/docs/index.html b/docs/index.html index 31b7968..92c6977 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,14 +1,25 @@ - - - The Old Dog and Duck: Documentation - - - -

The Old Dog and Duck: Documentation

- - - + + + + The Old Dog and Duck: Documentation + + + + +

The Old Dog and Duck: Documentation

+ Victorian painting by Thomas H. Shepherd, based on a 1646 drawing +

'The Old Dog and Duck' is a fairly common pub name; + + the instance in the picture + stood in St George's Fields, London from the seventeenth century + until 1812, when the site was cleared to make way for the new Bethlehem + Hospital. +

+ + + + \ No newline at end of file diff --git a/project.clj b/project.clj index 4d7fa1f..e4c3dee 100644 --- a/project.clj +++ b/project.clj @@ -6,8 +6,7 @@ :doc/format :markdown} :output-path "docs/codox" :source-uri "https://github.com/simon-brooke/dog-and-duck/blob/master/{filepath}#L{line}"} - - :description "A Clojure library designed to implement the ActivityPub protocol." + :description "A playground for hacking ActivityPub stuff." :dependencies [[org.clojure/clojure "1.11.1"] [org.clojure/data.json "2.4.0"] [org.clojure/math.numeric-tower "0.0.5"] diff --git a/resources/test_documents/announce.json b/resources/test_documents/announce.json new file mode 100644 index 0000000..c6bfa78 --- /dev/null +++ b/resources/test_documents/announce.json @@ -0,0 +1,14 @@ +{ + "id": "https://mastodon.scot/users/simon_brooke/statuses/109545810804143431/activity", + "type": "Announce", + "actor": "https://mastodon.scot/users/simon_brooke", + "published": "2022-12-20T11:40:19Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://mastodon.social/users/borkdude", + "https://mastodon.scot/users/simon_brooke/followers" + ], + "object": "https://mastodon.social/users/borkdude/statuses/109545794618575668" +} \ No newline at end of file diff --git a/resources/test_documents/create.json b/resources/test_documents/create.json new file mode 100644 index 0000000..9c1a7ac --- /dev/null +++ b/resources/test_documents/create.json @@ -0,0 +1,63 @@ +{ + "id": "https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/activity", + "type": "Create", + "actor": "https://mastodon.scot/users/simon_brooke", + "published": "2022-12-20T11:39:10Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://mastodon.scot/users/simon_brooke/followers" + ], + "object": { + "inReplyTo": null, + "content": "

I'm still looking for a repository of #ActivityPub sample documents to run tests against. There's one of #ActivityStreams documents here

https://github.com/w3c-social/activitystreams-test-documents

but they don't conform to the ActivityPub specification. Please, has anyone got pointers?

Captures of actual live interchanges between ActivityPub servers would be especially useful.

#ActivityPubInClojure

", + "sensitive": false, + "cc": [ + "https://mastodon.scot/users/simon_brooke/followers" + ], + "type": "Note", + "attributedTo": "https://mastodon.scot/users/simon_brooke", + "summary": null, + "replies": { + "id": "https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/replies", + "type": "Collection", + "first": { + "type": "CollectionPage", + "next": "https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/replies?only_other_accounts=true&page=true", + "partOf": "https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/replies", + "items": [] + } + }, + "id": "https://mastodon.scot/users/simon_brooke/statuses/109545806233872328", + "contentMap": { + "en": "

I'm still looking for a repository of #ActivityPub sample documents to run tests against. There's one of #ActivityStreams documents here

https://github.com/w3c-social/activitystreams-test-documents

but they don't conform to the ActivityPub specification. Please, has anyone got pointers?

Captures of actual live interchanges between ActivityPub servers would be especially useful.

#ActivityPubInClojure

" + }, + "conversation": "tag:mastodon.scot,2022-12-20:objectId=39176769:objectType=Conversation", + "url": "https://mastodon.scot/@simon_brooke/109545806233872328", + "inReplyToAtomUri": null, + "tag": [ + { + "type": "Hashtag", + "href": "https://mastodon.scot/tags/activitypubinclojure", + "name": "#activitypubinclojure" + }, + { + "type": "Hashtag", + "href": "https://mastodon.scot/tags/activitystreams", + "name": "#activitystreams" + }, + { + "type": "Hashtag", + "href": "https://mastodon.scot/tags/activitypub", + "name": "#activitypub" + } + ], + "atomUri": "https://mastodon.scot/users/simon_brooke/statuses/109545806233872328", + "published": "2022-12-20T11:39:10Z", + "attachment": [], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ] + } +} \ No newline at end of file diff --git a/resources/test_documents/outbox.json b/resources/test_documents/outbox.json new file mode 100644 index 0000000..b433059 --- /dev/null +++ b/resources/test_documents/outbox.json @@ -0,0 +1,8 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://mastodon.scot/users/simon_brooke/outbox", + "type": "OrderedCollection", + "totalItems": 2881, + "first": "https://mastodon.scot/users/simon_brooke/outbox?page=true", + "last": "https://mastodon.scot/users/simon_brooke/outbox?min_id=0\u0026page=true" +} \ No newline at end of file diff --git a/resources/test_documents/outbox_page.json b/resources/test_documents/outbox_page.json new file mode 100644 index 0000000..17324ff --- /dev/null +++ b/resources/test_documents/outbox_page.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams",{"ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#","votersCount":"toot:votersCount","Hashtag":"as:Hashtag"}],"id":"https://mastodon.scot/users/simon_brooke/outbox?page=true","type":"OrderedCollectionPage","next":"https://mastodon.scot/users/simon_brooke/outbox?max_id=109545376833627822\u0026page=true","prev":"https://mastodon.scot/users/simon_brooke/outbox?min_id=109545810804143431\u0026page=true","partOf":"https://mastodon.scot/users/simon_brooke/outbox","orderedItems":[{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545810804143431/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T11:40:19Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/borkdude","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.social/users/borkdude/statuses/109545794618575668"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545810323860104/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T11:40:12Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/borkdude","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.social/users/borkdude/statuses/109545644305699233"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/activity","type":"Create","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T11:39:10Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.scot/users/simon_brooke/followers"],"object":{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545806233872328","type":"Note","summary":null,"inReplyTo":null,"published":"2022-12-20T11:39:10Z","url":"https://mastodon.scot/@simon_brooke/109545806233872328","attributedTo":"https://mastodon.scot/users/simon_brooke","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.scot/users/simon_brooke/followers"],"sensitive":false,"atomUri":"https://mastodon.scot/users/simon_brooke/statuses/109545806233872328","inReplyToAtomUri":null,"conversation":"tag:mastodon.scot,2022-12-20:objectId=39176769:objectType=Conversation","content":"\u003cp\u003eI\u0026#39;m still looking for a repository of \u003ca href=\"https://mastodon.scot/tags/ActivityPub\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eActivityPub\u003c/span\u003e\u003c/a\u003e sample documents to run tests against. There\u0026#39;s one of \u003ca href=\"https://mastodon.scot/tags/ActivityStreams\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eActivityStreams\u003c/span\u003e\u003c/a\u003e documents here\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://github.com/w3c-social/activitystreams-test-documents\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"ellipsis\"\u003egithub.com/w3c-social/activity\u003c/span\u003e\u003cspan class=\"invisible\"\u003estreams-test-documents\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e\u003cp\u003ebut they don\u0026#39;t conform to the ActivityPub specification. Please, has anyone got pointers?\u003c/p\u003e\u003cp\u003eCaptures of actual live interchanges between ActivityPub servers would be especially useful.\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://mastodon.scot/tags/ActivityPubInClojure\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eActivityPubInClojure\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","contentMap":{"en":"\u003cp\u003eI\u0026#39;m still looking for a repository of \u003ca href=\"https://mastodon.scot/tags/ActivityPub\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eActivityPub\u003c/span\u003e\u003c/a\u003e sample documents to run tests against. There\u0026#39;s one of \u003ca href=\"https://mastodon.scot/tags/ActivityStreams\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eActivityStreams\u003c/span\u003e\u003c/a\u003e documents here\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://github.com/w3c-social/activitystreams-test-documents\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"ellipsis\"\u003egithub.com/w3c-social/activity\u003c/span\u003e\u003cspan class=\"invisible\"\u003estreams-test-documents\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e\u003cp\u003ebut they don\u0026#39;t conform to the ActivityPub specification. Please, has anyone got pointers?\u003c/p\u003e\u003cp\u003eCaptures of actual live interchanges between ActivityPub servers would be especially useful.\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://mastodon.scot/tags/ActivityPubInClojure\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eActivityPubInClojure\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e"},"attachment":[],"tag":[{"type":"Hashtag","href":"https://mastodon.scot/tags/activitypubinclojure","name":"#activitypubinclojure"},{"type":"Hashtag","href":"https://mastodon.scot/tags/activitystreams","name":"#activitystreams"},{"type":"Hashtag","href":"https://mastodon.scot/tags/activitypub","name":"#activitypub"}],"replies":{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/replies","type":"Collection","first":{"type":"CollectionPage","next":"https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/replies?only_other_accounts=true\u0026page=true","partOf":"https://mastodon.scot/users/simon_brooke/statuses/109545806233872328/replies","items":[]}}}},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545501162819122/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:21:34Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://akademienl.social/users/cdutilhnovaes","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://akademienl.social/users/cdutilhnovaes/statuses/109544843998251465"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545499838467414/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:21:14Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.art/users/alannawrites","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.art/users/alannawrites/statuses/109541652106156076"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545476694378392/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:15:21Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://toot.bike/users/Gazza_d","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://toot.bike/users/Gazza_d/statuses/109544977675121529"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545471035592188/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:13:55Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://climatejustice.social/users/ClimateHuman","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://climatejustice.social/users/ClimateHuman/statuses/109537884570556967"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545469518129117/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:13:32Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://toot.bike/users/Gazza_d","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://toot.bike/users/Gazza_d/statuses/109545038098698744"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545463482296525/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:12:00Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.scot/users/twoowls73","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.scot/users/twoowls73/statuses/109542574900368955"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545453837098817/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:09:32Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://infosec.exchange/users/cirku17","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://infosec.exchange/users/cirku17/statuses/109545130229264028"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545449914771673/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:08:33Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.green/users/davidallengreen","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.green/users/davidallengreen/statuses/109545124476645849"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545446679560505/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:07:43Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.me.uk/users/ldodds","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.me.uk/users/ldodds/statuses/109545227282251474"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545440486626310/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:06:09Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/davewalker","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.social/users/davewalker/statuses/109545233052005311"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545439348320542/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T10:05:51Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.world/users/ktrebeck","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.world/users/ktrebeck/statuses/109545340208667032"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545413972134505/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T09:59:24Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://masto.ai/users/rbreich","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://masto.ai/users/rbreich/statuses/109544854399221809"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545409572874619/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T09:58:17Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://respublicae.eu/users/CarolineLucas","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://respublicae.eu/users/CarolineLucas/statuses/109545373825218540"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545404004890974/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T09:56:52Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/katrinbretscher","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.social/users/katrinbretscher/statuses/109338712958281369"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545394695330333/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T09:54:30Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mstdn.social/users/DrLygo","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mstdn.social/users/DrLygo/statuses/109530261063885901"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545392785161717/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T09:54:01Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.online/users/Andy_European","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://mastodon.online/users/Andy_European/statuses/109529692969910381"},{"id":"https://mastodon.scot/users/simon_brooke/statuses/109545376833627822/activity","type":"Announce","actor":"https://mastodon.scot/users/simon_brooke","published":"2022-12-20T09:49:57Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://zirk.us/users/ChrisMayLA6","https://mastodon.scot/users/simon_brooke/followers"],"object":"https://zirk.us/users/ChrisMayLA6/statuses/109544661675672403"}]} \ No newline at end of file diff --git a/src/dog_and_duck/quack/picky.clj b/src/dog_and_duck/quack/picky.clj new file mode 100644 index 0000000..28f3c84 --- /dev/null +++ b/src/dog_and_duck/quack/picky.clj @@ -0,0 +1,143 @@ +(ns dog-and-duck.quack.picky "Fault-finder for ActivityPub documents. + + Generally, each `-faults` function will return: + 1. `nil` if no faults were found; + 2. a sequence of fault objects if faults were found. + + Each fault object shall have the properties: + 1. `:@context` whose value shall be the URL of a + document specifying this vocabulary; + 2. `:type` whose value shall be `Fault`; + 3. `:severity` whose value shall be one of + `minor`, `should`, `must` or `critical`; + 4. `:fault` whose value shall be a unique token + representing the particular fault type; + 5. `:narrative` whose value shall be a natural + language description of the fault type. + + Note that the reason for the `:fault` property is + to be able to have a well known place, linked to + from the @context URL, which allows narratives + for each fault type to be served in as many + natural languages as possible. + + The idea further is that it should ultimately be + possible to serialise a fault report as a + document which in its own right conforms to the + ActivityStreams spec." + (:require [dog-and-duck.utils.process :refer [pid]])) + +(def ^:const severity + "Severity of faults found, as follows: + + 1. `:minor` things which I consider to be faults, but which + don't actually breach the spec; + 2. `:should` instances where the spec says something SHOULD + be done, which isn't; + 3. `:must` instances where the spec says something MUST + be done, which isn't; + 4. `:critical` instances where I believe the fault means that + the object cannot be meaningfully processed." + #{:minor :should :must :critical}) + +(def ^:const severity-filters + "Hack for implementing a severity hierarchy" + {:all #{} + :minor #{:minor} + :should #{:minor :should} + :must #{:minor :should :must} + :critical severity}) + +(defn filter-severity + "Return a list of reports taken from these `reports` where the severity + of the report is greater than this `severity`." + [reports severity] + (assert + (and + (coll? reports) + (every? map? reports) + (every? :severity reports))) + (remove + #((severity-filters severity) (:severity %)) + reports)) + +(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") + +(def ^:const validation-fault-context-uri + "The URI of the context of a validation fault report object shall be this + literal string." + "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html") + +(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? + "True if `x` is an ActivityStreams object with a valid context, else `false`." + [x] + `(context? ((keyword "@context") ~x))) + + + +(defn make-fault-object + "Return a fault object with these `severity`, `fault` and `narrative` values. + + An ActivityPub object MUST have a globally unique ID. Whether this is + meaningful depends on whether we persist fault report objects and serve + them, which at present I have no plans to do." + [severity fault narrative] + (assoc {} + (keyword "@context") validation-fault-context-uri + :id (str "https://" + (.. java.net.InetAddress getLocalHost getHostName) + "/fault/" + pid + ":" + (inst-ms (java.util.Date.))) + :type "Fault" + :severity severity + :fault fault + :narrative narrative)) + +(defn object-faults + [x] + (remove + empty? + (list + (when-not + (has-context? x) + (make-fault-object + :should + :no-context + "Section 3 of the ActivityPub specification states + `Implementers SHOULD include the ActivityPub context in + their object definitions`.") + (when-not (:type x) + (make-fault-object + :minor + :no-type + "The ActivityPub specification states that the `type` field is + optional, but it is hard to process objects with no known type.")) + (when-not (contains? x :id) + (make-fault-object + :minor + :no-id-transient + "The ActivityPub specification allows objects without `id` fields + only if they are intentionally transient; even so it is preferred + that the object should have an explicit null id." + )) + )))) diff --git a/src/dog_and_duck/quack/quack.clj b/src/dog_and_duck/quack/quack.clj index ab3a432..6005a30 100644 --- a/src/dog_and_duck/quack/quack.clj +++ b/src/dog_and_duck/quack/quack.clj @@ -1,6 +1,21 @@ (ns dog-and-duck.quack.quack - "Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck..." + "Validator for ActivityPub objects: if it walks like a duck, and it quacks + like a duck... + + **NOTE THAT the ActivityPub spec + [says](https://www.w3.org/TR/activitypub/#obj) + + > Servers SHOULD validate the content they receive to avoid content + > spoofing attacks + + but in practice ActivityPub content collected in the wild bears only + a hazy relationship to the spec, so this is difficult. I suspect that + I may have to implement a `*strict*` dynamic variable, so that users can + toggle some checks off." + ;;(:require [clojure.spec.alpha as s]) + (:require [dog-and-duck.quack.picky :refer [filter-severity has-context? + object-faults]]) (:import [java.net URI URISyntaxException])) ;;; Copyright (C) Simon Brooke, 2022 @@ -30,9 +45,18 @@ But we are *just not having that*, because otherwise we're flying blind. We *shall* reject objects lacking at least `:type`. Missing `:id` keys are tolerable because they represent transient objects, which we expect to - handle." - [x] + handle. + + **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj) + + > Implementers SHOULD include the ActivityPub context in their object + > definitions + + but in samples found in the wild they typically don't." + ([x] (and (map? x) (:type x) true)) + ([x severity] + (empty? (filter-severity (object-faults x) severity)))) (defn persistent-object? "`true` iff `x` is a persistent object. @@ -44,7 +68,7 @@ (and (object? x) (uri? (URI. (:id x)))) (catch URISyntaxException _ false))) -(persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"}) +;; (persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"}) (def ^:const actor-types "The set of types we will accept as actors. @@ -57,10 +81,12 @@ "Person" "Service"}) -(defn actor-type? - ;; TODO: better as a macro - [x] - (if (actor-types x) true false)) +(defmacro actor-type? + "Return `true` iff the `x` is a recognised actor type, else `false`." + [^String x] + `(if (actor-types ~x) true false)) + +;; (actor-type? "Group") (def ^:const verb-types "The set of types we will accept as verbs. @@ -72,47 +98,14 @@ "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept" "TentativeReject" "Travel" "Undo" "Update" "View"}) -(defn verb-type? +(defmacro verb-type? ;; TODO: better as a macro - [x] - (if (verb-types x) true false)) + [^String x] + `(if (verb-types ~x) true false)) -(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. + "Returns `true` if `x` quacks like an actor, else false. **NOTE THAT** [Section 4.1 of the spec] (https://www.w3.org/TR/activitypub/#actor-objects) says explicitly that @@ -126,11 +119,35 @@ However, none of the provided examples in the [activitystreams-test-documents repository]() does in fact have these properties" [x] + (and + (object? x) + (has-context? x) + (uri? (URI. (:inbox x))) + (uri? (URI. (:outbox x))) + (actor-type? (:type x)) + true)) + +(defn actor-or-uri? + "`true` if `x` is either a URI or an actor. + + **TODO**: I need to decide about whether to reify referenced objects + before validation or after. After reification, every reference to an actor + *must be* to an actor object, but before, may only be to a URI pointing to + one." + [x] + (and + (cond (string? x) (uri? (URI. x)) + :else (actor? x)) + true)) + +(defn activity? + "`true` iff `x` quacks like an activity, else false." + [x] (try (and (object? x) (has-context? x) (string? (:summary x)) - (actor? (:actor x)) + (actor-or-uri? (:actor x)) (verb-type? (:type x)) (or (object? (:object x)) (uri? (URI. (:object x)))) true) @@ -156,35 +173,46 @@ true)) (defn collection? - "`true` iff `x` quacks like a collection of type `type`, else `false`. + "`true` iff `x` quacks like a collection of type `object-type`, else `false`. With one argument, will recognise plain collections and ordered collections, but (currently) not collection pages." - ([x type] + ([x ^String object-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 + (:orderedItems x) (nil? (:items x)) ;; can't have both properties + (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems. + :else false) (object? x) - (= (:type x) type) - (coll? items) - (every? object? items) - (integer? (:totalItems x)) - true))) + (= (:type x) object-type) + (if items + (and (coll? items) + (every? object? items) ;; if there are items, they must form a + ;; collection of objects. + true) + true) ;; but it's OK if there aren't. + true) + ;; test for totalItems not done here, because collection pages don't + ;; have it. + )) ([x] - (or (collection? x "Collection") - (collection? x "OrderedCollection")))) + (and + (or (collection? x "Collection") + (collection? x "OrderedCollection")) + (integer? (:totalItems x)) + true))) (defn unordered-collection? "`true` iff `x` quacks like an unordered collection, else `false`." [x] - (collection? x "Collection")) + (and (collection? x "Collection") (integer? (:totalItems x)) true)) (defn ordered-collection? "`true` iff `x` quacks like an ordered collection, else `false`." [x] - (collection? x "OrderedCollection")) + (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true)) (defn collection-page? "`true` iff `x` quacks like a page in a paged collection, else `false`." diff --git a/src/dog_and_duck/scratch/scratch.clj b/src/dog_and_duck/scratch/scratch.clj index f4319d1..d96f6ee 100644 --- a/src/dog_and_duck/scratch/scratch.clj +++ b/src/dog_and_duck/scratch/scratch.clj @@ -42,7 +42,7 @@ (apply webfinger/fetch-user-id (map handle [:domain :username])))))) ;;; examine what you got back! -(:outbox account) +(:inbox account) (def rsa (pgp-gen/rsa-keypair-generator 2048)) diff --git a/src/dog_and_duck/utils/process.clj b/src/dog_and_duck/utils/process.clj new file mode 100644 index 0000000..d52acfe --- /dev/null +++ b/src/dog_and_duck/utils/process.clj @@ -0,0 +1,23 @@ +(ns dog-and-duck.utils.process + (:require [clojure.string :refer [split]])) + +(def pid + "OK, this is hacky as fuck, but I hope it works. The problem is that the + way to get the process id has changed several times during the history + of Java development, and the code for one version of Java won't even compile + in a different version." + (let [java-version (read-string (apply str (take 2 + (split + (System/getProperty "java.version") + #"[_\.]")))) + cmd (case java-version + 18 "(let [[_ pid hostname] + (re-find + #\"^(\\d+)@(.*)\" + (.getName + (java.lang.management.ManagementFactory/getRuntimeMXBean)))] + pid)" + (19 110) "(.pid (java.lang.ProcessHandle/current))" + 111 "(.getPid (java.lang.management.ManagementFactory/getRuntimeMXBean))" + ":default")] + (eval (read-string cmd)))) diff --git a/test/dog_and_duck/quack/quack_test.clj b/test/dog_and_duck/quack/quack_test.clj index cf699ae..973a67d 100644 --- a/test/dog_and_duck/quack/quack_test.clj +++ b/test/dog_and_duck/quack/quack_test.clj @@ -1,8 +1,10 @@ (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? + [dog-and-duck.quack.picky :refer [activitystreams-context-uri + context?]] + [dog-and-duck.quack.quack :refer [actor? actor-type? + object? ordered-collection-page? + persistent-object? verb-type?]] [dog-and-duck.scratch.parser :refer [clean]])) @@ -126,4 +128,14 @@ (let [expected true actual (actor? (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first :actor))] (is (= actual expected) "A Person is an actor")) + )) + +(deftest ordered-collection-page-test + (testing "identification of ordered collection pages." + (let [expected false + actual (ordered-collection-page? (-> "resources/activitystreams-test-documents/simple0020.json" slurp clean first))] + (is (= actual expected) "A Note is not an ordered collection page.")) +(let [expected true + actual (ordered-collection-page? (-> "resources/test_documents/outbox_page.json" slurp clean first))] + (is (= actual expected) "A page from an outbox is an ordered collection page.")) )) \ No newline at end of file