mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 8f6f2705a38e2515b6c57fda12c5be29fb9a798f
commit | author | age
5cb5f7 1 ;;; graphql.el --- GraphQL utilities                 -*- lexical-binding: t; -*-
C 2
3 ;; Copyright (C) 2017  Sean Allred
4
5 ;; Author: Sean Allred <code@seanallred.com>
6 ;; Keywords: hypermedia, tools, lisp
7 ;; Homepage: https://github.com/vermiculus/graphql.el
8 ;; Package-Version: 20180912.31
9 ;; Package-X-Original-Version: 0.1.1
10 ;; Package-Requires: ((emacs "25"))
11
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
16
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
25 ;;; Commentary:
26
27 ;; GraphQL.el provides a generally-applicable domain-specific language
28 ;; for creating and executing GraphQL queries against your favorite
29 ;; web services.
30
31 ;;; Code:
32
33 (require 'pcase)
34
35 (defun graphql--encode-object (obj)
36   "Encode OBJ as a GraphQL string."
37   (cond
38    ((stringp obj)
39     obj)
40    ((symbolp obj)
41     (symbol-name obj))
42    ((numberp obj)
43     (number-to-string obj))
44    ((and (consp obj)
45          (not (consp (cdr obj))))
46     (symbol-name (car obj)))))
47
48 (defun graphql--encode-argument-spec (spec)
49   "Encode an argument spec SPEC.
50 SPEC is of the form..."
51   (graphql--encode-argument (car spec) (cdr spec)))
52
53 (defun graphql--encode-argument (key value)
54   "Encode an argument KEY with value VALUE."
55   (format "%s:%s" key (graphql--encode-argument-value value)))
56
57 (defun graphql--encode-argument-value (value)
58   "Encode an argument value VALUE.
59 VALUE is expected to be one of the following:
60
61 * a symbol
62 * a 'variable', i.e. \\='($ variableName)
63 * an object (as a list)
64 * a string
65 * a vector of values (e.g., symbols)
66 * a number
67 * something encode-able by `graphql-encode'."
68   (cond
69    ((symbolp value)
70     (symbol-name value))
71    ((eq '$ (car-safe value))
72     (format "$%s" (cadr value)))
73    ((listp value)
74     (format "{%s}" (mapconcat #'graphql--encode-argument-spec value ",")))
75    ((stringp value)
76     (format "\"%s\"" value))
77    ((vectorp value)
78     (format "[%s]" (mapconcat #'graphql-encode value ",")))
79    ((numberp value)
80     (number-to-string value))
81    (t
82     (graphql-encode value))))
83
84 (defun graphql--encode-parameter-spec (spec)
85   "Encode a parameter SPEC.
86 SPEC is expected to be of the following form:
87
88    (NAME TYPE [REQUIRED] . [DEFAULT])
89
90 NAME is the name of the parameter.
91
92 TYPE is the parameter's type.
93
94 A non-nil value for REQUIRED will indicate the parameter is
95 required.  A value of `!' is recommended.
96
97 A non-nil value for DEFAULT will provide a default value for the
98 parameter."
99   ;; Unfortunately can't use `pcase' here because the first DEFAULT
100   ;; value (in the case of a complex value) might be misunderstood as
101   ;; the value for REQUIRED.  We need to know if the third cons is the
102   ;; very last one; not just that the list has at least three
103   ;; elements.
104   (if (eq (last spec) (nthcdr 2 spec))
105       (graphql--encode-parameter (nth 0 spec)
106                                  (nth 1 spec)
107                                  (car (last spec))
108                                  (cdr (last spec)))
109     (graphql--encode-parameter (nth 0 spec)
110                                (nth 1 spec)
111                                nil
112                                (nthcdr 2 spec))))
113
114 (defun graphql--encode-parameter (name type &optional required default)
115   "Encode a GraphQL parameter with a NAME and TYPE.
116 If REQUIRED is non-nil, mark the parameter as required.
117 If DEFAULT is non-nil, is the default value of the parameter."
118   (format "$%s:%s%s%s"
119           (symbol-name name)
120           (symbol-name type)
121           (if required "!" "")
122           (if default
123               (concat "=" (graphql--encode-argument-value default))
124             "")))
125
126 (defun graphql--get-keys (g)
127   "Get the keyword arguments from a graph G.
128 Returns a list where the first element is a plist of arguments
129 and the second is a 'clean' copy of G."
130   (or (and (not (consp g))
131            (list nil g))
132       (let (graph keys)
133         (while g
134           (if (keywordp (car g))
135               (let* ((param (pop g))
136                      (value (pop g)))
137                 (push (cons param value) keys))
138             (push (pop g) graph)))
139         (list keys (nreverse graph)))))
140
141 (defun graphql-encode (g)
142   "Encode graph G as a GraphQL string."
143   (pcase (graphql--get-keys g)
144     (`(,keys ,graph)
145      (let ((object (or (car-safe graph) graph))
146            (name (alist-get :op-name keys))
147            (params (alist-get :op-params keys))
148            (arguments (alist-get :arguments keys))
149            (fields (cdr-safe graph)))
150        (concat
151         (graphql--encode-object object)
152         (when name
153           (format " %S" name))
154         (when arguments
155           ;; Format arguments "key:value,key:value,..."
156           (format "(%s)"
157                   (mapconcat #'graphql--encode-argument-spec arguments ",")))
158         (when params
159           (format "(%s)"
160                   (mapconcat #'graphql--encode-parameter-spec params ",")))
161         (when fields
162           (format "{%s}"
163                   (mapconcat #'graphql-encode fields " "))))))))
164
165 (defun graphql-simplify-response-edges (data)
166   "Simplify DATA to collapse edges into their nodes."
167   (pcase data
168     ;; When we encounter a collection of edges, simplify those edges
169     ;; into their nodes
170     (`(,object (edges . ,edges))
171      (cons object (mapcar #'graphql-simplify-response-edges
172                           (mapcar (lambda (edge) (alist-get 'node edge))
173                                   edges))))
174     ;; When we encounter a plain cons cell (not a list), let it pass
175     (`(,(and key (guard (not (consp key)))) . ,(and value (guard (not (consp value)))))
176      (cons key value))
177     ;; symbols should pass unaltered
178     (`,(and symbol (guard (symbolp symbol)))
179      symbol)
180     ;; everything else should be mapped
181     (_ (mapcar #'graphql-simplify-response-edges data))))
182
183 (defun graphql--genform-operation (args kind)
184   "Generate the Lisp form for an operation.
185 ARGS is is a list ([NAME [PARAMETERS]] GRAPH) where NAME is the
186 name of the operation, PARAMETERS are its parameters, and GRAPH
187 is the form of the actual operation.
188
189 KIND can be `query' or `mutation'."
190   (pcase args
191     (`(,name ,parameters ,graph)
192      `(graphql-encode '(,kind :op-name ,name
193                               :op-params ,parameters
194                               ,@graph)))
195
196     (`(,name ,graph)
197      `(graphql-encode '(,kind :op-name ,name
198                               ,@graph)))
199
200     (`(,graph)
201      `(graphql-encode '(,kind ,@graph)))
202
203     (_ (error "Bad form"))))
204
205 (defmacro graphql-query (&rest args)
206   "Construct a Query object.
207 ARGS is a listof the form described by `graphql--genform-operation'.
208
209 \(fn [NAME] [(PARAMETER-SPEC...)] GRAPH)"
210   (graphql--genform-operation args 'query))
211
212 (defmacro graphql-mutation (&rest args)
213   "Construct a Mutation object.
214 ARGS is a listof the form described by `graphql--genform-operation'.
215
216 \(fn [NAME] [(PARAMETER-SPEC...)] GRAPH)"
217   (graphql--genform-operation args 'mutation))
218
219 (provide 'graphql)
220 ;;; graphql.el ends here