Skip to content

Commit a63036a

Browse files
authored
Fix Uri for host substitution when the path is / (Azure#45314)
1 parent fb5612f commit a63036a

File tree

4 files changed

+136
-17
lines changed

4 files changed

+136
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package io.clientcore.annotation.processor.test;
4+
5+
import io.clientcore.core.http.models.HttpHeaderName;
6+
import io.clientcore.core.http.models.HttpMethod;
7+
import io.clientcore.core.http.models.HttpRequest;
8+
import io.clientcore.core.http.models.RequestContext;
9+
import io.clientcore.core.http.models.Response;
10+
import io.clientcore.core.http.pipeline.HttpPipeline;
11+
import io.clientcore.core.implementation.utils.UriEscapers;
12+
import io.clientcore.core.models.binarydata.BinaryData;
13+
import io.clientcore.annotation.processor.test.implementation.HostPathEdgeCase3Service;
14+
import io.clientcore.core.instrumentation.logging.ClientLogger;
15+
import io.clientcore.core.serialization.json.JsonSerializer;
16+
import io.clientcore.core.serialization.xml.XmlSerializer;
17+
import io.clientcore.core.http.models.HttpResponseException;
18+
19+
/**
20+
* Initializes a new instance of the HostPathEdgeCase3ServiceImpl type.
21+
*/
22+
public class HostPathEdgeCase3ServiceImpl implements HostPathEdgeCase3Service {
23+
24+
private static final ClientLogger LOGGER = new ClientLogger(HostPathEdgeCase3Service.class);
25+
26+
private final HttpPipeline httpPipeline;
27+
28+
private final JsonSerializer jsonSerializer;
29+
30+
private final XmlSerializer xmlSerializer;
31+
32+
private HostPathEdgeCase3ServiceImpl(HttpPipeline httpPipeline) {
33+
this.httpPipeline = httpPipeline;
34+
this.jsonSerializer = JsonSerializer.getInstance();
35+
this.xmlSerializer = XmlSerializer.getInstance();
36+
}
37+
38+
/**
39+
* Creates an instance of HostPathEdgeCase3Service that is capable of sending requests to the service.
40+
* @param httpPipeline The HTTP pipeline to use for sending requests.
41+
* @return An instance of `HostPathEdgeCase3Service`;
42+
*/
43+
public static HostPathEdgeCase3Service getNewInstance(HttpPipeline httpPipeline) {
44+
return new HostPathEdgeCase3ServiceImpl(httpPipeline);
45+
}
46+
47+
@SuppressWarnings("cast")
48+
@Override
49+
public Response<Void> noOperationParams(String endpoint, String apiVersion, RequestContext requestContext) {
50+
// Create the HttpRequest.
51+
HttpRequest httpRequest = new HttpRequest().setMethod(HttpMethod.GET).setUri(endpoint + "/server/path/multiple/" + apiVersion + "/");
52+
httpRequest.setContext(requestContext);
53+
httpRequest.getContext().getRequestCallback().accept(httpRequest);
54+
// Send the request through the httpPipeline
55+
try (Response<BinaryData> networkResponse = this.httpPipeline.send(httpRequest)) {
56+
int responseCode = networkResponse.getStatusCode();
57+
boolean expectedResponse = responseCode == 204;
58+
if (!expectedResponse) {
59+
String errorMessage = networkResponse.getValue().toString();
60+
throw new HttpResponseException(errorMessage, networkResponse, null);
61+
}
62+
return new Response<>(networkResponse.getRequest(), responseCode, networkResponse.getHeaders(), null);
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package io.clientcore.annotation.processor.test.implementation;
4+
5+
import io.clientcore.core.annotations.ServiceInterface;
6+
import io.clientcore.core.http.annotations.HostParam;
7+
import io.clientcore.core.http.annotations.HttpRequestInformation;
8+
import io.clientcore.core.http.annotations.UnexpectedResponseExceptionDetail;
9+
import io.clientcore.core.http.models.HttpMethod;
10+
import io.clientcore.core.http.models.RequestContext;
11+
import io.clientcore.core.http.models.Response;
12+
import io.clientcore.core.http.pipeline.HttpPipeline;
13+
import java.lang.reflect.InvocationTargetException;
14+
15+
/**
16+
* Tests that there isn't a variable name conflict if the parameterized host segment is called {@code endpoint}.
17+
*/
18+
@ServiceInterface(name = "HostPathEdgeCase3", host = "{endpoint}/server/path/multiple/{apiVersion}")
19+
public interface HostPathEdgeCase3Service {
20+
/**
21+
* Creates a new instance of {@link HostPathEdgeCase3Service}.
22+
*
23+
* @param pipeline The HTTP pipeline to use.
24+
* @return A new instance of HostEdgeCase2.
25+
*/
26+
static HostPathEdgeCase3Service getNewInstance(HttpPipeline pipeline) {
27+
if (pipeline == null) {
28+
throw new IllegalArgumentException("pipeline cannot be null");
29+
}
30+
try {
31+
Class<?> clazz = Class
32+
.forName("io.clientcore.annotation.processor.test.HostPathEdgeCase3ServiceImpl");
33+
return (HostPathEdgeCase3Service) clazz.getMethod("getNewInstance", HttpPipeline.class)
34+
.invoke(null, pipeline);
35+
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
36+
| InvocationTargetException e) {
37+
throw new RuntimeException(e);
38+
}
39+
}
40+
41+
42+
/**
43+
* Performs a no operation.
44+
* @param endpoint The endpoint to connect to.
45+
* @param apiVersion The API version to use.
46+
* @param requestContext The request context to use.
47+
* @return A response with no content.
48+
*/
49+
@HttpRequestInformation(method = HttpMethod.GET, path = "/", expectedStatusCodes = { 204 })
50+
@UnexpectedResponseExceptionDetail
51+
Response<Void> noOperationParams(@HostParam("endpoint") String endpoint,
52+
@HostParam("apiVersion") String apiVersion, RequestContext requestContext);
53+
54+
}

sdk/clientcore/annotation-processor/src/main/java/io/clientcore/annotation/processor/AnnotationProcessor.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import io.clientcore.core.implementation.utils.UriEscapers;
2525
import io.clientcore.core.models.binarydata.BinaryData;
2626
import io.clientcore.core.utils.CoreUtils;
27-
2827
import java.util.List;
2928
import java.util.Objects;
3029
import java.util.Set;
@@ -222,20 +221,23 @@ private HttpRequestContext createHttpRequestContext(ExecutableElement requestMet
222221

223222
private String getHost(HttpRequestContext method) {
224223
String path = method.getPath();
225-
// Set the path after host, concatenating the path segment in the host.
226-
if (path != null && !path.isEmpty() && !"/".equals(path)) {
227-
String hostPath = method.getHost();
228-
if (hostPath == null || hostPath.isEmpty() || "/".equals(hostPath) || path.contains("://")) {
224+
String hostPath = method.getHost();
225+
226+
// If hostPath is set (from @ServiceInterface), always use it as the base.
227+
if (hostPath != null && !hostPath.isEmpty() && !"/".equals(hostPath)) {
228+
if (path == null || path.isEmpty() || "/".equals(path)) {
229+
// Path is "/" or empty, so append "/" to the host
230+
method.setPath(hostPath + "/");
231+
} else if (path.contains("://")) {
232+
// Path is a full URL, use as is
229233
method.setPath(path);
234+
} else if (path.startsWith("/")) {
235+
method.setPath(hostPath + path);
230236
} else {
231-
if (path.startsWith("/")) {
232-
method.setPath(hostPath + path);
233-
} else {
234-
method.setPath(hostPath + "/" + path);
235-
}
237+
method.setPath(hostPath + "/" + path);
236238
}
237239
}
238-
240+
// else: hostPath is empty, use the path as is
239241
return PathBuilder.buildPath(method.getPath(), method);
240242
}
241243
}

sdk/clientcore/annotation-processor/src/main/java/io/clientcore/annotation/processor/templating/JavaParserTemplateProcessor.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@
4545
import io.clientcore.core.utils.CoreUtils;
4646
import io.clientcore.core.utils.GeneratedCodeUtils;
4747
import io.clientcore.core.utils.UriBuilder;
48-
49-
import javax.annotation.processing.ProcessingEnvironment;
50-
import javax.lang.model.type.TypeKind;
51-
import javax.lang.model.type.TypeMirror;
52-
import javax.tools.Diagnostic;
5348
import java.io.IOException;
5449
import java.io.Writer;
5550
import java.lang.reflect.Field;
@@ -63,6 +58,10 @@
6358
import java.util.Optional;
6459
import java.util.TreeMap;
6560
import java.util.stream.Collectors;
61+
import javax.annotation.processing.ProcessingEnvironment;
62+
import javax.lang.model.type.TypeKind;
63+
import javax.lang.model.type.TypeMirror;
64+
import javax.tools.Diagnostic;
6665

6766
import static io.clientcore.annotation.processor.utils.ResponseHandler.generateResponseHandling;
6867

@@ -363,7 +362,6 @@ void setHttpRequestUri(BlockStmt body, Expression createHttpRequest, HttpRequest
363362
} else {
364363
urlStatement = method.getHost();
365364
}
366-
367365
// If the method doesn't have query parameters to set, inline the call to HttpRequest.setUri and return.
368366
if (method.getQueryParams().isEmpty()) {
369367
// The 'createHttpRequest' expression is the scope for the method call expression being added.

0 commit comments

Comments
 (0)