/
core.clj
220 lines (186 loc) · 7.69 KB
/
core.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
(ns liberator-mixin.core
"Functions for defining liberator mixins: partial liberator resource
definitions that can be composed together to build up a liberator resource
with canned functionality.
The most important function in this namespace is [[build-resource]] which
accepts a sequence of liberator mixins (or resource definition maps) and
produces a liberator resource."
(:require
[clojure.string :as str]
[liberator.core :as liberator]
[liberator.util :as liberator-util]))
(defn is-decision?
"Returns `true` if `k`, a keyword, represents a liberator decision, `false`
otherwise."
[k]
(str/ends-with? (name k) "?"))
(defn is-action?
"Returns `true` if `k`, a keyword, represents a liberator action, `false`
otherwise."
[k]
(or (= k :initialize-context) (str/ends-with? (name k) "!")))
(defn is-handler?
"Returns `true` if `k`, a keyword, represents a liberator handler, `false`
otherwise."
[k]
(str/starts-with? (name k) "handle"))
(defn is-configuration?
"Return `true` if `k`, a keyword, represents a liberator configuration
parameter, `false` otherwise."
[k]
(let [n (name k)]
(or
(= k :patch-content-types)
(and (not (is-decision? k))
(or
(str/starts-with? n "available")
(str/starts-with? n "allowed")
(str/starts-with? n "known"))))))
(defn merge-decisions
"Merges together two liberator decisions, `left` and `right`.
Decisions can return various different shapes of result:
- boolean, i.e., `true` or `false`
- truthy, e.g., `{:foo :bar}` which represents `true` and is used to update
the context
- vector of boolean and context update, e.g., `[true, {:foo :bar}]`
The resulting decision merges these return values in such a way that both
the boolean result of the decision is retained and all context updates are
made correctly.
The decisions are applied in the order `left` first, then `right`, such that
the `right` decision will see any context updates made by the `left`."
[left right comparator]
(fn [context]
(letfn [(if-vector? [thing f]
(if (vector? thing) (f thing) thing))
(execute-and-update [[result context] f]
(let [decision (f context)
comparison (if (nil? result)
(if-vector? decision first)
(comparator result
(if-vector? decision first)))
result (boolean comparison)
context-update (if-vector? decision second)
context (liberator/update-context context context-update)]
[result context]))]
(-> [nil context]
(execute-and-update (liberator-util/make-function left))
(execute-and-update (liberator-util/make-function right))))))
(defn merge-actions
"Merges together two liberator actions, `left` and `right`.
The resulting action will execute both actions in the order `left` first,
then `right`, such that the `right` action will see any context updates made
by the `left`. The result will be that of the `right` action."
[left right]
(fn [context]
(letfn [(execute-and-update [context f]
(liberator/update-context context (f context)))]
(let [left-result (execute-and-update context
(liberator-util/make-function left))
right-result (execute-and-update left-result
(liberator-util/make-function right))]
right-result))))
(defn merge-handlers
"Merges together two liberator handlers, `left` and `right`.
Currently, the `left` handler is discarded and the `right` is used in its
place. In future, this may be improved such that some aspect of the `left`
handler is retained."
[left right]
; TODO: Can we do better than this
right)
(defn merge-configurations
"Merges together two liberator configuration parameters, `left` and `right`.
The resulting configuration parameter will be deduced as follows:
- If `right` includes `:replace` in its metadata, the result will be
`right`.
- If `left` results in a list, the result will be a list containing all
elements from `right` followed by all elements from `left`, such that
`right` takes precedence.
- If `left` results in a vector, the result will be a vector containing all
elements from `right` followed by all elements from `left`, such that
`right` takes precedence.
- If `left` results in a set, the result will be a set containing the union
of `left` and `right`.
- Otherwise, the result will be `right`.
Both `left` and `right` can also be functions taking `context` returning
in line with the above types."
[left right]
(fn merged
([] (merged {}))
([context]
(let [left-conf ((liberator-util/make-function left) context)
right-conf ((liberator-util/make-function right) context)]
(cond
(-> right-conf meta :replace)
right-conf
(and (list? left-conf) (coll? right-conf))
(apply list (concat right-conf left-conf))
(and (vector? left-conf) (coll? right-conf))
(into right-conf left-conf)
(and (set? left-conf) (coll? right-conf))
(into left-conf right-conf)
:otherwise right-conf)))))
(def or-decisions
#{:malformed?
:can-post-to-gone?
:conflict?
:existed?
:moved-permanently?
:moved-temporarily?
:multiple-representations?
:post-redirect?
:put-to-different-url?
:respond-with-entity?
:uri-too-long?})
(defn or-comparator
[left right]
(or left right))
(defn and-comparator
[left right]
(and left right))
(defn get-comparator
[decision]
(if (contains? or-decisions decision)
or-comparator
and-comparator))
(defn merge-resource-definitions
"Merges together multiple liberator resource definitions, specified as maps.
For the mechanism employed:
- for liberator decisions, see [[merge-decisions]],
- for liberator actions, see [[merge-actions]],
- for liberator handlers, see [[merge-handlers]],
- for liberator configuration, see [[merge-configurations]].
Any other map keys that do not correspond to the above liberator definition
types will be retained in the resulting resource definition. If the same
non-liberator definition map key is specified more than once, the rightmost
definition takes precedence."
[& maps]
(let [definition-pieces (mapcat vec maps)]
(reduce
(fn [result [k override]]
(if (contains? result k)
(let [current (get result k)]
(assoc result
k (cond
(is-decision? k) (merge-decisions
current
override
(get-comparator k))
(is-action? k) (merge-actions current override)
(is-handler? k) (merge-handlers current override)
(is-configuration? k) (merge-configurations current override)
:else override)))
(assoc result k override)))
{}
definition-pieces)))
(defn build-resource
"Builds a liberator resource from the specified resource definitions,
specified as either maps or sequences of maps.
This function represents the core of the mixin functionality in that
each mixin produces either a map or a sequence of maps representing partial
resource definitions.
The resource definitions are merged together using
[[merge-resource-definitions]]. See the documentation there for specific
details of the merge process used."
[& ms-or-seqs]
(liberator/resource
(apply merge-resource-definitions (flatten ms-or-seqs))))