Skip to content

Instantly share code, notes, and snippets.

@breadchris
Created May 26, 2023 17:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save breadchris/9b7ec8afc5261283cffa6f30cca23051 to your computer and use it in GitHub Desktop.
Save breadchris/9b7ec8afc5261283cffa6f30cca23051 to your computer and use it in GitHub Desktop.
Protobuf Protoflow Workflow
package grpc
import (
"fmt"
"github.com/jhump/protoreflect/desc"
"google.golang.org/protobuf/types/descriptorpb"
"strings"
"testing"
)
// TODO breadchris figure out if we can base a graph off of just protobuf files
func TestParseProto(t *testing.T) {
res, err := ParseProto("")
if err != nil {
t.Fatal(err)
}
m := map[string]interface{}{}
for _, r := range res {
buildUninterpretedMapForFile(r.GetFile(), m)
for _, s := range r.GetServices() {
t.Log(s.GetFullyQualifiedName())
for _, m := range s.GetMethods() {
t.Log(m.GetFullyQualifiedName())
mo := m.GetMethodOptions()
mo.GetUninterpretedOption()
t.Log(mo.String())
for _, o := range mo.GetUninterpretedOption() {
for _, n := range o.GetName() {
t.Log(n)
}
}
}
}
}
}
func buildUninterpretedMapForFile(fd *desc.FileDescriptor, opts map[string]interface{}) {
buildUninterpretedMap(fd.GetName(), fd.GetFileOptions().GetUninterpretedOption(), opts)
for _, md := range fd.GetMessageTypes() {
buildUninterpretedMapForMessage(fd.GetPackage(), md.AsDescriptorProto(), opts)
}
//for _, extd := range fd.GetExtensions() {
// buildUninterpretedMap(qualify(fd.GetPackage(), extd.GetName()), extd.GetOp.GetUninterpretedOption(), opts)
//}
for _, ed := range fd.GetEnumTypes() {
buildUninterpretedMapForEnum(fd.GetPackage(), ed.AsEnumDescriptorProto(), opts)
}
for _, sd := range fd.GetServices() {
buildUninterpretedMap(sd.GetFullyQualifiedName(), sd.GetServiceOptions().GetUninterpretedOption(), opts)
for _, mtd := range sd.GetMethods() {
buildUninterpretedMap(mtd.GetFullyQualifiedName(), mtd.GetMethodOptions().GetUninterpretedOption(), opts)
}
}
}
func buildUninterpretedMapForMessage(qual string, md *descriptorpb.DescriptorProto, opts map[string]interface{}) {
fqn := qualify(qual, md.GetName())
buildUninterpretedMap(fqn, md.GetOptions().GetUninterpretedOption(), opts)
for _, fld := range md.GetField() {
buildUninterpretedMap(qualify(fqn, fld.GetName()), fld.GetOptions().GetUninterpretedOption(), opts)
}
for _, ood := range md.GetOneofDecl() {
buildUninterpretedMap(qualify(fqn, ood.GetName()), ood.GetOptions().GetUninterpretedOption(), opts)
}
for _, extr := range md.GetExtensionRange() {
buildUninterpretedMap(qualify(fqn, fmt.Sprintf("%d-%d", extr.GetStart(), extr.GetEnd()-1)), extr.GetOptions().GetUninterpretedOption(), opts)
}
for _, nmd := range md.GetNestedType() {
buildUninterpretedMapForMessage(fqn, nmd, opts)
}
for _, extd := range md.GetExtension() {
buildUninterpretedMap(qualify(fqn, extd.GetName()), extd.GetOptions().GetUninterpretedOption(), opts)
}
for _, ed := range md.GetEnumType() {
buildUninterpretedMapForEnum(fqn, ed, opts)
}
}
func buildUninterpretedMapForEnum(qual string, ed *descriptorpb.EnumDescriptorProto, opts map[string]interface{}) {
fqn := qualify(qual, ed.GetName())
buildUninterpretedMap(fqn, ed.GetOptions().GetUninterpretedOption(), opts)
for _, evd := range ed.GetValue() {
buildUninterpretedMap(qualify(fqn, evd.GetName()), evd.GetOptions().GetUninterpretedOption(), opts)
}
}
type ident string
type aggregate string
func buildUninterpretedMap(prefix string, uos []*descriptorpb.UninterpretedOption, opts map[string]interface{}) {
for _, uo := range uos {
parts := make([]string, len(uo.GetName()))
for i, np := range uo.GetName() {
if np.GetIsExtension() {
parts[i] = fmt.Sprintf("(%s)", np.GetNamePart())
} else {
parts[i] = np.GetNamePart()
}
}
uoName := fmt.Sprintf("%s:%s", prefix, strings.Join(parts, "."))
key := uoName
i := 0
for {
if _, ok := opts[key]; !ok {
break
}
i++
key = fmt.Sprintf("%s#%d", uoName, i)
}
var val interface{}
switch {
case uo.AggregateValue != nil:
val = aggregate(uo.GetAggregateValue())
case uo.IdentifierValue != nil:
val = ident(uo.GetIdentifierValue())
case uo.DoubleValue != nil:
val = uo.GetDoubleValue()
case uo.PositiveIntValue != nil:
val = int(uo.GetPositiveIntValue())
case uo.NegativeIntValue != nil:
val = int(uo.GetNegativeIntValue())
default:
val = string(uo.GetStringValue())
}
opts[key] = val
}
}
func qualify(base, name string) string {
if base == "" {
return name
}
return base + "." + name
}
syntax = "proto3";
import "google/protobuf/empty.proto";
import "google/protobuf/descriptor.proto";
import "block.proto";
import "resource.proto";
enum ServiceMethods {
ServiceOne_MethodOne = 0;
ServiceOne_MethodTwo = 1;
RemoteService_ExternalMethod = 2;
ServiceTwo_MethodOne = 3;
}
message MethodOneResponse {
string message = 1;
}
service RemoteService {
option (resource_info) = {
grpc_service: {
host: "localhost:50051"
}
};
rpc ExternalMethod (google.protobuf.Empty) returns (MethodOneResponse) {};
}
service ServiceOne {
rpc MethodOne (block.Input) returns (MethodOneResponse) {
option (protoflow) = {
calls: [
ServiceOne_MethodTwo
]
};
};
rpc MethodTwo (MethodOneResponse) returns (google.protobuf.Empty) {
option (protoflow) = {
calls: [
ServiceTwo_MethodOne
],
};
};
}
service ServiceTwo {
rpc MethodTwo (MethodOneResponse) returns (google.protobuf.Empty) {};
}
// Definition of the `build.info` custom option
extend google.protobuf.MethodOptions {
Runtime protoflow = 1;
}
extend google.protobuf.ServiceOptions {
resource.Resource resource_info = 1;
}
// Definition of the `BuildMetadata` message used in `build.info`
message Runtime {
repeated ServiceMethods calls = 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment