Skip to content

Commit

Permalink
Allow a user choose a path when a Thrift/gRPC service has more than t…
Browse files Browse the repository at this point in the history
…wo paths
  • Loading branch information
kojilin committed Aug 25, 2020
1 parent be8124b commit 31c4879
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ private ServiceInfo addServiceExamples(ServiceInfo service) {
// generated by the plugin.
concatAndDedup(exampleHeaders.get(m.name()), m.exampleHeaders()),
concatAndDedup(exampleRequests.get(m.name()), m.exampleRequests()),
examplePaths.get(m.name()),
concatAndDedup(examplePaths.get(m.name()), m.examplePaths()),
exampleQueries.get(m.name()),
m.httpMethod(), m.docString()))::iterator,
Iterables.concat(service.exampleHeaders(), exampleHeaders.get("")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ public MethodInfo(String name,
this.exampleRequests = ImmutableList.copyOf(requireNonNull(exampleRequests, "exampleRequests"));

requireNonNull(examplePaths, "examplePaths");
final ImmutableList.Builder<String> examplePathsBuilder =
ImmutableList.builderWithExpectedSize(Iterables.size(examplePaths));
final ImmutableList.Builder<String> examplePathsBuilder = ImmutableList.builder();
for (String path : examplePaths) {
final PathAndQuery pathAndQuery = PathAndQuery.parse(path);
checkArgument(pathAndQuery != null, "examplePaths contains an invalid path: %s", path);
Expand Down
53 changes: 52 additions & 1 deletion docs-client/src/containers/MethodPage/DebugPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import React, {
ChangeEvent,
useCallback,
useEffect,
useMemo,
useReducer,
useState,
} from 'react';
Expand Down Expand Up @@ -195,6 +196,26 @@ const DebugPage: React.FunctionComponent<Props> = ({
setSnackbarOpen(false);
}, []);

const validateRpcEndpointPath = useCallback(
(newEndpointPath: string) => {
if (!newEndpointPath) {
throw new Error('You must specify the endpoint path.');
}
if (
!method.endpoints
.map((endpoint) => endpoint.pathMapping)
.includes(newEndpointPath)
) {
throw new Error(
`The path: '${newEndpointPath}' should be one of the: ${method.endpoints.map(
(endpoint) => endpoint.pathMapping,
)}`,
);
}
},
[method],
);

const validateEndpointPath = useCallback(
(newEndpointPath: string) => {
if (!newEndpointPath) {
Expand Down Expand Up @@ -324,6 +345,9 @@ const DebugPage: React.FunctionComponent<Props> = ({
`'${host}${escapeSingleQuote(additionalPath)}'` +
`${queries.length > 0 ? `?${escapeSingleQuote(queries)}` : ''}'`;
}
} else if (additionalPath.length > 0) {
validateRpcEndpointPath(additionalPath);
uri = `'${host}${escapeSingleQuote(additionalPath)}'`;
} else {
uri = `'${host}${escapeSingleQuote(path)}'`;
}
Expand Down Expand Up @@ -358,6 +382,7 @@ const DebugPage: React.FunctionComponent<Props> = ({
additionalQueries,
exactPathMapping,
validateEndpointPath,
validateRpcEndpointPath,
additionalPath,
]);

Expand Down Expand Up @@ -390,6 +415,8 @@ const DebugPage: React.FunctionComponent<Props> = ({
if (!exactPathMapping) {
executedEndpointPath = params.get('endpoint_path') || undefined;
}
} else {
executedEndpointPath = params.get('endpoint_path') || undefined;
}

const headersText = params.get('headers');
Expand Down Expand Up @@ -442,6 +469,12 @@ const DebugPage: React.FunctionComponent<Props> = ({
validateEndpointPath(additionalPath);
params.set('endpoint_path', additionalPath);
}
} else if (additionalPath.length > 0) {
validateRpcEndpointPath(additionalPath);
params.set('endpoint_path', additionalPath);
} else {
// Fall back to default endpoint.
params.delete('endpoint_path');
}

if (headers) {
Expand Down Expand Up @@ -482,10 +515,28 @@ const DebugPage: React.FunctionComponent<Props> = ({
requestBody,
exactPathMapping,
validateEndpointPath,
validateRpcEndpointPath,
additionalPath,
history,
]);

const supportedExamplePaths = useMemo(() => {
const transport = TRANSPORTS.getDebugTransport(method);
if (!transport) {
throw new Error("This method doesn't have a debug transport.");
}
return examplePaths.filter((path) =>
method.endpoints.some((endpoint) => {
return (
endpoint.pathMapping === path.value &&
endpoint.availableMimeTypes.some((mimeType) => {
return transport.supportsMimeType(mimeType);
})
);
}),
);
}, [examplePaths, method]);

return (
<Section>
<div id="debug-form">
Expand All @@ -507,7 +558,7 @@ const DebugPage: React.FunctionComponent<Props> = ({
.
</Alert>
<EndpointPath
examplePaths={examplePaths}
examplePaths={supportedExamplePaths}
editable={!exactPathMapping}
endpointPathOpen={endpointPathOpen}
additionalPath={additionalPath}
Expand Down
3 changes: 2 additions & 1 deletion docs-client/src/lib/transports/grpc-unframed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default class GrpcUnframedTransport extends Transport {
method: Method,
headers: { [name: string]: string },
bodyJson?: string,
endpointPath?: string,
): Promise<string> {
if (!bodyJson) {
throw new Error('A gRPC request must have body.');
Expand All @@ -47,7 +48,7 @@ export default class GrpcUnframedTransport extends Transport {
hdrs.set(name, value);
}

const httpResponse = await fetch(endpoint.pathMapping, {
const httpResponse = await fetch(endpointPath || endpoint.pathMapping, {
headers: hdrs,
method: 'POST',
body: bodyJson,
Expand Down
3 changes: 2 additions & 1 deletion docs-client/src/lib/transports/thrift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default class ThriftTransport extends Transport {
method: Method,
headers: { [name: string]: string },
bodyJson?: string,
endpointPath?: string,
): Promise<string> {
if (!bodyJson) {
throw new Error('A Thrift request must have body.');
Expand All @@ -58,7 +59,7 @@ export default class ThriftTransport extends Transport {
hdrs.set(name, value);
}

const httpResponse = await fetch(endpoint.pathMapping, {
const httpResponse = await fetch(endpointPath || endpoint.pathMapping, {
headers: hdrs,
method: 'POST',
body: `{"method": "${thriftMethod}", "type": "CALL", "args": ${bodyJson}}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
Expand Down Expand Up @@ -276,6 +277,10 @@ static MethodInfo newMethodInfo(MethodDescriptor method, ServiceEntry service) {
return builder.availableMimeTypes(e.availableMimeTypes()).build();
})
.collect(toImmutableSet());

final List<String> examplePaths =
Streams.stream(methodEndpoints).map(EndpointInfo::pathMapping).collect(toImmutableList());

return new MethodInfo(
method.getName(),
namedMessageSignature(method.getOutputType()),
Expand All @@ -286,7 +291,7 @@ static MethodInfo newMethodInfo(MethodDescriptor method, ServiceEntry service) {
methodEndpoints,
/* exampleHeaders */ ImmutableList.of(),
defaultExamples(method),
/* examplePaths */ ImmutableList.of(),
examplePaths,
/* exampleQueries */ ImmutableList.of(),
HttpMethod.POST,
/* docString */ null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,31 @@ void servicesTest() throws Exception {
UnitTestServiceGrpc.SERVICE_NAME,
ReconnectServiceGrpc.SERVICE_NAME);

services.get(TestServiceGrpc.SERVICE_NAME).methods().forEach(m -> m.endpoints().forEach(e -> {
assertThat(e.pathMapping()).isEqualTo("/armeria.grpc.testing.TestService/" + m.name());
}));
services.get(UnitTestServiceGrpc.SERVICE_NAME).methods().forEach(m -> m.endpoints().forEach(e -> {
assertThat(e.pathMapping()).isEqualTo("/test/armeria.grpc.testing.UnitTestService/" + m.name());
}));
services.get(ReconnectServiceGrpc.SERVICE_NAME).methods().forEach(m -> m.endpoints().forEach(e -> {
assertThat(e.pathMapping()).isEqualTo("/reconnect/armeria.grpc.testing.ReconnectService/" +
m.name());
}));
services.get(TestServiceGrpc.SERVICE_NAME).methods().forEach(m -> {
m.endpoints().forEach(e -> {
assertThat(e.pathMapping()).isEqualTo("/armeria.grpc.testing.TestService/" + m.name());
});
m.examplePaths().forEach(p -> {
assertThat(p).isEqualTo("/armeria.grpc.testing.TestService/" + m.name());
});
});
services.get(UnitTestServiceGrpc.SERVICE_NAME).methods().forEach(m -> {
m.endpoints().forEach(e -> {
assertThat(e.pathMapping()).isEqualTo("/test/armeria.grpc.testing.UnitTestService/" + m.name());
});
m.examplePaths().forEach(p -> {
assertThat(p).isEqualTo("/test/armeria.grpc.testing.UnitTestService/" + m.name());
});
});
services.get(ReconnectServiceGrpc.SERVICE_NAME).methods().forEach(m -> {
m.endpoints().forEach(e -> {
assertThat(e.pathMapping()).isEqualTo("/reconnect/armeria.grpc.testing.ReconnectService/" +
m.name());
});
m.examplePaths().forEach(p -> {
assertThat(p).isEqualTo("/reconnect/armeria.grpc.testing.ReconnectService/" + m.name());
});
});
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;

import com.linecorp.armeria.common.HttpMethod;
import com.linecorp.armeria.common.thrift.ThriftProtocolFactories;
import com.linecorp.armeria.server.Route;
import com.linecorp.armeria.server.RoutePathType;
Expand Down Expand Up @@ -266,7 +268,15 @@ private static MethodInfo newMethodInfo(String name,
.map(TypeSignature::ofNamed)
.collect(toImmutableList());

return new MethodInfo(name, returnTypeSignature, parameters, exceptionTypeSignatures, endpoints);
final List<String> examplePaths =
Streams.stream(endpoints).map(EndpointInfo::pathMapping).collect(toImmutableList());

return new MethodInfo(name, returnTypeSignature, parameters, exceptionTypeSignatures, endpoints,
ImmutableList.of(),
ImmutableList.of(),
examplePaths,
ImmutableList.of(),
HttpMethod.POST, null);
}

private static NamedTypeInfo newNamedTypeInfo(TypeSignature typeSignature) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,48 @@ void servicesTest() {
assertThat(methods).containsKey("bar4");
final MethodInfo bar4 = methods.get("bar4");
assertThat(bar4.exampleRequests()).isEmpty();
assertThat(bar4.examplePaths()).containsExactly("/foo");
assertThat(bar4.endpoints())
.containsExactly(EndpointInfo.builder("*", "/foo")
.defaultFormat(ThriftSerializationFormats.COMPACT).build());
}

@Test
public void multiplePaths() {
final Server server =
Server.builder()
.service(Route.builder()
.exact("/foo1")
.build(),
THttpService.ofFormats(mock(FooService.AsyncIface.class),
ThriftSerializationFormats.COMPACT))
.service(Route.builder()
.exact("/foo2")
.build(),
THttpService.ofFormats(mock(FooService.AsyncIface.class),
ThriftSerializationFormats.COMPACT))
.build();
final ServiceSpecification specification = generator.generateSpecification(
ImmutableSet.copyOf(server.serviceConfigs()),
unifyFilter((plugin, service, method) -> true,
(plugin, service, method) -> false));

final ServiceInfo fooServiceInfo = specification.services().iterator().next();
final Map<String, MethodInfo> methods =
fooServiceInfo.methods().stream()
.collect(toImmutableMap(MethodInfo::name, Function.identity()));
final MethodInfo bar4 = methods.get("bar4");
assertThat(bar4.examplePaths()).containsExactlyInAnyOrder("/foo1", "/foo2");
assertThat(bar4.endpoints())
.containsExactlyInAnyOrder(
EndpointInfo.builder("*", "/foo1")
.defaultFormat(ThriftSerializationFormats.COMPACT)
.build(),
EndpointInfo.builder("*", "/foo2")
.defaultFormat(ThriftSerializationFormats.COMPACT)
.build());
}

@Test
void include() {

Expand Down

0 comments on commit 31c4879

Please sign in to comment.