-
Notifications
You must be signed in to change notification settings - Fork 1
/
lang.lisp
269 lines (210 loc) · 10.5 KB
/
lang.lisp
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
(in-package :odata/lang)
(defgeneric odata-url (object)
(:documentation "Get the ODATA service url of OBJECT."))
(defmethod odata-url ((uri quri:uri))
uri)
(defmethod odata-url ((string string))
(quri:uri string))
(defmethod odata-url ((entity odata/entity:odata-entity))
(odata/entity:odata-id entity))
(defmethod odata-url ((entity-set odata/entity:odata-entity-set))
(odata/entity:odata-context entity-set))
(defun read-odata-response (data type)
"Read the ODATA response from DATA.
TYPE indicates how to do it:
- If NIL, data is left as it is, an association list (the default).
- If :COLLECTION, then the collection elements are returned in an association list.
- If :VALUE, then the property value is returned.
- If T, then read the response as an ODATA entity. See ODATA/ENTITY:READ-ODATA-RESPONSE.
"
(cond
((null type) data)
((eql type :collection) (access data :value))
((eql type :value) (access data :value))
(t (odata/entity:read-odata-response data))))
(defun singleton (url name)
"Access the ODATA singleton with name NAME at URL.
See: https://www.odata.org/getting-started/advanced-tutorial/#querySingleton ."
(quri:uri (format nil "~a~a" (odata-url url)
(if (stringp name) name
(odata-client::lisp-to-camel-case (string name))))))
(defun fetch (url &optional type)
"Perform and HTTP GET to the ODATA URL."
(let ((data (odata-client:odata-get url)))
(read-odata-response data type)))
(defun post (url &optional data)
"Perform an HTTP POST to the ODATA URL."
(odata-client:odata-post url data))
(defun link (url data)
"Add a link to a related entity.
Relationships from one entity to another are represented as navigation properties.
A successful POST request to a navigation property's references collection adds a relationship to an existing entity.
Example: add 'vincentcalabrese' to friends of 'scottketchum'
(-> +trip-pin-modify+
(collection \"People\") (id \"scottketchum\")
(property \"Friends\") ($ref)
(link `((\"@odata.context\" . ,(quri:render-uri +trip-pin-modify+))
(\"@odata.id\" . \"People('vincentcalabrese')\"))))
"
(multiple-value-bind (response status)
(odata-client::http-request (quri:render-uri (odata-url url))
:method :post
:preserve-uri t
:content (odata-client::encode-json-to-string data)
:content-type "application/json"
:accept "application/json")
(when (>= status 400)
(error "Error ~a: ~a" status (accesses (odata-client::decode-json-from-string response) :error :message)))))
(defun update-link (url data)
"Update an already existent link.
A successful PUT request to a single-valued navigation property’s reference resource changes the related entity.
Example: change the Airline of a Flight
(-> +trip-pin-modify+
(collection \"People\")
(id \"russellwhyte\")
(path \"Trips(0)\"
\"PlanItems(11)\"
\"Microsoft.OData.SampleService.Models.TripPin.Flight\"
\"Airline\")
(update-link `((\"@odata.context\" . ,(quri:render-uri +trip-pin-modify+))
(\"@odata.id\" . \"Airlines('FM')\"))))
"
(multiple-value-bind (response status)
(odata-client::http-request (quri:render-uri (odata-url url))
:method :put
:preserve-uri t
:content (odata-client::encode-json-to-string data)
:content-type "application/json"
:accept "application/json")
(when (>= status 400)
(error "Error ~a: ~a" status (accesses (odata-client::decode-json-from-string response) :error :message)))))
(defun create (url data)
"Perform a resource creation request with DATA at ODATA service at URL."
(post (odata-url url) data))
(defun del (url)
"Perform a resource deletion request at ODATA service at URL."
(odata-client::http-request (quri:render-uri (odata-url url))
:method :delete
:preserve-uri t))
(defun patch (url data)
"Perform a resource PATCH request with DATA to ODATA service at URL."
(odata-client:odata-patch (odata-url url) data))
(defun update (url data)
"Perform a resource update (PUT request) with DATA to ODATA service at URL."
(odata-client::odata-put (odata-url url) data))
(defun property (url name)
"Access the resource property with name NAME."
(quri:uri (format nil "~a/~a" (odata-url url)
(if (stringp name) name
(odata-client::lisp-to-camel-case (string name))))))
(defun collection (url name)
"Access the resource collection at NAME."
(quri:uri (format nil "~a/~a" (odata-url url)
(if (stringp name) name
(odata-client::lisp-to-camel-case (string name))))))
(defun id (url id)
"Get ODATA resource id."
(quri:uri (format nil "~a('~a')" (odata-url url) id)))
(defun parameter (url param value)
"Add parameter PARAM with VALUE to current request."
(push (cons param value)
(quri:uri-query-params url))
url)
(defun $filter (url exp)
"Add ODATA $filter parameter to URL.
The $filter system query option allows clients to filter a collection of resources that are addressed by a request URL. The expression specified with $filter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response.
See: ODATA-CLIENT::compile-$filter
See: https://www.odata.org/getting-started/basic-tutorial/#filter"
(parameter url "$filter" (odata-client::compile-$filter exp)))
(defun $expand (url exp)
"Add ODATA $expand parameter to URL.
The $expand system query option specifies the related resources to be included in line with retrieved resources.
EXP is the list of things to expand.
Examples:
'(\"asdf\" \"foo\")) => \"asdf,foo\"
'(\"asdf\" \"foo\" (\"Bar\" \"Baz\")) => \"asdf,foo,Bar/Baz\"
See: ODATA-CLIENT::COMPILE-$EXPAND .
See: https://www.odata.org/getting-started/basic-tutorial/#expand ."
(parameter url "$expand" (odata-client::compile-$expand exp)))
(defun $select (url exp)
"Adds ODATA $select parameter to URL.
The $select system query option allows the clients to requests a limited set of properties for each entity.
EXP can be either a string or a list of strings.
Elements of EXP are just separated by comma.
Examples:
(compile-$select \"name\") => \"foo\"
(compile-$select '(\"name\" \"surname\")) => \"name,surname\"
See: ODATA-CLIENT::COMPILE-$SELECT
See: https://www.odata.org/getting-started/basic-tutorial/#select"
(parameter url "$select" (odata-client::compile-$select exp)))
(defun $search (url exp)
"The $search system query option restricts the result to include only those entities matching the specified search expression.
See: https://www.odata.org/getting-started/basic-tutorial/#search"
(parameter url "$search" (odata-client::compile-$search exp)))
(defun $top (url top)
"The $top system query option requests the number of items in the queried collection to be included in the result.
See: https://www.odata.org/getting-started/basic-tutorial/#topskip"
(check-type top integer)
(parameter url "$top" top))
(defun $skip (url skip)
"The $skip query option requests the number of items in the queried collection that are to be skipped and not included in the result.
See: https://www.odata.org/getting-started/basic-tutorial/#topskip"
(check-type skip integer)
(parameter url "$skip" skip))
(defun $value (url)
"Address the raw value of a primitive property.
Example: returns the raw value of property Name of an Airport.
(-> +trip-pin-modify+
(collection \"Airports\") (id \"KSFO\")
(property \"Name\") ($value)
See: https://www.odata.org/getting-started/basic-tutorial/#propertyVal
"
(property url "$value"))
(defun $orderby (url property &optional (order :asc))
"The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc. If asc or desc not specified, then the resources will be ordered in ascending order."
(check-type order (member :asc :desc))
(parameter url "$orderby" (format nil "~a ~a" property
(string-downcase (princ-to-string order)))))
(defun $count (url)
"The $count system query option allows clients to request a count of the matching resources included with the resources in the response."
(parameter url "$count" "true"))
(defun $ref (url)
"A successful POST request to a navigation property's references collection adds a relationship to an existing entity."
(property url "$ref"))
(defun path (url &rest path)
"Access entity in a PATH.
Example:
(-> +trip-pin-modify+
(collection \"People\")
(id \"russellwhyte\")
(path \"Trips(0)\"
\"PlanItems(11)\"
\"Microsoft.OData.SampleService.Models.TripPin.Flight\"
\"Airline\"))"
(let ((uri (odata-url url)))
(loop
for x in path
do (setf uri (property uri x)))
uri))
(defun fcall (url name &rest args)
"Call the ODATA action with name NAME and arguments ARGS.
Actions are operations exposed by an OData service that MAY have side effects when invoked. Actions MAY return data but MUST NOT be further composed with additional path segments.
See: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_Actions"
(property url
(with-output-to-string (s)
(flet ((print-arg (arg)
(princ (odata-client::lisp-to-camel-case (string (car arg))) s)
(princ "=" s)
(princ (cdr arg) s)))
(princ (if (stringp name) name
(string-upcase (odata-client::lisp-to-camel-case (string name)) :end 1))
s)
(princ "(" s)
(when args
(let ((args (alexandria:plist-alist args)))
(print-arg (first args))
(loop
for arg in (rest args)
do (princ "," s)
do (print-arg arg))))
(princ ")" s)))))