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
Open

JSON Option for List Command #57

wants to merge 12 commits into from

Conversation

peteboothroyd
Copy link

JSON Output

  • Adding JSON option to list command to allow easy parsing of output.
  • Altering with_message output to be json which can be copy and pasted straight into a call command.
  • Now printing response message template as this may be useful for users to determine which service they want to call.
  • Updating tests to reflect this update.

@@ -0,0 +1,20 @@
package me.dinowernli.grpc.polyglot.command;

public class Method {
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need to define these custom types (here and services below)?

Can we not just use the methoddescriptor produced by protobuf?

Copy link
Author

Choose a reason for hiding this comment

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

Sure, we could hard code the logic to print straight to JSON from the protobuf descriptors. I think though that if there is going to be more than one output format option that it will be much easier to use pre-existing tools to process the java object, eg using gson takes one line to go to JSON, I imagine there will be other options to go to other formats just as easily.

Copy link
Member

@dinowernli dinowernli left a comment

Choose a reason for hiding this comment

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

General approach looks sound, but I don't think we should be introducing those mutable types for service and method.


if (jsonOutput.isPresent() && jsonOutput.get()) {
Gson gson = new Gson();
output.writeLine(gson.toJson(services));
Copy link
Member

Choose a reason for hiding this comment

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

As discussed above, can we not just json-print the descriptors rather than converting them to our custom data type?

Copy link
Author

@peteboothroyd peteboothroyd Aug 7, 2017

Choose a reason for hiding this comment

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

I looked into using the JSON printing but when I tried this it looked like it would take more work. The JSON printed did not resolve all the way down to the primitive level as the as the output currently does. Say a proto message is defined:

"foo": {
    "name": "STRING",
    "id": "INT"
}

The JSON output would be:

"foo": "Foo"

This doesn't give enough information to people to allow them to construct their own messages.

Copy link
Member

Choose a reason for hiding this comment

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

I think that's what you get when you print a message descriptor. What you actually want is the service descriptor [1]. That should get you the service name, the method name, and the type name for the messages. Service descriptors are already conveniently returns by listServices()

[1] https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto#L230

@@ -84,6 +84,13 @@
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 = "--json_output",
Copy link
Member

Choose a reason for hiding this comment

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

Can we have this be a --list_output_format and have "json" be one of the valid flag values?

Copy link
Author

Choose a reason for hiding this comment

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

Sure this should be pretty easy and will be more extensible in the future

@@ -5,6 +5,7 @@
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import me.dinowernli.grpc.polyglot.command.ServiceCall;
import me.dinowernli.grpc.polyglot.command.ServiceList;
//import me.dinowernli.grpc.polyglot.command.Server;
Copy link
Member

Choose a reason for hiding this comment

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

spurious comment


if (jsonOutput.isPresent() && jsonOutput.get()) {
Gson gson = new Gson();
output.writeLine(gson.toJson(services));
Copy link
Member

Choose a reason for hiding this comment

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

I think that's what you get when you print a message descriptor. What you actually want is the service descriptor [1]. That should get you the service name, the method name, and the type name for the messages. Service descriptors are already conveniently returns by listServices()

[1] https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto#L230

@peteboothroyd
Copy link
Author

I've updated with your suggestions. The merging process went a little awry on my end, the main changes are in the ServiceList, Main, and CommandLineArgs.

Changes:

  • There is now a list_output_format flag with two options, json or the default readable option
  • Tests updated and added for command line argument parsing
  • Listing the methods now reports true/false for client & server streaming
  • Tests updated for ServiceList

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Check your existing CLA data and verify that your email is set on your git commits.
  • If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot.
  • In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again.

@peteboothroyd
Copy link
Author

peteboothroyd commented Aug 17, 2017 via email

@googlebot
Copy link

CLAs look good, thanks!

if (listOutputFormat.isPresent() && listOutputFormat.get().equals("json")) output.write("[\n"); else output.newLine();

// Dealing with commas for JSON
boolean firstServiceHasBeenPrinted = false;

for (ServiceDescriptor descriptor : serviceResolver.listServices()) {
boolean matchingDescriptor =
!serviceFilter.isPresent()
|| descriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase());

if (matchingDescriptor) {
Copy link
Member

Choose a reason for hiding this comment

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

Almost there :) Any chance we can just print the json of descriptor.toProto()? That should give you the information you need and avoid manually constructing json output.

Copy link
Author

Choose a reason for hiding this comment

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

I could try but will need some help. As I think I mentioned before as far as I can tell using the JsonPrinters do not do what we need. eg.
printing:String jsonFormat = JsonFormat.printer().print(descriptor.toProto().toBuilder());
Returns something similar to:
{ "name": "TestMethod", "inputType": ".polyglot.test.TestRequest", "outputType": ".polyglot.test.TestResponse", "options": {} }
There is not enough information about the types. I imagine you could recursively resolve every type which is more than one level deep to get down to the primitive level of strings, int etc. but this will require a decent change to the code.
I think this approach is the simplest method given what's already written but if you know a better way then let me know.

File protoDiscoveryDir = new File(protoDiscoveryRoot).getParentFile();
Optional<Boolean> withMessage,
Optional<String> listOutputFormat) {
boolean firstMethodHasBeenPrinted = false;

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

Copy link
Member

Choose a reason for hiding this comment

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

Same here, you should be able to just json-format the method protos.

@peteboothroyd
Copy link
Author

peteboothroyd commented Sep 5, 2017

Changes

  • JSON output now prints out the service protos, and dependency file protos.
  • JSON output structure is
{
  serviceProtos: [//list of serviceDescriptorProtos],
  fileProtos: [//list of fileDescriptorProtos]
}

See src/test/java/me/dinowernli/grpc/polyglot/command/jsonOutputGold.txt for more illustrative example

  • MessageWriter can use custom separator string.
  • Separator string only used in between messages.

Notes

  • Services are filtered according to the serviceFilter flag, but the methodFilter flag is ignored for json output

.orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name));
.filter(config -> config.getName().equals(name))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name));
Copy link
Member

Choose a reason for hiding this comment

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

nit: indentation here was correct, please revert

@@ -65,7 +75,14 @@ public void onError(Throwable t) {
@Override
public void onNext(T message) {
try {
output.write(jsonPrinter.print(message) + MESSAGE_SEPARATOR);
String outputString = jsonPrinter.print(message);
// Only separate messages if there are more than one
Copy link
Member

Choose a reason for hiding this comment

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

I don't actually think this is the right thing to do. To make things easier to parse, I believe we alway want to append a separator, even if there is nothing else coming. Can discuss in person.


/**
* Creates a new {@link MessageWriter} which writes the messages it sees to the supplied
* {@link Output}.
*/
public static <T extends Message> MessageWriter<T> create(Output output) {
return new MessageWriter<T>(JsonFormat.printer(), output);
public static <T extends Message> MessageWriter<T> create(Output output, String messageSeparator) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's have two factory methods, one called create(), and the other called createWithSeparator().

That way, there is a sensible default and we don't need the ServiceCall class to understand how these separators work.

.addFile(TestProto.getDescriptor().toProto())
.addFile(FooProto.getDescriptor().toProto())
.build();
.addFile(TestProto.getDescriptor().toProto())
Copy link
Member

Choose a reason for hiding this comment

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

indentation was correct, please revert (thoughout most of this file)

recordingOutput.close();

validateMessageOutput(recordingOutput.getContentsAsString());
}

// @Test
// public void testServiceListOutputWithMessageDetailJsonFormatted() throws Throwable {
// ServiceList.listServices(
Copy link
Member

Choose a reason for hiding this comment

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

please remove this commented-out test

}

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).

@peteboothroyd
Copy link
Author

Ok the things you mentioned should be addressed now. Formatting is almost definitely not correct, can you advise of an automated way to share whatever settings you have as doing this manually is soul destroying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants