|
16 | 16 | import lombok.NonNull;
|
17 | 17 | import org.springframework.beans.factory.annotation.Value;
|
18 | 18 | import org.springframework.boot.test.web.client.TestRestTemplate;
|
| 19 | +import org.springframework.core.io.ClassPathResource; |
19 | 20 | import org.springframework.core.io.Resource;
|
20 | 21 | import org.springframework.core.io.ResourceLoader;
|
21 | 22 | import org.springframework.http.HttpEntity;
|
22 | 23 | import org.springframework.http.HttpHeaders;
|
23 | 24 | import org.springframework.http.HttpMethod;
|
24 | 25 | import org.springframework.http.ResponseEntity;
|
25 | 26 | import org.springframework.lang.Nullable;
|
| 27 | +import org.springframework.util.LinkedMultiValueMap; |
26 | 28 | import org.springframework.util.MultiValueMap;
|
27 | 29 | import org.springframework.util.StreamUtils;
|
28 | 30 |
|
@@ -239,13 +241,23 @@ public GraphQLResponse perform(
|
239 | 241 | ObjectNode variables,
|
240 | 242 | List<String> fragmentResources)
|
241 | 243 | throws IOException {
|
| 244 | + String payload = getPayload(graphqlResource, operationName, variables, fragmentResources); |
| 245 | + return post(payload); |
| 246 | + } |
| 247 | + |
| 248 | + /** Generate GraphQL payload, which consist of 3 elements: query, operationName and variables */ |
| 249 | + private String getPayload( |
| 250 | + String graphqlResource, |
| 251 | + String operationName, |
| 252 | + ObjectNode variables, |
| 253 | + List<String> fragmentResources) |
| 254 | + throws IOException { |
242 | 255 | StringBuilder sb = new StringBuilder();
|
243 | 256 | for (String fragmentResource : fragmentResources) {
|
244 | 257 | sb.append(loadQuery(fragmentResource));
|
245 | 258 | }
|
246 | 259 | String graphql = sb.append(loadQuery(graphqlResource)).toString();
|
247 |
| - String payload = createJsonQuery(graphql, operationName, variables); |
248 |
| - return post(payload); |
| 260 | + return createJsonQuery(graphql, operationName, variables); |
249 | 261 | }
|
250 | 262 |
|
251 | 263 | /**
|
@@ -279,6 +291,76 @@ public GraphQLResponse postMultipart(String query, String variables) {
|
279 | 291 | return postRequest(RequestFactory.forMultipart(query, variables, headers));
|
280 | 292 | }
|
281 | 293 |
|
| 294 | + /** |
| 295 | + * Handle the multipart files upload request to GraphQL servlet |
| 296 | + * |
| 297 | + * <p>In contrast with usual the GraphQL request with body as json payload (consist of query, |
| 298 | + * operationName and variables), multipart file upload request will use multipart/form-data body |
| 299 | + * with the following structure: |
| 300 | + * |
| 301 | + * <ul> |
| 302 | + * <li><b>operations</b> the payload that we used to use for the normal GraphQL request |
| 303 | + * <li><b>map</b> a map for referencing between one part of multi-part request and the |
| 304 | + * corresponding <i>Upload</i> element inside <i>variables</i> |
| 305 | + * <li>a consequence of upload files embedded into the multi-part request, keyed as numeric |
| 306 | + * number starting from 1, valued as File payload of usual multipart file upload |
| 307 | + * </ul> |
| 308 | + * |
| 309 | + * <p>Example uploading two files: |
| 310 | + * |
| 311 | + * <p>* Please note that we can't embed binary data into json. Clients library supporting graphql |
| 312 | + * file upload will set variable.files to null for every element inside the array, but each file |
| 313 | + * will be a part of multipart request. GraphQL Servlet will use <i>map</i> part to walk through |
| 314 | + * variables.files and validate the request in combination with other binary file parts |
| 315 | + * |
| 316 | + * <p>-------------- request beginning --------------- |
| 317 | + * |
| 318 | + * <p>operations: { "query": "mutation($files:[Upload]!) {uploadFiles(files:$files)}", |
| 319 | + * "operationName": "uploadFiles", "variables": { "files": [null, null] } } |
| 320 | + * |
| 321 | + * <p>----------------------------------------------- |
| 322 | + * |
| 323 | + * <p>map: { "1":["variables.files.0"], "2":["variables.files.1"] } |
| 324 | + * |
| 325 | + * <p>----------------------------------------------- |
| 326 | + * |
| 327 | + * <p>1: --file 1 binary code-- |
| 328 | + * |
| 329 | + * <p>----------------------------------------------- |
| 330 | + * |
| 331 | + * <p>2: --file 2 binary code-- |
| 332 | + * |
| 333 | + * <p>-------------- request end --------------------- |
| 334 | + * |
| 335 | + * @param graphqlResource path to the classpath resource containing the GraphQL query |
| 336 | + * @param variables the input variables for the GraphQL query |
| 337 | + * @param files ClassPathResource instance for each file that will be uploaded to GraphQL server. |
| 338 | + * When Spring RestTemplate processes the request, it will automatically produce a valid part |
| 339 | + * representing given file inside multipart request (including size, submittedFileName, etc.) |
| 340 | + * @return {@link GraphQLResponse} containing the result of query execution |
| 341 | + * @throws IOException if the resource cannot be loaded from the classpath |
| 342 | + */ |
| 343 | + public GraphQLResponse postFiles( |
| 344 | + String graphqlResource, ObjectNode variables, List<ClassPathResource> files) |
| 345 | + throws IOException { |
| 346 | + |
| 347 | + MultiValueMap<String, Object> values = new LinkedMultiValueMap<>(); |
| 348 | + MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); |
| 349 | + |
| 350 | + for (int i = 0; i < files.size(); i++) { |
| 351 | + String valueKey = String.valueOf(i + 1); // map value and part index starts at 1 |
| 352 | + map.add(valueKey, String.format("variables.files.%d", i)); |
| 353 | + |
| 354 | + values.add(valueKey, files.get(i)); |
| 355 | + } |
| 356 | + |
| 357 | + String payload = getPayload(graphqlResource, null, variables, Collections.emptyList()); |
| 358 | + values.add("operations", payload); |
| 359 | + values.add("map", map); |
| 360 | + |
| 361 | + return postRequest(RequestFactory.forMultipart(values, headers)); |
| 362 | + } |
| 363 | + |
282 | 364 | /**
|
283 | 365 | * Performs a GraphQL request with the provided payload.
|
284 | 366 | *
|
|
0 commit comments