Skip to content

Commit a2c301f

Browse files
authored
Add back RestProxy for vnext generator tests (Azure#45149)
* Add back RestProxy * Delete qq * checkstyle
1 parent 24808f3 commit a2c301f

28 files changed

+4102
-0
lines changed

sdk/clientcore/core/checkstyle-suppressions.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@
55
<suppressions>
66
<suppress files="io.clientcore.core.implementation.utils.InternalContext.java" checks="JavadocMethodCheck" />
77
<suppress files="io.clientcore.core.http.models.RequestContext.java" checks="MethodNameCheck" />
8+
<suppress files="io.clientcore.core.implementation.http.rest.RequestDataConfiguration.java" checks="MissingJavadocMethodCheck" />
9+
<suppress files="io.clientcore.core.implementation.http.rest.SwaggerMethodParser.java" checks="MissingJavadocMethodCheck" />
810
<suppress files="io.clientcore.core.implementation.instrumentation.DefaultLogger.java" checks="MissingJavadocMethodCheck" />
911
<suppress files="io.clientcore.core.implementation.instrumentation.Slf4jLoggerShim.java" checks="MissingJavadocMethodCheck" />
1012
<suppress files="io.clientcore.core.implementation.utils.ImplUtils.java" checks="MissingJavadocMethodCheck" />
1113
<suppress files="io.clientcore.core.implementation.utils.IterableOfByteBuffersInputStream.java" checks="MissingJavadocMethodCheck" />
1214
<suppress files="io.clientcore.core.implementation.utils.ServerSentEventHelper.java" checks="MissingJavadocMethodCheck" />
15+
<suppress files="io.clientcore.core.implementation.http.rest.HeaderSubstitution.java" checks="MissingJavadocTypeCheck" />
16+
<suppress files="io.clientcore.core.implementation.http.rest.RequestDataConfiguration.java" checks="MissingJavadocTypeCheck" />
17+
<suppress files="io.clientcore.core.implementation.http.rest.RestProxyImpl.java" checks="MissingJavadocTypeCheck" />
1318
<suppress files="io.clientcore.core.implementation.instrumentation.Slf4jLoggerShim.java" checks="MissingJavadocTypeCheck" />
1419
<suppress files="io.clientcore.core.implementation.utils.ImplUtils.java" checks="MissingJavadocTypeCheck" />
1520
<suppress files="io.clientcore.core.implementation.instrumentation.Slf4jLoggerShim.java" checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" />
1621
<suppress files="io.clientcore.core.implementation.ReflectionSerializable.java" checks="com.azure.tools.checkstyle.checks.JavadocThrowsChecks" />
22+
<suppress files="io.clientcore.core.implementation.http.serializer.HttpResponseBodyDecoder.java" checks="com.azure.tools.checkstyle.checks.JavadocThrowsChecks" />
1723
<suppress files="io.clientcore.core.http.client.JdkHttpClientBuilder.java" checks="com.azure.tools.checkstyle.checks.ServiceClientBuilderCheck" />
1824
<suppress files="io.clientcore.core.http.pipeline.HttpInstrumentationPolicy.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
1925
<suppress files="io.clientcore.core.implementation.MethodHandleReflectiveInvoker.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
26+
<suppress files="io.clientcore.core.implementation.http.rest.LengthValidatingInputStream.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
2027
<suppress files="io.clientcore.core.implementation.instrumentation.fallback.FallbackInstrumentation.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
2128
<suppress files="io.clientcore.core.implementation.instrumentation.otel.OTelInstrumentation.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />
2229
<suppress files="io.clientcore.core.instrumentation.logging.LoggingEvent.java" checks="com.azure.tools.checkstyle.checks.ThrowFromClientLoggerCheck" />

sdk/clientcore/core/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
--add-opens io.clientcore.core/io.clientcore.core.http=ALL-UNNAMED
4747
--add-opens io.clientcore.core/io.clientcore.core.http.annotations=ALL-UNNAMED
4848
--add-opens io.clientcore.core/io.clientcore.core.http.client=ALL-UNNAMED
49+
--add-opens io.clientcore.core/io.clientcore.core.implementation.http.rest=ALL-UNNAMED
4950
--add-opens io.clientcore.core/io.clientcore.core.http.models=ALL-UNNAMED
5051
--add-opens io.clientcore.core/io.clientcore.core.http.pipeline=ALL-UNNAMED
5152
--add-opens io.clientcore.core/io.clientcore.core.implementation=ALL-UNNAMED

sdk/clientcore/core/spotbugs-exclude.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Class name="io.clientcore.core.implementation.GenericParameterizedType" />
2424
<Class name="io.clientcore.core.implementation.ReflectionUtilsMethodHandle" />
2525
<Class name="io.clientcore.core.implementation.http.HttpPipelineCallState" />
26+
<Class name="io.clientcore.core.implementation.http.rest.RestProxyImpl" />
2627
<Class name="io.clientcore.core.implementation.utils.AuthenticateChallengeParser" />
2728
<Class name="io.clientcore.core.implementation.utils.InternalContext" />
2829
<Class name="io.clientcore.core.implementation.utils.PercentEscaper" />
@@ -64,6 +65,7 @@
6465
<Class name="io.clientcore.core.http.client.JdkHttpClientBuilder" />
6566
<Class name="io.clientcore.core.http.pipeline.KeyCredentialPolicy" />
6667
<Class name="io.clientcore.core.http.pipeline.UserAgentPolicy" />
68+
<Class name="io.clientcore.core.implementation.http.rest.SwaggerMethodParser" />
6769
<Class name="io.clientcore.core.implementation.instrumentation.otel.OTelInstrumentation" />
6870
<Class name="io.clientcore.core.instrumentation.logging.ClientLogger" />
6971
<Class name="io.clientcore.core.serialization.json.implementation.jackson.core.util.RequestPayload" />
@@ -81,6 +83,7 @@
8183
<Class name="io.clientcore.core.http.pipeline.AddHeadersPolicyJavadocCodeSnippets" />
8284
<Class name="io.clientcore.core.http.pipeline.SetRequestIdPolicyJavadocCodeSnippets" />
8385
<Class name="io.clientcore.core.http.pipeline.UserAgentPolicyJavadocCodeSnippets" />
86+
<Class name="io.clientcore.core.implementation.http.serializer.HttpResponseDecodeData" />
8487
<Class name="io.clientcore.core.instrumentation.TelemetryForLibraryDevelopersJavaDocCodeSnippets" />
8588
<Class name="io.clientcore.core.models.CloudEventJavaDocCodeSnippet" />
8689
<Class name="io.clientcore.core.serialization.xml.implementation.aalto.in.ReaderScanner" />
@@ -107,6 +110,7 @@
107110
<Or>
108111
<Class name="io.clientcore.core.http.client.SimpleBasicAuthHttpProxyServer" />
109112
<Class name="io.clientcore.core.implementation.http.client.JdkHttpClientIT" />
113+
<Class name="io.clientcore.core.implementation.http.rest.RestProxyImplTests" />
110114
<Class name="io.clientcore.core.implementation.serializer.AdditionalPropertiesSerializerTests" />
111115
<Class name="io.clientcore.core.implementation.serializer.BinaryDataSerializationTests" />
112116
<Class name="io.clientcore.core.implementation.serializer.BinaryDataSerializationTests$ListProperty" />
@@ -241,6 +245,7 @@
241245
<Match>
242246
<Bug pattern="NP_NULL_PARAM_DEREF_NONVIRTUAL" />
243247
<Or>
248+
<Class name="io.clientcore.core.implementation.http.rest.LengthValidatingInputStreamTests" />
244249
<Class name="io.clientcore.core.implementation.instrumentation.fallback.FallbackInstrumentationTests" />
245250
<Class name="io.clientcore.core.models.binarydata.BinaryDataTest" />
246251
<Class name="io.clientcore.core.models.geo.GeoCollectionTests" />
@@ -298,6 +303,7 @@
298303
<Match>
299304
<Bug pattern="RR_NOT_CHECKED" />
300305
<Or>
306+
<Class name="io.clientcore.core.implementation.http.rest.LengthValidatingInputStreamTests" />
301307
<Class name="io.clientcore.core.models.CloudEventTests" />
302308
<Class name="io.clientcore.core.models.binarydata.BinaryDataJavaDocCodeSnippet" />
303309
</Or>
@@ -367,6 +373,10 @@
367373
<Class name="io.clientcore.core.shared.LocalTestServer" />
368374
</Or>
369375
</Match>
376+
<Match>
377+
<Bug pattern="SR_NOT_CHECKED" />
378+
<Class name="io.clientcore.core.implementation.http.rest.LengthValidatingInputStreamTests" />
379+
</Match>
370380
<Match>
371381
<Bug pattern="SS_SHOULD_BE_STATIC" />
372382
<Or>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package io.clientcore.core.http;
5+
6+
import io.clientcore.core.http.models.RequestContext;
7+
import io.clientcore.core.http.pipeline.HttpPipeline;
8+
import io.clientcore.core.implementation.http.rest.RestProxyImpl;
9+
import io.clientcore.core.implementation.http.rest.SwaggerInterfaceParser;
10+
import io.clientcore.core.implementation.http.rest.SwaggerMethodParser;
11+
import io.clientcore.core.serialization.json.JsonSerializer;
12+
import io.clientcore.core.serialization.ObjectSerializer;
13+
import io.clientcore.core.serialization.xml.XmlSerializer;
14+
15+
import java.lang.reflect.InvocationHandler;
16+
import java.lang.reflect.Method;
17+
import java.lang.reflect.Proxy;
18+
19+
/**
20+
* Type to create a proxy implementation for an interface describing REST API methods.
21+
* <p>
22+
* RestProxy can create proxy implementations for interfaces with methods that return deserialized Java objects.
23+
*/
24+
public final class RestProxy implements InvocationHandler {
25+
private final SwaggerInterfaceParser interfaceParser;
26+
private final RestProxyImpl restProxyImpl;
27+
28+
/**
29+
* Create a RestProxy.
30+
*
31+
* @param httpPipeline the HttpPipelinePolicy and HttpClient httpPipeline that will be used to send HTTP requests.
32+
* @param interfaceParser the parser that contains information about the interface describing REST API methods that
33+
* this RestProxy "implements".
34+
* @param serializers the serializers that will be used to convert response bodies to POJOs.
35+
*/
36+
private RestProxy(HttpPipeline httpPipeline, SwaggerInterfaceParser interfaceParser,
37+
ObjectSerializer... serializers) {
38+
this.interfaceParser = interfaceParser;
39+
this.restProxyImpl = new RestProxyImpl(httpPipeline, interfaceParser, serializers);
40+
}
41+
42+
/**
43+
* Get the SwaggerMethodParser for the provided method. The Method must exist on the Swagger interface that this
44+
* RestProxy was created to "implement".
45+
*
46+
* @param method the method to get a SwaggerMethodParser for
47+
*
48+
* @return the SwaggerMethodParser for the provided method
49+
*/
50+
private SwaggerMethodParser getMethodParser(Method method) {
51+
return interfaceParser.getMethodParser(method);
52+
}
53+
54+
@Override
55+
public Object invoke(Object proxy, final Method method, Object[] args) {
56+
// Note: RequestContext need to be evaluated here, as it is a public class with package private methods.
57+
// Evaluating here allows the package private methods to be invoked here for downstream use.
58+
final SwaggerMethodParser methodParser = getMethodParser(method);
59+
RequestContext options = methodParser.setRequestContext(args);
60+
61+
return restProxyImpl.invoke(proxy, options, methodParser, args);
62+
}
63+
64+
/**
65+
* Create a proxy implementation of the provided Swagger interface.
66+
*
67+
* @param swaggerInterface the Swagger interface to provide a proxy implementation for
68+
* @param httpPipeline the HttpPipelinePolicy and HttpClient pipeline that will be used to send Http requests
69+
* @param <A> the type of the Swagger interface
70+
* @return a proxy implementation of the provided Swagger interface
71+
*/
72+
@SuppressWarnings("unchecked")
73+
public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline) {
74+
final SwaggerInterfaceParser interfaceParser = SwaggerInterfaceParser.getInstance(swaggerInterface);
75+
final RestProxy restProxy
76+
= new RestProxy(httpPipeline, interfaceParser, new JsonSerializer(), new XmlSerializer());
77+
78+
return (A) Proxy.newProxyInstance(swaggerInterface.getClassLoader(), new Class<?>[] { swaggerInterface },
79+
restProxy);
80+
}
81+
82+
/**
83+
* Create a proxy implementation of the provided Swagger interface.
84+
*
85+
* @param swaggerInterface the Swagger interface to provide a proxy implementation for
86+
* @param httpPipeline the HttpPipelinePolicy and HttpClient pipeline that will be used to send Http requests
87+
* @param serializers the serializers that will be used to convert POJOs to and from request and response bodies
88+
* @param <A> the type of the Swagger interface.
89+
* @return a proxy implementation of the provided Swagger interface
90+
* @throws IllegalArgumentException If {@code serializers} is null or empty.
91+
*/
92+
@SuppressWarnings("unchecked")
93+
public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline, ObjectSerializer... serializers) {
94+
final SwaggerInterfaceParser interfaceParser = SwaggerInterfaceParser.getInstance(swaggerInterface);
95+
final RestProxy restProxy = new RestProxy(httpPipeline, interfaceParser, serializers);
96+
97+
return (A) Proxy.newProxyInstance(swaggerInterface.getClassLoader(), new Class<?>[] { swaggerInterface },
98+
restProxy);
99+
}
100+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package io.clientcore.core.implementation.http.rest;
5+
6+
import io.clientcore.core.http.models.HttpHeaderName;
7+
8+
public final class HeaderSubstitution extends Substitution {
9+
private final HttpHeaderName headerName;
10+
11+
/**
12+
* Create a new Substitution.
13+
*
14+
* @param uriParameterName The name that is used between curly quotes as a placeholder in the target URI.
15+
* @param methodParameterIndex The index of the parameter in the original interface method where the value for the
16+
* placeholder is.
17+
* @param shouldEncode Whether the value from the method's argument should be encoded when the substitution is
18+
* taking place.
19+
*/
20+
public HeaderSubstitution(String uriParameterName, int methodParameterIndex, boolean shouldEncode) {
21+
super(uriParameterName, methodParameterIndex, shouldEncode);
22+
this.headerName = (uriParameterName == null) ? null : HttpHeaderName.fromString(uriParameterName);
23+
}
24+
25+
/**
26+
* Gets the header name.
27+
* <p>
28+
* header name.
29+
*
30+
* @return The header name.
31+
*/
32+
public HttpHeaderName getHeaderName() {
33+
return headerName;
34+
}
35+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package io.clientcore.core.implementation.http.rest;
4+
5+
import io.clientcore.core.instrumentation.logging.ClientLogger;
6+
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.util.Objects;
10+
11+
/**
12+
* An {@link InputStream} decorator that tracks the number of bytes read from an inner {@link InputStream} and throws
13+
* an exception if the number of bytes read doesn't match what was expected.
14+
* <p>
15+
* This implementation assumes that reader is going to read until EOF.
16+
*/
17+
final class LengthValidatingInputStream extends InputStream {
18+
19+
private static final ClientLogger LOGGER = new ClientLogger(LengthValidatingInputStream.class);
20+
21+
private final InputStream inner;
22+
private final long expectedReadSize;
23+
24+
private long position;
25+
private long mark = -1;
26+
27+
/**
28+
* Creates a new {@link LengthValidatingInputStream}.
29+
*
30+
* @param inputStream The {@link InputStream} being decorated.
31+
* @param expectedReadSize The expected number of bytes to be read from the inner {@code inputStream}.
32+
*/
33+
LengthValidatingInputStream(InputStream inputStream, long expectedReadSize) {
34+
this.inner = Objects.requireNonNull(inputStream, "'inputStream' cannot be null.");
35+
36+
if (expectedReadSize < 0) {
37+
throw LOGGER.logThrowableAsError(new IllegalArgumentException("'expectedReadSize' cannot be less than 0."));
38+
}
39+
40+
this.expectedReadSize = expectedReadSize;
41+
}
42+
43+
@Override
44+
public synchronized int read(byte[] b, int off, int len) throws IOException {
45+
int totalRead = 0;
46+
int readSize;
47+
do {
48+
// Attempt to read until the byte array is filled or the inner stream ends.
49+
// This results in finishing validation faster and prevents scenarios such as with JDK HttpClient where
50+
// a large buffer will be requested and it validates read length before we can finish validation.
51+
readSize = inner.read(b, off + totalRead, len - totalRead);
52+
validateLength(readSize);
53+
54+
if (readSize != -1) {
55+
totalRead += readSize;
56+
} else if (totalRead == 0) {
57+
// If the inner stream was already read to completion the first read will return -1, need to set
58+
// total read to -1 to prevent any infinite read loops by returning 0.
59+
totalRead = -1;
60+
}
61+
} while (readSize != -1 && totalRead != len);
62+
63+
return totalRead;
64+
}
65+
66+
@Override
67+
public synchronized long skip(long n) throws IOException {
68+
long skipped = inner.skip(n);
69+
position += skipped;
70+
return skipped;
71+
}
72+
73+
@Override
74+
public int available() throws IOException {
75+
return inner.available();
76+
}
77+
78+
@Override
79+
public void close() throws IOException {
80+
inner.close();
81+
}
82+
83+
@Override
84+
public synchronized void mark(int readlimit) {
85+
inner.mark(readlimit);
86+
mark = position;
87+
}
88+
89+
@Override
90+
public synchronized void reset() throws IOException {
91+
inner.reset();
92+
position = mark;
93+
}
94+
95+
@Override
96+
public boolean markSupported() {
97+
return inner.markSupported();
98+
}
99+
100+
@Override
101+
public synchronized int read() throws IOException {
102+
int read = inner.read();
103+
validateLength(read == -1 ? -1 : 1);
104+
105+
return read;
106+
}
107+
108+
private void validateLength(int readSize) {
109+
if (readSize == -1) {
110+
// If the inner InputStream has reached termination validate that the read bytes matches what was expected.
111+
if (position > expectedReadSize) {
112+
throw new IllegalStateException(RestProxyImpl.bodyTooLarge(position, expectedReadSize));
113+
} else if (position < expectedReadSize) {
114+
throw new IllegalStateException(RestProxyImpl.bodyTooSmall(position, expectedReadSize));
115+
}
116+
} else {
117+
position += readSize;
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)