From 6e6435908db9e864315b52140af302cbe852fa81 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 16 Jun 2021 15:57:21 -0700 Subject: [PATCH 01/11] Add RequestOptions & handling in RestProxy --- sdk/core/azure-core/pom.xml | 6 + .../com/azure/core/http/RequestOptions.java | 152 ++++++++++++++++++ .../azure/core/http/ResponseStatusOption.java | 19 +++ .../com/azure/core/http/rest/RestProxy.java | 24 ++- .../core/http/rest/SwaggerMethodParser.java | 11 ++ .../RequestOptionsJavaDocCodeSnippets.java | 75 +++++++++ .../azure/core/http/RequestOptionsTests.java | 76 +++++++++ .../http/rest/SwaggerMethodParserTests.java | 37 +++++ 8 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java create mode 100644 sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java create mode 100644 sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java diff --git a/sdk/core/azure-core/pom.xml b/sdk/core/azure-core/pom.xml index 9576ce59ab03a..5fd3dc37a9612 100644 --- a/sdk/core/azure-core/pom.xml +++ b/sdk/core/azure-core/pom.xml @@ -162,6 +162,12 @@ 1.22 test + + javax.json + javax.json-api + 1.1.4 + test + diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java new file mode 100644 index 0000000000000..bec960419ff8f --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http; + +import com.azure.core.util.BinaryData; + +import java.util.function.Consumer; + +/** + * This class contains the options to customize a HTTP request. {@link RequestOptions} can be + * used to configure the request headers, query params, the request body, or add a callback + * to modify all aspects of the HTTP request. + * + *

+ * An instance of fully configured {@link RequestOptions} can be passed to a service method that + * preconfigures known components of the request like URL, path params etc, further modifying both + * un-configured, or preconfigured components. + *

+ * + *

+ * To demonstrate how this class can be used to construct a request, let's use a Pet Store service as an example. The + * list of APIs available on this service are documented in the swagger definition. + *

+ * + *

Creating an instance of DynamicRequest using the constructor

+ * {@codesnippet com.azure.core.experimental.http.requestoptions.instantiation} + ** + *

Configuring the request with a path param and making a HTTP GET request

+ * Continuing with the pet store example, getting information about a pet requires making a + * HTTP GET call + * to the pet service and setting the pet id in path param as shown in the sample below. + * + * {@codesnippet com.azure.core.experimental.http.dynamicrequest.getrequest} + * + *

Configuring the request with JSON body and making a HTTP POST request

+ * To add a new pet to the pet store, a HTTP POST call should + * be made to the service with the details of the pet that is to be added. The details of the pet are included as the + * request body in JSON format. + * + * The JSON structure for the request is defined as follows: + *
{@code
+ * {
+ *   "id": 0,
+ *   "category": {
+ *     "id": 0,
+ *     "name": "string"
+ *   },
+ *   "name": "doggie",
+ *   "photoUrls": [
+ *     "string"
+ *   ],
+ *   "tags": [
+ *     {
+ *       "id": 0,
+ *       "name": "string"
+ *     }
+ *   ],
+ *   "status": "available"
+ * }
+ * }
+ * + * To create a concrete request, Json builder provided in javax package is used here for demonstration. However, any + * other Json building library can be used to achieve similar results. + * + * {@codesnippet com.azure.core.experimental.http.requestoptions.createjsonrequest} + * + * Now, this string representation of the JSON request can be set as body of RequestOptions + * + * {@codesnippet com.azure.core.experimental.http.requestoptions.postrequest} + */ +public class RequestOptions { + private Consumer requestCallback = request -> { }; + private ResponseStatusOption statusOption = ResponseStatusOption.DEFAULT; + + /** + * Gets the request callback, applying all the configurations set on this RequestOptions. + * @return the request callback + */ + public Consumer getRequestCallback() { + return this.requestCallback; + } + + /** + * Gets under what conditions the operation raises an exception if the underlying response indicates a failure. + * @return the configured option + */ + public ResponseStatusOption getStatusOption() { + return this.statusOption; + } + + /** + * Adds a header to the HTTP request. + * @param header the header key + * @param value the header value + * + * @return the modified RequestOptions object + */ + public RequestOptions addHeader(String header, String value) { + this.requestCallback = this.requestCallback.andThen(request -> + request.getHeaders().set(header, value)); + return this; + } + + /** + * Adds a query parameter to the request URL. + * @param parameterName the name of the query parameter + * @param value the value of the query parameter + * @return the modified RequestOptions object + */ + public RequestOptions addQueryParam(String parameterName, String value) { + this.requestCallback = this.requestCallback.andThen(request -> { + String url = request.getUrl().toString(); + request.setUrl(url + (url.contains("?") ? "&" : "?") + parameterName + "=" + value); + }); + return this; + } + + /** + * Adds a custom request callback to modify the HTTP request before it's sent by the HttpClient. + * The modifications made on a RequestOptions object is applied in order on the request. + * + * @param requestCallback the request callback + * @return the modified RequestOptions object + */ + public RequestOptions addRequestCallback(Consumer requestCallback) { + this.requestCallback = this.requestCallback.andThen(requestCallback); + return this; + } + + /** + * Sets the body to send as part of the HTTP request. + * @param requestBody the request body data + * @return the modified RequestOptions object + */ + public RequestOptions setBody(BinaryData requestBody) { + this.requestCallback = this.requestCallback.andThen(request -> { + request.setBody(requestBody.toBytes()); + }); + return this; + } + + /** + * Sets under what conditions the operation raises an exception if the underlying response indicates a failure. + * @param statusOption the option to control under what conditions the operation raises an exception + * @return the modified RequestOptions object + */ + public RequestOptions setStatusOption(ResponseStatusOption statusOption) { + this.statusOption = statusOption; + return this; + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java new file mode 100644 index 0000000000000..dd7ae7d604ae2 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http; + +/** + * ResponseStatusOption controls the behavior of an operation based on the status code of a response. + */ +public enum ResponseStatusOption { + /** + * Indicates that an operation should throw an exception when the response indicates a failure. + */ + DEFAULT, + + /** + * Indicates that an operation should not throw an exception when the response indicates a failure. + */ + NO_THROW; +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java index 4e1bf7b482c37..3ac807b4dfa6c 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java @@ -13,6 +13,8 @@ import com.azure.core.http.HttpPipelineBuilder; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; +import com.azure.core.http.RequestOptions; +import com.azure.core.http.ResponseStatusOption; import com.azure.core.http.policy.CookiePolicy; import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.RetryPolicy; @@ -134,11 +136,17 @@ public Object invoke(Object proxy, final Method method, Object[] args) { request.setBody(validateLength(request)); } + RequestOptions options = methodParser.setRequestOptions(args); + if (options != null) { + options.getRequestCallback().accept(request); + } + final Mono asyncResponse = send(request, context); Mono asyncDecodedResponse = this.decoder.decode(asyncResponse, methodParser); - return handleRestReturnType(asyncDecodedResponse, methodParser, methodParser.getReturnType(), context); + return handleRestReturnType(asyncDecodedResponse, methodParser, + methodParser.getReturnType(), context, options); } catch (IOException e) { throw logger.logExceptionAsError(Exceptions.propagate(e)); } @@ -318,9 +326,9 @@ private HttpRequest configRequest(final HttpRequest request, final SwaggerMethod } private Mono ensureExpectedStatus(final Mono asyncDecodedResponse, - final SwaggerMethodParser methodParser) { + final SwaggerMethodParser methodParser, RequestOptions options) { return asyncDecodedResponse - .flatMap(decodedHttpResponse -> ensureExpectedStatus(decodedHttpResponse, methodParser)); + .flatMap(decodedHttpResponse -> ensureExpectedStatus(decodedHttpResponse, methodParser, options)); } private static Exception instantiateUnexpectedException(final UnexpectedExceptionInformation exception, @@ -365,10 +373,11 @@ private static Exception instantiateUnexpectedException(final UnexpectedExceptio * @return An async-version of the provided decodedResponse. */ private Mono ensureExpectedStatus(final HttpDecodedResponse decodedResponse, - final SwaggerMethodParser methodParser) { + final SwaggerMethodParser methodParser, RequestOptions options) { final int responseStatusCode = decodedResponse.getSourceResponse().getStatusCode(); final Mono asyncResult; - if (!methodParser.isExpectedResponseStatusCode(responseStatusCode)) { + if (!methodParser.isExpectedResponseStatusCode(responseStatusCode) + && (options == null || options.getStatusOption() != ResponseStatusOption.NO_THROW)) { Mono bodyAsBytes = decodedResponse.getSourceResponse().getBodyAsByteArray(); asyncResult = bodyAsBytes.flatMap((Function>) responseContent -> { @@ -490,9 +499,10 @@ private Mono handleBodyReturnType(final HttpDecodedResponse response, private Object handleRestReturnType(final Mono asyncHttpDecodedResponse, final SwaggerMethodParser methodParser, final Type returnType, - final Context context) { + final Context context, + final RequestOptions options) { final Mono asyncExpectedResponse = - ensureExpectedStatus(asyncHttpDecodedResponse, methodParser) + ensureExpectedStatus(asyncHttpDecodedResponse, methodParser, options) .doOnEach(RestProxy::endTracingSpan) .contextWrite(reactor.util.context.Context.of("TRACING_CONTEXT", context)); diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java index 4a8fee390c050..f41006ed078d6 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java @@ -24,6 +24,7 @@ import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpMethod; +import com.azure.core.http.RequestOptions; import com.azure.core.implementation.TypeUtil; import com.azure.core.implementation.UnixTime; import com.azure.core.implementation.http.UnexpectedExceptionInformation; @@ -347,6 +348,16 @@ public Context setContext(Object[] swaggerMethodArguments) { return (context != null) ? context : Context.NONE; } + /** + * Get the {@link RequestOptions} passed into the proxy method. + * + * @param swaggerMethodArguments the arguments passed to the proxy method + * @return the request options + */ + public RequestOptions setRequestOptions(Object[] swaggerMethodArguments) { + return CoreUtils.findFirstOfType(swaggerMethodArguments, RequestOptions.class); + } + /** * Get whether or not the provided response status code is one of the expected status codes for this Swagger * method. diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java new file mode 100644 index 0000000000000..461cb51e5c063 --- /dev/null +++ b/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http; + +import com.azure.core.util.BinaryData; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; + +/** + * JavaDoc code snippets for {@link RequestOptions}. + */ +public class RequestOptionsJavaDocCodeSnippets { + + /** + * Sample to demonstrate how to create an instance of {@link RequestOptions}. + * @return An instance of {@link RequestOptions}. + */ + public RequestOptions createInstance() { + // BEGIN: com.azure.core.experimental.http.requestoptions.instantiation + RequestOptions options = new RequestOptions() + .setBody(BinaryData.fromString("{\"name\":\"Fluffy\"}")) + .addHeader("x-ms-pet-version", "2021-06-01"); + // END: com.azure.core.experimental.http.requestoptions.instantiation + return options; + } + + /** + * Sample to demonstrate setting the JSON request body in a {@link RequestOptions}. + * @return An instance of {@link RequestOptions}. + */ + public RequestOptions setJsonRequestBodyInRequestOptions() { + // BEGIN: com.azure.core.experimental.http.requestoptions.createjsonrequest + JsonArray photoUrls = Json.createArrayBuilder() + .add("https://imgur.com/pet1") + .add("https://imgur.com/pet2") + .build(); + + JsonArray tags = Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("id", 0) + .add("name", "Labrador") + .build()) + .add(Json.createObjectBuilder() + .add("id", 1) + .add("name", "2021") + .build()) + .build(); + + JsonObject requestBody = Json.createObjectBuilder() + .add("id", 0) + .add("name", "foo") + .add("status", "available") + .add("category", Json.createObjectBuilder().add("id", 0).add("name", "dog")) + .add("photoUrls", photoUrls) + .add("tags", tags) + .build(); + + String requestBodyStr = requestBody.toString(); + // END: com.azure.core.experimental.http.requestoptions.createjsonrequest + + // BEGIN: com.azure.core.experimental.http.requestoptions.postrequest + RequestOptions options = new RequestOptions() + .addRequestCallback(request -> request + // may already be set if request is created from a client + .setUrl("https://petstore.example.com/pet") + .setHttpMethod(HttpMethod.POST) + .setBody(requestBodyStr) + .setHeader("Content-Type", "application/json")); + // END: com.azure.core.experimental.http.requestoptions.postrequest + return options; + } +} diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java new file mode 100644 index 0000000000000..7d541385307b7 --- /dev/null +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http; + +import com.azure.core.util.BinaryData; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestOptionsTests { + @Test + public void addQueryParam() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + RequestOptions options = new RequestOptions() + .addQueryParam("foo", "bar") + .addQueryParam("$skipToken", "1"); + options.getRequestCallback().accept(request); + + assertTrue(request.getUrl().toString().contains("?foo=bar&$skipToken=1")); + } + + @Test + public void addHeader() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + RequestOptions options = new RequestOptions() + .addHeader("x-ms-foo", "bar") + .addHeader("Content-Type", "application/json"); + options.getRequestCallback().accept(request); + + HttpHeaders headers = request.getHeaders(); + assertEquals("bar", headers.getValue("x-ms-foo")); + assertEquals("application/json", headers.getValue("Content-Type")); + } + + @Test + public void setBody() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + String expected = "{\"id\":\"123\"}"; + + RequestOptions options = new RequestOptions() + .setBody(BinaryData.fromString(expected)); + options.getRequestCallback().accept(request); + + StepVerifier.create(BinaryData.fromFlux(request.getBody()).map(BinaryData::toString)) + .expectNext(expected) + .verifyComplete(); + } + + @Test + public void addRequestCallback() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + RequestOptions options = new RequestOptions() + .addHeader("x-ms-foo", "bar") + .addRequestCallback(r -> r.setHttpMethod(HttpMethod.GET)) + .addRequestCallback(r -> r.setUrl("https://request.url")) + .addQueryParam("$skipToken", "1") + .addRequestCallback(r -> r.setHeader("x-ms-foo", "baz")); + + options.getRequestCallback().accept(request); + + HttpHeaders headers = request.getHeaders(); + assertEquals("baz", headers.getValue("x-ms-foo")); + assertEquals(HttpMethod.GET, request.getHttpMethod()); + assertEquals("https://request.url?$skipToken=1", request.getUrl().toString()); + } +} diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java index 67f9367b275ce..b3f2f1042db34 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java @@ -26,8 +26,11 @@ import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpMethod; +import com.azure.core.http.RequestOptions; +import com.azure.core.http.ResponseStatusOption; import com.azure.core.implementation.UnixTime; import com.azure.core.util.Base64Url; +import com.azure.core.util.BinaryData; import com.azure.core.util.Context; import com.azure.core.util.DateTimeRfc1123; import com.azure.core.util.UrlBuilder; @@ -480,6 +483,12 @@ public void setContext(SwaggerMethodParser swaggerMethodParser, Object[] argumen assertEquals(expectedContext, swaggerMethodParser.setContext(arguments)); } + @ParameterizedTest + @MethodSource("setRequestOptionsSupplier") + public void setRequestOptions(SwaggerMethodParser swaggerMethodParser, Object[] arguments, RequestOptions expectedRequestOptions) { + assertEquals(expectedRequestOptions, swaggerMethodParser.setRequestOptions(arguments)); + } + private static Stream setContextSupplier() throws NoSuchMethodException { Method method = OperationMethods.class.getDeclaredMethod("getMethod"); SwaggerMethodParser swaggerMethodParser = new SwaggerMethodParser(method, "https://raw.host.com"); @@ -494,6 +503,34 @@ private static Stream setContextSupplier() throws NoSuchMethodExcepti ); } + private static Stream setRequestOptionsSupplier() throws NoSuchMethodException { + Method method = OperationMethods.class.getDeclaredMethod("getMethod"); + SwaggerMethodParser swaggerMethodParser = new SwaggerMethodParser(method, "https://raw.host.com"); + + RequestOptions bodyOptions = new RequestOptions() + .setBody(BinaryData.fromString("{\"id\":\"123\"}")); + + RequestOptions headerQueryOptions = new RequestOptions() + .addHeader("x-ms-foo", "bar") + .addQueryParam("foo", "bar"); + + RequestOptions urlOptions = new RequestOptions() + .addRequestCallback(httpRequest -> httpRequest.setUrl("https://foo.host.com")); + + RequestOptions statusOptionOptions = new RequestOptions() + .setStatusOption(ResponseStatusOption.NO_THROW); + + return Stream.of( + Arguments.of(swaggerMethodParser, null, null), + Arguments.of(swaggerMethodParser, toObjectArray(), null), + Arguments.of(swaggerMethodParser, toObjectArray("string"), null), + Arguments.of(swaggerMethodParser, toObjectArray(bodyOptions), bodyOptions), + Arguments.of(swaggerMethodParser, toObjectArray("string", headerQueryOptions), headerQueryOptions), + Arguments.of(swaggerMethodParser, toObjectArray("string1", "string2", urlOptions), urlOptions), + Arguments.of(swaggerMethodParser, toObjectArray(statusOptionOptions), statusOptionOptions) + ); + } + interface ExpectedStatusCodeMethods { @Get("test") void noExpectedStatusCodes(); From 73731538005e293f97357a8d2a58b5053b101b56 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Fri, 18 Jun 2021 16:41:34 -0700 Subject: [PATCH 02/11] Handle BinaryData request & response types --- .../main/java/com/azure/core/http/rest/RestProxy.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java index 3ac807b4dfa6c..af637ad0d6f1f 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java @@ -25,6 +25,7 @@ import com.azure.core.implementation.serializer.HttpResponseDecoder; import com.azure.core.implementation.serializer.HttpResponseDecoder.HttpDecodedResponse; import com.azure.core.util.Base64Url; +import com.azure.core.util.BinaryData; import com.azure.core.util.Context; import com.azure.core.util.FluxUtil; import com.azure.core.util.UrlBuilder; @@ -294,7 +295,11 @@ private HttpRequest configRequest(final HttpRequest request, final SwaggerMethod } } - if (isJson) { + if (bodyContentObject instanceof BinaryData) { + byte[] body = ((BinaryData) bodyContentObject).toBytes(); + request.setBody(body); + request.setHeader("Content-Length", String.valueOf(body.length)); + } else if (isJson) { ByteArrayOutputStream stream = new AccessibleByteArrayOutputStream(); serializer.serialize(bodyContentObject, SerializerEncoding.JSON, stream); @@ -480,6 +485,9 @@ private Mono handleBodyReturnType(final HttpDecodedResponse response, } else if (FluxUtil.isFluxByteBuffer(entityType)) { // Mono> asyncResult = Mono.just(response.getSourceResponse().getBody()); + } else if (TypeUtil.isTypeOrSubTypeOf(entityType, BinaryData.class)) { + // Mono + asyncResult = BinaryData.fromFlux(response.getSourceResponse().getBody()); } else { // Mono or Mono> asyncResult = response.getDecodedBody((byte[]) null); From ba8aa050e5567f46c58229e179aa760035c1c166 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 22 Jun 2021 17:43:12 -0700 Subject: [PATCH 03/11] Address some feedback --- .../com/azure/core/http/RequestOptions.java | 152 ------------------ .../com/azure/core/http/rest/RestProxy.java | 5 +- .../core/http/rest/SwaggerMethodParser.java | 1 - .../azure-core/src/main/java/module-info.java | 1 + .../RequestOptionsJavaDocCodeSnippets.java | 1 + .../azure/core/http/RequestOptionsTests.java | 76 --------- .../http/rest/SwaggerMethodParserTests.java | 1 - 7 files changed, 3 insertions(+), 234 deletions(-) delete mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java delete mode 100644 sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java deleted file mode 100644 index bec960419ff8f..0000000000000 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/RequestOptions.java +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.core.http; - -import com.azure.core.util.BinaryData; - -import java.util.function.Consumer; - -/** - * This class contains the options to customize a HTTP request. {@link RequestOptions} can be - * used to configure the request headers, query params, the request body, or add a callback - * to modify all aspects of the HTTP request. - * - *

- * An instance of fully configured {@link RequestOptions} can be passed to a service method that - * preconfigures known components of the request like URL, path params etc, further modifying both - * un-configured, or preconfigured components. - *

- * - *

- * To demonstrate how this class can be used to construct a request, let's use a Pet Store service as an example. The - * list of APIs available on this service are documented in the swagger definition. - *

- * - *

Creating an instance of DynamicRequest using the constructor

- * {@codesnippet com.azure.core.experimental.http.requestoptions.instantiation} - ** - *

Configuring the request with a path param and making a HTTP GET request

- * Continuing with the pet store example, getting information about a pet requires making a - * HTTP GET call - * to the pet service and setting the pet id in path param as shown in the sample below. - * - * {@codesnippet com.azure.core.experimental.http.dynamicrequest.getrequest} - * - *

Configuring the request with JSON body and making a HTTP POST request

- * To add a new pet to the pet store, a HTTP POST call should - * be made to the service with the details of the pet that is to be added. The details of the pet are included as the - * request body in JSON format. - * - * The JSON structure for the request is defined as follows: - *
{@code
- * {
- *   "id": 0,
- *   "category": {
- *     "id": 0,
- *     "name": "string"
- *   },
- *   "name": "doggie",
- *   "photoUrls": [
- *     "string"
- *   ],
- *   "tags": [
- *     {
- *       "id": 0,
- *       "name": "string"
- *     }
- *   ],
- *   "status": "available"
- * }
- * }
- * - * To create a concrete request, Json builder provided in javax package is used here for demonstration. However, any - * other Json building library can be used to achieve similar results. - * - * {@codesnippet com.azure.core.experimental.http.requestoptions.createjsonrequest} - * - * Now, this string representation of the JSON request can be set as body of RequestOptions - * - * {@codesnippet com.azure.core.experimental.http.requestoptions.postrequest} - */ -public class RequestOptions { - private Consumer requestCallback = request -> { }; - private ResponseStatusOption statusOption = ResponseStatusOption.DEFAULT; - - /** - * Gets the request callback, applying all the configurations set on this RequestOptions. - * @return the request callback - */ - public Consumer getRequestCallback() { - return this.requestCallback; - } - - /** - * Gets under what conditions the operation raises an exception if the underlying response indicates a failure. - * @return the configured option - */ - public ResponseStatusOption getStatusOption() { - return this.statusOption; - } - - /** - * Adds a header to the HTTP request. - * @param header the header key - * @param value the header value - * - * @return the modified RequestOptions object - */ - public RequestOptions addHeader(String header, String value) { - this.requestCallback = this.requestCallback.andThen(request -> - request.getHeaders().set(header, value)); - return this; - } - - /** - * Adds a query parameter to the request URL. - * @param parameterName the name of the query parameter - * @param value the value of the query parameter - * @return the modified RequestOptions object - */ - public RequestOptions addQueryParam(String parameterName, String value) { - this.requestCallback = this.requestCallback.andThen(request -> { - String url = request.getUrl().toString(); - request.setUrl(url + (url.contains("?") ? "&" : "?") + parameterName + "=" + value); - }); - return this; - } - - /** - * Adds a custom request callback to modify the HTTP request before it's sent by the HttpClient. - * The modifications made on a RequestOptions object is applied in order on the request. - * - * @param requestCallback the request callback - * @return the modified RequestOptions object - */ - public RequestOptions addRequestCallback(Consumer requestCallback) { - this.requestCallback = this.requestCallback.andThen(requestCallback); - return this; - } - - /** - * Sets the body to send as part of the HTTP request. - * @param requestBody the request body data - * @return the modified RequestOptions object - */ - public RequestOptions setBody(BinaryData requestBody) { - this.requestCallback = this.requestCallback.andThen(request -> { - request.setBody(requestBody.toBytes()); - }); - return this; - } - - /** - * Sets under what conditions the operation raises an exception if the underlying response indicates a failure. - * @param statusOption the option to control under what conditions the operation raises an exception - * @return the modified RequestOptions object - */ - public RequestOptions setStatusOption(ResponseStatusOption statusOption) { - this.statusOption = statusOption; - return this; - } -} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java index af637ad0d6f1f..e450337ef7d2a 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java @@ -13,7 +13,6 @@ import com.azure.core.http.HttpPipelineBuilder; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; -import com.azure.core.http.RequestOptions; import com.azure.core.http.ResponseStatusOption; import com.azure.core.http.policy.CookiePolicy; import com.azure.core.http.policy.HttpPipelinePolicy; @@ -296,9 +295,7 @@ private HttpRequest configRequest(final HttpRequest request, final SwaggerMethod } if (bodyContentObject instanceof BinaryData) { - byte[] body = ((BinaryData) bodyContentObject).toBytes(); - request.setBody(body); - request.setHeader("Content-Length", String.valueOf(body.length)); + request.setBody(((BinaryData) bodyContentObject).toBytes()); } else if (isJson) { ByteArrayOutputStream stream = new AccessibleByteArrayOutputStream(); serializer.serialize(bodyContentObject, SerializerEncoding.JSON, stream); diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java index f41006ed078d6..ba8c9c0085dcf 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/SwaggerMethodParser.java @@ -24,7 +24,6 @@ import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpMethod; -import com.azure.core.http.RequestOptions; import com.azure.core.implementation.TypeUtil; import com.azure.core.implementation.UnixTime; import com.azure.core.implementation.http.UnexpectedExceptionInformation; diff --git a/sdk/core/azure-core/src/main/java/module-info.java b/sdk/core/azure-core/src/main/java/module-info.java index e67c0592af2a0..fbdc6c2a747b7 100644 --- a/sdk/core/azure-core/src/main/java/module-info.java +++ b/sdk/core/azure-core/src/main/java/module-info.java @@ -38,6 +38,7 @@ opens com.azure.core.implementation to com.fasterxml.jackson.databind; opens com.azure.core.implementation.logging to com.fasterxml.jackson.databind; opens com.azure.core.implementation.serializer to com.fasterxml.jackson.databind; + opens com.azure.core.http.rest to com.fasterxml.jackson.databind; // Service Provider Interfaces uses com.azure.core.http.HttpClientProvider; diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java index 461cb51e5c063..757c28d12b0c3 100644 --- a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java +++ b/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java @@ -3,6 +3,7 @@ package com.azure.core.http; +import com.azure.core.http.rest.RequestOptions; import com.azure.core.util.BinaryData; import javax.json.Json; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java deleted file mode 100644 index 7d541385307b7..0000000000000 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/RequestOptionsTests.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.core.http; - -import com.azure.core.util.BinaryData; -import org.junit.jupiter.api.Test; -import reactor.test.StepVerifier; - -import java.net.MalformedURLException; -import java.net.URL; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class RequestOptionsTests { - @Test - public void addQueryParam() throws MalformedURLException { - final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); - - RequestOptions options = new RequestOptions() - .addQueryParam("foo", "bar") - .addQueryParam("$skipToken", "1"); - options.getRequestCallback().accept(request); - - assertTrue(request.getUrl().toString().contains("?foo=bar&$skipToken=1")); - } - - @Test - public void addHeader() throws MalformedURLException { - final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); - - RequestOptions options = new RequestOptions() - .addHeader("x-ms-foo", "bar") - .addHeader("Content-Type", "application/json"); - options.getRequestCallback().accept(request); - - HttpHeaders headers = request.getHeaders(); - assertEquals("bar", headers.getValue("x-ms-foo")); - assertEquals("application/json", headers.getValue("Content-Type")); - } - - @Test - public void setBody() throws MalformedURLException { - final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); - - String expected = "{\"id\":\"123\"}"; - - RequestOptions options = new RequestOptions() - .setBody(BinaryData.fromString(expected)); - options.getRequestCallback().accept(request); - - StepVerifier.create(BinaryData.fromFlux(request.getBody()).map(BinaryData::toString)) - .expectNext(expected) - .verifyComplete(); - } - - @Test - public void addRequestCallback() throws MalformedURLException { - final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); - - RequestOptions options = new RequestOptions() - .addHeader("x-ms-foo", "bar") - .addRequestCallback(r -> r.setHttpMethod(HttpMethod.GET)) - .addRequestCallback(r -> r.setUrl("https://request.url")) - .addQueryParam("$skipToken", "1") - .addRequestCallback(r -> r.setHeader("x-ms-foo", "baz")); - - options.getRequestCallback().accept(request); - - HttpHeaders headers = request.getHeaders(); - assertEquals("baz", headers.getValue("x-ms-foo")); - assertEquals(HttpMethod.GET, request.getHttpMethod()); - assertEquals("https://request.url?$skipToken=1", request.getUrl().toString()); - } -} diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java index b3f2f1042db34..944de9b5dd3be 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java @@ -26,7 +26,6 @@ import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpMethod; -import com.azure.core.http.RequestOptions; import com.azure.core.http.ResponseStatusOption; import com.azure.core.implementation.UnixTime; import com.azure.core.util.Base64Url; From 3aaf0c42c8a0c20d912d96793d21d5fa347adef7 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Tue, 22 Jun 2021 17:48:20 -0700 Subject: [PATCH 04/11] Move RequestOptions --- .../azure/core/http/rest/RequestOptions.java | 173 ++++++++++++++++++ .../core/http/rest/RequestOptionsTests.java | 79 ++++++++ 2 files changed, 252 insertions(+) create mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java create mode 100644 sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java new file mode 100644 index 0000000000000..f8056580127d7 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.rest; + +import com.azure.core.annotation.QueryParam; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.ResponseStatusOption; +import com.azure.core.util.BinaryData; + +import java.util.function.Consumer; + +/** + * This class contains the options to customize a HTTP request. {@link RequestOptions} can be + * used to configure the request headers, query params, the request body, or add a callback + * to modify all aspects of the HTTP request. + * + *

+ * An instance of fully configured {@link RequestOptions} can be passed to a service method that + * preconfigures known components of the request like URL, path params etc, further modifying both + * un-configured, or preconfigured components. + *

+ * + *

+ * To demonstrate how this class can be used to construct a request, let's use a Pet Store service as an example. The + * list of APIs available on this service are documented in the swagger definition. + *

+ * + *

Creating an instance of DynamicRequest using the constructor

+ * {@codesnippet com.azure.core.http.requestoptions.instantiation} + ** + *

Configuring the request with a path param and making a HTTP GET request

+ * Continuing with the pet store example, getting information about a pet requires making a + * HTTP GET call + * to the pet service and setting the pet id in path param as shown in the sample below. + * + * {@codesnippet com.azure.core.http.dynamicrequest.getrequest} + * + *

Configuring the request with JSON body and making a HTTP POST request

+ * To add a new pet to the pet store, a HTTP POST call should + * be made to the service with the details of the pet that is to be added. The details of the pet are included as the + * request body in JSON format. + * + * The JSON structure for the request is defined as follows: + *
{@code
+ * {
+ *   "id": 0,
+ *   "category": {
+ *     "id": 0,
+ *     "name": "string"
+ *   },
+ *   "name": "doggie",
+ *   "photoUrls": [
+ *     "string"
+ *   ],
+ *   "tags": [
+ *     {
+ *       "id": 0,
+ *       "name": "string"
+ *     }
+ *   ],
+ *   "status": "available"
+ * }
+ * }
+ * + * To create a concrete request, Json builder provided in javax package is used here for demonstration. However, any + * other Json building library can be used to achieve similar results. + * + * {@codesnippet com.azure.core.http.requestoptions.createjsonrequest} + * + * Now, this string representation of the JSON request can be set as body of RequestOptions + * + * {@codesnippet com.azure.core.http.requestoptions.postrequest} + */ +public final class RequestOptions { + private Consumer requestCallback = request -> { }; + private ResponseStatusOption statusOption = ResponseStatusOption.DEFAULT; + private BinaryData requestBody; + + /** + * Gets the request callback, applying all the configurations set on this RequestOptions. + * @return the request callback + */ + Consumer getRequestCallback() { + return this.requestCallback; + } + + /** + * Gets under what conditions the operation raises an exception if the underlying response indicates a failure. + * @return the configured option + */ + ResponseStatusOption getStatusOption() { + return this.statusOption; + } + + /** + * Adds a header to the HTTP request. + * @param header the header key + * @param value the header value + * + * @return the modified RequestOptions object + */ + public RequestOptions addHeader(String header, String value) { + this.requestCallback = this.requestCallback.andThen(request -> + request.getHeaders().get(header).addValue(value)); + return this; + } + + /** + * Adds a query parameter to the request URL. + * + * @param parameterName the name of the query parameter + * @param value the value of the query parameter + * @return the modified RequestOptions object + */ + public RequestOptions addQueryParam(String parameterName, String value) { + return addQueryParam(parameterName, value, false); + } + + /** + * Adds a query parameter to the request URL, specifying whether the parameter is already encoded. + * A value true for this argument indicates that value of {@link QueryParam#value()} is already encoded + * hence engine should not encode it, by default value will be encoded. + * + * @param parameterName the name of the query parameter + * @param value the value of the query parameter + * @param encoded whether or not this query parameter is already encoded + * @return the modified RequestOptions object + */ + public RequestOptions addQueryParam(String parameterName, String value, boolean encoded) { + this.requestCallback = this.requestCallback.andThen(request -> { + String url = request.getUrl().toString(); + request.setUrl(url + (url.contains("?") ? "&" : "?") + + UrlEscapers.QUERY_ESCAPER.escape(parameterName) + "=" + + UrlEscapers.QUERY_ESCAPER.escape(value)); + }); + return this; + } + + /** + * Adds a custom request callback to modify the HTTP request before it's sent by the HttpClient. + * The modifications made on a RequestOptions object is applied in order on the request. + * + * @param requestCallback the request callback + * @return the modified RequestOptions object + */ + public RequestOptions addRequestCallback(Consumer requestCallback) { + this.requestCallback = this.requestCallback.andThen(requestCallback); + return this; + } + + /** + * Sets the body to send as part of the HTTP request. + * @param requestBody the request body data + * @return the modified RequestOptions object + */ + public RequestOptions setBody(BinaryData requestBody) { + this.requestCallback = this.requestCallback.andThen(request -> { + request.setBody(requestBody.toBytes()); + }); + return this; + } + + /** + * Sets under what conditions the operation raises an exception if the underlying response indicates a failure. + * @param statusOption the option to control under what conditions the operation raises an exception + * @return the modified RequestOptions object + */ + public RequestOptions setStatusOption(ResponseStatusOption statusOption) { + this.statusOption = statusOption; + return this; + } +} diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java new file mode 100644 index 0000000000000..a19e230aad539 --- /dev/null +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.rest; + +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.util.BinaryData; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestOptionsTests { + @Test + public void addQueryParam() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + RequestOptions options = new RequestOptions() + .addQueryParam("foo", "bar") + .addQueryParam("$skipToken", "1"); + options.getRequestCallback().accept(request); + + assertTrue(request.getUrl().toString().contains("?foo=bar&$skipToken=1")); + } + + @Test + public void addHeader() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + RequestOptions options = new RequestOptions() + .addHeader("x-ms-foo", "bar") + .addHeader("Content-Type", "application/json"); + options.getRequestCallback().accept(request); + + HttpHeaders headers = request.getHeaders(); + assertEquals("bar", headers.getValue("x-ms-foo")); + assertEquals("application/json", headers.getValue("Content-Type")); + } + + @Test + public void setBody() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + String expected = "{\"id\":\"123\"}"; + + RequestOptions options = new RequestOptions() + .setBody(BinaryData.fromString(expected)); + options.getRequestCallback().accept(request); + + StepVerifier.create(BinaryData.fromFlux(request.getBody()).map(BinaryData::toString)) + .expectNext(expected) + .verifyComplete(); + } + + @Test + public void addRequestCallback() throws MalformedURLException { + final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url")); + + RequestOptions options = new RequestOptions() + .addHeader("x-ms-foo", "bar") + .addRequestCallback(r -> r.setHttpMethod(HttpMethod.GET)) + .addRequestCallback(r -> r.setUrl("https://request.url")) + .addQueryParam("$skipToken", "1") + .addRequestCallback(r -> r.setHeader("x-ms-foo", "baz")); + + options.getRequestCallback().accept(request); + + HttpHeaders headers = request.getHeaders(); + assertEquals("baz", headers.getValue("x-ms-foo")); + assertEquals(HttpMethod.GET, request.getHttpMethod()); + assertEquals("https://request.url?$skipToken=1", request.getUrl().toString()); + } +} From 34056504a5482848e53dfc0e083ceee85e26c512 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 11:26:29 -0700 Subject: [PATCH 05/11] Change ResponseStatusOption to boolean --- .../azure/core/http/ResponseStatusOption.java | 19 ------------ .../azure/core/http/rest/RequestOptions.java | 29 ++++++++++--------- .../com/azure/core/http/rest/RestProxy.java | 3 +- .../http/rest/SwaggerMethodParserTests.java | 1 - 4 files changed, 17 insertions(+), 35 deletions(-) delete mode 100644 sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java deleted file mode 100644 index dd7ae7d604ae2..0000000000000 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/ResponseStatusOption.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.core.http; - -/** - * ResponseStatusOption controls the behavior of an operation based on the status code of a response. - */ -public enum ResponseStatusOption { - /** - * Indicates that an operation should throw an exception when the response indicates a failure. - */ - DEFAULT, - - /** - * Indicates that an operation should not throw an exception when the response indicates a failure. - */ - NO_THROW; -} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java index f8056580127d7..5e0f8f096cec2 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java @@ -5,7 +5,6 @@ import com.azure.core.annotation.QueryParam; import com.azure.core.http.HttpRequest; -import com.azure.core.http.ResponseStatusOption; import com.azure.core.util.BinaryData; import java.util.function.Consumer; @@ -74,7 +73,7 @@ */ public final class RequestOptions { private Consumer requestCallback = request -> { }; - private ResponseStatusOption statusOption = ResponseStatusOption.DEFAULT; + private boolean throwOnError = true; private BinaryData requestBody; /** @@ -86,11 +85,13 @@ Consumer getRequestCallback() { } /** - * Gets under what conditions the operation raises an exception if the underlying response indicates a failure. - * @return the configured option + * Gets whether or not to throw an exception when an HTTP response with a status code indicating an error + * (400 or above) is received. + * + * @return true if to throw on status codes of 400 or above, false if not. Default is true. */ - ResponseStatusOption getStatusOption() { - return this.statusOption; + boolean isThrowOnError() { + return this.throwOnError; } /** @@ -130,9 +131,9 @@ public RequestOptions addQueryParam(String parameterName, String value) { public RequestOptions addQueryParam(String parameterName, String value, boolean encoded) { this.requestCallback = this.requestCallback.andThen(request -> { String url = request.getUrl().toString(); - request.setUrl(url + (url.contains("?") ? "&" : "?") - + UrlEscapers.QUERY_ESCAPER.escape(parameterName) + "=" - + UrlEscapers.QUERY_ESCAPER.escape(value)); + String encodedParameterName = encoded ? parameterName : UrlEscapers.QUERY_ESCAPER.escape(parameterName); + String encodedParameterValue = encoded ? value : UrlEscapers.QUERY_ESCAPER.escape(value); + request.setUrl(url + (url.contains("?") ? "&" : "?") + encodedParameterName + "=" + encodedParameterValue); }); return this; } @@ -162,12 +163,14 @@ public RequestOptions setBody(BinaryData requestBody) { } /** - * Sets under what conditions the operation raises an exception if the underlying response indicates a failure. - * @param statusOption the option to control under what conditions the operation raises an exception + * Sets whether or not to throw an exception when an HTTP response with a status code indicating an error + * (400 or above) is received. By default an exception will be thrown when an error response is received. + * + * @param throwOnError true if to throw on status codes of 400 or above, false if not. Default is true. * @return the modified RequestOptions object */ - public RequestOptions setStatusOption(ResponseStatusOption statusOption) { - this.statusOption = statusOption; + public RequestOptions setThrowOnError(boolean throwOnError) { + this.throwOnError = throwOnError; return this; } } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java index e450337ef7d2a..e52deecbc1cb4 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RestProxy.java @@ -13,7 +13,6 @@ import com.azure.core.http.HttpPipelineBuilder; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; -import com.azure.core.http.ResponseStatusOption; import com.azure.core.http.policy.CookiePolicy; import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.RetryPolicy; @@ -379,7 +378,7 @@ private Mono ensureExpectedStatus(final HttpDecodedResponse final int responseStatusCode = decodedResponse.getSourceResponse().getStatusCode(); final Mono asyncResult; if (!methodParser.isExpectedResponseStatusCode(responseStatusCode) - && (options == null || options.getStatusOption() != ResponseStatusOption.NO_THROW)) { + && (options == null || options.isThrowOnError())) { Mono bodyAsBytes = decodedResponse.getSourceResponse().getBodyAsByteArray(); asyncResult = bodyAsBytes.flatMap((Function>) responseContent -> { diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java index 944de9b5dd3be..f63b09e325568 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java @@ -26,7 +26,6 @@ import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpMethod; -import com.azure.core.http.ResponseStatusOption; import com.azure.core.implementation.UnixTime; import com.azure.core.util.Base64Url; import com.azure.core.util.BinaryData; From 548ad2c091a8f741d2d7121746dc3bdab7a1fffb Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 13:36:14 -0700 Subject: [PATCH 06/11] Fix issues from cr feedback --- .../azure/core/http/rest/RequestOptions.java | 27 ++++++++++--------- .../RequestOptionsJavaDocCodeSnippets.java | 12 ++++----- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java index 5e0f8f096cec2..aab56b854339a 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java @@ -4,6 +4,7 @@ package com.azure.core.http.rest; import com.azure.core.annotation.QueryParam; +import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpRequest; import com.azure.core.util.BinaryData; @@ -26,14 +27,7 @@ *

* *

Creating an instance of DynamicRequest using the constructor

- * {@codesnippet com.azure.core.http.requestoptions.instantiation} - ** - *

Configuring the request with a path param and making a HTTP GET request

- * Continuing with the pet store example, getting information about a pet requires making a - * HTTP GET call - * to the pet service and setting the pet id in path param as shown in the sample below. - * - * {@codesnippet com.azure.core.http.dynamicrequest.getrequest} + * {@codesnippet com.azure.core.http.rest.requestoptions.instantiation} * *

Configuring the request with JSON body and making a HTTP POST request

* To add a new pet to the pet store, a HTTP POST call should @@ -65,11 +59,11 @@ * To create a concrete request, Json builder provided in javax package is used here for demonstration. However, any * other Json building library can be used to achieve similar results. * - * {@codesnippet com.azure.core.http.requestoptions.createjsonrequest} + * {@codesnippet com.azure.core.http.rest.requestoptions.createjsonrequest} * * Now, this string representation of the JSON request can be set as body of RequestOptions * - * {@codesnippet com.azure.core.http.requestoptions.postrequest} + * {@codesnippet com.azure.core.http.rest.requestoptions.postrequest} */ public final class RequestOptions { private Consumer requestCallback = request -> { }; @@ -102,13 +96,20 @@ boolean isThrowOnError() { * @return the modified RequestOptions object */ public RequestOptions addHeader(String header, String value) { - this.requestCallback = this.requestCallback.andThen(request -> - request.getHeaders().get(header).addValue(value)); + this.requestCallback = this.requestCallback.andThen(request -> { + HttpHeader httpHeader = request.getHeaders().get(header); + if (httpHeader == null) { + request.getHeaders().set(header, value); + } else { + httpHeader.addValue(value); + } + }); return this; } /** - * Adds a query parameter to the request URL. + * Adds a query parameter to the request URL. The parameter name and value will be URL encoded. + * To use an already encoded parameter name and value, call {@code addQueryParam("name", "value", true)}. * * @param parameterName the name of the query parameter * @param value the value of the query parameter diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java index 757c28d12b0c3..a3b57cc880644 100644 --- a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java +++ b/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java @@ -20,11 +20,11 @@ public class RequestOptionsJavaDocCodeSnippets { * @return An instance of {@link RequestOptions}. */ public RequestOptions createInstance() { - // BEGIN: com.azure.core.experimental.http.requestoptions.instantiation + // BEGIN: com.azure.core.http.rest.requestoptions.instantiation RequestOptions options = new RequestOptions() .setBody(BinaryData.fromString("{\"name\":\"Fluffy\"}")) .addHeader("x-ms-pet-version", "2021-06-01"); - // END: com.azure.core.experimental.http.requestoptions.instantiation + // END: com.azure.core.http.rest.requestoptions.instantiation return options; } @@ -33,7 +33,7 @@ public RequestOptions createInstance() { * @return An instance of {@link RequestOptions}. */ public RequestOptions setJsonRequestBodyInRequestOptions() { - // BEGIN: com.azure.core.experimental.http.requestoptions.createjsonrequest + // BEGIN: com.azure.core.http.rest.requestoptions.createjsonrequest JsonArray photoUrls = Json.createArrayBuilder() .add("https://imgur.com/pet1") .add("https://imgur.com/pet2") @@ -60,9 +60,9 @@ public RequestOptions setJsonRequestBodyInRequestOptions() { .build(); String requestBodyStr = requestBody.toString(); - // END: com.azure.core.experimental.http.requestoptions.createjsonrequest + // END: com.azure.core.http.rest.requestoptions.createjsonrequest - // BEGIN: com.azure.core.experimental.http.requestoptions.postrequest + // BEGIN: com.azure.core.http.rest.requestoptions.postrequest RequestOptions options = new RequestOptions() .addRequestCallback(request -> request // may already be set if request is created from a client @@ -70,7 +70,7 @@ public RequestOptions setJsonRequestBodyInRequestOptions() { .setHttpMethod(HttpMethod.POST) .setBody(requestBodyStr) .setHeader("Content-Type", "application/json")); - // END: com.azure.core.experimental.http.requestoptions.postrequest + // END: com.azure.core.http.rest.requestoptions.postrequest return options; } } From 97952416c6a43592a43fcf4df3003a26e8d24d84 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 13:51:12 -0700 Subject: [PATCH 07/11] Move javadoc samples --- .../http/{ => rest}/RequestOptionsJavaDocCodeSnippets.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename sdk/core/azure-core/src/samples/java/com/azure/core/http/{ => rest}/RequestOptionsJavaDocCodeSnippets.java (97%) diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java similarity index 97% rename from sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java rename to sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java index a3b57cc880644..9b938fb000fcf 100644 --- a/sdk/core/azure-core/src/samples/java/com/azure/core/http/RequestOptionsJavaDocCodeSnippets.java +++ b/sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.http; +package com.azure.core.http.rest; +import com.azure.core.http.HttpMethod; import com.azure.core.http.rest.RequestOptions; import com.azure.core.util.BinaryData; From 0620138aeb34fd62b820e41c14a33647e5e6d5b0 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 14:04:42 -0700 Subject: [PATCH 08/11] Fix copy paste error in javadocs --- .../src/main/java/com/azure/core/http/rest/RequestOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java index aab56b854339a..627fd5dac86f8 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java @@ -26,7 +26,7 @@ * list of APIs available on this service are documented in the swagger definition. *

* - *

Creating an instance of DynamicRequest using the constructor

+ *

Creating an instance of RequestOptions using the constructor

* {@codesnippet com.azure.core.http.rest.requestoptions.instantiation} * *

Configuring the request with JSON body and making a HTTP POST request

From fef0373a1c6089bf27c177220e319a6fda27ad2f Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 15:45:29 -0700 Subject: [PATCH 09/11] Fix swagger method parser test --- .../src/main/java/com/azure/core/http/rest/RequestOptions.java | 2 +- .../java/com/azure/core/http/rest/SwaggerMethodParserTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java index 627fd5dac86f8..9ea4770041051 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/rest/RequestOptions.java @@ -26,7 +26,7 @@ * list of APIs available on this service are documented in the swagger definition. *

* - *

Creating an instance of RequestOptions using the constructor

+ *

Creating an instance of RequestOptions

* {@codesnippet com.azure.core.http.rest.requestoptions.instantiation} * *

Configuring the request with JSON body and making a HTTP POST request

diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java index f63b09e325568..964a3fd35725f 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/SwaggerMethodParserTests.java @@ -516,7 +516,7 @@ private static Stream setRequestOptionsSupplier() throws NoSuchMethod .addRequestCallback(httpRequest -> httpRequest.setUrl("https://foo.host.com")); RequestOptions statusOptionOptions = new RequestOptions() - .setStatusOption(ResponseStatusOption.NO_THROW); + .setThrowOnError(false); return Stream.of( Arguments.of(swaggerMethodParser, null, null), From b0689bbea1eab4a6ef1b80175df7dc33712b1d79 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 16:11:14 -0700 Subject: [PATCH 10/11] Fix test broken by encoding --- .../java/com/azure/core/http/rest/RequestOptionsTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java index a19e230aad539..ec0ec5694e13c 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RequestOptionsTests.java @@ -26,7 +26,7 @@ public void addQueryParam() throws MalformedURLException { .addQueryParam("$skipToken", "1"); options.getRequestCallback().accept(request); - assertTrue(request.getUrl().toString().contains("?foo=bar&$skipToken=1")); + assertTrue(request.getUrl().toString().contains("?foo=bar&%24skipToken=1")); } @Test @@ -74,6 +74,6 @@ public void addRequestCallback() throws MalformedURLException { HttpHeaders headers = request.getHeaders(); assertEquals("baz", headers.getValue("x-ms-foo")); assertEquals(HttpMethod.GET, request.getHttpMethod()); - assertEquals("https://request.url?$skipToken=1", request.getUrl().toString()); + assertEquals("https://request.url?%24skipToken=1", request.getUrl().toString()); } } From 281629d0b3f9956cf6172dc2840a0b19573672e0 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Wed, 23 Jun 2021 16:37:54 -0700 Subject: [PATCH 11/11] Fix checkstyle error --- .../azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java b/sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java index 9b938fb000fcf..46a49c2d672ad 100644 --- a/sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java +++ b/sdk/core/azure-core/src/samples/java/com/azure/core/http/rest/RequestOptionsJavaDocCodeSnippets.java @@ -4,7 +4,6 @@ package com.azure.core.http.rest; import com.azure.core.http.HttpMethod; -import com.azure.core.http.rest.RequestOptions; import com.azure.core.util.BinaryData; import javax.json.Json;