Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Option for List Command #57

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion src/main/java/me/dinowernli/grpc/polyglot/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public static void main(String[] args) {
ServiceList.listServices(
commandLineOutput,
fileDescriptorSet, config.getProtoConfig().getProtoDiscoveryRoot(),
arguments.serviceFilter(), arguments.methodFilter(), arguments.withMessage());
arguments.serviceFilter(), arguments.methodFilter(), arguments.withMessage(),
arguments.listOutputFormat());
break;

case CommandLineArgs.CALL_COMMAND:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ public static void callEndpoint(
dynamicClient = DynamicGrpcClient.create(methodDescriptor, hostAndPort, callConfig);
}

logger.info("Reading input from stdin");

ImmutableList<DynamicMessage> requestMessages =
MessageReader.forStdin(methodDescriptor.getInputType()).read();
StreamObserver<DynamicMessage> streamObserver =
CompositeStreamObserver.of(new LoggingStatsWriter(), MessageWriter.create(output));
CompositeStreamObserver.of(new LoggingStatsWriter(), MessageWriter.create(output, "\n\n"));
logger.info(String.format(
"Making rpc with %d request(s) to endpoint [%s]", requestMessages.size(), hostAndPort));
try {
Expand Down
124 changes: 82 additions & 42 deletions src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package me.dinowernli.grpc.polyglot.command;

import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.MethodDescriptor;
import com.google.protobuf.Descriptors.ServiceDescriptor;

import me.dinowernli.grpc.polyglot.io.MessageWriter;
import me.dinowernli.grpc.polyglot.io.Output;
import me.dinowernli.grpc.polyglot.protobuf.ServiceResolver;

Expand All @@ -20,67 +26,72 @@ public class ServiceList {

/** Lists the GRPC services - filtered by service name (contains) or method name (contains) */
public static void listServices(
Output output,
FileDescriptorSet fileDescriptorSet,
String protoDiscoveryRoot,
Optional<String> serviceFilter,
Optional<String> methodFilter,
Optional<Boolean> withMessage) {
Output output,
FileDescriptorSet fileDescriptorSet,
String protoDiscoveryRoot,
Optional<String> serviceFilter,
Optional<String> methodFilter,
Optional<Boolean> withMessage,
Optional<String> listOutputFormat) {

ServiceResolver serviceResolver = ServiceResolver.fromFileDescriptorSet(fileDescriptorSet);

// Add white-space before the rendered output
output.newLine();
if (listOutputFormat.isPresent() && listOutputFormat.get().equals("json")) {
printJsonOutput(output, serviceResolver, serviceFilter);
} else {

for (ServiceDescriptor descriptor : serviceResolver.listServices()) {
boolean matchingDescriptor =
!serviceFilter.isPresent()
|| descriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase());
// Add white-space before the rendered output
output.newLine();

if (matchingDescriptor) {
listMethods(output, protoDiscoveryRoot, descriptor, methodFilter, withMessage);
for (ServiceDescriptor descriptor : serviceResolver.listServices()) {
boolean matchingDescriptor =
!serviceFilter.isPresent() || descriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase());

if (matchingDescriptor) {
listMethods(output, protoDiscoveryRoot, descriptor, methodFilter, withMessage);
}
}
}
}

/** Lists the methods on the service (the methodFilter will be applied if non-empty) */
private static void listMethods(
/** Lists the methods on the service (the methodFilter will be applied if non-empty) */
private static void listMethods(
Output output,
String protoDiscoveryRoot,
ServiceDescriptor descriptor,
Optional<String> methodFilter,
Optional<Boolean> withMessage) {

boolean printedService = false;
boolean printedService = false;

// Due to the way the protos are discovered, the leaf directly of the protoDiscoveryRoot
// is the same as the root directory as the proto file
File protoDiscoveryDir = new File(protoDiscoveryRoot).getParentFile();
// Due to the way the protos are discovered, the leaf directly of the protoDiscoveryRoot
// is the same as the root directory as the proto file
File protoDiscoveryDir = new File(protoDiscoveryRoot).getParentFile();

for (MethodDescriptor method : descriptor.getMethods()) {
if (!methodFilter.isPresent() || method.getName().contains(methodFilter.get())) {
for (MethodDescriptor method : descriptor.getMethods()) {
if (!methodFilter.isPresent() || method.getName().contains(methodFilter.get())) {

// Only print the service name once - and only if a method is going to be printed
if (!printedService) {
File pFile = new File(protoDiscoveryDir, descriptor.getFile().getName());
output.writeLine(descriptor.getFullName() + " -> " + pFile.getAbsolutePath());
printedService = true;
}
// Only print the service name once - and only if a method is going to be printed
if (!printedService) {
File pFile = new File(protoDiscoveryDir, descriptor.getFile().getName());
output.writeLine(descriptor.getFullName() + " -> " + pFile.getAbsolutePath());
printedService = true;
}

output.writeLine(" " + descriptor.getFullName() + "/" + method.getName());
output.writeLine(" " + descriptor.getFullName() + "/" + method.getName());

// If requested, add the message definition
if (withMessage.isPresent() && withMessage.get()) {
output.writeLine(renderDescriptor(method.getInputType(), " "));
output.newLine();
// If requested, add the message definition
if (withMessage.isPresent() && withMessage.get()) {
output.writeLine(renderDescriptor(method.getInputType(), " "));
output.newLine();
}
}
}
}

if (printedService) {
output.newLine();
if (printedService) {
output.newLine();
}
}
}

/** Creates a human-readable string to help the user build a message to send to an end-point */
private static String renderDescriptor(Descriptor descriptor, String indent) {
Expand All @@ -89,8 +100,8 @@ private static String renderDescriptor(Descriptor descriptor, String indent) {
}

List<String> fieldsAsStrings = descriptor.getFields().stream()
.map(field -> renderDescriptor(field, indent + " "))
.collect(Collectors.toList());
.map(field -> renderDescriptor(field, indent + " "))
.collect(Collectors.toList());

return Joiner.on(System.lineSeparator()).join(fieldsAsStrings);
}
Expand All @@ -103,8 +114,8 @@ private static String renderDescriptor(FieldDescriptor descriptor, String indent

if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
return fieldPrefix + " {" + System.lineSeparator()
+ renderDescriptor(descriptor.getMessageType(), indent + " ")
+ System.lineSeparator() + indent + "}";
+ renderDescriptor(descriptor.getMessageType(), indent + " ")
+ System.lineSeparator() + indent + "}";

} else if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) {
return fieldPrefix + ": " + descriptor.getEnumType().getValues();
Expand All @@ -113,4 +124,33 @@ private static String renderDescriptor(FieldDescriptor descriptor, String indent
return fieldPrefix + ": " + descriptor.getJavaType();
}
}
}

private static void printJsonOutput(Output output, ServiceResolver serviceResolver, Optional<String> serviceFilter) {
output.writeLine("{ \"serviceProtos\": [");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably not be constructing json ourselves here. If we want to wrap the existing protobuf output, we should define a proto which wraps the messages we need and print that a single time (this would also obviate the need for changing the messageprinter).


List<ServiceDescriptor> serviceDescriptors = Lists.newArrayList(serviceResolver.listServices()).stream()
.filter(serviceDescriptor -> !serviceFilter.isPresent()
|| serviceDescriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase()))
.collect(Collectors.toList());

ImmutableList<DescriptorProtos.ServiceDescriptorProto> serviceDescriptorProtos = ImmutableList.copyOf(serviceDescriptors.stream()
.map(serviceDescriptor -> serviceDescriptor.toProto())
.collect(Collectors.toList()));

output.writeLine((MessageWriter.writeJsonStream(serviceDescriptorProtos, ",")));
output.writeLine(("], \"fileProtos\": ["));

Set<DescriptorProtos.FileDescriptorProto> fileProtosSet = new HashSet<>();

serviceDescriptors.forEach(serviceDescriptor -> {
fileProtosSet.add(serviceDescriptor.getFile().toProto());
serviceDescriptor.getFile().getDependencies().stream()
.forEach(fileDescriptor -> fileProtosSet.add(fileDescriptor.toProto()));
});

ImmutableList<DescriptorProtos.FileDescriptorProto> fileProtos = ImmutableList.copyOf(fileProtosSet);

output.writeLine((MessageWriter.writeJsonStream(fileProtos, ",")));
output.writeLine("]}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -56,6 +58,21 @@ public class CommandLineArgs {
@Option(name = "--tls_client_override_authority", metaVar = "<host>")
private String tlsClientOverrideAuthority;

@Option(name = "--oauth_refresh_token_endpoint_url", metaVar = "<url>")
private String oauthRefreshTokenEndpointUrl;

@Option(name = "--oauth_client_id", metaVar = "<client-id>")
private String oauthClientId;

@Option(name = "--oauth_client_secret", metaVar = "<client-secret>")
private String oauthClientSecret;

@Option(name = "--oauth_refresh_token_path", metaVar = "<path>")
private String oauthRefreshTokenPath;

@Option(name = "--oauth_access_token_path", metaVar = "<path>")
private String oauthAccessTokenPath;

@Option(name = "--help")
private Boolean help;

Expand All @@ -74,25 +91,32 @@ public class CommandLineArgs {

// TODO: Move to a "list_services"-specific flag container
@Option(
name = "--service_filter",
metaVar = "service_name",
usage="Filters service names containing this string e.g. --service_filter TestService")
name = "--service_filter",
metaVar = "service_name",
usage="Filters service names containing this string e.g. --service_filter TestService")
private String serviceFilterArg;

// TODO: Move to a "list_services"-specific flag container
@Option(
name = "--method_filter",
metaVar = "method_name",
usage="Filters service methods to those containing this string e.g. --method_name List")
name = "--method_filter",
metaVar = "method_name",
usage="Filters service methods to those containing this string e.g. --method_name List")
private String methodFilterArg;

//TODO: Move to a "list_services"-specific flag container
@Option(
name = "--with_message",
metaVar = "true|false",
usage="If true, then the message specification for the method is rendered")
name = "--with_message",
metaVar = "true|false",
usage="If true, then the message specification for the method is rendered")
private String withMessageArg;

//TODO: Move to a "list_services"-specific flag container
@Option(
name = "--list_output_format",
metaVar = "readable|json",
usage="The output format of the list service command, defaults to readable if arg is omitted ")
private String listOutputFormatArg;

// *************************************************************************

/**
Expand Down Expand Up @@ -174,6 +198,26 @@ public Optional<String> tlsClientOverrideAuthority() {
return Optional.ofNullable(tlsClientOverrideAuthority);
}

public Optional<URL> oauthRefreshTokenEndpointUrl() {
return maybeUrl(oauthRefreshTokenEndpointUrl);
}

public Optional<String> oauthClientId() {
return Optional.ofNullable(oauthClientId);
}

public Optional<String> oauthClientSecret() {
return Optional.ofNullable(oauthClientSecret);
}

public Optional<Path> oauthRefreshTokenPath() {
return maybePath(oauthRefreshTokenPath);
}

public Optional<Path> oauthAccessTokenPath() {
return maybePath(oauthAccessTokenPath);
}

/**
* First stage of a migration towards a "command"-based instantiation of polyglot.
* Supported commands:
Expand Down Expand Up @@ -203,6 +247,12 @@ public Optional<Boolean> withMessage() {
}
return Optional.of(Boolean.parseBoolean(withMessageArg));
}

//TODO: Move to a "list_services"-specific flag container
public Optional<String> listOutputFormat() {
return Optional.ofNullable(listOutputFormatArg);
}

// *************************************************************************

public ImmutableList<Path> additionalProtocIncludes() {
Expand Down Expand Up @@ -235,4 +285,17 @@ private static Optional<Path> maybePath(String rawPath) {
Preconditions.checkArgument(Files.exists(path), "File " + rawPath + " does not exist");
return Optional.of(Paths.get(rawPath));
}

private static Optional<URL> maybeUrl(String rawUrl) {
if (rawUrl == null) {
return Optional.empty();
}
try {
URL url = new URL(rawUrl);
return Optional.of(url);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("URL " + rawUrl + " is invalid", e);
}

}
}
Loading