guile-fediverse/activitystreams/activitystreams.scm

activitystreams.scm

1
;;;; Copyright (C) 2020 Julien Lepiller <julien@lepiller.eu>
2
;;;; 
3
;;;; This library is free software; you can redistribute it and/or
4
;;;; modify it under the terms of the GNU Lesser General Public
5
;;;; License as published by the Free Software Foundation; either
6
;;;; version 3 of the License, or (at your option) any later version.
7
;;;; 
8
;;;; This library is distributed in the hope that it will be useful,
9
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11
;;;; Lesser General Public License for more details.
12
;;;; 
13
;;;; You should have received a copy of the GNU Lesser General Public
14
;;;; License along with this library; if not, write to the Free Software
15
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
;;;; 
17
18
(define-module (activitystreams activitystreams)
19
  #:use-module (ice-9 match)
20
  #:use-module (srfi srfi-9)
21
  #:export (as:type
22
            as:type?
23
            as:type-label
24
            as:type-uri
25
            as:type-comment
26
            as:type-subclass-of
27
28
            as:property
29
            as:property?
30
            as:property-label
31
            as:property-uri
32
            as:property-range
33
            as:property-domain
34
            as:property-functional?
35
            as:property-subproperty-of
36
            as:property-comment
37
38
            make-as-type
39
            make-as-property
40
41
            make-ontological-parser
42
            as-ref
43
            as:type-of-type?
44
            as:property-of-type?))
45
46
(define as:vocab "https://www.w3.org/ns/activitystreams#")
47
48
(define-record-type as:type
49
  (make-as:type label uri comment subclass-of)
50
  as:type?
51
  (label       as:type-label)
52
  (uri         as:type-uri)
53
  (comment     as:type-comment)
54
  (subclass-of as:type-subclass-of))
55
56
(define* (make-as-type label #:key (uri (string-append as:vocab label))
57
                       (comment "") (subclass-of '()))
58
  (make-as:type label uri comment subclass-of))
59
60
(define-record-type as:property
61
  (make-as:property label uri range domain functional? subproperty-of comment)
62
  as:property?
63
  (label          as:property-label)
64
  (uri            as:property-uri)
65
  (domain         as:property-domain)
66
  (range          as:property-range)
67
  (functional?    as:property-functional?)
68
  (subproperty-of as:property-subproperty-of)
69
  (comment        as:property-comment))
70
71
(define* (make-as-property label domain range
72
                           #:key (uri (string-append as:vocab label))
73
                           (functional? #f) (subproperty-of '()) (comment ""))
74
  (make-as:property label uri range domain functional? subproperty-of comment))
75
76
(define (make-ontological-parser ontology)
77
  "Creates a parser from an ontology: takes json document as input and outputs
78
the best possible document as an alist (or array of alists) whose keys are an
79
as:property and value is the associated expanded value.  It also perfoms type
80
checking to ensure the document is well-parsed.  When the document doesn't respect
81
the ontology, throws an exception.  Type is in the key @type and its value
82
is a as:type."
83
  (define (candidate-type type)
84
    (filter
85
      (lambda (o)
86
        (and
87
          (as:type? o)
88
          (equal? (as:type-uri o) type)))
89
      ontology))
90
91
  (define (candidate-property name)
92
    (filter
93
      (lambda (p)
94
        (and
95
          (as:property? p)
96
          (equal? (as:property-uri p) name)))
97
      ontology))
98
99
  (define (in-range property value)
100
    (match (as:property-range property)
101
      ((? string? type)
102
       (as:type-of-type? (assoc-ref value "@type") type))
103
      ((? procedure? proc)
104
       (proc value))
105
      (()
106
       #f)
107
      ((? list? l)
108
       (or (in-range (car l) value) (in-range (cdr l) value)))
109
      (range (throw 'invalid-property-range range))))
110
111
  (define (in-domain property type)
112
    (match (as:property-domain property)
113
      ((? string? parent)
114
       (as:type-of-type? type parent))
115
      ((? procedure? proc)
116
       (proc type))
117
      (()
118
       #f)
119
      ((? list? l)
120
       (or (in-domain (car l) type) (in-domain (cdr l) type)))))
121
122
  (define (parse json)
123
    (if (assoc-ref json "@value")
124
      json
125
      ;; TODO: there might be more than one @type...
126
      (let ((type (car (candidate-type (assoc-ref json "@type")))))
127
        (let loop ((result `(("@type" . ,type))) (json json))
128
          (match json
129
            (() result)
130
            (((k . v) json ...)
131
             (let ((candidates (candidate-property k)))
132
               (cond
133
                 ((equal? k "@type")
134
                  (loop result json))
135
                 ((null? candidates)
136
                  (loop result json))
137
                 (else
138
                   (let ((property (car candidates))
139
                         (value (parse v)))
140
                     (unless (in-range property value)
141
                       (throw 'value-type-mismatch property value))
142
                     (unless (in-domain property type)
143
                       (throw 'property-not-supported property value))
144
                     (loop (cons (cons property value) result) json)))))))))))
145
146
  parse)
147
148
(define (as:property-of-type? property label)
149
  (or (equal? (as:property-label property) label)
150
      (let loop ((subproperties (as:property-subproperty-of property)))
151
        (match subproperties
152
          (() #f)
153
          ((property subproperties ...)
154
           (or (as:property-of-type? property label)
155
               (loop subproperties)))))))
156
157
(define (as:type-of-type? type label)
158
  (or (equal? (as:type-label type) label)
159
      (let loop ((subclasses (as:type-subclass-of type)))
160
        (match subclasses
161
          (() #f)
162
          ((type subclasses ...)
163
           (or (as:type-of-type? type label)
164
               (loop subclasses)))))))
165
166
(define (as-ref document key)
167
  "Takes a parsed document and returns the value associated with the property.
168
This takes care of subproperties: if you look for a property that's not in the
169
document directly, but the document has a subproperty of it, this will be
170
returned.  The key must be a proper label as defined in the ontology."
171
  (define (is-candidate kv)
172
    (match kv
173
      ((k . v)
174
       (as:property-of-type? k key))))
175
  (let ((candidates (filter is-candidate document)))
176
    (map cdr candidates)))
177