broker
parent
dcae38d656
commit
3cd4f8126f
@ -0,0 +1,316 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
"os" |
||||
"regexp" |
||||
"strings" |
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect" |
||||
|
||||
"google.golang.org/genproto/googleapis/api/annotations" |
||||
"google.golang.org/protobuf/compiler/protogen" |
||||
"google.golang.org/protobuf/proto" |
||||
"google.golang.org/protobuf/types/descriptorpb" |
||||
) |
||||
|
||||
const ( |
||||
contextPackage = protogen.GoImportPath("context") |
||||
transportHTTPPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http") |
||||
bindingPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http/binding") |
||||
) |
||||
|
||||
var methodSets = make(map[string]int) |
||||
|
||||
// generateFile generates a _http.pb.go file containing kratos errors definitions.
|
||||
func generateFile(gen *protogen.Plugin, file *protogen.File, omitempty bool) *protogen.GeneratedFile { |
||||
if len(file.Services) == 0 || (omitempty && !hasHTTPRule(file.Services)) { |
||||
return nil |
||||
} |
||||
filename := file.GeneratedFilenamePrefix + "_broker.pb.go" |
||||
g := gen.NewGeneratedFile(filename, file.GoImportPath) |
||||
g.P("// Code generated by protoc-gen-go-broker. DO NOT EDIT.") |
||||
g.P("// versions:") |
||||
g.P(fmt.Sprintf("// - protoc-gen-go-broker %s", release)) |
||||
g.P("// - protoc ", protocVersion(gen)) |
||||
if file.Proto.GetOptions().GetDeprecated() { |
||||
g.P("// ", file.Desc.Path(), " is a deprecated file.") |
||||
} else { |
||||
g.P("// source: ", file.Desc.Path()) |
||||
} |
||||
g.P() |
||||
g.P("package ", file.GoPackageName) |
||||
g.P() |
||||
generateFileContent(gen, file, g, omitempty) |
||||
return g |
||||
} |
||||
|
||||
// generateFileContent generates the kratos errors definitions, excluding the package statement.
|
||||
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, omitempty bool) { |
||||
if len(file.Services) == 0 { |
||||
return |
||||
} |
||||
for _, service := range file.Services { |
||||
genService(gen, file, g, service, omitempty) |
||||
} |
||||
} |
||||
|
||||
func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, service *protogen.Service, omitempty bool) { |
||||
if service.Desc.Options().(*descriptorpb.ServiceOptions).GetDeprecated() { |
||||
g.P("//") |
||||
g.P(deprecationComment) |
||||
} |
||||
// HTTP Server.
|
||||
sd := &serviceDesc{ |
||||
ServiceType: service.GoName, |
||||
ServiceName: string(service.Desc.FullName()), |
||||
Metadata: file.Desc.Path(), |
||||
} |
||||
for _, method := range service.Methods { |
||||
if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { |
||||
continue |
||||
} |
||||
rule, ok := proto.GetExtension(method.Desc.Options(), annotations.E_Http).(*annotations.HttpRule) |
||||
if rule != nil && ok { |
||||
for _, bind := range rule.AdditionalBindings { |
||||
sd.Methods = append(sd.Methods, buildHTTPRule(g, method, bind)) |
||||
} |
||||
sd.Methods = append(sd.Methods, buildHTTPRule(g, method, rule)) |
||||
} else if !omitempty { |
||||
path := fmt.Sprintf("/%s/%s", service.Desc.FullName(), method.Desc.Name()) |
||||
sd.Methods = append(sd.Methods, buildMethodDesc(g, method, http.MethodPost, path)) |
||||
} |
||||
} |
||||
if len(sd.Methods) != 0 { |
||||
g.P(sd.execute()) |
||||
} |
||||
} |
||||
|
||||
func hasHTTPRule(services []*protogen.Service) bool { |
||||
for _, service := range services { |
||||
for _, method := range service.Methods { |
||||
if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { |
||||
continue |
||||
} |
||||
rule, ok := proto.GetExtension(method.Desc.Options(), annotations.E_Http).(*annotations.HttpRule) |
||||
if rule != nil && ok { |
||||
return true |
||||
} |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotations.HttpRule) *methodDesc { |
||||
var ( |
||||
path string |
||||
method string |
||||
body string |
||||
responseBody string |
||||
) |
||||
|
||||
switch pattern := rule.Pattern.(type) { |
||||
case *annotations.HttpRule_Get: |
||||
path = pattern.Get |
||||
method = http.MethodGet |
||||
case *annotations.HttpRule_Put: |
||||
path = pattern.Put |
||||
method = http.MethodPut |
||||
case *annotations.HttpRule_Post: |
||||
path = pattern.Post |
||||
method = http.MethodPost |
||||
case *annotations.HttpRule_Delete: |
||||
path = pattern.Delete |
||||
method = http.MethodDelete |
||||
case *annotations.HttpRule_Patch: |
||||
path = pattern.Patch |
||||
method = http.MethodPatch |
||||
case *annotations.HttpRule_Custom: |
||||
path = pattern.Custom.Path |
||||
method = pattern.Custom.Kind |
||||
} |
||||
body = rule.Body |
||||
responseBody = rule.ResponseBody |
||||
md := buildMethodDesc(g, m, method, path) |
||||
if method == http.MethodGet || method == http.MethodDelete { |
||||
if body != "" { |
||||
_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s body should not be declared.\n", method, path) |
||||
} |
||||
} else { |
||||
if body == "" { |
||||
_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s does not declare a body.\n", method, path) |
||||
} |
||||
} |
||||
if body == "*" { |
||||
md.HasBody = true |
||||
md.Body = "" |
||||
} else if body != "" { |
||||
md.HasBody = true |
||||
md.Body = "." + camelCaseVars(body) |
||||
} else { |
||||
md.HasBody = false |
||||
} |
||||
if responseBody == "*" { |
||||
md.ResponseBody = "" |
||||
} else if responseBody != "" { |
||||
md.ResponseBody = "." + camelCaseVars(responseBody) |
||||
} |
||||
return md |
||||
} |
||||
|
||||
func buildMethodDesc(g *protogen.GeneratedFile, m *protogen.Method, method, path string) *methodDesc { |
||||
defer func() { methodSets[m.GoName]++ }() |
||||
|
||||
vars := buildPathVars(path) |
||||
|
||||
for v, s := range vars { |
||||
fields := m.Input.Desc.Fields() |
||||
|
||||
if s != nil { |
||||
path = replacePath(v, *s, path) |
||||
} |
||||
for _, field := range strings.Split(v, ".") { |
||||
if strings.TrimSpace(field) == "" { |
||||
continue |
||||
} |
||||
if strings.Contains(field, ":") { |
||||
field = strings.Split(field, ":")[0] |
||||
} |
||||
fd := fields.ByName(protoreflect.Name(field)) |
||||
if fd == nil { |
||||
fmt.Fprintf(os.Stderr, "\u001B[31mERROR\u001B[m: The corresponding field '%s' declaration in message could not be found in '%s'\n", v, path) |
||||
os.Exit(2) |
||||
} |
||||
if fd.IsMap() { |
||||
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: The field in path:'%s' shouldn't be a map.\n", v) |
||||
} else if fd.IsList() { |
||||
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: The field in path:'%s' shouldn't be a list.\n", v) |
||||
} else if fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind { |
||||
fields = fd.Message().Fields() |
||||
} |
||||
} |
||||
} |
||||
return &methodDesc{ |
||||
Name: m.GoName, |
||||
OriginalName: string(m.Desc.Name()), |
||||
Num: methodSets[m.GoName], |
||||
Request: g.QualifiedGoIdent(m.Input.GoIdent), |
||||
Reply: g.QualifiedGoIdent(m.Output.GoIdent), |
||||
Path: path, |
||||
Method: method, |
||||
HasVars: len(vars) > 0, |
||||
} |
||||
} |
||||
|
||||
func buildPathVars(path string) (res map[string]*string) { |
||||
if strings.HasSuffix(path, "/") { |
||||
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: Path %s should not end with \"/\" \n", path) |
||||
} |
||||
pattern := regexp.MustCompile(`(?i){([a-z.0-9_\s]*)=?([^{}]*)}`) |
||||
matches := pattern.FindAllStringSubmatch(path, -1) |
||||
res = make(map[string]*string, len(matches)) |
||||
for _, m := range matches { |
||||
name := strings.TrimSpace(m[1]) |
||||
if len(name) > 1 && len(m[2]) > 0 { |
||||
res[name] = &m[2] |
||||
} else { |
||||
res[name] = nil |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func replacePath(name string, value string, path string) string { |
||||
pattern := regexp.MustCompile(fmt.Sprintf(`(?i){([\s]*%s[\s]*)=?([^{}]*)}`, name)) |
||||
idx := pattern.FindStringIndex(path) |
||||
if len(idx) > 0 { |
||||
path = fmt.Sprintf("%s{%s:%s}%s", |
||||
path[:idx[0]], // The start of the match
|
||||
name, |
||||
strings.ReplaceAll(value, "*", ".*"), |
||||
path[idx[1]:], |
||||
) |
||||
} |
||||
return path |
||||
} |
||||
|
||||
func camelCaseVars(s string) string { |
||||
subs := strings.Split(s, ".") |
||||
vars := make([]string, 0, len(subs)) |
||||
for _, sub := range subs { |
||||
vars = append(vars, camelCase(sub)) |
||||
} |
||||
return strings.Join(vars, ".") |
||||
} |
||||
|
||||
// camelCase returns the CamelCased name.
|
||||
// If there is an interior underscore followed by a lower case letter,
|
||||
// drop the underscore and convert the letter to upper case.
|
||||
// There is a remote possibility of this rewrite causing a name collision,
|
||||
// but it's so remote we're prepared to pretend it's nonexistent - since the
|
||||
// C++ generator lowercase names, it's extremely unlikely to have two fields
|
||||
// with different capitalization.
|
||||
// In short, _my_field_name_2 becomes XMyFieldName_2.
|
||||
func camelCase(s string) string { |
||||
if s == "" { |
||||
return "" |
||||
} |
||||
t := make([]byte, 0, 32) |
||||
i := 0 |
||||
if s[0] == '_' { |
||||
// Need a capital letter; drop the '_'.
|
||||
t = append(t, 'X') |
||||
i++ |
||||
} |
||||
// Invariant: if the next letter is lower case, it must be converted
|
||||
// to upper case.
|
||||
// That is, we process a word at a time, where words are marked by _ or
|
||||
// upper case letter. Digits are treated as words.
|
||||
for ; i < len(s); i++ { |
||||
c := s[i] |
||||
if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { |
||||
continue // Skip the underscore in s.
|
||||
} |
||||
if isASCIIDigit(c) { |
||||
t = append(t, c) |
||||
continue |
||||
} |
||||
// Assume we have a letter now - if not, it's a bogus identifier.
|
||||
// The next word is a sequence of characters that must start upper case.
|
||||
if isASCIILower(c) { |
||||
c ^= ' ' // Make it a capital letter.
|
||||
} |
||||
t = append(t, c) // Guaranteed not lower case.
|
||||
// Accept lower case sequence that follows.
|
||||
for i+1 < len(s) && isASCIILower(s[i+1]) { |
||||
i++ |
||||
t = append(t, s[i]) |
||||
} |
||||
} |
||||
return string(t) |
||||
} |
||||
|
||||
// Is c an ASCII lower-case letter?
|
||||
func isASCIILower(c byte) bool { |
||||
return 'a' <= c && c <= 'z' |
||||
} |
||||
|
||||
// Is c an ASCII digit?
|
||||
func isASCIIDigit(c byte) bool { |
||||
return '0' <= c && c <= '9' |
||||
} |
||||
|
||||
func protocVersion(gen *protogen.Plugin) string { |
||||
v := gen.Request.GetCompilerVersion() |
||||
if v == nil { |
||||
return "(unknown)" |
||||
} |
||||
var suffix string |
||||
if s := v.GetSuffix(); s != "" { |
||||
suffix = "-" + s |
||||
} |
||||
return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix) |
||||
} |
||||
|
||||
const deprecationComment = "// Deprecated: Do not use." |
@ -0,0 +1,113 @@ |
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.19.4
|
||||
// source: cmd/proto-gen-go-broker/broker/broker.proto
|
||||
|
||||
package broker |
||||
|
||||
import ( |
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect" |
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl" |
||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb" |
||||
reflect "reflect" |
||||
) |
||||
|
||||
const ( |
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) |
||||
) |
||||
|
||||
var file_cmd_proto_gen_go_broker_broker_broker_proto_extTypes = []protoimpl.ExtensionInfo{ |
||||
{ |
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil), |
||||
ExtensionType: (*string)(nil), |
||||
Field: 1001011, |
||||
Name: "errors.receive_topic", |
||||
Tag: "bytes,1001011,opt,name=receive_topic", |
||||
Filename: "cmd/proto-gen-go-broker/broker/broker.proto", |
||||
}, |
||||
{ |
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil), |
||||
ExtensionType: (*string)(nil), |
||||
Field: 1008611, |
||||
Name: "errors.output_topic", |
||||
Tag: "bytes,1008611,opt,name=output_topic", |
||||
Filename: "cmd/proto-gen-go-broker/broker/broker.proto", |
||||
}, |
||||
} |
||||
|
||||
// Extension fields to descriptorpb.MethodOptions.
|
||||
var ( |
||||
// optional string receive_topic = 1001011;
|
||||
E_ReceiveTopic = &file_cmd_proto_gen_go_broker_broker_broker_proto_extTypes[0] |
||||
// optional string output_topic = 1008611;
|
||||
E_OutputTopic = &file_cmd_proto_gen_go_broker_broker_broker_proto_extTypes[1] |
||||
) |
||||
|
||||
var File_cmd_proto_gen_go_broker_broker_broker_proto protoreflect.FileDescriptor |
||||
|
||||
var file_cmd_proto_gen_go_broker_broker_broker_proto_rawDesc = []byte{ |
||||
0x0a, 0x2b, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x67, 0x65, 0x6e, 0x2d, |
||||
0x67, 0x6f, 0x2d, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, |
||||
0x2f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, |
||||
0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, |
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, |
||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x45, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x69, |
||||
0x76, 0x65, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, |
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, |
||||
0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb3, 0x8c, 0x3d, 0x20, 0x01, 0x28, 0x09, |
||||
0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x3a, 0x43, |
||||
0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x1e, |
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, |
||||
0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe3, |
||||
0xc7, 0x3d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, |
||||
0x70, 0x69, 0x63, 0x42, 0x6a, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, |
||||
0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x50, |
||||
0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, |
||||
0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, |
||||
0x32, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x67, 0x65, 0x6e, 0x2d, |
||||
0x67, 0x6f, 0x2d, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x3b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, |
||||
0xa2, 0x02, 0x0c, 0x4b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x42, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x62, |
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, |
||||
} |
||||
|
||||
var file_cmd_proto_gen_go_broker_broker_broker_proto_goTypes = []interface{}{ |
||||
(*descriptorpb.MethodOptions)(nil), // 0: google.protobuf.MethodOptions
|
||||
} |
||||
var file_cmd_proto_gen_go_broker_broker_broker_proto_depIdxs = []int32{ |
||||
0, // 0: errors.receive_topic:extendee -> google.protobuf.MethodOptions
|
||||
0, // 1: errors.output_topic:extendee -> google.protobuf.MethodOptions
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
0, // [0:2] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
} |
||||
|
||||
func init() { file_cmd_proto_gen_go_broker_broker_broker_proto_init() } |
||||
func file_cmd_proto_gen_go_broker_broker_broker_proto_init() { |
||||
if File_cmd_proto_gen_go_broker_broker_broker_proto != nil { |
||||
return |
||||
} |
||||
type x struct{} |
||||
out := protoimpl.TypeBuilder{ |
||||
File: protoimpl.DescBuilder{ |
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |
||||
RawDescriptor: file_cmd_proto_gen_go_broker_broker_broker_proto_rawDesc, |
||||
NumEnums: 0, |
||||
NumMessages: 0, |
||||
NumExtensions: 2, |
||||
NumServices: 0, |
||||
}, |
||||
GoTypes: file_cmd_proto_gen_go_broker_broker_broker_proto_goTypes, |
||||
DependencyIndexes: file_cmd_proto_gen_go_broker_broker_broker_proto_depIdxs, |
||||
ExtensionInfos: file_cmd_proto_gen_go_broker_broker_broker_proto_extTypes, |
||||
}.Build() |
||||
File_cmd_proto_gen_go_broker_broker_broker_proto = out.File |
||||
file_cmd_proto_gen_go_broker_broker_broker_proto_rawDesc = nil |
||||
file_cmd_proto_gen_go_broker_broker_broker_proto_goTypes = nil |
||||
file_cmd_proto_gen_go_broker_broker_broker_proto_depIdxs = nil |
||||
} |
@ -0,0 +1,17 @@ |
||||
syntax = "proto3"; |
||||
|
||||
package errors; |
||||
|
||||
option go_package = "github.com/go-kratos/kratos/v2/cmd/proto-gen-go-broker;broker"; |
||||
option java_multiple_files = true; |
||||
option java_package = "com.github.kratos.Broker"; |
||||
option objc_class_prefix = "KratosBroker"; |
||||
|
||||
import "google/protobuf/descriptor.proto"; |
||||
|
||||
|
||||
|
||||
extend google.protobuf.MethodOptions { |
||||
string receive_topic = 1001011; |
||||
string output_topic = 1008611; |
||||
} |
@ -0,0 +1,87 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
func TestNoParameters(t *testing.T) { |
||||
path := "/test/noparams" |
||||
m := buildPathVars(path) |
||||
if !reflect.DeepEqual(m, map[string]*string{}) { |
||||
t.Fatalf("Map should be empty") |
||||
} |
||||
} |
||||
|
||||
func TestSingleParam(t *testing.T) { |
||||
path := "/test/{message.id}" |
||||
m := buildPathVars(path) |
||||
if !reflect.DeepEqual(len(m), 1) { |
||||
t.Fatalf("len(m) not is 1") |
||||
} |
||||
if m["message.id"] != nil { |
||||
t.Fatalf(`m["message.id"] should be empty`) |
||||
} |
||||
} |
||||
|
||||
func TestTwoParametersReplacement(t *testing.T) { |
||||
path := "/test/{message.id}/{message.name=messages/*}" |
||||
m := buildPathVars(path) |
||||
if len(m) != 2 { |
||||
t.Fatal("len(m) should be 2") |
||||
} |
||||
if m["message.id"] != nil { |
||||
t.Fatal(`m["message.id"] should be nil`) |
||||
} |
||||
if m["message.name"] == nil { |
||||
t.Fatal(`m["message.name"] should not be nil`) |
||||
} |
||||
if *m["message.name"] != "messages/*" { |
||||
t.Fatal(`m["message.name"] should be "messages/*"`) |
||||
} |
||||
} |
||||
|
||||
func TestNoReplacePath(t *testing.T) { |
||||
path := "/test/{message.id=test}" |
||||
if !reflect.DeepEqual(replacePath("message.id", "test", path), "/test/{message.id:test}") { |
||||
t.Fatal(`replacePath("message.id", "test", path) should be "/test/{message.id:test}"`) |
||||
} |
||||
path = "/test/{message.id=test/*}" |
||||
if !reflect.DeepEqual(replacePath("message.id", "test/*", path), "/test/{message.id:test/.*}") { |
||||
t.Fatal(`replacePath("message.id", "test/*", path) should be "/test/{message.id:test/.*}"`) |
||||
} |
||||
} |
||||
|
||||
func TestReplacePath(t *testing.T) { |
||||
path := "/test/{message.id}/{message.name=messages/*}" |
||||
newPath := replacePath("message.name", "messages/*", path) |
||||
if !reflect.DeepEqual("/test/{message.id}/{message.name:messages/.*}", newPath) { |
||||
t.Fatal(`replacePath("message.name", "messages/*", path) should be "/test/{message.id}/{message.name:messages/.*}"`) |
||||
} |
||||
} |
||||
|
||||
func TestIteration(t *testing.T) { |
||||
path := "/test/{message.id}/{message.name=messages/*}" |
||||
vars := buildPathVars(path) |
||||
for v, s := range vars { |
||||
if s != nil { |
||||
path = replacePath(v, *s, path) |
||||
} |
||||
} |
||||
if !reflect.DeepEqual("/test/{message.id}/{message.name:messages/.*}", path) { |
||||
t.Fatal(`replacePath("message.name", "messages/*", path) should be "/test/{message.id}/{message.name:messages/.*}"`) |
||||
} |
||||
} |
||||
|
||||
func TestIterationMiddle(t *testing.T) { |
||||
path := "/test/{message.name=messages/*}/books" |
||||
vars := buildPathVars(path) |
||||
for v, s := range vars { |
||||
if s != nil { |
||||
path = replacePath(v, *s, path) |
||||
} |
||||
} |
||||
if !reflect.DeepEqual("/test/{message.name:messages/.*}/books", path) { |
||||
t.Fatal(`replacePath("message.name", "messages/*", path) should be "/test/{message.name:messages/.*}/books"`) |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
module github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2 |
||||
|
||||
go 1.16 |
||||
|
||||
require ( |
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd |
||||
google.golang.org/protobuf v1.28.0 |
||||
) |
@ -0,0 +1,34 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
|
||||
"google.golang.org/protobuf/compiler/protogen" |
||||
"google.golang.org/protobuf/types/pluginpb" |
||||
) |
||||
|
||||
var ( |
||||
showVersion = flag.Bool("version", false, "print the version and exit") |
||||
omitempty = flag.Bool("omitempty", true, "omit if google.api is empty") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
if *showVersion { |
||||
fmt.Printf("protoc-gen-go-http %v\n", release) |
||||
return |
||||
} |
||||
protogen.Options{ |
||||
ParamFunc: flag.CommandLine.Set, |
||||
}.Run(func(gen *protogen.Plugin) error { |
||||
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) |
||||
for _, f := range gen.Files { |
||||
if !f.Generate { |
||||
continue |
||||
} |
||||
generateFile(gen, f, *omitempty) |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -0,0 +1,140 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strings" |
||||
"text/template" |
||||
) |
||||
|
||||
var httpTemplate = ` |
||||
{{$svrType := .ServiceType}} |
||||
{{$svrName := .ServiceName}} |
||||
|
||||
{{- range .MethodSets}} |
||||
const Operation{{$svrType}}{{.OriginalName}} = "/{{$svrName}}/{{.OriginalName}}" |
||||
{{- end}} |
||||
|
||||
type {{.ServiceType}}HTTPServer interface { |
||||
{{- range .MethodSets}} |
||||
{{.Name}}(context.Context, *{{.Request}}) (*{{.Reply}}, error) |
||||
{{- end}} |
||||
} |
||||
|
||||
func Register{{.ServiceType}}HTTPServer(s *http.Server, srv {{.ServiceType}}HTTPServer) { |
||||
r := s.Route("/") |
||||
{{- range .Methods}} |
||||
r.{{.Method}}("{{.Path}}", _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv)) |
||||
{{- end}} |
||||
} |
||||
|
||||
{{range .Methods}} |
||||
func _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv {{$svrType}}HTTPServer) func(ctx http.Context) error { |
||||
return func(ctx http.Context) error { |
||||
var in {{.Request}} |
||||
{{- if .HasBody}} |
||||
if err := ctx.Bind(&in{{.Body}}); err != nil { |
||||
return err |
||||
} |
||||
|
||||
{{- if not (eq .Body "")}} |
||||
if err := ctx.BindQuery(&in); err != nil { |
||||
return err |
||||
} |
||||
{{- end}} |
||||
{{- else}} |
||||
if err := ctx.BindQuery(&in{{.Body}}); err != nil { |
||||
return err |
||||
} |
||||
{{- end}} |
||||
{{- if .HasVars}} |
||||
if err := ctx.BindVars(&in); err != nil { |
||||
return err |
||||
} |
||||
{{- end}} |
||||
http.SetOperation(ctx,Operation{{$svrType}}{{.OriginalName}}) |
||||
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { |
||||
return srv.{{.Name}}(ctx, req.(*{{.Request}})) |
||||
}) |
||||
out, err := h(ctx, &in) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
reply := out.(*{{.Reply}}) |
||||
return ctx.Result(200, reply{{.ResponseBody}}) |
||||
} |
||||
} |
||||
{{end}} |
||||
|
||||
type {{.ServiceType}}HTTPClient interface { |
||||
{{- range .MethodSets}} |
||||
{{.Name}}(ctx context.Context, req *{{.Request}}, opts ...http.CallOption) (rsp *{{.Reply}}, err error)
|
||||
{{- end}} |
||||
} |
||||
|
||||
type {{.ServiceType}}HTTPClientImpl struct{ |
||||
cc *http.Client |
||||
} |
||||
|
||||
func New{{.ServiceType}}HTTPClient (client *http.Client) {{.ServiceType}}HTTPClient { |
||||
return &{{.ServiceType}}HTTPClientImpl{client} |
||||
} |
||||
|
||||
{{range .MethodSets}} |
||||
func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Request}}, opts ...http.CallOption) (*{{.Reply}}, error) { |
||||
var out {{.Reply}} |
||||
pattern := "{{.Path}}" |
||||
path := binding.EncodeURL(pattern, in, {{not .HasBody}}) |
||||
opts = append(opts, http.Operation(Operation{{$svrType}}{{.OriginalName}})) |
||||
opts = append(opts, http.PathTemplate(pattern)) |
||||
{{if .HasBody -}} |
||||
err := c.cc.Invoke(ctx, "{{.Method}}", path, in{{.Body}}, &out{{.ResponseBody}}, opts...) |
||||
{{else -}}
|
||||
err := c.cc.Invoke(ctx, "{{.Method}}", path, nil, &out{{.ResponseBody}}, opts...) |
||||
{{end -}} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &out, err |
||||
} |
||||
{{end}} |
||||
` |
||||
|
||||
type serviceDesc struct { |
||||
ServiceType string // Greeter
|
||||
ServiceName string // helloworld.Greeter
|
||||
Metadata string // api/helloworld/helloworld.proto
|
||||
Methods []*methodDesc |
||||
MethodSets map[string]*methodDesc |
||||
} |
||||
|
||||
type methodDesc struct { |
||||
// method
|
||||
Name string |
||||
OriginalName string // The parsed original name
|
||||
Num int |
||||
Request string |
||||
Reply string |
||||
// http_rule
|
||||
Path string |
||||
Method string |
||||
HasVars bool |
||||
HasBody bool |
||||
Body string |
||||
ResponseBody string |
||||
} |
||||
|
||||
func (s *serviceDesc) execute() string { |
||||
s.MethodSets = make(map[string]*methodDesc) |
||||
for _, m := range s.Methods { |
||||
s.MethodSets[m.Name] = m |
||||
} |
||||
buf := new(bytes.Buffer) |
||||
tmpl, err := template.New("http").Parse(strings.TrimSpace(httpTemplate)) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if err := tmpl.Execute(buf, s); err != nil { |
||||
panic(err) |
||||
} |
||||
return strings.Trim(buf.String(), "\r\n") |
||||
} |
@ -0,0 +1,4 @@ |
||||
package main |
||||
|
||||
// release is the current protoc-gen-go-http version.
|
||||
const release = "v2.5.2" |
@ -0,0 +1,10 @@ |
||||
# Generated with protoc-gen-openapi |
||||
# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi |
||||
|
||||
openapi: 3.0.3 |
||||
info: |
||||
title: "" |
||||
version: 0.0.1 |
||||
paths: {} |
||||
components: |
||||
schemas: {} |
@ -0,0 +1,17 @@ |
||||
package broker |
||||
|
||||
import "context" |
||||
|
||||
type Event interface { |
||||
Key() string |
||||
Header() map[string]string |
||||
Value() []byte |
||||
} |
||||
|
||||
type Handler func(context.Context, Event) error |
||||
|
||||
type Broker interface { |
||||
Receive(topic string, handler Handler) error |
||||
Send(ctx context.Context, msg Event) error |
||||
Close() error |
||||
} |
@ -0,0 +1,37 @@ |
||||
package broker |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/go-kratos/kratos/v2/transport" |
||||
) |
||||
|
||||
type broker struct { |
||||
broker Broker |
||||
receives map[string]Handler |
||||
} |
||||
|
||||
type HandlerFunc func(context.Context, Event) error |
||||
|
||||
func (b *broker) Start(_ context.Context) error { |
||||
for topic, handler := range b.receives { |
||||
if err := b.broker.Receive(topic, func(ctx context.Context, event Event) error { |
||||
return handler(ctx, event) |
||||
}); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (b *broker) Stop(ctx context.Context) error { |
||||
return b.broker.Close() |
||||
} |
||||
|
||||
func NewBroker(b Broker) transport.Server { |
||||
return &broker{} |
||||
} |
||||
|
||||
// Receive registers a handler for the given topic.
|
||||
func (b *broker) Receive(topic string, handler Handler) error { |
||||
return b.broker.Receive(topic, handler) |
||||
} |
Loading…
Reference in new issue