graphql.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package v1
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "net/http"
  15. "devt.de/krotik/common/lang/graphql/parser"
  16. "devt.de/krotik/common/stringutil"
  17. "devt.de/krotik/eliasdb/api"
  18. "devt.de/krotik/eliasdb/graphql"
  19. )
  20. /*
  21. EndpointGraphQL is the GraphQL endpoint URL (rooted). Handles everything under graphql/...
  22. */
  23. const EndpointGraphQL = api.APIRoot + APIv1 + "/graphql/"
  24. /*
  25. GraphQLEndpointInst creates a new endpoint handler.
  26. */
  27. func GraphQLEndpointInst() api.RestEndpointHandler {
  28. return &graphQLEndpoint{}
  29. }
  30. /*
  31. Handler object for GraphQL operations.
  32. */
  33. type graphQLEndpoint struct {
  34. *api.DefaultEndpointHandler
  35. }
  36. /*
  37. HandlePOST handles GraphQL queries.
  38. */
  39. func (e *graphQLEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
  40. var err error
  41. var res map[string]interface{}
  42. dec := json.NewDecoder(r.Body)
  43. data := make(map[string]interface{})
  44. if err := dec.Decode(&data); err != nil {
  45. http.Error(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
  46. return
  47. }
  48. toAST, ok1 := data["query-to-ast"]
  49. toQuery, ok2 := data["ast-to-query"]
  50. if ok1 || ok2 {
  51. res := make(map[string]interface{})
  52. if ok1 {
  53. resast, err := parser.Parse("request", fmt.Sprint(toAST))
  54. if err != nil {
  55. http.Error(w, err.Error(), http.StatusBadRequest)
  56. return
  57. }
  58. res["result-ast"] = resast.Plain()
  59. }
  60. if ok2 {
  61. astmap, ok := toQuery.(map[string]interface{})
  62. if !ok {
  63. http.Error(w, "Plain AST object expected as 'ast-to-query' value", http.StatusBadRequest)
  64. return
  65. }
  66. // Try to create a proper AST from plain AST
  67. astnode, err := parser.ASTFromPlain(astmap)
  68. if err != nil {
  69. http.Error(w, err.Error(), http.StatusBadRequest)
  70. return
  71. }
  72. // Now pretty print the AST
  73. ppres, err := parser.PrettyPrint(astnode)
  74. if err != nil {
  75. http.Error(w, err.Error(), http.StatusBadRequest)
  76. return
  77. }
  78. res["result-query"] = ppres
  79. }
  80. w.Header().Set("content-type", "application/json; charset=utf-8")
  81. json.NewEncoder(w).Encode(res)
  82. return
  83. } else {
  84. partData, ok := data["partition"]
  85. if !ok && len(resources) > 0 {
  86. partData = resources[0]
  87. ok = true
  88. }
  89. if !ok || partData == "" {
  90. http.Error(w, "Need a partition", http.StatusBadRequest)
  91. return
  92. }
  93. part := fmt.Sprint(partData)
  94. if _, ok := data["variables"]; !ok {
  95. data["variables"] = nil
  96. }
  97. if _, ok := data["operationName"]; !ok {
  98. data["operationName"] = nil
  99. }
  100. res, err = graphql.RunQuery(stringutil.CreateDisplayString(part)+" query",
  101. part, data, api.GM, nil, false)
  102. }
  103. if err != nil {
  104. http.Error(w, err.Error(), http.StatusBadRequest)
  105. return
  106. }
  107. w.Header().Set("content-type", "application/json; charset=utf-8")
  108. json.NewEncoder(w).Encode(res)
  109. }
  110. /*
  111. SwaggerDefs is used to describe the endpoint in swagger.
  112. */
  113. func (e *graphQLEndpoint) SwaggerDefs(s map[string]interface{}) {
  114. graphqlRequestParam := map[string]interface{}{
  115. "name": "graphql_request",
  116. "in": "body",
  117. "description": "GraphQL request",
  118. "required": true,
  119. "schema": map[string]interface{}{
  120. "$ref": "#/definitions/GraphQLRequest",
  121. },
  122. }
  123. s["paths"].(map[string]interface{})["/v1/graphql/{partition}"] = map[string]interface{}{
  124. "post": map[string]interface{}{
  125. "summary": "GraphQL interface.",
  126. "description": "The GraphQL interface can be used to query and modify data.",
  127. "consumes": []string{
  128. "application/json",
  129. },
  130. "produces": []string{
  131. "text/plain",
  132. "application/json",
  133. },
  134. "parameters": []map[string]interface{}{
  135. {
  136. "name": "partition",
  137. "in": "path",
  138. "description": "Partition to query.",
  139. "required": true,
  140. "type": "string",
  141. },
  142. graphqlRequestParam,
  143. },
  144. "responses": map[string]interface{}{
  145. "200": map[string]interface{}{
  146. "description": "The operation was successful.",
  147. },
  148. "default": map[string]interface{}{
  149. "description": "Error response",
  150. "schema": map[string]interface{}{
  151. "$ref": "#/definitions/Error",
  152. },
  153. },
  154. },
  155. },
  156. }
  157. s["definitions"].(map[string]interface{})["GraphQLRequest"] = map[string]interface{}{
  158. "type": "object",
  159. "properties": map[string]interface{}{
  160. "operationName": map[string]interface{}{
  161. "description": "GraphQL query operation name.",
  162. "type": "string",
  163. },
  164. "query": map[string]interface{}{
  165. "description": "GraphQL query.",
  166. "type": "string",
  167. },
  168. "variables": map[string]interface{}{
  169. "description": "GraphQL query variable values.",
  170. "type": "object",
  171. },
  172. },
  173. }
  174. s["paths"].(map[string]interface{})["/v1/graphql"] = map[string]interface{}{
  175. "post": map[string]interface{}{
  176. "summary": "GraphQL parser and pretty printer endpoint.",
  177. "description": "The GraphQL endpoint without specifying a partition should be used to parse a given GraphQL query into an Abstract Syntax Tree or pretty print a given Abstract Syntax Tree into a GraphQL query.",
  178. "consumes": []string{
  179. "application/json",
  180. },
  181. "produces": []string{
  182. "text/plain",
  183. "application/json",
  184. },
  185. "parameters": []map[string]interface{}{
  186. {
  187. "name": "data",
  188. "in": "body",
  189. "description": "Query or AST which should be converted.",
  190. "required": true,
  191. "schema": map[string]interface{}{
  192. "type": "object",
  193. "properties": map[string]interface{}{
  194. "query-to-ast": map[string]interface{}{
  195. "description": "Query which should be parsed.",
  196. "type": "string",
  197. },
  198. "ast-to-query": map[string]interface{}{
  199. "description": "AST which should be pretty printed.",
  200. "type": "object",
  201. },
  202. },
  203. },
  204. },
  205. },
  206. "responses": map[string]interface{}{
  207. "200": map[string]interface{}{
  208. "description": "The operation was successful.",
  209. "schema": map[string]interface{}{
  210. "type": "object",
  211. "properties": map[string]interface{}{
  212. "result-ast": map[string]interface{}{
  213. "description": "The resulting AST if a query was parsed.",
  214. "type": "object",
  215. },
  216. "result-query": map[string]interface{}{
  217. "description": "The pretty printed query if an AST was given.",
  218. "type": "string",
  219. },
  220. },
  221. },
  222. },
  223. "default": map[string]interface{}{
  224. "description": "Error response",
  225. "schema": map[string]interface{}{
  226. "$ref": "#/definitions/Error",
  227. },
  228. },
  229. },
  230. },
  231. }
  232. }