diff --git a/README.md b/README.md index dae9439..6bf06bc 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,26 @@ # HTTP Client ![Coverage](https://img.shields.io/badge/Coverage-22.5%25-red) -This plugin is a http client for micro. +This plugin is an HTTP client for [Micro](https://pkg.go.dev/go.unistack.org/micro/v4). +It implements the [micro.Client](https://pkg.go.dev/go.unistack.org/micro/v4/client#Client) interface. ## Overview -The http client wraps `net/http` to provide a robust micro client with service discovery, load balancing and streaming. -It complies with the [micro.Client](https://godoc.org/go.unistack.org/micro-client-http/v3#Client) interface. +The HTTP client wraps `net/http` to provide a robust client with service discovery, load balancing and +implements HTTP rules defined in the [google/api/http.proto](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto) specification. + +## Limitations + +* Streaming is not yet implemented. +* Only protobuf-generated messages are supported. ## Usage -### Use directly - ```go -import "go.unistack.org/micro-client-http/v3" +import ( + "go.unistack.org/micro/v4" + http "go.unistack.org/micro-client-http/v4" +) service := micro.NewService( micro.Name("my.service"), @@ -21,43 +28,127 @@ service := micro.NewService( ) ``` -### Call Service +### Simple call -Assuming you have a http service "my.service" with path "/foo/bar" ```go -// new client -client := http.NewClient() +import ( + "go.unistack.org/micro/v4/client" + http "go.unistack.org/micro-client-http/v4" + jsoncodec "go.unistack.org/micro-codec-json/v4" +) -// create request/response -request := client.NewRequest("my.service", "/foo/bar", protoRequest{}) -response := new(protoResponse) +c := http.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), +) -// call service -err := client.Call(context.TODO(), request, response) +req := c.NewRequest( + "user-service", + "/user/{user_id}/order/{order_id}", + &protoReq{UserId: "123", OrderId: 456}, +) +rsp := new(protoRsp) + +err := c.Call( + ctx, + req, + rsp, + client.WithAddress("example.com"), +) ``` -or you can call any rest api or site and unmarshal to response struct +### Call with specific options + ```go -// new client -client := client.NewClientCallOptions(http.NewClient(), http.Address("https://api.github.com")) +import ( + "go.unistack.org/micro/v4/client" + http "go.unistack.org/micro-client-http/v4" +) -req := client.NewRequest("github", "/users/vtolstov", nil) -rsp := make(map[string]interface{}) - -err := c.Call(context.TODO(), req, &rsp, mhttp.Method(http.MethodGet)) +err := c.Call( + ctx, + req, + rsp, + client.WithAddress("example.com"), + http.Method("POST"), + http.Path("/user/{user_id}/order/{order_id}"), + http.Body("*"), // <- use all fields from the proto request as HTTP request body or specify a single field name to use only that field (see Google API HTTP spec: google/api/http.proto) +) ``` -Look at http_test.go for detailed use. +### Call with request headers -### Encoding - -Default protobuf with content-type application/proto ```go -client.NewRequest("service", "/path", protoRequest{}) +import ( + "go.unistack.org/micro/v4/metadata" + http "go.unistack.org/micro-client-http/v4" +) + +ctx := metadata.NewOutgoingContext(ctx, metadata.Pairs( + "Authorization", "Bearer token", + "My-Header", "My-Header-Value", +)) + +err := c.Call( + ctx, + req, + rsp, + http.Header("Authorization", "true", "My-Header", "false"), // <- call option that declares required/optional headers +) ``` -Json with content-type application/json +### Call with response headers + ```go -client.NewJsonRequest("service", "/path", jsonRequest{}) +import ( + "go.unistack.org/micro/v4/metadata" + http "go.unistack.org/micro-client-http/v4" +) + +respMetadata := metadata.Metadata{} + +err := c.Call( + ctx, + req, + rsp, + client.WithResponseMetadata(&respMetadata), // <- metadata with response headers +) ``` +### Call with cookies + +```go +import ( + "go.unistack.org/micro/v4/metadata" + http "go.unistack.org/micro-client-http/v4" +) + +ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs( + "Cookie", "session_id=abc123; theme=dark", +)) + +err := c.Call( + ctx, + req, + rsp, + http.Cookie("session_id", "true", "theme", "false"), // <- call option that declares required/optional cookies +) +``` + +### Call with error mapping + +```go +import ( + http "go.unistack.org/micro-client-http/v4" + jsoncodec "go.unistack.org/micro-codec-json/v4" +) + +err := c.Call( + ctx, + req, + rsp, + http.ErrorMap(map[string]any{ + "default": &protoDefaultError{}, // <- default case + "403": &protoSpecialError{}, // <- key is the HTTP status code that is mapped to this error + }), +) +``` diff --git a/builder/body.go b/builder/body.go new file mode 100644 index 0000000..749946a --- /dev/null +++ b/builder/body.go @@ -0,0 +1,116 @@ +package builder + +import ( + "fmt" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func buildSingleFieldBody(msg proto.Message, fieldName string) (proto.Message, error) { + msgReflect := msg.ProtoReflect() + + fd, found := findFieldByName(msgReflect, fieldName) + if !found || fd == nil { + return nil, fmt.Errorf("field %s not found", fieldName) + } + if !msgReflect.Has(fd) { + return nil, fmt.Errorf("field %s is not set", fieldName) + } + + val := msgReflect.Get(fd) + + if fd.Kind() == protoreflect.MessageKind { + return val.Message().Interface(), nil + } + + newMsg := proto.Clone(msg) + newMsgReflect := newMsg.ProtoReflect() + newMsgReflect.Range(func(f protoreflect.FieldDescriptor, _ protoreflect.Value) bool { + if f != fd { + newMsgReflect.Clear(f) + } + return true + }) + + return newMsg, nil +} + +func buildFullBody(msg proto.Message, usedFieldsPath *usedFields) (proto.Message, error) { + var ( + msgReflect = msg.ProtoReflect() + newMsg = msgReflect.New().Interface() + newMsgReflect = newMsg.ProtoReflect() + ) + + fields := msgReflect.Descriptor().Fields() + for i := 0; i < fields.Len(); i++ { + fd := fields.Get(i) + fieldName := fd.JSONName() + + if usedFieldsPath.hasTopLevelKey(fieldName) { + continue + } + + val := msgReflect.Get(fd) + if !val.IsValid() { + continue + } + + // Note: order of the cases is important! + switch { + case fd.IsList(): + list := val.List() + newList := newMsgReflect.Mutable(fd).List() + + if fd.Kind() == protoreflect.MessageKind { + for j := 0; j < list.Len(); j++ { + elem, err := buildFullBody(list.Get(j).Message().Interface(), usedFieldsPath) + if err != nil { + return nil, fmt.Errorf("recursive build full body: %w", err) + } + newList.Append(protoreflect.ValueOfMessage(elem.ProtoReflect())) + } + } else { + for j := 0; j < list.Len(); j++ { + newList.Append(list.Get(j)) + } + } + + case fd.IsMap(): + var ( + m = val.Map() + newMap = newMsgReflect.Mutable(fd).Map() + rangeErr error + ) + m.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { + if fd.MapValue().Kind() == protoreflect.MessageKind { + elem, err := buildFullBody(v.Message().Interface(), usedFieldsPath) + if err != nil { + rangeErr = fmt.Errorf("recursive build full body: %w", err) + return false + } + newMap.Set(k, protoreflect.ValueOfMessage(elem.ProtoReflect())) + } else { + newMap.Set(k, v) + } + return true + }) + if rangeErr != nil { + return nil, fmt.Errorf("map range error: %w", rangeErr) + } + + case fd.Kind() == protoreflect.MessageKind: + elem, err := buildFullBody(val.Message().Interface(), usedFieldsPath) + if err != nil { + return nil, fmt.Errorf("recursive build full body: %w", err) + } + newMsgReflect.Set(fd, protoreflect.ValueOfMessage(elem.ProtoReflect())) + + default: + newMsgReflect.Set(fd, val) + } + } + + return newMsg, nil +} diff --git a/builder/body_option.go b/builder/body_option.go new file mode 100644 index 0000000..6b2b883 --- /dev/null +++ b/builder/body_option.go @@ -0,0 +1,22 @@ +package builder + +const ( + singleWildcard string = "*" + doubleWildcard string = "**" +) + +type bodyOption string + +func (o bodyOption) String() string { return string(o) } + +func (o bodyOption) isFullBody() bool { + return o.String() == singleWildcard +} + +func (o bodyOption) isWithoutBody() bool { + return o == "" +} + +func (o bodyOption) isSingleField() bool { + return o != "" && o.String() != singleWildcard +} diff --git a/builder/body_option_test.go b/builder/body_option_test.go new file mode 100644 index 0000000..3e50b41 --- /dev/null +++ b/builder/body_option_test.go @@ -0,0 +1,79 @@ +package builder + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBodyOption_String(t *testing.T) { + tests := []struct { + name string + opt bodyOption + want string + }{ + {"empty", bodyOption(""), ""}, + {"star", bodyOption("*"), "*"}, + {"field", bodyOption("field"), "field"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.opt.String()) + }) + } +} + +func TestBodyOption_isFullBody(t *testing.T) { + tests := []struct { + name string + opt bodyOption + want bool + }{ + {"empty", bodyOption(""), false}, + {"star", bodyOption("*"), true}, + {"field", bodyOption("field"), false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.opt.isFullBody()) + }) + } +} + +func TestBodyOption_isWithoutBody(t *testing.T) { + tests := []struct { + name string + opt bodyOption + want bool + }{ + {"empty", bodyOption(""), true}, + {"star", bodyOption("*"), false}, + {"field", bodyOption("field"), false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.opt.isWithoutBody()) + }) + } +} + +func TestBodyOption_isSingleField(t *testing.T) { + tests := []struct { + name string + opt bodyOption + want bool + }{ + {"empty", bodyOption(""), false}, + {"star", bodyOption("*"), false}, + {"field", bodyOption("field"), true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.opt.isSingleField()) + }) + } +} diff --git a/builder/helpers.go b/builder/helpers.go new file mode 100644 index 0000000..f2fd1db --- /dev/null +++ b/builder/helpers.go @@ -0,0 +1,114 @@ +package builder + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +// findFieldByPath resolves a dot-separated field path in a protobuf message and returns the protoreflect value and its descriptor. +func findFieldByPath(msg protoreflect.Message, fieldPath string) (protoreflect.Value, protoreflect.FieldDescriptor, bool) { + var ( + current = msg + parts = strings.Split(fieldPath, ".") + partsCount = len(parts) - 1 + ) + + for i, part := range parts { + fd, ok := findFieldByName(current, part) + if !ok { + return protoreflect.Value{}, nil, false + } + + val := current.Get(fd) + if i == partsCount { // it's last part + return val, fd, true + } + + if fd.Kind() != protoreflect.MessageKind { + return protoreflect.Value{}, nil, false + } + current = val.Message() + } + + return protoreflect.Value{}, nil, false +} + +// findFieldByName find a field name in a protobuf message and returns the protoreflect field descriptor. +func findFieldByName(msg protoreflect.Message, fieldName string) (protoreflect.FieldDescriptor, bool) { + fields := msg.Descriptor().Fields() + for i := 0; i < fields.Len(); i++ { + fd := fields.Get(i) + if fd.JSONName() == fieldName { + return fd, true + } + } + return nil, false +} + +// isZeroValue checks if protoreflect.Value is zero for the field. +func isZeroValue(val protoreflect.Value, fd protoreflect.FieldDescriptor) bool { + if fd.IsList() { + return val.List().Len() == 0 + } + if fd.IsMap() { + return val.Map().Len() == 0 + } + + switch fd.Kind() { + case protoreflect.StringKind: + return val.String() == "" + case protoreflect.BytesKind: + return len(val.Bytes()) == 0 + case protoreflect.BoolKind: + return !val.Bool() + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, + protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + return val.Int() == 0 + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, + protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + return val.Uint() == 0 + case protoreflect.FloatKind, protoreflect.DoubleKind: + return val.Float() == 0 + case protoreflect.EnumKind: + return val.Enum() == 0 + case protoreflect.MessageKind: + return !val.Message().IsValid() + default: + return !val.IsValid() + } +} + +// stringifyValue converts protoreflect.Value to string for path/query substitution. +func stringifyValue(val protoreflect.Value, fd protoreflect.FieldDescriptor) (string, error) { + switch fd.Kind() { + case protoreflect.StringKind: + return val.String(), nil + case protoreflect.BytesKind: + return base64.StdEncoding.EncodeToString(val.Bytes()), nil + case protoreflect.BoolKind: + if val.Bool() { + return "true", nil + } + return "false", nil + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, + protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + return fmt.Sprintf("%d", val.Int()), nil + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, + protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + return fmt.Sprintf("%d", val.Uint()), nil + case protoreflect.FloatKind, protoreflect.DoubleKind: + return strconv.FormatFloat(val.Float(), 'g', -1, 64), nil + case protoreflect.EnumKind: + ed := fd.Enum().Values().ByNumber(val.Enum()) + if ed != nil { + return string(ed.Name()), nil + } + return fmt.Sprintf("%d", val.Enum()), nil + default: + return "", fmt.Errorf("unsupported field kind: %s", fd.Kind()) + } +} diff --git a/builder/path_template.go b/builder/path_template.go new file mode 100644 index 0000000..f5ac14b --- /dev/null +++ b/builder/path_template.go @@ -0,0 +1,313 @@ +package builder + +import ( + "errors" + "fmt" + "net/url" + "strings" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// -------------------------- Path template representation ------------------------- + +// pathSegment is a helper interface for elements of a path. +type pathSegment interface { + isSegment() bool +} + +// pathTemplate represents a parsed URL path template. +type pathTemplate struct { + // literalPrefix is the fixed part of the path before the first {var}, + // e.g. "/v1/users/" for "/v1/users/{user_id}/orders:get". + // It is removed from segments, so segments contain only the remaining path literals and variables. + literalPrefix string + + // segments is a sequence of pathLiteral or pathVar representing the rest of the path after literalPrefix. + segments []pathSegment + + // customVerb is an optional ":verb" suffix, e.g. ":get". + customVerb string +} + +// pathLiteral represents a fixed literal segment in a path template, e.g., "/v1/users/". +type pathLiteral struct { + text string +} + +func (p pathLiteral) isSegment() bool { return true } + +// pathVar represents a variable segment in a path template, e.g., "{user.id}". +type pathVar struct { + // fieldPath is the dotted path to the field in the struct, e.g., "user.id". + fieldPath string + + // pattern is the optional pattern after '=', e.g., "*" or "**/orders". + // It specifies how the variable can match parts of the URL path. + pattern string + + // multiSegment is true if the pattern can match multiple path segments + // (contains '/' or "**"). + multiSegment bool +} + +func (p pathVar) isSegment() bool { return true } + +// ----------------------------- Path template parsing ----------------------------- + +// parsePathTemplate parses a URL path template into a pathTemplate. +// It extracts: +// 1. literalPrefix — fixed part before the first variable, +// 2. segments — sequence of pathLiteral and pathVar, +// 3. customVerb — optional ":verb" suffix. +// +// Complexity: time O(n), memory O(n). +// +// Example: +// +// input: "/v1/users/{user_id}/orders:get" +// output: pathTemplate{ +// literalPrefix: "/v1/users/", +// segments: [{user_id}, "/orders"], +// customVerb: ":get", +// } +func parsePathTemplate(input string) (*pathTemplate, error) { + // Step 1: extract custom verb after the last colon, e.g. ":get" + var customVerb string + if i := strings.LastIndex(input, ":"); i >= 0 && i > strings.LastIndex(input, "/") { + customVerb = input[i:] + input = input[:i] + } + + var ( + segments []pathSegment + buf strings.Builder + ) + + // Step 2: iterate over the input and split into segments + for i := 0; i < len(input); { + if input[i] != '{' { + buf.WriteByte(input[i]) + i++ + continue + } + + // Add literal before '{' if any + if buf.Len() > 0 { + segments = append(segments, pathLiteral{text: buf.String()}) + buf.Reset() + } + + // Find closing '}' + start := i + 1 + offset := strings.IndexByte(input[start:], '}') // relative offset from start + if offset < 0 { + return nil, fmt.Errorf("unclosed '{' in path: %s", input) + } + end := start + offset + + token := input[start:end] + i = end + 1 // jump past '}' + + // Split field path and optional pattern + var fieldPath, pattern string + if k := strings.IndexByte(token, '='); k >= 0 { + fieldPath = strings.TrimSpace(token[:k]) + pattern = strings.TrimSpace(token[k+1:]) + } else { + fieldPath = strings.TrimSpace(token) + } + + if fieldPath == "" { + return nil, fmt.Errorf("empty variable in path: %s", input) + } + + pv := pathVar{ + fieldPath: fieldPath, + pattern: pattern, + multiSegment: isMultiSegmentPattern(pattern), + } + segments = append(segments, pv) + } + + // Step 3: add any trailing literal after last '}' + if buf.Len() > 0 { + segments = append(segments, pathLiteral{text: buf.String()}) + } + + // Step 4: extract literalPrefix if the first segment is a literal + var literalPrefix string + if len(segments) > 0 { + if pl, ok := segments[0].(pathLiteral); ok { + literalPrefix = pl.text + segments = segments[1:] // remove from segments to avoid duplication + } + } + + // Step 5: return fully parsed pathTemplate + return &pathTemplate{ + literalPrefix: literalPrefix, + segments: segments, + customVerb: customVerb, + }, nil +} + +// isMultiSegmentPattern returns true if pattern can match multiple path segments (contains '/' or '**'). +// Examples: +// | Pattern | Result | Usecase | +// |----------------|--------|------------------------------------------| +// | "" | false | {var} => single segment | +// | "*" | false | {var=*} => single segment | +// | "**" | true | {var=**} => multiple segments | +// | "foo/*" | true | {var=foo/*} => multiple segments | +// | "foo/**" | true | {var=foo/**} => multiple segments | +// | "users/*/orders"| true | {users/*/orders} => multiple segments | +func isMultiSegmentPattern(pattern string) bool { + if pattern == "" { + return false + } + if pattern == singleWildcard { + return false + } + return strings.Contains(pattern, "/") || strings.Contains(pattern, doubleWildcard) +} + +// ----------------------------- Path template resolving ----------------------------- + +// resolvePathPlaceholders expands placeholders in a path template using values from proto.Message. +// Placeholders must be bound to non-repeated scalar fields (not lists, maps, or messages). +// +// Example: +// +// tmpl: "/v1/users/{user_id}/orders:get" +// msg: &pb.Message{UserId: 12345} +// +// path: "/v1/users/12345/orders:get" +// usedFields: {"user_id"} +func resolvePathPlaceholders(tmpl *pathTemplate, msg proto.Message) (path string, usedFields *usedFields, err error) { + usedFields = newUsedFields() + + var sb strings.Builder + sb.WriteString(tmpl.literalPrefix) + + msgReflect := msg.ProtoReflect() + + for _, segment := range tmpl.segments { + switch s := segment.(type) { + case pathLiteral: + sb.WriteString(s.text) + + case pathVar: + val, fd, ok := findFieldByPath(msgReflect, s.fieldPath) + if !ok { + return "", nil, fmt.Errorf("path placeholder %s not found", s.fieldPath) + } + if isZeroValue(val, fd) { + // it's the only case that allows zero-value matches. + if s.pattern == doubleWildcard { + usedFields.add(s.fieldPath) + continue + } + return "", nil, fmt.Errorf("path placeholder %s has zero value", s.fieldPath) + } + + // must be scalar (non-repeated, non-map, non-message) + if fd.IsList() || fd.IsMap() || fd.Kind() == protoreflect.MessageKind { + return "", nil, fmt.Errorf("path placeholder %s must be scalar", s.fieldPath) + } + + usedFields.add(s.fieldPath) + + var strVal string + strVal, err = stringifyValue(val, fd) + if err != nil { + return "", nil, fmt.Errorf("stringify placeholder %s: %w", s.fieldPath, err) + } + + if err = validatePattern(s.pattern, strVal); err != nil { + return "", nil, fmt.Errorf("validate pattern, %s:%s: %w", s.fieldPath, strVal, err) + } + + parts := strings.Split(strVal, "/") + for i := range parts { + parts[i] = url.PathEscape(parts[i]) + } + sb.WriteString(strings.Join(parts, "/")) + } + } + + sb.WriteString(tmpl.customVerb) + return sb.String(), usedFields, nil +} + +// validatePattern checks whether input matches the given path pattern. +// +// Rules: +// - "" or "*" => exactly one segment, no "/" allowed +// - "**" => zero or more segments (may include "/") +// - composite patterns like "*/orders/*" must match literally +// +// Example for composite pattern case: +// +// pattern: "*/orders/*" +// input: "42/orders/123" +// +// patternSegments = ["*", "orders", "*"] +// valueParts = ["42", "orders", "123"] +// +// Match: +// "*" -> "42" +// "orders" -> "orders" +// "*" -> "123" +func validatePattern(pattern, input string) error { + var ( + parts = strings.Split(input, "/") + lenParts = len(parts) + ) + + if pattern == "" || pattern == singleWildcard { + if lenParts != 1 { + return errors.New("must be a single path segment") + } + return nil + } + + if pattern == doubleWildcard { + if lenParts < 1 { + return errors.New("must contain at least one segment") + } + return nil + } + + var ( + patternSegments = strings.Split(pattern, "/") + patternIndex int + ) + + for i := 0; i < len(patternSegments); i++ { + switch patternSegments[i] { + case singleWildcard: + if patternIndex >= lenParts || parts[patternIndex] == "" { + return fmt.Errorf("segment %d must not be empty", patternIndex) + } + patternIndex++ + case doubleWildcard: + if patternIndex >= lenParts { + return fmt.Errorf("must contain at least one segment at position %d", patternIndex) + } + return nil + default: + if patternIndex >= lenParts || parts[patternIndex] != patternSegments[i] { + return fmt.Errorf("expected literal %s at position %d", patternSegments[i], patternIndex) + } + patternIndex++ + } + } + + if patternIndex != lenParts { + return errors.New("extra segments in value") + } + + return nil +} diff --git a/builder/path_template_cache.go b/builder/path_template_cache.go new file mode 100644 index 0000000..2750ff5 --- /dev/null +++ b/builder/path_template_cache.go @@ -0,0 +1,21 @@ +package builder + +import "sync" + +var ( + pathTemplateCache = make(map[string]*pathTemplate) + pathTemplateCacheMu sync.RWMutex +) + +func getCachedPathTemplate(path string) (*pathTemplate, bool) { + pathTemplateCacheMu.RLock() + defer pathTemplateCacheMu.RUnlock() + tmpl, ok := pathTemplateCache[path] + return tmpl, ok +} + +func setPathTemplateCache(path string, tmpl *pathTemplate) { + pathTemplateCacheMu.Lock() + defer pathTemplateCacheMu.Unlock() + pathTemplateCache[path] = tmpl +} diff --git a/builder/proto/errors.pb.go b/builder/proto/errors.pb.go new file mode 100644 index 0000000..87bc082 --- /dev/null +++ b/builder/proto/errors.pb.go @@ -0,0 +1,15 @@ +package proto + +import "google.golang.org/protobuf/encoding/protojson" + +var marshaler = protojson.MarshalOptions{} + +func (m *Test_Client_Call_DefaultError) Error() string { + buf, _ := marshaler.Marshal(m) + return string(buf) +} + +func (m *Test_Client_Call_SpecialError) Error() string { + buf, _ := marshaler.Marshal(m) + return string(buf) +} diff --git a/builder/proto/test_messages.pb.go b/builder/proto/test_messages.pb.go new file mode 100644 index 0000000..0fc419d --- /dev/null +++ b/builder/proto/test_messages.pb.go @@ -0,0 +1,3906 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v6.31.0 +// source: test_messages.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +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) +) + +type TestRequestBuilder struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRequestBuilder) Reset() { + *x = TestRequestBuilder{} + mi := &file_test_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRequestBuilder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRequestBuilder) ProtoMessage() {} + +func (x *TestRequestBuilder) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestRequestBuilder.ProtoReflect.Descriptor instead. +func (*TestRequestBuilder) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{0} +} + +type Test_PathOnly struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly) Reset() { + *x = Test_PathOnly{} + mi := &file_test_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly) ProtoMessage() {} + +func (x *Test_PathOnly) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly.ProtoReflect.Descriptor instead. +func (*Test_PathOnly) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1} +} + +type Test_QueryOnly struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly) Reset() { + *x = Test_QueryOnly{} + mi := &file_test_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly) ProtoMessage() {} + +func (x *Test_QueryOnly) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2} +} + +type Test_BodyOnly struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly) Reset() { + *x = Test_BodyOnly{} + mi := &file_test_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly) ProtoMessage() {} + +func (x *Test_BodyOnly) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3} +} + +type Test_Mixed struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed) Reset() { + *x = Test_Mixed{} + mi := &file_test_messages_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed) ProtoMessage() {} + +func (x *Test_Mixed) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed.ProtoReflect.Descriptor instead. +func (*Test_Mixed) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4} +} + +type Benchmark struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Benchmark) Reset() { + *x = Benchmark{} + mi := &file_test_messages_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Benchmark) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Benchmark) ProtoMessage() {} + +func (x *Benchmark) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Benchmark.ProtoReflect.Descriptor instead. +func (*Benchmark) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{5} +} + +type Test_Client_Call struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Client_Call) Reset() { + *x = Test_Client_Call{} + mi := &file_test_messages_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Client_Call) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Client_Call) ProtoMessage() {} + +func (x *Test_Client_Call) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Client_Call.ProtoReflect.Descriptor instead. +func (*Test_Client_Call) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{6} +} + +type Test_PathOnly_PrimitiveCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_PrimitiveCase) Reset() { + *x = Test_PathOnly_PrimitiveCase{} + mi := &file_test_messages_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_PrimitiveCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_PrimitiveCase) ProtoMessage() {} + +func (x *Test_PathOnly_PrimitiveCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_PrimitiveCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_PrimitiveCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *Test_PathOnly_PrimitiveCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_PathOnly_PrimitiveCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type Test_PathOnly_NestedCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + User *Test_PathOnly_NestedCase_User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + Order *Test_PathOnly_NestedCase_Order `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NestedCase) Reset() { + *x = Test_PathOnly_NestedCase{} + mi := &file_test_messages_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NestedCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NestedCase) ProtoMessage() {} + +func (x *Test_PathOnly_NestedCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NestedCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NestedCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 1} +} + +func (x *Test_PathOnly_NestedCase) GetUser() *Test_PathOnly_NestedCase_User { + if x != nil { + return x.User + } + return nil +} + +func (x *Test_PathOnly_NestedCase) GetOrder() *Test_PathOnly_NestedCase_Order { + if x != nil { + return x.Order + } + return nil +} + +type Test_PathOnly_MultipleCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + Order *Test_PathOnly_MultipleCase_Order `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_MultipleCase) Reset() { + *x = Test_PathOnly_MultipleCase{} + mi := &file_test_messages_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_MultipleCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_MultipleCase) ProtoMessage() {} + +func (x *Test_PathOnly_MultipleCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_MultipleCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_MultipleCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 2} +} + +func (x *Test_PathOnly_MultipleCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_PathOnly_MultipleCase) GetOrder() *Test_PathOnly_MultipleCase_Order { + if x != nil { + return x.Order + } + return nil +} + +type Test_PathOnly_RepeatedCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId []string `protobuf:"bytes,1,rep,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_RepeatedCase) Reset() { + *x = Test_PathOnly_RepeatedCase{} + mi := &file_test_messages_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_RepeatedCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_RepeatedCase) ProtoMessage() {} + +func (x *Test_PathOnly_RepeatedCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_RepeatedCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_RepeatedCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 3} +} + +func (x *Test_PathOnly_RepeatedCase) GetUserId() []string { + if x != nil { + return x.UserId + } + return nil +} + +func (x *Test_PathOnly_RepeatedCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type Test_PathOnly_NonPrimitiveMessageCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId *Test_PathOnly_NonPrimitiveMessageCase_User `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase) Reset() { + *x = Test_PathOnly_NonPrimitiveMessageCase{} + mi := &file_test_messages_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NonPrimitiveMessageCase) ProtoMessage() {} + +func (x *Test_PathOnly_NonPrimitiveMessageCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NonPrimitiveMessageCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NonPrimitiveMessageCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 4} +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase) GetUserId() *Test_PathOnly_NonPrimitiveMessageCase_User { + if x != nil { + return x.UserId + } + return nil +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type Test_PathOnly_NonPrimitiveMapCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId map[string]string `protobuf:"bytes,1,rep,name=userId,json=user_id,proto3" json:"userId,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NonPrimitiveMapCase) Reset() { + *x = Test_PathOnly_NonPrimitiveMapCase{} + mi := &file_test_messages_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NonPrimitiveMapCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NonPrimitiveMapCase) ProtoMessage() {} + +func (x *Test_PathOnly_NonPrimitiveMapCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NonPrimitiveMapCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NonPrimitiveMapCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 5} +} + +func (x *Test_PathOnly_NonPrimitiveMapCase) GetUserId() map[string]string { + if x != nil { + return x.UserId + } + return nil +} + +func (x *Test_PathOnly_NonPrimitiveMapCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type Test_PathOnly_PatternCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_PatternCase) Reset() { + *x = Test_PathOnly_PatternCase{} + mi := &file_test_messages_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_PatternCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_PatternCase) ProtoMessage() {} + +func (x *Test_PathOnly_PatternCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_PatternCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_PatternCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 6} +} + +func (x *Test_PathOnly_PatternCase) GetPattern() string { + if x != nil { + return x.Pattern + } + return "" +} + +type Test_PathOnly_CompositePatternCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + ProductId string `protobuf:"bytes,3,opt,name=productId,json=product_id,proto3" json:"productId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_CompositePatternCase) Reset() { + *x = Test_PathOnly_CompositePatternCase{} + mi := &file_test_messages_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_CompositePatternCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_CompositePatternCase) ProtoMessage() {} + +func (x *Test_PathOnly_CompositePatternCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_CompositePatternCase.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_CompositePatternCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 7} +} + +func (x *Test_PathOnly_CompositePatternCase) GetPattern() string { + if x != nil { + return x.Pattern + } + return "" +} + +func (x *Test_PathOnly_CompositePatternCase) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *Test_PathOnly_CompositePatternCase) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +type Test_PathOnly_NestedCase_User struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NestedCase_User) Reset() { + *x = Test_PathOnly_NestedCase_User{} + mi := &file_test_messages_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NestedCase_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NestedCase_User) ProtoMessage() {} + +func (x *Test_PathOnly_NestedCase_User) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NestedCase_User.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NestedCase_User) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 1, 0} +} + +func (x *Test_PathOnly_NestedCase_User) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type Test_PathOnly_NestedCase_Order struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Product *Test_PathOnly_NestedCase_Order_Product `protobuf:"bytes,2,opt,name=product,proto3" json:"product,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NestedCase_Order) Reset() { + *x = Test_PathOnly_NestedCase_Order{} + mi := &file_test_messages_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NestedCase_Order) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NestedCase_Order) ProtoMessage() {} + +func (x *Test_PathOnly_NestedCase_Order) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NestedCase_Order.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NestedCase_Order) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 1, 1} +} + +func (x *Test_PathOnly_NestedCase_Order) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Test_PathOnly_NestedCase_Order) GetProduct() *Test_PathOnly_NestedCase_Order_Product { + if x != nil { + return x.Product + } + return nil +} + +type Test_PathOnly_NestedCase_Order_Product struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NestedCase_Order_Product) Reset() { + *x = Test_PathOnly_NestedCase_Order_Product{} + mi := &file_test_messages_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NestedCase_Order_Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NestedCase_Order_Product) ProtoMessage() {} + +func (x *Test_PathOnly_NestedCase_Order_Product) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NestedCase_Order_Product.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NestedCase_Order_Product) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 1, 1, 0} +} + +func (x *Test_PathOnly_NestedCase_Order_Product) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +type Test_PathOnly_MultipleCase_Order struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_MultipleCase_Order) Reset() { + *x = Test_PathOnly_MultipleCase_Order{} + mi := &file_test_messages_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_MultipleCase_Order) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_MultipleCase_Order) ProtoMessage() {} + +func (x *Test_PathOnly_MultipleCase_Order) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_MultipleCase_Order.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_MultipleCase_Order) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 2, 0} +} + +func (x *Test_PathOnly_MultipleCase_Order) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type Test_PathOnly_NonPrimitiveMessageCase_User struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase_User) Reset() { + *x = Test_PathOnly_NonPrimitiveMessageCase_User{} + mi := &file_test_messages_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase_User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_PathOnly_NonPrimitiveMessageCase_User) ProtoMessage() {} + +func (x *Test_PathOnly_NonPrimitiveMessageCase_User) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_PathOnly_NonPrimitiveMessageCase_User.ProtoReflect.Descriptor instead. +func (*Test_PathOnly_NonPrimitiveMessageCase_User) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{1, 4, 0} +} + +func (x *Test_PathOnly_NonPrimitiveMessageCase_User) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type Test_QueryOnly_PrimitiveCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + Flag bool `protobuf:"varint,3,opt,name=flag,proto3" json:"flag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_PrimitiveCase) Reset() { + *x = Test_QueryOnly_PrimitiveCase{} + mi := &file_test_messages_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_PrimitiveCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_PrimitiveCase) ProtoMessage() {} + +func (x *Test_QueryOnly_PrimitiveCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_PrimitiveCase.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_PrimitiveCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *Test_QueryOnly_PrimitiveCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_QueryOnly_PrimitiveCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +func (x *Test_QueryOnly_PrimitiveCase) GetFlag() bool { + if x != nil { + return x.Flag + } + return false +} + +type Test_QueryOnly_RepeatedCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + Strings []string `protobuf:"bytes,1,rep,name=strings,proto3" json:"strings,omitempty"` + Integers []int64 `protobuf:"varint,2,rep,packed,name=integers,proto3" json:"integers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_RepeatedCase) Reset() { + *x = Test_QueryOnly_RepeatedCase{} + mi := &file_test_messages_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_RepeatedCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_RepeatedCase) ProtoMessage() {} + +func (x *Test_QueryOnly_RepeatedCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_RepeatedCase.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_RepeatedCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 1} +} + +func (x *Test_QueryOnly_RepeatedCase) GetStrings() []string { + if x != nil { + return x.Strings + } + return nil +} + +func (x *Test_QueryOnly_RepeatedCase) GetIntegers() []int64 { + if x != nil { + return x.Integers + } + return nil +} + +type Test_QueryOnly_NestedMessageCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + Filter *Test_QueryOnly_NestedMessageCase_Filter `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_NestedMessageCase) Reset() { + *x = Test_QueryOnly_NestedMessageCase{} + mi := &file_test_messages_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_NestedMessageCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_NestedMessageCase) ProtoMessage() {} + +func (x *Test_QueryOnly_NestedMessageCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_NestedMessageCase.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_NestedMessageCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 2} +} + +func (x *Test_QueryOnly_NestedMessageCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_QueryOnly_NestedMessageCase) GetFilter() *Test_QueryOnly_NestedMessageCase_Filter { + if x != nil { + return x.Filter + } + return nil +} + +type Test_QueryOnly_NestedMapCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + FirstFilter map[string]string `protobuf:"bytes,2,rep,name=firstFilter,json=first_filter,proto3" json:"firstFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + SecondFilter map[string]*Test_QueryOnly_NestedMapCase_SubFilter `protobuf:"bytes,4,rep,name=secondFilter,json=second_filter,proto3" json:"secondFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_NestedMapCase) Reset() { + *x = Test_QueryOnly_NestedMapCase{} + mi := &file_test_messages_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_NestedMapCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_NestedMapCase) ProtoMessage() {} + +func (x *Test_QueryOnly_NestedMapCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_NestedMapCase.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_NestedMapCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 3} +} + +func (x *Test_QueryOnly_NestedMapCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_QueryOnly_NestedMapCase) GetFirstFilter() map[string]string { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_QueryOnly_NestedMapCase) GetSecondFilter() map[string]*Test_QueryOnly_NestedMapCase_SubFilter { + if x != nil { + return x.SecondFilter + } + return nil +} + +type Test_QueryOnly_MultipleCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + Strings []string `protobuf:"bytes,2,rep,name=strings,proto3" json:"strings,omitempty"` + FirstFilter *Test_QueryOnly_MultipleCase_Filter `protobuf:"bytes,3,opt,name=firstFilter,json=first_filter,proto3" json:"firstFilter,omitempty"` + SecondFilter map[string]*Test_QueryOnly_MultipleCase_SubFilter `protobuf:"bytes,4,rep,name=secondFilter,json=second_filter,proto3" json:"secondFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_MultipleCase) Reset() { + *x = Test_QueryOnly_MultipleCase{} + mi := &file_test_messages_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_MultipleCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_MultipleCase) ProtoMessage() {} + +func (x *Test_QueryOnly_MultipleCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_MultipleCase.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_MultipleCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 4} +} + +func (x *Test_QueryOnly_MultipleCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_QueryOnly_MultipleCase) GetStrings() []string { + if x != nil { + return x.Strings + } + return nil +} + +func (x *Test_QueryOnly_MultipleCase) GetFirstFilter() *Test_QueryOnly_MultipleCase_Filter { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_QueryOnly_MultipleCase) GetSecondFilter() map[string]*Test_QueryOnly_MultipleCase_SubFilter { + if x != nil { + return x.SecondFilter + } + return nil +} + +type Test_QueryOnly_RepeatedMessageCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + Filters []*Test_QueryOnly_RepeatedMessageCase_Filter `protobuf:"bytes,1,rep,name=filters,proto3" json:"filters,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_RepeatedMessageCase) Reset() { + *x = Test_QueryOnly_RepeatedMessageCase{} + mi := &file_test_messages_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_RepeatedMessageCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_RepeatedMessageCase) ProtoMessage() {} + +func (x *Test_QueryOnly_RepeatedMessageCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_RepeatedMessageCase.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_RepeatedMessageCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 5} +} + +func (x *Test_QueryOnly_RepeatedMessageCase) GetFilters() []*Test_QueryOnly_RepeatedMessageCase_Filter { + if x != nil { + return x.Filters + } + return nil +} + +type Test_QueryOnly_NestedMessageCase_Filter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Age int64 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + SubFilter *Test_QueryOnly_NestedMessageCase_Filter_SubFilter `protobuf:"bytes,3,opt,name=subFilter,json=sub_filter,proto3" json:"subFilter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter) Reset() { + *x = Test_QueryOnly_NestedMessageCase_Filter{} + mi := &file_test_messages_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_NestedMessageCase_Filter) ProtoMessage() {} + +func (x *Test_QueryOnly_NestedMessageCase_Filter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_NestedMessageCase_Filter.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_NestedMessageCase_Filter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 2, 0} +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter) GetAge() int64 { + if x != nil { + return x.Age + } + return 0 +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter) GetSubFilter() *Test_QueryOnly_NestedMessageCase_Filter_SubFilter { + if x != nil { + return x.SubFilter + } + return nil +} + +type Test_QueryOnly_NestedMessageCase_Filter_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter_SubFilter) Reset() { + *x = Test_QueryOnly_NestedMessageCase_Filter_SubFilter{} + mi := &file_test_messages_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_NestedMessageCase_Filter_SubFilter) ProtoMessage() {} + +func (x *Test_QueryOnly_NestedMessageCase_Filter_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_NestedMessageCase_Filter_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_NestedMessageCase_Filter_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 2, 0, 0} +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_QueryOnly_NestedMessageCase_Filter_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_QueryOnly_NestedMapCase_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_NestedMapCase_SubFilter) Reset() { + *x = Test_QueryOnly_NestedMapCase_SubFilter{} + mi := &file_test_messages_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_NestedMapCase_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_NestedMapCase_SubFilter) ProtoMessage() {} + +func (x *Test_QueryOnly_NestedMapCase_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_NestedMapCase_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_NestedMapCase_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 3, 2} +} + +func (x *Test_QueryOnly_NestedMapCase_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_QueryOnly_NestedMapCase_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_QueryOnly_MultipleCase_Filter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Age int64 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` + SubFilter *Test_QueryOnly_MultipleCase_SubFilter `protobuf:"bytes,2,opt,name=subFilter,json=sub_filter,proto3" json:"subFilter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_MultipleCase_Filter) Reset() { + *x = Test_QueryOnly_MultipleCase_Filter{} + mi := &file_test_messages_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_MultipleCase_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_MultipleCase_Filter) ProtoMessage() {} + +func (x *Test_QueryOnly_MultipleCase_Filter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_MultipleCase_Filter.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_MultipleCase_Filter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 4, 1} +} + +func (x *Test_QueryOnly_MultipleCase_Filter) GetAge() int64 { + if x != nil { + return x.Age + } + return 0 +} + +func (x *Test_QueryOnly_MultipleCase_Filter) GetSubFilter() *Test_QueryOnly_MultipleCase_SubFilter { + if x != nil { + return x.SubFilter + } + return nil +} + +type Test_QueryOnly_MultipleCase_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_MultipleCase_SubFilter) Reset() { + *x = Test_QueryOnly_MultipleCase_SubFilter{} + mi := &file_test_messages_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_MultipleCase_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_MultipleCase_SubFilter) ProtoMessage() {} + +func (x *Test_QueryOnly_MultipleCase_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_MultipleCase_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_MultipleCase_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 4, 2} +} + +func (x *Test_QueryOnly_MultipleCase_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +type Test_QueryOnly_RepeatedMessageCase_Filter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Age int64 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_QueryOnly_RepeatedMessageCase_Filter) Reset() { + *x = Test_QueryOnly_RepeatedMessageCase_Filter{} + mi := &file_test_messages_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_QueryOnly_RepeatedMessageCase_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_QueryOnly_RepeatedMessageCase_Filter) ProtoMessage() {} + +func (x *Test_QueryOnly_RepeatedMessageCase_Filter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_QueryOnly_RepeatedMessageCase_Filter.ProtoReflect.Descriptor instead. +func (*Test_QueryOnly_RepeatedMessageCase_Filter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{2, 5, 0} +} + +func (x *Test_QueryOnly_RepeatedMessageCase_Filter) GetAge() int64 { + if x != nil { + return x.Age + } + return 0 +} + +type Test_BodyOnly_PrimitiveCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + Flag bool `protobuf:"varint,3,opt,name=flag,proto3" json:"flag,omitempty"` + Strings []string `protobuf:"bytes,4,rep,name=strings,proto3" json:"strings,omitempty"` + Product *Test_BodyOnly_PrimitiveCase_Product `protobuf:"bytes,6,opt,name=product,proto3" json:"product,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_PrimitiveCase) Reset() { + *x = Test_BodyOnly_PrimitiveCase{} + mi := &file_test_messages_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_PrimitiveCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_PrimitiveCase) ProtoMessage() {} + +func (x *Test_BodyOnly_PrimitiveCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_PrimitiveCase.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_PrimitiveCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *Test_BodyOnly_PrimitiveCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_BodyOnly_PrimitiveCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +func (x *Test_BodyOnly_PrimitiveCase) GetFlag() bool { + if x != nil { + return x.Flag + } + return false +} + +func (x *Test_BodyOnly_PrimitiveCase) GetStrings() []string { + if x != nil { + return x.Strings + } + return nil +} + +func (x *Test_BodyOnly_PrimitiveCase) GetProduct() *Test_BodyOnly_PrimitiveCase_Product { + if x != nil { + return x.Product + } + return nil +} + +type Test_BodyOnly_NestedCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + FirstFilter *Test_BodyOnly_NestedCase_Filter `protobuf:"bytes,2,opt,name=first_filter,proto3" json:"first_filter,omitempty"` + SecondFilter *Test_BodyOnly_NestedCase_Filter `protobuf:"bytes,3,opt,name=second_filter,proto3" json:"second_filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_NestedCase) Reset() { + *x = Test_BodyOnly_NestedCase{} + mi := &file_test_messages_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_NestedCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_NestedCase) ProtoMessage() {} + +func (x *Test_BodyOnly_NestedCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_NestedCase.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_NestedCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 1} +} + +func (x *Test_BodyOnly_NestedCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_BodyOnly_NestedCase) GetFirstFilter() *Test_BodyOnly_NestedCase_Filter { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_BodyOnly_NestedCase) GetSecondFilter() *Test_BodyOnly_NestedCase_Filter { + if x != nil { + return x.SecondFilter + } + return nil +} + +type Test_BodyOnly_RepeatedMessageCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + Products []*Test_BodyOnly_RepeatedMessageCase_Product `protobuf:"bytes,2,rep,name=products,proto3" json:"products,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_RepeatedMessageCase) Reset() { + *x = Test_BodyOnly_RepeatedMessageCase{} + mi := &file_test_messages_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_RepeatedMessageCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_RepeatedMessageCase) ProtoMessage() {} + +func (x *Test_BodyOnly_RepeatedMessageCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_RepeatedMessageCase.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_RepeatedMessageCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 2} +} + +func (x *Test_BodyOnly_RepeatedMessageCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_BodyOnly_RepeatedMessageCase) GetProducts() []*Test_BodyOnly_RepeatedMessageCase_Product { + if x != nil { + return x.Products + } + return nil +} + +type Test_BodyOnly_MapCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + FirstFilter map[string]string `protobuf:"bytes,1,rep,name=firstFilter,json=first_filter,proto3" json:"firstFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + SecondFilter map[string]*Test_BodyOnly_MapCase_SubFilter `protobuf:"bytes,2,rep,name=secondFilter,json=second_filter,proto3" json:"secondFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_MapCase) Reset() { + *x = Test_BodyOnly_MapCase{} + mi := &file_test_messages_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_MapCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_MapCase) ProtoMessage() {} + +func (x *Test_BodyOnly_MapCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_MapCase.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_MapCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 3} +} + +func (x *Test_BodyOnly_MapCase) GetFirstFilter() map[string]string { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_BodyOnly_MapCase) GetSecondFilter() map[string]*Test_BodyOnly_MapCase_SubFilter { + if x != nil { + return x.SecondFilter + } + return nil +} + +type Test_BodyOnly_MultipleCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + FirstFilter []*Test_BodyOnly_MultipleCase_SubFilter `protobuf:"bytes,2,rep,name=firstFilter,json=first_filter,proto3" json:"firstFilter,omitempty"` + SecondFilter map[string]*Test_BodyOnly_MultipleCase_SubFilter `protobuf:"bytes,3,rep,name=secondFilter,json=second_filter,proto3" json:"secondFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + ThirdFilter *Test_BodyOnly_MultipleCase_SubFilter `protobuf:"bytes,4,opt,name=thirdFilter,json=third_filter,proto3" json:"thirdFilter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_MultipleCase) Reset() { + *x = Test_BodyOnly_MultipleCase{} + mi := &file_test_messages_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_MultipleCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_MultipleCase) ProtoMessage() {} + +func (x *Test_BodyOnly_MultipleCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_MultipleCase.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_MultipleCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 4} +} + +func (x *Test_BodyOnly_MultipleCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_BodyOnly_MultipleCase) GetFirstFilter() []*Test_BodyOnly_MultipleCase_SubFilter { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_BodyOnly_MultipleCase) GetSecondFilter() map[string]*Test_BodyOnly_MultipleCase_SubFilter { + if x != nil { + return x.SecondFilter + } + return nil +} + +func (x *Test_BodyOnly_MultipleCase) GetThirdFilter() *Test_BodyOnly_MultipleCase_SubFilter { + if x != nil { + return x.ThirdFilter + } + return nil +} + +type Test_BodyOnly_PrimitiveCase_Product struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_PrimitiveCase_Product) Reset() { + *x = Test_BodyOnly_PrimitiveCase_Product{} + mi := &file_test_messages_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_PrimitiveCase_Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_PrimitiveCase_Product) ProtoMessage() {} + +func (x *Test_BodyOnly_PrimitiveCase_Product) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_PrimitiveCase_Product.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_PrimitiveCase_Product) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 0, 0} +} + +func (x *Test_BodyOnly_PrimitiveCase_Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Test_BodyOnly_PrimitiveCase_Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Test_BodyOnly_NestedCase_Filter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Age int64 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + SubFilter *Test_BodyOnly_NestedCase_Filter_SubFilter `protobuf:"bytes,3,opt,name=subFilter,json=sub_filter,proto3" json:"subFilter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_NestedCase_Filter) Reset() { + *x = Test_BodyOnly_NestedCase_Filter{} + mi := &file_test_messages_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_NestedCase_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_NestedCase_Filter) ProtoMessage() {} + +func (x *Test_BodyOnly_NestedCase_Filter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_NestedCase_Filter.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_NestedCase_Filter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 1, 0} +} + +func (x *Test_BodyOnly_NestedCase_Filter) GetAge() int64 { + if x != nil { + return x.Age + } + return 0 +} + +func (x *Test_BodyOnly_NestedCase_Filter) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Test_BodyOnly_NestedCase_Filter) GetSubFilter() *Test_BodyOnly_NestedCase_Filter_SubFilter { + if x != nil { + return x.SubFilter + } + return nil +} + +type Test_BodyOnly_NestedCase_Filter_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_NestedCase_Filter_SubFilter) Reset() { + *x = Test_BodyOnly_NestedCase_Filter_SubFilter{} + mi := &file_test_messages_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_NestedCase_Filter_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_NestedCase_Filter_SubFilter) ProtoMessage() {} + +func (x *Test_BodyOnly_NestedCase_Filter_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_NestedCase_Filter_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_NestedCase_Filter_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 1, 0, 0} +} + +func (x *Test_BodyOnly_NestedCase_Filter_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_BodyOnly_NestedCase_Filter_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_BodyOnly_RepeatedMessageCase_Product struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_RepeatedMessageCase_Product) Reset() { + *x = Test_BodyOnly_RepeatedMessageCase_Product{} + mi := &file_test_messages_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_RepeatedMessageCase_Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_RepeatedMessageCase_Product) ProtoMessage() {} + +func (x *Test_BodyOnly_RepeatedMessageCase_Product) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_RepeatedMessageCase_Product.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_RepeatedMessageCase_Product) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 2, 0} +} + +func (x *Test_BodyOnly_RepeatedMessageCase_Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Test_BodyOnly_RepeatedMessageCase_Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Test_BodyOnly_MapCase_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_MapCase_SubFilter) Reset() { + *x = Test_BodyOnly_MapCase_SubFilter{} + mi := &file_test_messages_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_MapCase_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_MapCase_SubFilter) ProtoMessage() {} + +func (x *Test_BodyOnly_MapCase_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_MapCase_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_MapCase_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 3, 2} +} + +func (x *Test_BodyOnly_MapCase_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_BodyOnly_MapCase_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_BodyOnly_MultipleCase_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_BodyOnly_MultipleCase_SubFilter) Reset() { + *x = Test_BodyOnly_MultipleCase_SubFilter{} + mi := &file_test_messages_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_BodyOnly_MultipleCase_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_BodyOnly_MultipleCase_SubFilter) ProtoMessage() {} + +func (x *Test_BodyOnly_MultipleCase_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_BodyOnly_MultipleCase_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_BodyOnly_MultipleCase_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{3, 4, 1} +} + +func (x *Test_BodyOnly_MultipleCase_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_BodyOnly_MultipleCase_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_Mixed_PrimitiveCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + Product *Test_Mixed_PrimitiveCase_Product `protobuf:"bytes,3,opt,name=product,proto3" json:"product,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_PrimitiveCase) Reset() { + *x = Test_Mixed_PrimitiveCase{} + mi := &file_test_messages_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_PrimitiveCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_PrimitiveCase) ProtoMessage() {} + +func (x *Test_Mixed_PrimitiveCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_PrimitiveCase.ProtoReflect.Descriptor instead. +func (*Test_Mixed_PrimitiveCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *Test_Mixed_PrimitiveCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_Mixed_PrimitiveCase) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +func (x *Test_Mixed_PrimitiveCase) GetProduct() *Test_Mixed_PrimitiveCase_Product { + if x != nil { + return x.Product + } + return nil +} + +type Test_Mixed_NestedCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + FirstFilter *Test_Mixed_NestedCase_Filter `protobuf:"bytes,2,opt,name=first_filter,proto3" json:"first_filter,omitempty"` + SecondFilter *Test_Mixed_NestedCase_Filter `protobuf:"bytes,3,opt,name=second_filter,proto3" json:"second_filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_NestedCase) Reset() { + *x = Test_Mixed_NestedCase{} + mi := &file_test_messages_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_NestedCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_NestedCase) ProtoMessage() {} + +func (x *Test_Mixed_NestedCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_NestedCase.ProtoReflect.Descriptor instead. +func (*Test_Mixed_NestedCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 1} +} + +func (x *Test_Mixed_NestedCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_Mixed_NestedCase) GetFirstFilter() *Test_Mixed_NestedCase_Filter { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_Mixed_NestedCase) GetSecondFilter() *Test_Mixed_NestedCase_Filter { + if x != nil { + return x.SecondFilter + } + return nil +} + +type Test_Mixed_RepeatedMessageCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + Products []*Test_Mixed_RepeatedMessageCase_Product `protobuf:"bytes,2,rep,name=products,proto3" json:"products,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_RepeatedMessageCase) Reset() { + *x = Test_Mixed_RepeatedMessageCase{} + mi := &file_test_messages_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_RepeatedMessageCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_RepeatedMessageCase) ProtoMessage() {} + +func (x *Test_Mixed_RepeatedMessageCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_RepeatedMessageCase.ProtoReflect.Descriptor instead. +func (*Test_Mixed_RepeatedMessageCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 2} +} + +func (x *Test_Mixed_RepeatedMessageCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_Mixed_RepeatedMessageCase) GetProducts() []*Test_Mixed_RepeatedMessageCase_Product { + if x != nil { + return x.Products + } + return nil +} + +type Test_Mixed_MapCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + FirstFilter map[string]string `protobuf:"bytes,1,rep,name=firstFilter,json=first_filter,proto3" json:"firstFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + SecondFilter map[string]*Test_Mixed_MapCase_SubFilter `protobuf:"bytes,2,rep,name=secondFilter,json=second_filter,proto3" json:"secondFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_MapCase) Reset() { + *x = Test_Mixed_MapCase{} + mi := &file_test_messages_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_MapCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_MapCase) ProtoMessage() {} + +func (x *Test_Mixed_MapCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_MapCase.ProtoReflect.Descriptor instead. +func (*Test_Mixed_MapCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 3} +} + +func (x *Test_Mixed_MapCase) GetFirstFilter() map[string]string { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_Mixed_MapCase) GetSecondFilter() map[string]*Test_Mixed_MapCase_SubFilter { + if x != nil { + return x.SecondFilter + } + return nil +} + +type Test_Mixed_MultipleCase struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + FirstFilter []*Test_Mixed_MultipleCase_SubFilter `protobuf:"bytes,2,rep,name=firstFilter,json=first_filter,proto3" json:"firstFilter,omitempty"` + SecondFilter map[string]*Test_Mixed_MultipleCase_SubFilter `protobuf:"bytes,3,rep,name=secondFilter,json=second_filter,proto3" json:"secondFilter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + ThirdFilter *Test_Mixed_MultipleCase_SubFilter `protobuf:"bytes,4,opt,name=thirdFilter,json=third_filter,proto3" json:"thirdFilter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_MultipleCase) Reset() { + *x = Test_Mixed_MultipleCase{} + mi := &file_test_messages_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_MultipleCase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_MultipleCase) ProtoMessage() {} + +func (x *Test_Mixed_MultipleCase) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_MultipleCase.ProtoReflect.Descriptor instead. +func (*Test_Mixed_MultipleCase) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 4} +} + +func (x *Test_Mixed_MultipleCase) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_Mixed_MultipleCase) GetFirstFilter() []*Test_Mixed_MultipleCase_SubFilter { + if x != nil { + return x.FirstFilter + } + return nil +} + +func (x *Test_Mixed_MultipleCase) GetSecondFilter() map[string]*Test_Mixed_MultipleCase_SubFilter { + if x != nil { + return x.SecondFilter + } + return nil +} + +func (x *Test_Mixed_MultipleCase) GetThirdFilter() *Test_Mixed_MultipleCase_SubFilter { + if x != nil { + return x.ThirdFilter + } + return nil +} + +type Test_Mixed_PrimitiveCase_Product struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_PrimitiveCase_Product) Reset() { + *x = Test_Mixed_PrimitiveCase_Product{} + mi := &file_test_messages_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_PrimitiveCase_Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_PrimitiveCase_Product) ProtoMessage() {} + +func (x *Test_Mixed_PrimitiveCase_Product) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[55] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_PrimitiveCase_Product.ProtoReflect.Descriptor instead. +func (*Test_Mixed_PrimitiveCase_Product) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 0, 0} +} + +func (x *Test_Mixed_PrimitiveCase_Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Test_Mixed_PrimitiveCase_Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Test_Mixed_NestedCase_Filter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Age int64 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + SubFilter *Test_Mixed_NestedCase_Filter_SubFilter `protobuf:"bytes,3,opt,name=subFilter,json=sub_filter,proto3" json:"subFilter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_NestedCase_Filter) Reset() { + *x = Test_Mixed_NestedCase_Filter{} + mi := &file_test_messages_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_NestedCase_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_NestedCase_Filter) ProtoMessage() {} + +func (x *Test_Mixed_NestedCase_Filter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_NestedCase_Filter.ProtoReflect.Descriptor instead. +func (*Test_Mixed_NestedCase_Filter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 1, 0} +} + +func (x *Test_Mixed_NestedCase_Filter) GetAge() int64 { + if x != nil { + return x.Age + } + return 0 +} + +func (x *Test_Mixed_NestedCase_Filter) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Test_Mixed_NestedCase_Filter) GetSubFilter() *Test_Mixed_NestedCase_Filter_SubFilter { + if x != nil { + return x.SubFilter + } + return nil +} + +type Test_Mixed_NestedCase_Filter_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_NestedCase_Filter_SubFilter) Reset() { + *x = Test_Mixed_NestedCase_Filter_SubFilter{} + mi := &file_test_messages_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_NestedCase_Filter_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_NestedCase_Filter_SubFilter) ProtoMessage() {} + +func (x *Test_Mixed_NestedCase_Filter_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[57] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_NestedCase_Filter_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_Mixed_NestedCase_Filter_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 1, 0, 0} +} + +func (x *Test_Mixed_NestedCase_Filter_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_Mixed_NestedCase_Filter_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_Mixed_RepeatedMessageCase_Product struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_RepeatedMessageCase_Product) Reset() { + *x = Test_Mixed_RepeatedMessageCase_Product{} + mi := &file_test_messages_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_RepeatedMessageCase_Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_RepeatedMessageCase_Product) ProtoMessage() {} + +func (x *Test_Mixed_RepeatedMessageCase_Product) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[58] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_RepeatedMessageCase_Product.ProtoReflect.Descriptor instead. +func (*Test_Mixed_RepeatedMessageCase_Product) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 2, 0} +} + +func (x *Test_Mixed_RepeatedMessageCase_Product) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Test_Mixed_RepeatedMessageCase_Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Test_Mixed_MapCase_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_MapCase_SubFilter) Reset() { + *x = Test_Mixed_MapCase_SubFilter{} + mi := &file_test_messages_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_MapCase_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_MapCase_SubFilter) ProtoMessage() {} + +func (x *Test_Mixed_MapCase_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_MapCase_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_Mixed_MapCase_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 3, 2} +} + +func (x *Test_Mixed_MapCase_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_Mixed_MapCase_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Test_Mixed_MultipleCase_SubFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + SubAge int64 `protobuf:"varint,1,opt,name=subAge,json=sub_age,proto3" json:"subAge,omitempty"` + SubName string `protobuf:"bytes,2,opt,name=subName,json=sub_name,proto3" json:"subName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Mixed_MultipleCase_SubFilter) Reset() { + *x = Test_Mixed_MultipleCase_SubFilter{} + mi := &file_test_messages_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Mixed_MultipleCase_SubFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Mixed_MultipleCase_SubFilter) ProtoMessage() {} + +func (x *Test_Mixed_MultipleCase_SubFilter) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[63] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Mixed_MultipleCase_SubFilter.ProtoReflect.Descriptor instead. +func (*Test_Mixed_MultipleCase_SubFilter) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{4, 4, 1} +} + +func (x *Test_Mixed_MultipleCase_SubFilter) GetSubAge() int64 { + if x != nil { + return x.SubAge + } + return 0 +} + +func (x *Test_Mixed_MultipleCase_SubFilter) GetSubName() string { + if x != nil { + return x.SubName + } + return "" +} + +type Benchmark_Case5 struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field1 string `protobuf:"bytes,1,opt,name=field1,proto3" json:"field1,omitempty"` + Field2 string `protobuf:"bytes,2,opt,name=field2,proto3" json:"field2,omitempty"` + Field3 string `protobuf:"bytes,3,opt,name=field3,proto3" json:"field3,omitempty"` + Field4 string `protobuf:"bytes,4,opt,name=field4,proto3" json:"field4,omitempty"` + Field5 string `protobuf:"bytes,5,opt,name=field5,proto3" json:"field5,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Benchmark_Case5) Reset() { + *x = Benchmark_Case5{} + mi := &file_test_messages_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Benchmark_Case5) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Benchmark_Case5) ProtoMessage() {} + +func (x *Benchmark_Case5) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Benchmark_Case5.ProtoReflect.Descriptor instead. +func (*Benchmark_Case5) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *Benchmark_Case5) GetField1() string { + if x != nil { + return x.Field1 + } + return "" +} + +func (x *Benchmark_Case5) GetField2() string { + if x != nil { + return x.Field2 + } + return "" +} + +func (x *Benchmark_Case5) GetField3() string { + if x != nil { + return x.Field3 + } + return "" +} + +func (x *Benchmark_Case5) GetField4() string { + if x != nil { + return x.Field4 + } + return "" +} + +func (x *Benchmark_Case5) GetField5() string { + if x != nil { + return x.Field5 + } + return "" +} + +type Benchmark_Case10 struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field1 string `protobuf:"bytes,1,opt,name=field1,proto3" json:"field1,omitempty"` + Field2 string `protobuf:"bytes,2,opt,name=field2,proto3" json:"field2,omitempty"` + Field3 string `protobuf:"bytes,3,opt,name=field3,proto3" json:"field3,omitempty"` + Field4 string `protobuf:"bytes,4,opt,name=field4,proto3" json:"field4,omitempty"` + Field5 string `protobuf:"bytes,5,opt,name=field5,proto3" json:"field5,omitempty"` + Field6 string `protobuf:"bytes,6,opt,name=field6,proto3" json:"field6,omitempty"` + Field7 string `protobuf:"bytes,7,opt,name=field7,proto3" json:"field7,omitempty"` + Field8 string `protobuf:"bytes,8,opt,name=field8,proto3" json:"field8,omitempty"` + Field9 string `protobuf:"bytes,9,opt,name=field9,proto3" json:"field9,omitempty"` + Field10 string `protobuf:"bytes,10,opt,name=field10,proto3" json:"field10,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Benchmark_Case10) Reset() { + *x = Benchmark_Case10{} + mi := &file_test_messages_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Benchmark_Case10) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Benchmark_Case10) ProtoMessage() {} + +func (x *Benchmark_Case10) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[65] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Benchmark_Case10.ProtoReflect.Descriptor instead. +func (*Benchmark_Case10) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{5, 1} +} + +func (x *Benchmark_Case10) GetField1() string { + if x != nil { + return x.Field1 + } + return "" +} + +func (x *Benchmark_Case10) GetField2() string { + if x != nil { + return x.Field2 + } + return "" +} + +func (x *Benchmark_Case10) GetField3() string { + if x != nil { + return x.Field3 + } + return "" +} + +func (x *Benchmark_Case10) GetField4() string { + if x != nil { + return x.Field4 + } + return "" +} + +func (x *Benchmark_Case10) GetField5() string { + if x != nil { + return x.Field5 + } + return "" +} + +func (x *Benchmark_Case10) GetField6() string { + if x != nil { + return x.Field6 + } + return "" +} + +func (x *Benchmark_Case10) GetField7() string { + if x != nil { + return x.Field7 + } + return "" +} + +func (x *Benchmark_Case10) GetField8() string { + if x != nil { + return x.Field8 + } + return "" +} + +func (x *Benchmark_Case10) GetField9() string { + if x != nil { + return x.Field9 + } + return "" +} + +func (x *Benchmark_Case10) GetField10() string { + if x != nil { + return x.Field10 + } + return "" +} + +type Benchmark_Case30 struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field1 string `protobuf:"bytes,1,opt,name=field1,proto3" json:"field1,omitempty"` + Field2 string `protobuf:"bytes,2,opt,name=field2,proto3" json:"field2,omitempty"` + Field3 string `protobuf:"bytes,3,opt,name=field3,proto3" json:"field3,omitempty"` + Field4 string `protobuf:"bytes,4,opt,name=field4,proto3" json:"field4,omitempty"` + Field5 string `protobuf:"bytes,5,opt,name=field5,proto3" json:"field5,omitempty"` + Field6 string `protobuf:"bytes,6,opt,name=field6,proto3" json:"field6,omitempty"` + Field7 string `protobuf:"bytes,7,opt,name=field7,proto3" json:"field7,omitempty"` + Field8 string `protobuf:"bytes,8,opt,name=field8,proto3" json:"field8,omitempty"` + Field9 string `protobuf:"bytes,9,opt,name=field9,proto3" json:"field9,omitempty"` + Field10 string `protobuf:"bytes,10,opt,name=field10,proto3" json:"field10,omitempty"` + Field11 string `protobuf:"bytes,11,opt,name=field11,proto3" json:"field11,omitempty"` + Field12 string `protobuf:"bytes,12,opt,name=field12,proto3" json:"field12,omitempty"` + Field13 string `protobuf:"bytes,13,opt,name=field13,proto3" json:"field13,omitempty"` + Field14 string `protobuf:"bytes,14,opt,name=field14,proto3" json:"field14,omitempty"` + Field15 string `protobuf:"bytes,15,opt,name=field15,proto3" json:"field15,omitempty"` + Field16 string `protobuf:"bytes,16,opt,name=field16,proto3" json:"field16,omitempty"` + Field17 string `protobuf:"bytes,17,opt,name=field17,proto3" json:"field17,omitempty"` + Field18 string `protobuf:"bytes,18,opt,name=field18,proto3" json:"field18,omitempty"` + Field19 string `protobuf:"bytes,19,opt,name=field19,proto3" json:"field19,omitempty"` + Field20 string `protobuf:"bytes,20,opt,name=field20,proto3" json:"field20,omitempty"` + Field21 string `protobuf:"bytes,21,opt,name=field21,proto3" json:"field21,omitempty"` + Field22 string `protobuf:"bytes,22,opt,name=field22,proto3" json:"field22,omitempty"` + Field23 string `protobuf:"bytes,23,opt,name=field23,proto3" json:"field23,omitempty"` + Field24 string `protobuf:"bytes,24,opt,name=field24,proto3" json:"field24,omitempty"` + Field25 string `protobuf:"bytes,25,opt,name=field25,proto3" json:"field25,omitempty"` + Field26 string `protobuf:"bytes,26,opt,name=field26,proto3" json:"field26,omitempty"` + Field27 string `protobuf:"bytes,27,opt,name=field27,proto3" json:"field27,omitempty"` + Field28 string `protobuf:"bytes,28,opt,name=field28,proto3" json:"field28,omitempty"` + Field29 string `protobuf:"bytes,29,opt,name=field29,proto3" json:"field29,omitempty"` + Field30 string `protobuf:"bytes,30,opt,name=field30,proto3" json:"field30,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Benchmark_Case30) Reset() { + *x = Benchmark_Case30{} + mi := &file_test_messages_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Benchmark_Case30) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Benchmark_Case30) ProtoMessage() {} + +func (x *Benchmark_Case30) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[66] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Benchmark_Case30.ProtoReflect.Descriptor instead. +func (*Benchmark_Case30) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{5, 2} +} + +func (x *Benchmark_Case30) GetField1() string { + if x != nil { + return x.Field1 + } + return "" +} + +func (x *Benchmark_Case30) GetField2() string { + if x != nil { + return x.Field2 + } + return "" +} + +func (x *Benchmark_Case30) GetField3() string { + if x != nil { + return x.Field3 + } + return "" +} + +func (x *Benchmark_Case30) GetField4() string { + if x != nil { + return x.Field4 + } + return "" +} + +func (x *Benchmark_Case30) GetField5() string { + if x != nil { + return x.Field5 + } + return "" +} + +func (x *Benchmark_Case30) GetField6() string { + if x != nil { + return x.Field6 + } + return "" +} + +func (x *Benchmark_Case30) GetField7() string { + if x != nil { + return x.Field7 + } + return "" +} + +func (x *Benchmark_Case30) GetField8() string { + if x != nil { + return x.Field8 + } + return "" +} + +func (x *Benchmark_Case30) GetField9() string { + if x != nil { + return x.Field9 + } + return "" +} + +func (x *Benchmark_Case30) GetField10() string { + if x != nil { + return x.Field10 + } + return "" +} + +func (x *Benchmark_Case30) GetField11() string { + if x != nil { + return x.Field11 + } + return "" +} + +func (x *Benchmark_Case30) GetField12() string { + if x != nil { + return x.Field12 + } + return "" +} + +func (x *Benchmark_Case30) GetField13() string { + if x != nil { + return x.Field13 + } + return "" +} + +func (x *Benchmark_Case30) GetField14() string { + if x != nil { + return x.Field14 + } + return "" +} + +func (x *Benchmark_Case30) GetField15() string { + if x != nil { + return x.Field15 + } + return "" +} + +func (x *Benchmark_Case30) GetField16() string { + if x != nil { + return x.Field16 + } + return "" +} + +func (x *Benchmark_Case30) GetField17() string { + if x != nil { + return x.Field17 + } + return "" +} + +func (x *Benchmark_Case30) GetField18() string { + if x != nil { + return x.Field18 + } + return "" +} + +func (x *Benchmark_Case30) GetField19() string { + if x != nil { + return x.Field19 + } + return "" +} + +func (x *Benchmark_Case30) GetField20() string { + if x != nil { + return x.Field20 + } + return "" +} + +func (x *Benchmark_Case30) GetField21() string { + if x != nil { + return x.Field21 + } + return "" +} + +func (x *Benchmark_Case30) GetField22() string { + if x != nil { + return x.Field22 + } + return "" +} + +func (x *Benchmark_Case30) GetField23() string { + if x != nil { + return x.Field23 + } + return "" +} + +func (x *Benchmark_Case30) GetField24() string { + if x != nil { + return x.Field24 + } + return "" +} + +func (x *Benchmark_Case30) GetField25() string { + if x != nil { + return x.Field25 + } + return "" +} + +func (x *Benchmark_Case30) GetField26() string { + if x != nil { + return x.Field26 + } + return "" +} + +func (x *Benchmark_Case30) GetField27() string { + if x != nil { + return x.Field27 + } + return "" +} + +func (x *Benchmark_Case30) GetField28() string { + if x != nil { + return x.Field28 + } + return "" +} + +func (x *Benchmark_Case30) GetField29() string { + if x != nil { + return x.Field29 + } + return "" +} + +func (x *Benchmark_Case30) GetField30() string { + if x != nil { + return x.Field30 + } + return "" +} + +type Test_Client_Call_Request struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,json=user_id,proto3" json:"userId,omitempty"` + OrderId int64 `protobuf:"varint,2,opt,name=orderId,json=order_id,proto3" json:"orderId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Client_Call_Request) Reset() { + *x = Test_Client_Call_Request{} + mi := &file_test_messages_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Client_Call_Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Client_Call_Request) ProtoMessage() {} + +func (x *Test_Client_Call_Request) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[67] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Client_Call_Request.ProtoReflect.Descriptor instead. +func (*Test_Client_Call_Request) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *Test_Client_Call_Request) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Test_Client_Call_Request) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type Test_Client_Call_Response struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Client_Call_Response) Reset() { + *x = Test_Client_Call_Response{} + mi := &file_test_messages_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Client_Call_Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Client_Call_Response) ProtoMessage() {} + +func (x *Test_Client_Call_Response) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[68] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Client_Call_Response.ProtoReflect.Descriptor instead. +func (*Test_Client_Call_Response) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{6, 1} +} + +func (x *Test_Client_Call_Response) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Test_Client_Call_Response) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Test_Client_Call_DefaultError struct { + state protoimpl.MessageState `protogen:"open.v1"` + Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Client_Call_DefaultError) Reset() { + *x = Test_Client_Call_DefaultError{} + mi := &file_test_messages_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Client_Call_DefaultError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Client_Call_DefaultError) ProtoMessage() {} + +func (x *Test_Client_Call_DefaultError) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Client_Call_DefaultError.ProtoReflect.Descriptor instead. +func (*Test_Client_Call_DefaultError) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{6, 2} +} + +func (x *Test_Client_Call_DefaultError) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +func (x *Test_Client_Call_DefaultError) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +type Test_Client_Call_SpecialError struct { + state protoimpl.MessageState `protogen:"open.v1"` + Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + Warning string `protobuf:"bytes,3,opt,name=warning,proto3" json:"warning,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Test_Client_Call_SpecialError) Reset() { + *x = Test_Client_Call_SpecialError{} + mi := &file_test_messages_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Test_Client_Call_SpecialError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_Client_Call_SpecialError) ProtoMessage() {} + +func (x *Test_Client_Call_SpecialError) ProtoReflect() protoreflect.Message { + mi := &file_test_messages_proto_msgTypes[70] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Test_Client_Call_SpecialError.ProtoReflect.Descriptor instead. +func (*Test_Client_Call_SpecialError) Descriptor() ([]byte, []int) { + return file_test_messages_proto_rawDescGZIP(), []int{6, 3} +} + +func (x *Test_Client_Call_SpecialError) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +func (x *Test_Client_Call_SpecialError) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +func (x *Test_Client_Call_SpecialError) GetWarning() string { + if x != nil { + return x.Warning + } + return "" +} + +var File_test_messages_proto protoreflect.FileDescriptor + +const file_test_messages_proto_rawDesc = "" + + "\n" + + "\x13test_messages.proto\x12\x05proto\"\x14\n" + + "\x12TestRequestBuilder\"\xa1\b\n" + + "\rTest_PathOnly\x1aC\n" + + "\rPrimitiveCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x1a\x98\x02\n" + + "\n" + + "NestedCase\x128\n" + + "\x04user\x18\x01 \x01(\v2$.proto.Test_PathOnly.NestedCase.UserR\x04user\x12;\n" + + "\x05order\x18\x02 \x01(\v2%.proto.Test_PathOnly.NestedCase.OrderR\x05order\x1a\x16\n" + + "\x04User\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x1a{\n" + + "\x05Order\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12G\n" + + "\aproduct\x18\x02 \x01(\v2-.proto.Test_PathOnly.NestedCase.Order.ProductR\aproduct\x1a\x19\n" + + "\aProduct\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x1a\x7f\n" + + "\fMultipleCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12=\n" + + "\x05order\x18\x02 \x01(\v2'.proto.Test_PathOnly.MultipleCase.OrderR\x05order\x1a\x17\n" + + "\x05Order\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x1aB\n" + + "\fRepeatedCase\x12\x17\n" + + "\x06userId\x18\x01 \x03(\tR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x1a\x98\x01\n" + + "\x17NonPrimitiveMessageCase\x12J\n" + + "\x06userId\x18\x01 \x01(\v21.proto.Test_PathOnly.NonPrimitiveMessageCase.UserR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x1a\x16\n" + + "\x04User\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x1a\xba\x01\n" + + "\x13NonPrimitiveMapCase\x12M\n" + + "\x06userId\x18\x01 \x03(\v24.proto.Test_PathOnly.NonPrimitiveMapCase.UserIdEntryR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x1a9\n" + + "\vUserIdEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a'\n" + + "\vPatternCase\x12\x18\n" + + "\apattern\x18\x01 \x01(\tR\apattern\x1aj\n" + + "\x14CompositePatternCase\x12\x18\n" + + "\apattern\x18\x01 \x01(\tR\apattern\x12\x19\n" + + "\aorderId\x18\x02 \x01(\tR\border_id\x12\x1d\n" + + "\tproductId\x18\x03 \x01(\tR\n" + + "product_id\"\xac\f\n" + + "\x0eTest_QueryOnly\x1aW\n" + + "\rPrimitiveCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x12\x12\n" + + "\x04flag\x18\x03 \x01(\bR\x04flag\x1aD\n" + + "\fRepeatedCase\x12\x18\n" + + "\astrings\x18\x01 \x03(\tR\astrings\x12\x1a\n" + + "\bintegers\x18\x02 \x03(\x03R\bintegers\x1a\xbf\x02\n" + + "\x11NestedMessageCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12F\n" + + "\x06filter\x18\x02 \x01(\v2..proto.Test_QueryOnly.NestedMessageCase.FilterR\x06filter\x1a\xc8\x01\n" + + "\x06Filter\x12\x10\n" + + "\x03age\x18\x01 \x01(\x03R\x03age\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12W\n" + + "\tsubFilter\x18\x03 \x01(\v28.proto.Test_QueryOnly.NestedMessageCase.Filter.SubFilterR\n" + + "sub_filter\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\x1a\xce\x03\n" + + "\rNestedMapCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12W\n" + + "\vfirstFilter\x18\x02 \x03(\v24.proto.Test_QueryOnly.NestedMapCase.FirstFilterEntryR\ffirst_filter\x12Z\n" + + "\fsecondFilter\x18\x04 \x03(\v25.proto.Test_QueryOnly.NestedMapCase.SecondFilterEntryR\rsecond_filter\x1a>\n" + + "\x10FirstFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1an\n" + + "\x11SecondFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12C\n" + + "\x05value\x18\x02 \x01(\v2-.proto.Test_QueryOnly.NestedMapCase.SubFilterR\x05value:\x028\x01\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\x1a\xe8\x03\n" + + "\fMultipleCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12\x18\n" + + "\astrings\x18\x02 \x03(\tR\astrings\x12L\n" + + "\vfirstFilter\x18\x03 \x01(\v2).proto.Test_QueryOnly.MultipleCase.FilterR\ffirst_filter\x12Y\n" + + "\fsecondFilter\x18\x04 \x03(\v24.proto.Test_QueryOnly.MultipleCase.SecondFilterEntryR\rsecond_filter\x1am\n" + + "\x11SecondFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12B\n" + + "\x05value\x18\x02 \x01(\v2,.proto.Test_QueryOnly.MultipleCase.SubFilterR\x05value:\x028\x01\x1ag\n" + + "\x06Filter\x12\x10\n" + + "\x03age\x18\x01 \x01(\x03R\x03age\x12K\n" + + "\tsubFilter\x18\x02 \x01(\v2,.proto.Test_QueryOnly.MultipleCase.SubFilterR\n" + + "sub_filter\x1a$\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x1a}\n" + + "\x13RepeatedMessageCase\x12J\n" + + "\afilters\x18\x01 \x03(\v20.proto.Test_QueryOnly.RepeatedMessageCase.FilterR\afilters\x1a\x1a\n" + + "\x06Filter\x12\x10\n" + + "\x03age\x18\x01 \x01(\x03R\x03age\"\x9b\r\n" + + "\rTest_BodyOnly\x1a\xe6\x01\n" + + "\rPrimitiveCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x12\x12\n" + + "\x04flag\x18\x03 \x01(\bR\x04flag\x12\x18\n" + + "\astrings\x18\x04 \x03(\tR\astrings\x12D\n" + + "\aproduct\x18\x06 \x01(\v2*.proto.Test_BodyOnly.PrimitiveCase.ProductR\aproduct\x1a-\n" + + "\aProduct\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x1a\x82\x03\n" + + "\n" + + "NestedCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12J\n" + + "\ffirst_filter\x18\x02 \x01(\v2&.proto.Test_BodyOnly.NestedCase.FilterR\ffirst_filter\x12L\n" + + "\rsecond_filter\x18\x03 \x01(\v2&.proto.Test_BodyOnly.NestedCase.FilterR\rsecond_filter\x1a\xc0\x01\n" + + "\x06Filter\x12\x10\n" + + "\x03age\x18\x01 \x01(\x03R\x03age\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12O\n" + + "\tsubFilter\x18\x03 \x01(\v20.proto.Test_BodyOnly.NestedCase.Filter.SubFilterR\n" + + "sub_filter\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\x1a\xab\x01\n" + + "\x13RepeatedMessageCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12L\n" + + "\bproducts\x18\x02 \x03(\v20.proto.Test_BodyOnly.RepeatedMessageCase.ProductR\bproducts\x1a-\n" + + "\aProduct\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x1a\x9a\x03\n" + + "\aMapCase\x12P\n" + + "\vfirstFilter\x18\x01 \x03(\v2-.proto.Test_BodyOnly.MapCase.FirstFilterEntryR\ffirst_filter\x12S\n" + + "\fsecondFilter\x18\x02 \x03(\v2..proto.Test_BodyOnly.MapCase.SecondFilterEntryR\rsecond_filter\x1a>\n" + + "\x10FirstFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ag\n" + + "\x11SecondFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12<\n" + + "\x05value\x18\x02 \x01(\v2&.proto.Test_BodyOnly.MapCase.SubFilterR\x05value:\x028\x01\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\x1a\xd0\x03\n" + + "\fMultipleCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12N\n" + + "\vfirstFilter\x18\x02 \x03(\v2+.proto.Test_BodyOnly.MultipleCase.SubFilterR\ffirst_filter\x12X\n" + + "\fsecondFilter\x18\x03 \x03(\v23.proto.Test_BodyOnly.MultipleCase.SecondFilterEntryR\rsecond_filter\x12N\n" + + "\vthirdFilter\x18\x04 \x01(\v2+.proto.Test_BodyOnly.MultipleCase.SubFilterR\fthird_filter\x1al\n" + + "\x11SecondFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12A\n" + + "\x05value\x18\x02 \x01(\v2+.proto.Test_BodyOnly.MultipleCase.SubFilterR\x05value:\x028\x01\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\"\xc6\f\n" + + "\n" + + "Test_Mixed\x1a\xb5\x01\n" + + "\rPrimitiveCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x12A\n" + + "\aproduct\x18\x03 \x01(\v2'.proto.Test_Mixed.PrimitiveCase.ProductR\aproduct\x1a-\n" + + "\aProduct\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x1a\xf9\x02\n" + + "\n" + + "NestedCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12G\n" + + "\ffirst_filter\x18\x02 \x01(\v2#.proto.Test_Mixed.NestedCase.FilterR\ffirst_filter\x12I\n" + + "\rsecond_filter\x18\x03 \x01(\v2#.proto.Test_Mixed.NestedCase.FilterR\rsecond_filter\x1a\xbd\x01\n" + + "\x06Filter\x12\x10\n" + + "\x03age\x18\x01 \x01(\x03R\x03age\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12L\n" + + "\tsubFilter\x18\x03 \x01(\v2-.proto.Test_Mixed.NestedCase.Filter.SubFilterR\n" + + "sub_filter\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\x1a\xa8\x01\n" + + "\x13RepeatedMessageCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12I\n" + + "\bproducts\x18\x02 \x03(\v2-.proto.Test_Mixed.RepeatedMessageCase.ProductR\bproducts\x1a-\n" + + "\aProduct\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x1a\x91\x03\n" + + "\aMapCase\x12M\n" + + "\vfirstFilter\x18\x01 \x03(\v2*.proto.Test_Mixed.MapCase.FirstFilterEntryR\ffirst_filter\x12P\n" + + "\fsecondFilter\x18\x02 \x03(\v2+.proto.Test_Mixed.MapCase.SecondFilterEntryR\rsecond_filter\x1a>\n" + + "\x10FirstFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ad\n" + + "\x11SecondFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x129\n" + + "\x05value\x18\x02 \x01(\v2#.proto.Test_Mixed.MapCase.SubFilterR\x05value:\x028\x01\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\x1a\xc4\x03\n" + + "\fMultipleCase\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12K\n" + + "\vfirstFilter\x18\x02 \x03(\v2(.proto.Test_Mixed.MultipleCase.SubFilterR\ffirst_filter\x12U\n" + + "\fsecondFilter\x18\x03 \x03(\v20.proto.Test_Mixed.MultipleCase.SecondFilterEntryR\rsecond_filter\x12K\n" + + "\vthirdFilter\x18\x04 \x01(\v2(.proto.Test_Mixed.MultipleCase.SubFilterR\fthird_filter\x1ai\n" + + "\x11SecondFilterEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12>\n" + + "\x05value\x18\x02 \x01(\v2(.proto.Test_Mixed.MultipleCase.SubFilterR\x05value:\x028\x01\x1a?\n" + + "\tSubFilter\x12\x17\n" + + "\x06subAge\x18\x01 \x01(\x03R\asub_age\x12\x19\n" + + "\asubName\x18\x02 \x01(\tR\bsub_name\"\x8e\t\n" + + "\tBenchmark\x1a\x7f\n" + + "\x05Case5\x12\x16\n" + + "\x06field1\x18\x01 \x01(\tR\x06field1\x12\x16\n" + + "\x06field2\x18\x02 \x01(\tR\x06field2\x12\x16\n" + + "\x06field3\x18\x03 \x01(\tR\x06field3\x12\x16\n" + + "\x06field4\x18\x04 \x01(\tR\x06field4\x12\x16\n" + + "\x06field5\x18\x05 \x01(\tR\x06field5\x1a\xfa\x01\n" + + "\x06Case10\x12\x16\n" + + "\x06field1\x18\x01 \x01(\tR\x06field1\x12\x16\n" + + "\x06field2\x18\x02 \x01(\tR\x06field2\x12\x16\n" + + "\x06field3\x18\x03 \x01(\tR\x06field3\x12\x16\n" + + "\x06field4\x18\x04 \x01(\tR\x06field4\x12\x16\n" + + "\x06field5\x18\x05 \x01(\tR\x06field5\x12\x16\n" + + "\x06field6\x18\x06 \x01(\tR\x06field6\x12\x16\n" + + "\x06field7\x18\a \x01(\tR\x06field7\x12\x16\n" + + "\x06field8\x18\b \x01(\tR\x06field8\x12\x16\n" + + "\x06field9\x18\t \x01(\tR\x06field9\x12\x18\n" + + "\afield10\x18\n" + + " \x01(\tR\afield10\x1a\x82\x06\n" + + "\x06Case30\x12\x16\n" + + "\x06field1\x18\x01 \x01(\tR\x06field1\x12\x16\n" + + "\x06field2\x18\x02 \x01(\tR\x06field2\x12\x16\n" + + "\x06field3\x18\x03 \x01(\tR\x06field3\x12\x16\n" + + "\x06field4\x18\x04 \x01(\tR\x06field4\x12\x16\n" + + "\x06field5\x18\x05 \x01(\tR\x06field5\x12\x16\n" + + "\x06field6\x18\x06 \x01(\tR\x06field6\x12\x16\n" + + "\x06field7\x18\a \x01(\tR\x06field7\x12\x16\n" + + "\x06field8\x18\b \x01(\tR\x06field8\x12\x16\n" + + "\x06field9\x18\t \x01(\tR\x06field9\x12\x18\n" + + "\afield10\x18\n" + + " \x01(\tR\afield10\x12\x18\n" + + "\afield11\x18\v \x01(\tR\afield11\x12\x18\n" + + "\afield12\x18\f \x01(\tR\afield12\x12\x18\n" + + "\afield13\x18\r \x01(\tR\afield13\x12\x18\n" + + "\afield14\x18\x0e \x01(\tR\afield14\x12\x18\n" + + "\afield15\x18\x0f \x01(\tR\afield15\x12\x18\n" + + "\afield16\x18\x10 \x01(\tR\afield16\x12\x18\n" + + "\afield17\x18\x11 \x01(\tR\afield17\x12\x18\n" + + "\afield18\x18\x12 \x01(\tR\afield18\x12\x18\n" + + "\afield19\x18\x13 \x01(\tR\afield19\x12\x18\n" + + "\afield20\x18\x14 \x01(\tR\afield20\x12\x18\n" + + "\afield21\x18\x15 \x01(\tR\afield21\x12\x18\n" + + "\afield22\x18\x16 \x01(\tR\afield22\x12\x18\n" + + "\afield23\x18\x17 \x01(\tR\afield23\x12\x18\n" + + "\afield24\x18\x18 \x01(\tR\afield24\x12\x18\n" + + "\afield25\x18\x19 \x01(\tR\afield25\x12\x18\n" + + "\afield26\x18\x1a \x01(\tR\afield26\x12\x18\n" + + "\afield27\x18\x1b \x01(\tR\afield27\x12\x18\n" + + "\afield28\x18\x1c \x01(\tR\afield28\x12\x18\n" + + "\afield29\x18\x1d \x01(\tR\afield29\x12\x18\n" + + "\afield30\x18\x1e \x01(\tR\afield30\"\x87\x02\n" + + "\x10Test_Client_Call\x1a=\n" + + "\aRequest\x12\x17\n" + + "\x06userId\x18\x01 \x01(\tR\auser_id\x12\x19\n" + + "\aorderId\x18\x02 \x01(\x03R\border_id\x1a.\n" + + "\bResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x1a4\n" + + "\fDefaultError\x12\x12\n" + + "\x04code\x18\x01 \x01(\tR\x04code\x12\x10\n" + + "\x03msg\x18\x02 \x01(\tR\x03msg\x1aN\n" + + "\fSpecialError\x12\x12\n" + + "\x04code\x18\x01 \x01(\tR\x04code\x12\x10\n" + + "\x03msg\x18\x02 \x01(\tR\x03msg\x12\x18\n" + + "\awarning\x18\x03 \x01(\tR\awarningB2Z0go.unistack.org/micro-client-http/v4/proto;protob\x06proto3" + +var ( + file_test_messages_proto_rawDescOnce sync.Once + file_test_messages_proto_rawDescData []byte +) + +func file_test_messages_proto_rawDescGZIP() []byte { + file_test_messages_proto_rawDescOnce.Do(func() { + file_test_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_test_messages_proto_rawDesc), len(file_test_messages_proto_rawDesc))) + }) + return file_test_messages_proto_rawDescData +} + +var file_test_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 71) +var file_test_messages_proto_goTypes = []any{ + (*TestRequestBuilder)(nil), // 0: proto.TestRequestBuilder + (*Test_PathOnly)(nil), // 1: proto.Test_PathOnly + (*Test_QueryOnly)(nil), // 2: proto.Test_QueryOnly + (*Test_BodyOnly)(nil), // 3: proto.Test_BodyOnly + (*Test_Mixed)(nil), // 4: proto.Test_Mixed + (*Benchmark)(nil), // 5: proto.Benchmark + (*Test_Client_Call)(nil), // 6: proto.Test_Client_Call + (*Test_PathOnly_PrimitiveCase)(nil), // 7: proto.Test_PathOnly.PrimitiveCase + (*Test_PathOnly_NestedCase)(nil), // 8: proto.Test_PathOnly.NestedCase + (*Test_PathOnly_MultipleCase)(nil), // 9: proto.Test_PathOnly.MultipleCase + (*Test_PathOnly_RepeatedCase)(nil), // 10: proto.Test_PathOnly.RepeatedCase + (*Test_PathOnly_NonPrimitiveMessageCase)(nil), // 11: proto.Test_PathOnly.NonPrimitiveMessageCase + (*Test_PathOnly_NonPrimitiveMapCase)(nil), // 12: proto.Test_PathOnly.NonPrimitiveMapCase + (*Test_PathOnly_PatternCase)(nil), // 13: proto.Test_PathOnly.PatternCase + (*Test_PathOnly_CompositePatternCase)(nil), // 14: proto.Test_PathOnly.CompositePatternCase + (*Test_PathOnly_NestedCase_User)(nil), // 15: proto.Test_PathOnly.NestedCase.User + (*Test_PathOnly_NestedCase_Order)(nil), // 16: proto.Test_PathOnly.NestedCase.Order + (*Test_PathOnly_NestedCase_Order_Product)(nil), // 17: proto.Test_PathOnly.NestedCase.Order.Product + (*Test_PathOnly_MultipleCase_Order)(nil), // 18: proto.Test_PathOnly.MultipleCase.Order + (*Test_PathOnly_NonPrimitiveMessageCase_User)(nil), // 19: proto.Test_PathOnly.NonPrimitiveMessageCase.User + nil, // 20: proto.Test_PathOnly.NonPrimitiveMapCase.UserIdEntry + (*Test_QueryOnly_PrimitiveCase)(nil), // 21: proto.Test_QueryOnly.PrimitiveCase + (*Test_QueryOnly_RepeatedCase)(nil), // 22: proto.Test_QueryOnly.RepeatedCase + (*Test_QueryOnly_NestedMessageCase)(nil), // 23: proto.Test_QueryOnly.NestedMessageCase + (*Test_QueryOnly_NestedMapCase)(nil), // 24: proto.Test_QueryOnly.NestedMapCase + (*Test_QueryOnly_MultipleCase)(nil), // 25: proto.Test_QueryOnly.MultipleCase + (*Test_QueryOnly_RepeatedMessageCase)(nil), // 26: proto.Test_QueryOnly.RepeatedMessageCase + (*Test_QueryOnly_NestedMessageCase_Filter)(nil), // 27: proto.Test_QueryOnly.NestedMessageCase.Filter + (*Test_QueryOnly_NestedMessageCase_Filter_SubFilter)(nil), // 28: proto.Test_QueryOnly.NestedMessageCase.Filter.SubFilter + nil, // 29: proto.Test_QueryOnly.NestedMapCase.FirstFilterEntry + nil, // 30: proto.Test_QueryOnly.NestedMapCase.SecondFilterEntry + (*Test_QueryOnly_NestedMapCase_SubFilter)(nil), // 31: proto.Test_QueryOnly.NestedMapCase.SubFilter + nil, // 32: proto.Test_QueryOnly.MultipleCase.SecondFilterEntry + (*Test_QueryOnly_MultipleCase_Filter)(nil), // 33: proto.Test_QueryOnly.MultipleCase.Filter + (*Test_QueryOnly_MultipleCase_SubFilter)(nil), // 34: proto.Test_QueryOnly.MultipleCase.SubFilter + (*Test_QueryOnly_RepeatedMessageCase_Filter)(nil), // 35: proto.Test_QueryOnly.RepeatedMessageCase.Filter + (*Test_BodyOnly_PrimitiveCase)(nil), // 36: proto.Test_BodyOnly.PrimitiveCase + (*Test_BodyOnly_NestedCase)(nil), // 37: proto.Test_BodyOnly.NestedCase + (*Test_BodyOnly_RepeatedMessageCase)(nil), // 38: proto.Test_BodyOnly.RepeatedMessageCase + (*Test_BodyOnly_MapCase)(nil), // 39: proto.Test_BodyOnly.MapCase + (*Test_BodyOnly_MultipleCase)(nil), // 40: proto.Test_BodyOnly.MultipleCase + (*Test_BodyOnly_PrimitiveCase_Product)(nil), // 41: proto.Test_BodyOnly.PrimitiveCase.Product + (*Test_BodyOnly_NestedCase_Filter)(nil), // 42: proto.Test_BodyOnly.NestedCase.Filter + (*Test_BodyOnly_NestedCase_Filter_SubFilter)(nil), // 43: proto.Test_BodyOnly.NestedCase.Filter.SubFilter + (*Test_BodyOnly_RepeatedMessageCase_Product)(nil), // 44: proto.Test_BodyOnly.RepeatedMessageCase.Product + nil, // 45: proto.Test_BodyOnly.MapCase.FirstFilterEntry + nil, // 46: proto.Test_BodyOnly.MapCase.SecondFilterEntry + (*Test_BodyOnly_MapCase_SubFilter)(nil), // 47: proto.Test_BodyOnly.MapCase.SubFilter + nil, // 48: proto.Test_BodyOnly.MultipleCase.SecondFilterEntry + (*Test_BodyOnly_MultipleCase_SubFilter)(nil), // 49: proto.Test_BodyOnly.MultipleCase.SubFilter + (*Test_Mixed_PrimitiveCase)(nil), // 50: proto.Test_Mixed.PrimitiveCase + (*Test_Mixed_NestedCase)(nil), // 51: proto.Test_Mixed.NestedCase + (*Test_Mixed_RepeatedMessageCase)(nil), // 52: proto.Test_Mixed.RepeatedMessageCase + (*Test_Mixed_MapCase)(nil), // 53: proto.Test_Mixed.MapCase + (*Test_Mixed_MultipleCase)(nil), // 54: proto.Test_Mixed.MultipleCase + (*Test_Mixed_PrimitiveCase_Product)(nil), // 55: proto.Test_Mixed.PrimitiveCase.Product + (*Test_Mixed_NestedCase_Filter)(nil), // 56: proto.Test_Mixed.NestedCase.Filter + (*Test_Mixed_NestedCase_Filter_SubFilter)(nil), // 57: proto.Test_Mixed.NestedCase.Filter.SubFilter + (*Test_Mixed_RepeatedMessageCase_Product)(nil), // 58: proto.Test_Mixed.RepeatedMessageCase.Product + nil, // 59: proto.Test_Mixed.MapCase.FirstFilterEntry + nil, // 60: proto.Test_Mixed.MapCase.SecondFilterEntry + (*Test_Mixed_MapCase_SubFilter)(nil), // 61: proto.Test_Mixed.MapCase.SubFilter + nil, // 62: proto.Test_Mixed.MultipleCase.SecondFilterEntry + (*Test_Mixed_MultipleCase_SubFilter)(nil), // 63: proto.Test_Mixed.MultipleCase.SubFilter + (*Benchmark_Case5)(nil), // 64: proto.Benchmark.Case5 + (*Benchmark_Case10)(nil), // 65: proto.Benchmark.Case10 + (*Benchmark_Case30)(nil), // 66: proto.Benchmark.Case30 + (*Test_Client_Call_Request)(nil), // 67: proto.Test_Client_Call.Request + (*Test_Client_Call_Response)(nil), // 68: proto.Test_Client_Call.Response + (*Test_Client_Call_DefaultError)(nil), // 69: proto.Test_Client_Call.DefaultError + (*Test_Client_Call_SpecialError)(nil), // 70: proto.Test_Client_Call.SpecialError +} +var file_test_messages_proto_depIdxs = []int32{ + 15, // 0: proto.Test_PathOnly.NestedCase.user:type_name -> proto.Test_PathOnly.NestedCase.User + 16, // 1: proto.Test_PathOnly.NestedCase.order:type_name -> proto.Test_PathOnly.NestedCase.Order + 18, // 2: proto.Test_PathOnly.MultipleCase.order:type_name -> proto.Test_PathOnly.MultipleCase.Order + 19, // 3: proto.Test_PathOnly.NonPrimitiveMessageCase.userId:type_name -> proto.Test_PathOnly.NonPrimitiveMessageCase.User + 20, // 4: proto.Test_PathOnly.NonPrimitiveMapCase.userId:type_name -> proto.Test_PathOnly.NonPrimitiveMapCase.UserIdEntry + 17, // 5: proto.Test_PathOnly.NestedCase.Order.product:type_name -> proto.Test_PathOnly.NestedCase.Order.Product + 27, // 6: proto.Test_QueryOnly.NestedMessageCase.filter:type_name -> proto.Test_QueryOnly.NestedMessageCase.Filter + 29, // 7: proto.Test_QueryOnly.NestedMapCase.firstFilter:type_name -> proto.Test_QueryOnly.NestedMapCase.FirstFilterEntry + 30, // 8: proto.Test_QueryOnly.NestedMapCase.secondFilter:type_name -> proto.Test_QueryOnly.NestedMapCase.SecondFilterEntry + 33, // 9: proto.Test_QueryOnly.MultipleCase.firstFilter:type_name -> proto.Test_QueryOnly.MultipleCase.Filter + 32, // 10: proto.Test_QueryOnly.MultipleCase.secondFilter:type_name -> proto.Test_QueryOnly.MultipleCase.SecondFilterEntry + 35, // 11: proto.Test_QueryOnly.RepeatedMessageCase.filters:type_name -> proto.Test_QueryOnly.RepeatedMessageCase.Filter + 28, // 12: proto.Test_QueryOnly.NestedMessageCase.Filter.subFilter:type_name -> proto.Test_QueryOnly.NestedMessageCase.Filter.SubFilter + 31, // 13: proto.Test_QueryOnly.NestedMapCase.SecondFilterEntry.value:type_name -> proto.Test_QueryOnly.NestedMapCase.SubFilter + 34, // 14: proto.Test_QueryOnly.MultipleCase.SecondFilterEntry.value:type_name -> proto.Test_QueryOnly.MultipleCase.SubFilter + 34, // 15: proto.Test_QueryOnly.MultipleCase.Filter.subFilter:type_name -> proto.Test_QueryOnly.MultipleCase.SubFilter + 41, // 16: proto.Test_BodyOnly.PrimitiveCase.product:type_name -> proto.Test_BodyOnly.PrimitiveCase.Product + 42, // 17: proto.Test_BodyOnly.NestedCase.first_filter:type_name -> proto.Test_BodyOnly.NestedCase.Filter + 42, // 18: proto.Test_BodyOnly.NestedCase.second_filter:type_name -> proto.Test_BodyOnly.NestedCase.Filter + 44, // 19: proto.Test_BodyOnly.RepeatedMessageCase.products:type_name -> proto.Test_BodyOnly.RepeatedMessageCase.Product + 45, // 20: proto.Test_BodyOnly.MapCase.firstFilter:type_name -> proto.Test_BodyOnly.MapCase.FirstFilterEntry + 46, // 21: proto.Test_BodyOnly.MapCase.secondFilter:type_name -> proto.Test_BodyOnly.MapCase.SecondFilterEntry + 49, // 22: proto.Test_BodyOnly.MultipleCase.firstFilter:type_name -> proto.Test_BodyOnly.MultipleCase.SubFilter + 48, // 23: proto.Test_BodyOnly.MultipleCase.secondFilter:type_name -> proto.Test_BodyOnly.MultipleCase.SecondFilterEntry + 49, // 24: proto.Test_BodyOnly.MultipleCase.thirdFilter:type_name -> proto.Test_BodyOnly.MultipleCase.SubFilter + 43, // 25: proto.Test_BodyOnly.NestedCase.Filter.subFilter:type_name -> proto.Test_BodyOnly.NestedCase.Filter.SubFilter + 47, // 26: proto.Test_BodyOnly.MapCase.SecondFilterEntry.value:type_name -> proto.Test_BodyOnly.MapCase.SubFilter + 49, // 27: proto.Test_BodyOnly.MultipleCase.SecondFilterEntry.value:type_name -> proto.Test_BodyOnly.MultipleCase.SubFilter + 55, // 28: proto.Test_Mixed.PrimitiveCase.product:type_name -> proto.Test_Mixed.PrimitiveCase.Product + 56, // 29: proto.Test_Mixed.NestedCase.first_filter:type_name -> proto.Test_Mixed.NestedCase.Filter + 56, // 30: proto.Test_Mixed.NestedCase.second_filter:type_name -> proto.Test_Mixed.NestedCase.Filter + 58, // 31: proto.Test_Mixed.RepeatedMessageCase.products:type_name -> proto.Test_Mixed.RepeatedMessageCase.Product + 59, // 32: proto.Test_Mixed.MapCase.firstFilter:type_name -> proto.Test_Mixed.MapCase.FirstFilterEntry + 60, // 33: proto.Test_Mixed.MapCase.secondFilter:type_name -> proto.Test_Mixed.MapCase.SecondFilterEntry + 63, // 34: proto.Test_Mixed.MultipleCase.firstFilter:type_name -> proto.Test_Mixed.MultipleCase.SubFilter + 62, // 35: proto.Test_Mixed.MultipleCase.secondFilter:type_name -> proto.Test_Mixed.MultipleCase.SecondFilterEntry + 63, // 36: proto.Test_Mixed.MultipleCase.thirdFilter:type_name -> proto.Test_Mixed.MultipleCase.SubFilter + 57, // 37: proto.Test_Mixed.NestedCase.Filter.subFilter:type_name -> proto.Test_Mixed.NestedCase.Filter.SubFilter + 61, // 38: proto.Test_Mixed.MapCase.SecondFilterEntry.value:type_name -> proto.Test_Mixed.MapCase.SubFilter + 63, // 39: proto.Test_Mixed.MultipleCase.SecondFilterEntry.value:type_name -> proto.Test_Mixed.MultipleCase.SubFilter + 40, // [40:40] is the sub-list for method output_type + 40, // [40:40] is the sub-list for method input_type + 40, // [40:40] is the sub-list for extension type_name + 40, // [40:40] is the sub-list for extension extendee + 0, // [0:40] is the sub-list for field type_name +} + +func init() { file_test_messages_proto_init() } +func file_test_messages_proto_init() { + if File_test_messages_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_test_messages_proto_rawDesc), len(file_test_messages_proto_rawDesc)), + NumEnums: 0, + NumMessages: 71, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_test_messages_proto_goTypes, + DependencyIndexes: file_test_messages_proto_depIdxs, + MessageInfos: file_test_messages_proto_msgTypes, + }.Build() + File_test_messages_proto = out.File + file_test_messages_proto_goTypes = nil + file_test_messages_proto_depIdxs = nil +} diff --git a/builder/proto/test_messages.proto b/builder/proto/test_messages.proto new file mode 100644 index 0000000..b81c45a --- /dev/null +++ b/builder/proto/test_messages.proto @@ -0,0 +1,330 @@ +syntax = "proto3"; + +package proto; + +option go_package = "go.unistack.org/micro-client-http/v4/proto;proto"; + +message TestRequestBuilder {} + +message Test_PathOnly { + message PrimitiveCase { + string userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + } + + message NestedCase { + User user = 1; + Order order = 2; + + message User { + string id = 1; + } + message Order { + int64 id = 1; + Product product = 2; + + message Product { + int64 id = 1; + } + } + } + + message MultipleCase { + string userId = 1 [json_name = "user_id"]; + Order order = 2; + + message Order { + string id = 1; + } + } + + message RepeatedCase { + repeated string userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + } + + message NonPrimitiveMessageCase { + User userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + + message User { + string id = 1; + } + } + + message NonPrimitiveMapCase { + map userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + } + + message PatternCase { + string pattern = 1; + } + + message CompositePatternCase { + string pattern = 1; + string orderId = 2 [json_name = "order_id"]; + string productId = 3 [json_name = "product_id"]; + } +} + +message Test_QueryOnly { + message PrimitiveCase { + string userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + bool flag = 3; + } + + message RepeatedCase { + repeated string strings = 1; + repeated int64 integers = 2; + } + + message NestedMessageCase { + string userId = 1 [json_name = "user_id"]; + Filter filter = 2; + + message Filter { + int64 age = 1; + string name = 2; + SubFilter subFilter = 3 [json_name = "sub_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } + } + + message NestedMapCase { + string userId = 1 [json_name = "user_id"]; + map firstFilter = 2 [json_name = "first_filter"]; + map secondFilter = 4 [json_name = "second_filter"]; + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } + + message MultipleCase { + string userId = 1 [json_name = "user_id"]; + repeated string strings = 2; + Filter firstFilter = 3 [json_name = "first_filter"]; + map secondFilter = 4 [json_name = "second_filter"]; + + message Filter { + int64 age = 1; + SubFilter subFilter = 2 [json_name = "sub_filter"]; + } + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + } + } + + message RepeatedMessageCase { + repeated Filter filters = 1; + message Filter { + int64 age = 1; + } + } +} + +message Test_BodyOnly { + message PrimitiveCase { + string userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + bool flag = 3; + repeated string strings = 4; + Product product = 6; + + message Product { + string id = 1; + string name = 2; + } + } + + message NestedCase { + string userId = 1 [json_name = "user_id"]; + Filter first_filter = 2 [json_name = "first_filter"]; + Filter second_filter = 3 [json_name = "second_filter"]; + + message Filter { + int64 age = 1; + string name = 2; + SubFilter subFilter = 3 [json_name = "sub_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } + } + + message RepeatedMessageCase { + string userId = 1 [json_name = "user_id"]; + repeated Product products = 2 [json_name = "products"]; + + message Product { + string id = 1; + string name = 2; + } + } + + message MapCase { + map firstFilter = 1 [json_name = "first_filter"]; + map secondFilter = 2 [json_name = "second_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } + + message MultipleCase { + string userId = 1 [json_name = "user_id"]; + repeated SubFilter firstFilter = 2 [json_name = "first_filter"]; + map secondFilter = 3 [json_name = "second_filter"]; + SubFilter thirdFilter = 4 [json_name = "third_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } +} + +message Test_Mixed { + message PrimitiveCase { + string userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + Product product = 3; + + message Product { + string id = 1; + string name = 2; + } + } + + message NestedCase { + string userId = 1 [json_name = "user_id"]; + Filter first_filter = 2 [json_name = "first_filter"]; + Filter second_filter = 3 [json_name = "second_filter"]; + + message Filter { + int64 age = 1; + string name = 2; + SubFilter subFilter = 3 [json_name = "sub_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } + } + + message RepeatedMessageCase { + string userId = 1 [json_name = "user_id"]; + repeated Product products = 2 [json_name = "products"]; + + message Product { + string id = 1; + string name = 2; + } + } + + message MapCase { + map firstFilter = 1 [json_name = "first_filter"]; + map secondFilter = 2 [json_name = "second_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } + + message MultipleCase { + string userId = 1 [json_name = "user_id"]; + repeated SubFilter firstFilter = 2 [json_name = "first_filter"]; + map secondFilter = 3 [json_name = "second_filter"]; + SubFilter thirdFilter = 4 [json_name = "third_filter"]; + + message SubFilter { + int64 subAge = 1 [json_name = "sub_age"]; + string subName = 2 [json_name = "sub_name"]; + } + } +} + +message Benchmark { + message Case5 { + string field1 = 1; + string field2 = 2; + string field3 = 3; + string field4 = 4; + string field5 = 5; + } + message Case10 { + string field1 = 1; + string field2 = 2; + string field3 = 3; + string field4 = 4; + string field5 = 5; + string field6 = 6; + string field7 = 7; + string field8 = 8; + string field9 = 9; + string field10 = 10; + } + message Case30 { + string field1 = 1; + string field2 = 2; + string field3 = 3; + string field4 = 4; + string field5 = 5; + string field6 = 6; + string field7 = 7; + string field8 = 8; + string field9 = 9; + string field10 = 10; + string field11 = 11; + string field12 = 12; + string field13 = 13; + string field14 = 14; + string field15 = 15; + string field16 = 16; + string field17 = 17; + string field18 = 18; + string field19 = 19; + string field20 = 20; + string field21 = 21; + string field22 = 22; + string field23 = 23; + string field24 = 24; + string field25 = 25; + string field26 = 26; + string field27 = 27; + string field28 = 28; + string field29 = 29; + string field30 = 30; + } +} + +message Test_Client_Call { + message Request { + string userId = 1 [json_name = "user_id"]; + int64 orderId = 2 [json_name = "order_id"]; + } + message Response { + string id = 1; + string name = 2; + } + message DefaultError { + string code = 1; + string msg = 2; + } + message SpecialError { + string code = 1; + string msg = 2; + string warning = 3; + } +} diff --git a/builder/query.go b/builder/query.go new file mode 100644 index 0000000..413379c --- /dev/null +++ b/builder/query.go @@ -0,0 +1,194 @@ +package builder + +import ( + "fmt" + "net/url" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func buildQuery(msg proto.Message, usedFieldsPath *usedFields, usedFieldBody string) (url.Values, error) { + var ( + query = url.Values{} + msgReflect = msg.ProtoReflect() + ) + + fields := msgReflect.Descriptor().Fields() + for i := 0; i < fields.Len(); i++ { + var ( + fd = fields.Get(i) + fieldName = fd.JSONName() + ) + + if usedFieldsPath.hasFullKey(fieldName) { + continue + } + if fieldName == usedFieldBody { + continue + } + + val := msgReflect.Get(fd) + if isZeroValue(val, fd) { + continue + } + + // Note: order of the cases is important! + switch { + case fd.IsList(): + if fd.Kind() == protoreflect.MessageKind { + return nil, fmt.Errorf("repeated message field %s cannot be mapped to URL query parameters", fieldName) + } + list := val.List() + for j := 0; j < list.Len(); j++ { + strVal, err := stringifyValue(list.Get(j), fd) + if err != nil { + return nil, fmt.Errorf("stringify value for query %s: %w", fieldName, err) + } + query.Add(fieldName, strVal) + } + + case fd.IsMap(): + var ( + m = val.Map() + rangeErr error + ) + m.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { + key := fmt.Sprintf("%s.%s", fieldName, k.String()) + + if fd.MapValue().Kind() == protoreflect.MessageKind { + flattened, err := flattenMsgForQuery(key, v.Message()) + if err != nil { + rangeErr = fmt.Errorf("flatten msg for query %s: %w", fieldName, err) + return false + } + for _, item := range flattened { + if item.val == "" { + continue + } + if usedFieldsPath.hasFullKey(item.key) { + continue + } + query.Add(item.key, item.val) + } + } else { + strVal, err := stringifyValue(v, fd.MapValue()) + if err != nil { + rangeErr = fmt.Errorf("stringify value for map %s: %w", fieldName, err) + return false + } + query.Add(key, strVal) + } + return true + }) + if rangeErr != nil { + return nil, fmt.Errorf("map range error: %w", rangeErr) + } + + case fd.Kind() == protoreflect.MessageKind: + flattened, err := flattenMsgForQuery(fieldName, val.Message()) + if err != nil { + return nil, fmt.Errorf("flatten msg for query %s: %w", fieldName, err) + } + for _, item := range flattened { + if item.val == "" { + continue + } + if usedFieldsPath.hasFullKey(item.key) { + continue + } + query.Add(item.key, item.val) + } + + default: + strVal, err := stringifyValue(val, fd) + if err != nil { + return nil, fmt.Errorf("stringify value for primitive %s: %w", fieldName, err) + } + query.Add(fieldName, strVal) + } + } + + return query, nil +} + +type flattenItem struct { + key string + val string +} + +// flattenMsgForQuery flattens a non-repeated message value under a given prefix. +func flattenMsgForQuery(prefix string, msg protoreflect.Message) ([]flattenItem, error) { + var out []flattenItem + + fields := msg.Descriptor().Fields() + for i := 0; i < fields.Len(); i++ { + var ( + fd = fields.Get(i) + val = msg.Get(fd) + ) + + if isZeroValue(val, fd) { + continue + } + + key := fmt.Sprintf("%s.%s", prefix, fd.JSONName()) + + switch { + case fd.IsList(): + if fd.Kind() == protoreflect.MessageKind { + return nil, fmt.Errorf("repeated message field %s cannot be flattened for query", key) + } + list := val.List() + for j := 0; j < list.Len(); j++ { + strVal, err := stringifyValue(list.Get(j), fd) + if err != nil { + return nil, fmt.Errorf("stringify query %s: %w", key, err) + } + out = append(out, flattenItem{key: key, val: strVal}) + } + + case fd.Kind() == protoreflect.MessageKind: + nested, err := flattenMsgForQuery(key, val.Message()) + if err != nil { + return nil, fmt.Errorf("flatten msg for query %s: %w", key, err) + } + out = append(out, nested...) + + case fd.IsMap(): + var mapErr error + val.Map().Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { + keyStr := k.String() + + if fd.MapValue().Kind() == protoreflect.MessageKind { + child, err := flattenMsgForQuery(keyStr, v.Message()) + if err != nil { + mapErr = fmt.Errorf("flatten map value %s: %w", key, err) + return false + } + out = append(out, child...) + } else { + strVal, err := stringifyValue(v, fd.MapValue()) + if err != nil { + mapErr = fmt.Errorf("stringify query %s: %w", keyStr, err) + return false + } + out = append(out, flattenItem{key: keyStr, val: strVal}) + } + return true + }) + if mapErr != nil { + return nil, mapErr + } + + default: + strVal, err := stringifyValue(val, fd) + if err != nil { + return nil, fmt.Errorf("stringify query %s: %w", key, err) + } + out = append(out, flattenItem{key: key, val: strVal}) + } + } + + return out, nil +} diff --git a/builder/request_builder.go b/builder/request_builder.go new file mode 100644 index 0000000..a7401fd --- /dev/null +++ b/builder/request_builder.go @@ -0,0 +1,180 @@ +// Package builder implements google.api.http-style request building (gRPC JSON transcoding) +// for HTTP requests, closely following the google.api.http spec. +// See full spec for details: https://github.com/googleapis/googleapis/blob/master/google/api/http.proto +package builder + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +type RequestBuilder struct { + path string // e.g. "/v1/{name=projects/*/topics/*}:publish" or "/users/{user.id}" + method string // GET, POST, PATCH, etc. (not used in mapping rules, but convenient for callers) + bodyOption bodyOption // "", "*", or top-level field name + msg proto.Message // request struct +} + +func NewRequestBuilder( + path string, + method string, + bodyOpt string, + msg proto.Message, +) ( + *RequestBuilder, + error, +) { + rb := &RequestBuilder{ + path: path, + method: method, + bodyOption: bodyOption(bodyOpt), + msg: msg, + } + + if err := rb.validate(); err != nil { + return nil, fmt.Errorf("validate: %w", err) + } + + return rb, nil +} + +// Build applies mapping rules and returns: +// +// resolvedPath — path with placeholders substituted and query appended +// newMsg — same concrete type as input, filtered to contain only the body fields +// err — if mapping/validation failed +func (b *RequestBuilder) Build() (resolvedPath string, newMsg proto.Message, err error) { + tmpl, isCached := getCachedPathTemplate(b.path) + if !isCached { + tmpl, err = parsePathTemplate(b.path) + if err != nil { + return "", nil, fmt.Errorf("parse path template: %w", err) + } + setPathTemplateCache(b.path, tmpl) + } + + var usedFieldsPath *usedFields + resolvedPath, usedFieldsPath, err = resolvePathPlaceholders(tmpl, b.msg) + if err != nil { + return "", nil, fmt.Errorf("resolve path placeholders: %w", err) + } + + // if all set fields are already used in path, no need to process query/body + if allFieldsUsed(b.msg, usedFieldsPath) { + return resolvedPath, initZeroMsg(b.msg), nil + } + + switch { + case b.bodyOption.isWithoutBody(): + var query url.Values + query, err = buildQuery(b.msg, usedFieldsPath, "") + if err != nil { + return "", nil, fmt.Errorf("build query: %w", err) + } + + return resolvedPath + encodeQuery(query), initZeroMsg(b.msg), nil + + case b.bodyOption.isSingleField(): + fieldBody := b.bodyOption.String() + + newMsg, err = buildSingleFieldBody(b.msg, fieldBody) + if err != nil { + return "", nil, fmt.Errorf("build single field body: %w", err) + } + + var query url.Values + query, err = buildQuery(b.msg, usedFieldsPath, fieldBody) + if err != nil { + return "", nil, fmt.Errorf("build query: %w", err) + } + + return resolvedPath + encodeQuery(query), newMsg, nil + + case b.bodyOption.isFullBody(): + newMsg, err = buildFullBody(b.msg, usedFieldsPath) + if err != nil { + return "", nil, fmt.Errorf("build full body: %w", err) + } + + return resolvedPath, newMsg, nil + + default: + return "", nil, fmt.Errorf("unsupported body option %s", b.bodyOption.String()) + } +} + +func (b *RequestBuilder) validate() error { + if b.path == "" { + return errors.New("path is empty") + } + if err := validateHTTPMethod(b.method); err != nil { + return fmt.Errorf("validate http method: %w", err) + } + if err := validateHTTPMethodAndBody(b.method, b.bodyOption); err != nil { + return fmt.Errorf("validate http method and body: %w", err) + } + if b.msg == nil { + return errors.New("msg is nil") + } + return nil +} + +func validateHTTPMethod(method string) error { + switch strings.ToUpper(method) { + case http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace: + return nil + default: + return errors.New("invalid http method") + } +} + +func validateHTTPMethodAndBody(method string, bodyOpt bodyOption) error { + switch method { + case http.MethodGet, http.MethodDelete, http.MethodHead, http.MethodOptions: + if !bodyOpt.isWithoutBody() { + return fmt.Errorf("%s method must not have a body", method) + } + } + return nil +} + +func allFieldsUsed(msg proto.Message, used *usedFields) bool { + if used.len() == 0 { + return false + } + count := 0 + msg.ProtoReflect().Range(func(protoreflect.FieldDescriptor, protoreflect.Value) bool { + count++ + return true + }) + return used.len() == count +} + +func encodeQuery(query url.Values) string { + if len(query) == 0 { + return "" + } + enc := query.Encode() + if enc == "" { + return "" + } + return "?" + enc +} + +func initZeroMsg(msg proto.Message) proto.Message { + return msg.ProtoReflect().New().Interface() +} diff --git a/builder/request_builder_bench_test.go b/builder/request_builder_bench_test.go new file mode 100644 index 0000000..c2617a4 --- /dev/null +++ b/builder/request_builder_bench_test.go @@ -0,0 +1,149 @@ +package builder + +import ( + "fmt" + "math/rand" + "strings" + "testing" + "time" + + "google.golang.org/protobuf/proto" + + pb "go.unistack.org/micro-client-http/v4/builder/proto" +) + +// sink prevents the compiler from optimizing away parsePathTemplate results. +var sink *pathTemplate + +func BenchmarkParsePathTemplate(b *testing.B) { + r := rand.New(rand.NewSource(1)) + + benchInput := func(size int) string { + sb := strings.Builder{} + sb.Grow(size * 10) + + for i := 0; i < size; i++ { + name := fmt.Sprintf("var%d", r.Intn(1000)) + + if r.Intn(5) == 0 { + sb.WriteString(fmt.Sprintf("{%s=**}", name)) + } else { + sb.WriteString(fmt.Sprintf("{%s}", name)) + } + } + return sb.String() + } + + sizes := []int{1_000, 10_000, 50_000, 100_000} + for _, size := range sizes { + input := benchInput(size) + b.Run(fmt.Sprintf("N=%d", size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + var err error + sink, err = parsePathTemplate(input) + if err != nil && testing.Verbose() { + b.Fatal(err) + } + } + }) + } +} + +func BenchmarkRequestBuilder(b *testing.B) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + makeMsg := func(fieldCount int) proto.Message { + switch fieldCount { + case 5: + return &pb.Benchmark_Case5{ + Field1: fmt.Sprintf("value%d", r.Intn(1000)), + Field2: fmt.Sprintf("value%d", r.Intn(1000)), + Field3: fmt.Sprintf("value%d", r.Intn(1000)), + Field4: fmt.Sprintf("value%d", r.Intn(1000)), + Field5: fmt.Sprintf("value%d", r.Intn(1000)), + } + case 10: + return &pb.Benchmark_Case10{ + Field1: fmt.Sprintf("value%d", r.Intn(1000)), + Field2: fmt.Sprintf("value%d", r.Intn(1000)), + Field3: fmt.Sprintf("value%d", r.Intn(1000)), + Field4: fmt.Sprintf("value%d", r.Intn(1000)), + Field5: fmt.Sprintf("value%d", r.Intn(1000)), + Field6: fmt.Sprintf("value%d", r.Intn(1000)), + Field7: fmt.Sprintf("value%d", r.Intn(1000)), + Field8: fmt.Sprintf("value%d", r.Intn(1000)), + Field9: fmt.Sprintf("value%d", r.Intn(1000)), + Field10: fmt.Sprintf("value%d", r.Intn(1000)), + } + case 30: + return &pb.Benchmark_Case30{ + Field1: fmt.Sprintf("value%d", r.Intn(1000)), + Field2: fmt.Sprintf("value%d", r.Intn(1000)), + Field3: fmt.Sprintf("value%d", r.Intn(1000)), + Field4: fmt.Sprintf("value%d", r.Intn(1000)), + Field5: fmt.Sprintf("value%d", r.Intn(1000)), + Field6: fmt.Sprintf("value%d", r.Intn(1000)), + Field7: fmt.Sprintf("value%d", r.Intn(1000)), + Field8: fmt.Sprintf("value%d", r.Intn(1000)), + Field9: fmt.Sprintf("value%d", r.Intn(1000)), + Field10: fmt.Sprintf("value%d", r.Intn(1000)), + Field11: fmt.Sprintf("value%d", r.Intn(1000)), + Field12: fmt.Sprintf("value%d", r.Intn(1000)), + Field13: fmt.Sprintf("value%d", r.Intn(1000)), + Field14: fmt.Sprintf("value%d", r.Intn(1000)), + Field15: fmt.Sprintf("value%d", r.Intn(1000)), + Field16: fmt.Sprintf("value%d", r.Intn(1000)), + Field17: fmt.Sprintf("value%d", r.Intn(1000)), + Field18: fmt.Sprintf("value%d", r.Intn(1000)), + Field19: fmt.Sprintf("value%d", r.Intn(1000)), + Field20: fmt.Sprintf("value%d", r.Intn(1000)), + Field21: fmt.Sprintf("value%d", r.Intn(1000)), + Field22: fmt.Sprintf("value%d", r.Intn(1000)), + Field23: fmt.Sprintf("value%d", r.Intn(1000)), + Field24: fmt.Sprintf("value%d", r.Intn(1000)), + Field25: fmt.Sprintf("value%d", r.Intn(1000)), + Field26: fmt.Sprintf("value%d", r.Intn(1000)), + Field27: fmt.Sprintf("value%d", r.Intn(1000)), + Field28: fmt.Sprintf("value%d", r.Intn(1000)), + Field29: fmt.Sprintf("value%d", r.Intn(1000)), + Field30: fmt.Sprintf("value%d", r.Intn(1000)), + } + default: + b.Fatal("undefined field count") + return nil + } + } + + tests := []struct { + name string + pathTmpl string + bodyOption string + }{ + {"all fields in path", "/resource/{field1}/{field2}", ""}, + {"single field body", "/resource/{field1}", "field4"}, + {"full body", "/resource", "*"}, + } + + for _, fields := range []int{5, 10, 30} { + for _, tt := range tests { + b.Run(fmt.Sprintf("%s_%d_fields", tt.name, fields), func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + msg := makeMsg(fields) + rb, err := NewRequestBuilder(tt.pathTmpl, "POST", tt.bodyOption, msg) + if err != nil { + b.Fatalf("new request builder: %v", err) + } + + _, _, err = rb.Build() + if err != nil { + b.Fatalf("build: %v", err) + } + } + }) + } + } +} diff --git a/builder/request_builder_test.go b/builder/request_builder_test.go new file mode 100644 index 0000000..3343eb1 --- /dev/null +++ b/builder/request_builder_test.go @@ -0,0 +1,1302 @@ +package builder_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "go.unistack.org/micro-client-http/v4/builder" + pb "go.unistack.org/micro-client-http/v4/builder/proto" +) + +func TestNewRequestBuilder(t *testing.T) { + tests := []struct { + name string + path string + method string + bodyOpt string + msg proto.Message + wantError bool + }{ + { + name: "empty path", + path: "", + method: "GET", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: true, + }, + { + name: "invalid method", + path: "/v1/users", + method: "INVALID", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: true, + }, + { + name: "nil msg", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: nil, + wantError: true, + }, + { + name: "GET without body", + path: "/v1/users", + method: "GET", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "GET with body", + path: "/v1/users", + method: "GET", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: true, + }, + { + name: "DELETE without body", + path: "/v1/users/42", + method: "DELETE", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "DELETE with body", + path: "/v1/users/42", + method: "DELETE", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: true, + }, + { + name: "POST with body", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "POST without body", + path: "/v1/users", + method: "POST", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "PUT with body", + path: "/v1/users/42", + method: "PUT", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "PUT without body", + path: "/v1/users/42", + method: "PUT", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "PATCH with body", + path: "/v1/users/42", + method: "PATCH", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "PATCH without body", + path: "/v1/users/42", + method: "PATCH", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "HEAD without body", + path: "/v1/users/42", + method: "HEAD", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "HEAD with body", + path: "/v1/users/42", + method: "HEAD", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: true, + }, + { + name: "OPTIONS without body", + path: "/v1/users/42", + method: "OPTIONS", + bodyOpt: "", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + { + name: "OPTIONS with body", + path: "/v1/users/42", + method: "OPTIONS", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: true, + }, + { + name: "lowercase method still valid", + path: "/v1/users", + method: "post", + bodyOpt: "*", + msg: &pb.TestRequestBuilder{}, + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := builder.NewRequestBuilder(tt.path, tt.method, tt.bodyOpt, tt.msg) + + if tt.wantError { + require.Error(t, err) + require.Nil(t, rb) + } else { + require.NoError(t, err) + require.NotNil(t, rb) + } + }) + } +} + +func TestRequestBuilder_PathOnly(t *testing.T) { + tests := []struct { + name string + path string + method string + msg func() proto.Message + expectedPath string + expectedMsg func() proto.Message + wantError bool + }{ + { + name: "primitive case", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PrimitiveCase + return &Msg{ + UserId: "42", + OrderId: 123, + } + }, + expectedPath: "/v1/users/42/orders/123", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_PrimitiveCase + return &Msg{} + }, + wantError: false, + }, + { + name: "nested case", + path: "/v1/users/{user.id}/orders/{order.id}/products/{order.product.id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_NestedCase + type User = pb.Test_PathOnly_NestedCase_User + type Order = pb.Test_PathOnly_NestedCase_Order + type Product = pb.Test_PathOnly_NestedCase_Order_Product + return &Msg{ + User: &User{Id: "42"}, + Order: &Order{ + Id: 123, + Product: &Product{Id: 456}, + }, + } + }, + expectedPath: "/v1/users/42/orders/123/products/456", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_NestedCase + return &Msg{} + }, + wantError: false, + }, + { + name: "multiply case", + path: "/v1/users/{user_id}/orders/{order.id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_MultipleCase + type Order = pb.Test_PathOnly_MultipleCase_Order + return &Msg{ + UserId: "42", + Order: &Order{ + Id: "123", + }, + } + }, + expectedPath: "/v1/users/42/orders/123", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_MultipleCase + return &Msg{} + }, + wantError: false, + }, + { + name: "not found case", + path: "/v1/users/{userId}/orders/{order_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PrimitiveCase + return &Msg{ + UserId: "42", + OrderId: 123, + } + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "zero-value case", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PrimitiveCase + return &Msg{UserId: "42"} + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "repeated case", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_RepeatedCase + return &Msg{ + UserId: []string{"42"}, + OrderId: 123, + } + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "non-primitive message case", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_NonPrimitiveMessageCase + type User = pb.Test_PathOnly_NonPrimitiveMessageCase_User + return &Msg{ + UserId: &User{Id: "42"}, + OrderId: 123, + } + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "non-primitive map case", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_NonPrimitiveMapCase + return &Msg{ + UserId: map[string]string{"": ""}, + OrderId: 123, + } + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "custom verb case", + path: "/v1/users/{user_id}:get", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PrimitiveCase + return &Msg{UserId: "42"} + }, + expectedPath: "/v1/users/42:get", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_PrimitiveCase + return &Msg{} + }, + wantError: false, + }, + // pattern cases + { + name: "pattern case -> *", + path: "/v1/users/{pattern=*}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{Pattern: "42"} + }, + expectedPath: "/v1/users/42", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{} + }, + wantError: false, + }, + { + name: "pattern case -> * (invalid value)", + path: "/v1/users/{pattern=*}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{Pattern: "a/b/c"} + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "pattern case -> * (empty value)", + path: "/v1/users/{pattern=*}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{} + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "pattern case -> **", + path: "/v1/users/{pattern=**}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{Pattern: "a/b/c"} + }, + expectedPath: "/v1/users/a/b/c", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{} + }, + wantError: false, + }, + { + name: "pattern case -> ** (empty value)", + path: "/v1/users/{pattern=**}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{} + }, + expectedPath: "/v1/users/", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{} + }, + wantError: false, + }, + { + name: "pattern case -> composite pattern", + path: "/v1/users/{pattern=*/orders/*}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{Pattern: "42/orders/123"} + }, + expectedPath: "/v1/users/42/orders/123", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{} + }, + wantError: false, + }, + { + name: "pattern case -> composite pattern (with extra segment)", + path: "/v1/users/{pattern=*/orders/*}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_PatternCase + return &Msg{Pattern: "42/orders/123/456"} + }, + expectedPath: "", + expectedMsg: func() proto.Message { + return nil + }, + wantError: true, + }, + { + name: "pattern case -> ** (composite segments)", + path: "/v1/users/{pattern=**}/orders/{order_id}/products/{product_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_CompositePatternCase + return &Msg{ + Pattern: "a/b/c", + OrderId: "123", + ProductId: "456", + } + }, + expectedPath: "/v1/users/a/b/c/orders/123/products/456", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_CompositePatternCase + return &Msg{} + }, + wantError: false, + }, + { + name: "pattern case -> ** (with empty value and composite segments)", + path: "/v1/users/{pattern=**}/orders/{order_id}/products/{product_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_CompositePatternCase + return &Msg{ + Pattern: "", + OrderId: "123", + ProductId: "456", + } + }, + expectedPath: "/v1/users//orders/123/products/456", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_CompositePatternCase + return &Msg{} + }, + wantError: false, + }, + { + name: "pattern case -> composite pattern (multiple consecutive variables)", + path: "/v1/{pattern}/{order_id}/{product_id}", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_PathOnly_CompositePatternCase + return &Msg{ + Pattern: "123", + OrderId: "456", + ProductId: "789", + } + }, + expectedPath: "/v1/123/456/789", + expectedMsg: func() proto.Message { + type Msg = pb.Test_PathOnly_CompositePatternCase + return &Msg{} + }, + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := builder.NewRequestBuilder(tt.path, tt.method, "", tt.msg()) + require.NoError(t, err) + + path, newMsg, err := rb.Build() + if tt.wantError { + require.Error(t, err) + require.Empty(t, path) + require.Nil(t, newMsg) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedPath, path) + require.True(t, proto.Equal(tt.expectedMsg(), newMsg)) + } + }) + } +} + +func TestRequestBuilder_QueryOnly(t *testing.T) { + tests := []struct { + name string + path string + method string + msg func() proto.Message + expectedPath string + expectedMsg func() proto.Message + wantError bool + }{ + { + name: "primitive case", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_PrimitiveCase + return &Msg{ + UserId: "42", + OrderId: 123, + Flag: true, + } + }, + expectedPath: "/v1/users?flag=true&order_id=123&user_id=42", + expectedMsg: func() proto.Message { + type Msg = pb.Test_QueryOnly_PrimitiveCase + return &Msg{} + }, + wantError: false, + }, + { + name: "primitive case (with empty fields)", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_PrimitiveCase + return &Msg{UserId: "42"} + }, + expectedPath: "/v1/users?user_id=42", + expectedMsg: func() proto.Message { + type Msg = pb.Test_QueryOnly_PrimitiveCase + return &Msg{} + }, + wantError: false, + }, + { + name: "repeated case", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_RepeatedCase + return &Msg{ + Strings: []string{"foo", "bar"}, + Integers: []int64{1, 2, 3}, + } + }, + expectedPath: "/v1/users?integers=1&integers=2&integers=3&strings=foo&strings=bar", + expectedMsg: func() proto.Message { + type Msg = pb.Test_QueryOnly_RepeatedCase + return &Msg{} + }, + wantError: false, + }, + { + name: "nested message case", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_NestedMessageCase + type Filter = pb.Test_QueryOnly_NestedMessageCase_Filter + type SubFilter = pb.Test_QueryOnly_NestedMessageCase_Filter_SubFilter + return &Msg{ + UserId: "42", + Filter: &Filter{ + Age: 30, + Name: "Alice", + SubFilter: &SubFilter{ + SubAge: 20, + SubName: "John", + }, + }, + } + }, + expectedPath: "/v1/users?filter.age=30&filter.name=Alice&filter.sub_filter.sub_age=20&filter.sub_filter.sub_name=John&user_id=42", + expectedMsg: func() proto.Message { + type Msg = pb.Test_QueryOnly_NestedMessageCase + return &Msg{} + }, + wantError: false, + }, + { + name: "nested map case", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_NestedMapCase + type SubFilter = pb.Test_QueryOnly_NestedMapCase_SubFilter + return &Msg{ + UserId: "42", + FirstFilter: map[string]string{"age": "30", "name": "Alice"}, + SecondFilter: map[string]*SubFilter{ + "filter1": {SubAge: 20, SubName: "John"}, + "filter2": {SubAge: 40, SubName: "Travolta"}, + }, + } + }, + expectedPath: "/v1/users?first_filter.age=30&first_filter.name=Alice&second_filter.filter1.sub_age=20&second_filter.filter1.sub_name=John&second_filter.filter2.sub_age=40&second_filter.filter2.sub_name=Travolta&user_id=42", + expectedMsg: func() proto.Message { + type Msg = pb.Test_QueryOnly_NestedMapCase + return &Msg{} + }, + wantError: false, + }, + { + name: "multiple case", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_MultipleCase + type Filter = pb.Test_QueryOnly_MultipleCase_Filter + type SubFilter = pb.Test_QueryOnly_MultipleCase_SubFilter + return &Msg{ + UserId: "42", + Strings: []string{"foo", "bar"}, + FirstFilter: &Filter{ + Age: 30, + SubFilter: &SubFilter{ + SubAge: 20, + }, + }, + SecondFilter: map[string]*SubFilter{ + "filter1": {SubAge: 20}, + "filter2": {SubAge: 40}, + }, + } + }, + expectedPath: "/v1/users?first_filter.age=30&first_filter.sub_filter.sub_age=20&second_filter.filter1.sub_age=20&second_filter.filter2.sub_age=40&strings=foo&strings=bar&user_id=42", + expectedMsg: func() proto.Message { + type Msg = pb.Test_QueryOnly_MultipleCase + return &Msg{} + }, + wantError: false, + }, + { + name: "repeated message case", + path: "/v1/users", + method: "GET", + msg: func() proto.Message { + type Msg = pb.Test_QueryOnly_RepeatedMessageCase + type Filter = pb.Test_QueryOnly_RepeatedMessageCase_Filter + return &Msg{Filters: []*Filter{{Age: 20}, {Age: 30}, {Age: 40}}} + }, + expectedPath: "", + expectedMsg: nil, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := builder.NewRequestBuilder(tt.path, tt.method, "", tt.msg()) + require.NoError(t, err) + + path, newMsg, err := rb.Build() + if tt.wantError { + require.Error(t, err) + require.Empty(t, path) + require.Nil(t, newMsg) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedPath, path) + require.True(t, proto.Equal(tt.expectedMsg(), newMsg)) + } + }) + } +} + +func TestRequestBuilder_BodyOnly(t *testing.T) { + tests := []struct { + name string + path string + method string + bodyOpt string + msg func() proto.Message + expectedPath string + expectedMsg func() proto.Message + wantError bool + }{ + { + name: "primitive case: full body", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Flag: true, + Strings: []string{"foo", "bar"}, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Flag: true, + Strings: []string{"foo", "bar"}, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + wantError: false, + }, + { + name: "primitive case: specified primitive field", + path: "/v1/users", + method: "POST", + bodyOpt: "user_id", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Flag: true, + Strings: []string{"foo", "bar"}, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users?flag=true&order_id=123&product.id=product_id&product.name=product_name&strings=foo&strings=bar", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + return &Msg{ + UserId: "42", + } + }, + wantError: false, + }, + { + name: "primitive case: specified non-primitive field", + path: "/v1/users", + method: "POST", + bodyOpt: "product", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Flag: true, + Strings: []string{"foo", "bar"}, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users?flag=true&order_id=123&strings=foo&strings=bar&user_id=42", + expectedMsg: func() proto.Message { + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Product{ + Id: "product_id", + Name: "product_name", + } + }, + wantError: false, + }, + { + name: "primitive case: empty fields", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + return &Msg{ + UserId: "42", + Flag: true, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 0, + Flag: true, + Strings: []string{}, + Product: &Product{}, + } + }, + wantError: false, + }, + { + name: "primitive case: empty all fields", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + return &Msg{} + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + type Product = pb.Test_BodyOnly_PrimitiveCase_Product + return &Msg{ + UserId: "", + OrderId: 0, + Flag: false, + Strings: []string{}, + Product: &Product{}, + } + }, + wantError: false, + }, + { + name: "nested case: full body", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_NestedCase + type Filter = pb.Test_BodyOnly_NestedCase_Filter + type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter + return &Msg{ + UserId: "42", + FirstFilter: &Filter{ + Age: 30, + Name: "Alice", + SubFilter: &SubFilter{ + SubAge: 40, + SubName: "John", + }, + }, + SecondFilter: &Filter{ + Age: 50, + Name: "Alex", + SubFilter: &SubFilter{ + SubAge: 60, + SubName: "Mike", + }, + }, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_NestedCase + type Filter = pb.Test_BodyOnly_NestedCase_Filter + type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter + return &Msg{ + UserId: "42", + FirstFilter: &Filter{ + Age: 30, + Name: "Alice", + SubFilter: &SubFilter{ + SubAge: 40, + SubName: "John", + }, + }, + SecondFilter: &Filter{ + Age: 50, + Name: "Alex", + SubFilter: &SubFilter{ + SubAge: 60, + SubName: "Mike", + }, + }, + } + }, + wantError: false, + }, + { + name: "nested case: specified non-primitive field", + path: "/v1/users", + method: "POST", + bodyOpt: "second_filter", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_NestedCase + type Filter = pb.Test_BodyOnly_NestedCase_Filter + type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter + return &Msg{ + UserId: "42", + FirstFilter: &Filter{ + Age: 30, + Name: "Alice", + SubFilter: &SubFilter{ + SubAge: 40, + SubName: "John", + }, + }, + SecondFilter: &Filter{ + Age: 50, + Name: "Alex", + SubFilter: &SubFilter{ + SubAge: 60, + SubName: "Mike", + }, + }, + } + }, + expectedPath: "/v1/users?first_filter.age=30&first_filter.name=Alice&first_filter.sub_filter.sub_age=40&first_filter.sub_filter.sub_name=John&user_id=42", + expectedMsg: func() proto.Message { + type Filter = pb.Test_BodyOnly_NestedCase_Filter + type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter + return &Filter{ + Age: 50, + Name: "Alex", + SubFilter: &SubFilter{ + SubAge: 60, + SubName: "Mike", + }, + } + }, + wantError: false, + }, + { + name: "repeated message case", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_RepeatedMessageCase + type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product + return &Msg{ + UserId: "42", + Products: []*Product{ + {Id: "product_id_1", Name: "product_name_1"}, + {Id: "product_id_2", Name: "product_name_2"}, + }, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_RepeatedMessageCase + type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product + return &Msg{ + UserId: "42", + Products: []*Product{ + {Id: "product_id_1", Name: "product_name_1"}, + {Id: "product_id_2", Name: "product_name_2"}, + }, + } + }, + wantError: false, + }, + { + name: "repeated message case (empty)", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_RepeatedMessageCase + type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product + return &Msg{ + UserId: "42", + Products: []*Product{}, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_RepeatedMessageCase + type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product + return &Msg{ + UserId: "42", + Products: []*Product{}, + } + }, + wantError: false, + }, + { + name: "primitive and non-primitive map case", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MapCase + type SubFilter = pb.Test_BodyOnly_MapCase_SubFilter + return &Msg{ + FirstFilter: map[string]string{"age": "50", "name": "Alex"}, + SecondFilter: map[string]*SubFilter{ + "second_filter_1": {SubAge: 30, SubName: "Alice"}, + "second_filter_2": {SubAge: 40, SubName: "John"}, + }, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MapCase + type SubFilter = pb.Test_BodyOnly_MapCase_SubFilter + return &Msg{ + FirstFilter: map[string]string{"age": "50", "name": "Alex"}, + SecondFilter: map[string]*SubFilter{ + "second_filter_1": {SubAge: 30, SubName: "Alice"}, + "second_filter_2": {SubAge: 40, SubName: "John"}, + }, + } + }, + wantError: false, + }, + { + name: "primitive and non-primitive map case (empty)", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MapCase + return &Msg{} + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MapCase + type SubFilter = pb.Test_BodyOnly_MapCase_SubFilter + return &Msg{ + FirstFilter: map[string]string{}, + SecondFilter: map[string]*SubFilter{}, + } + }, + wantError: false, + }, + { + name: "multiple case", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MultipleCase + type SubFilter = pb.Test_BodyOnly_MultipleCase_SubFilter + return &Msg{ + UserId: "42", + FirstFilter: []*SubFilter{ + {SubAge: 30, SubName: "Alice"}, + {SubAge: 40, SubName: "John"}, + }, + SecondFilter: map[string]*SubFilter{ + "second_filter_1": {SubAge: 50, SubName: "Alex"}, + "second_filter_2": {SubAge: 60, SubName: "Max"}, + }, + ThirdFilter: &SubFilter{SubAge: 70, SubName: "Ricardo"}, + } + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MultipleCase + type SubFilter = pb.Test_BodyOnly_MultipleCase_SubFilter + return &Msg{ + UserId: "42", + FirstFilter: []*SubFilter{ + {SubAge: 30, SubName: "Alice"}, + {SubAge: 40, SubName: "John"}, + }, + SecondFilter: map[string]*SubFilter{ + "second_filter_1": {SubAge: 50, SubName: "Alex"}, + "second_filter_2": {SubAge: 60, SubName: "Max"}, + }, + ThirdFilter: &SubFilter{SubAge: 70, SubName: "Ricardo"}, + } + }, + wantError: false, + }, + { + name: "multiple case (empty)", + path: "/v1/users", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MultipleCase + return &Msg{} + }, + expectedPath: "/v1/users", + expectedMsg: func() proto.Message { + type Msg = pb.Test_BodyOnly_MultipleCase + type SubFilter = pb.Test_BodyOnly_MultipleCase_SubFilter + return &Msg{ + UserId: "", + FirstFilter: []*SubFilter{}, + SecondFilter: map[string]*SubFilter{}, + ThirdFilter: &SubFilter{}, + } + }, + wantError: false, + }, + { + name: "nonexistent body field", + path: "/v1/users", + method: "POST", + bodyOpt: "nonexistent_field", + msg: func() proto.Message { + type Msg = pb.Test_BodyOnly_PrimitiveCase + return &Msg{} + }, + expectedPath: "", + expectedMsg: nil, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := builder.NewRequestBuilder(tt.path, tt.method, tt.bodyOpt, tt.msg()) + require.NoError(t, err) + + path, newMsg, err := rb.Build() + if tt.wantError { + require.Error(t, err) + require.Empty(t, path) + require.Nil(t, newMsg) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedPath, path) + require.True(t, proto.Equal(tt.expectedMsg(), newMsg)) + } + }) + } +} + +func TestRequestBuilder_Mixed(t *testing.T) { + tests := []struct { + name string + path string + method string + bodyOpt string + msg func() proto.Message + expectedPath string + expectedMsg func() proto.Message + wantError bool + }{ + { + name: "path + query", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "POST", + bodyOpt: "", + msg: func() proto.Message { + type Msg = pb.Test_Mixed_PrimitiveCase + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users/42/orders/123?product.id=product_id&product.name=product_name", + expectedMsg: func() proto.Message { + type Msg = pb.Test_Mixed_PrimitiveCase + return &Msg{} + }, + wantError: false, + }, + { + name: "path + body", + path: "/v1/users/{user_id}/orders/{order_id}", + method: "POST", + bodyOpt: "*", + msg: func() proto.Message { + type Msg = pb.Test_Mixed_PrimitiveCase + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users/42/orders/123", + expectedMsg: func() proto.Message { + type Msg = pb.Test_Mixed_PrimitiveCase + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Msg{ + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + wantError: false, + }, + { + name: "path + body + query", + path: "/v1/users/{user_id}", + method: "POST", + bodyOpt: "product", + msg: func() proto.Message { + type Msg = pb.Test_Mixed_PrimitiveCase + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users/42?order_id=123", + expectedMsg: func() proto.Message { + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Product{ + Id: "product_id", + Name: "product_name", + } + }, + wantError: false, + }, + { + name: "query + body", + path: "/v1/users", + method: "POST", + bodyOpt: "product", + msg: func() proto.Message { + type Msg = pb.Test_Mixed_PrimitiveCase + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Msg{ + UserId: "42", + OrderId: 123, + Product: &Product{ + Id: "product_id", + Name: "product_name", + }, + } + }, + expectedPath: "/v1/users?order_id=123&user_id=42", + expectedMsg: func() proto.Message { + type Product = pb.Test_Mixed_PrimitiveCase_Product + return &Product{ + Id: "product_id", + Name: "product_name", + } + }, + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := builder.NewRequestBuilder(tt.path, tt.method, tt.bodyOpt, tt.msg()) + require.NoError(t, err) + + path, newMsg, err := rb.Build() + if tt.wantError { + require.Error(t, err) + require.Empty(t, path) + require.Nil(t, newMsg) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedPath, path) + require.True(t, proto.Equal(tt.expectedMsg(), newMsg)) + } + }) + } +} diff --git a/builder/used_fields.go b/builder/used_fields.go new file mode 100644 index 0000000..af6c099 --- /dev/null +++ b/builder/used_fields.go @@ -0,0 +1,44 @@ +package builder + +import "strings" + +// usedFields stores keys and their top-level parts, +// turning top-level lookups from O(N) into O(1). +type usedFields struct { + full map[string]struct{} + top map[string]struct{} +} + +func newUsedFields() *usedFields { + return &usedFields{ + full: make(map[string]struct{}), + top: make(map[string]struct{}), + } +} + +// add inserts a new key and updates the top-level index. +func (u *usedFields) add(key string) { + u.full[key] = struct{}{} + top := key + if i := strings.IndexByte(key, '.'); i != -1 { + top = key[:i] + } + u.top[top] = struct{}{} +} + +// hasTopLevelKey checks if a top-level key exists. +func (u *usedFields) hasTopLevelKey(top string) bool { + _, ok := u.top[top] + return ok +} + +// hasFullKey checks if an exact key exists. +func (u *usedFields) hasFullKey(key string) bool { + _, ok := u.full[key] + return ok +} + +// len returns the number of full keys stored in the set. +func (u *usedFields) len() int { + return len(u.full) +} diff --git a/builder/used_fields_test.go b/builder/used_fields_test.go new file mode 100644 index 0000000..b52d741 --- /dev/null +++ b/builder/used_fields_test.go @@ -0,0 +1,78 @@ +package builder + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewUsedFields(t *testing.T) { + u := newUsedFields() + require.NotNil(t, u) + require.NotNil(t, u.full) + require.NotNil(t, u.top) + require.Len(t, u.full, 0) + require.Len(t, u.top, 0) +} + +func TestUsedFields_Add(t *testing.T) { + u := newUsedFields() + + u.add("user.name") + u.add("profile") + + _, ok := u.full["user.name"] + require.True(t, ok) + + _, ok = u.full["profile"] + require.True(t, ok) + + _, ok = u.top["user"] + require.True(t, ok) + + _, ok = u.top["profile"] + require.True(t, ok) + + require.Len(t, u.full, 2) + require.Len(t, u.top, 2) +} + +func TestUsedFields_HasFullKey(t *testing.T) { + u := newUsedFields() + u.add("user.name") + + require.True(t, u.hasFullKey("user.name")) + require.False(t, u.hasFullKey("user.email")) +} + +func TestUsedFields_HasTopLevelKey(t *testing.T) { + u := newUsedFields() + u.add("user.name") + u.add("settings.theme") + + require.True(t, u.hasTopLevelKey("user")) + require.True(t, u.hasTopLevelKey("settings")) + require.False(t, u.hasTopLevelKey("profile")) +} + +func TestUsedFields_AddDuplicate(t *testing.T) { + u := newUsedFields() + u.add("user.name") + u.add("user.name") + + require.True(t, u.hasFullKey("user.name")) + require.True(t, u.hasTopLevelKey("user")) + require.Len(t, u.full, 1) + require.Len(t, u.top, 1) +} + +func TestUsedFields_Len(t *testing.T) { + u := newUsedFields() + + u.add("user.name") + u.add("profile") + u.add("user.name") + u.add("profile") + + require.Equal(t, u.len(), 2) +} diff --git a/client.go b/client.go new file mode 100644 index 0000000..dc6b2a5 --- /dev/null +++ b/client.go @@ -0,0 +1,122 @@ +package http + +import ( + "context" + "net/http" + "strconv" + "sync" + "time" + + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/errors" + "go.unistack.org/micro/v4/options" + "go.unistack.org/micro/v4/semconv" + "go.unistack.org/micro/v4/tracer" +) + +var DefaultContentType = "application/json" + +type Client struct { + funcCall client.FuncCall + funcStream client.FuncStream + httpClient *http.Client + opts client.Options + mu sync.RWMutex +} + +func NewClient(opts ...client.Option) *Client { + clientOpts := client.NewOptions(opts...) + + if len(clientOpts.ContentType) == 0 { + clientOpts.ContentType = DefaultContentType + } + + c := &Client{opts: clientOpts} + + dialer, ok := httpDialerFromOpts(clientOpts) + if !ok { + dialer = defaultHTTPDialer() + } + + c.httpClient, ok = httpClientFromOpts(clientOpts) + if !ok { + c.httpClient = defaultHTTPClient(dialer, clientOpts.TLSConfig) + } + + c.funcCall = c.fnCall + c.funcStream = c.fnStream + + return c +} + +func (c *Client) Name() string { + return c.opts.Name +} + +func (c *Client) Init(opts ...client.Option) error { + for _, o := range opts { + o(&c.opts) + } + + c.opts.Hooks.EachPrev(func(hook options.Hook) { + switch h := hook.(type) { + case client.HookCall: + c.funcCall = h(c.funcCall) + case client.HookStream: + c.funcStream = h(c.funcStream) + } + }) + + return nil +} + +func (c *Client) Options() client.Options { + return c.opts +} + +func (c *Client) NewRequest(service, method string, req any, opts ...client.RequestOption) client.Request { + reqOpts := client.NewRequestOptions(opts...) + if reqOpts.ContentType == "" { + reqOpts.ContentType = c.opts.ContentType + } + + return &httpRequest{ + service: service, + method: method, + request: req, + opts: reqOpts, + } +} + +func (c *Client) Call(ctx context.Context, req client.Request, rsp any, opts ...client.CallOption) error { + ts := time.Now() + c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc() + var sp tracer.Span + ctx, sp = c.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client", + tracer.WithSpanKind(tracer.SpanKindClient), + tracer.WithSpanLabels("endpoint", req.Endpoint()), + ) + err := c.funcCall(ctx, req, rsp, opts...) + c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec() + te := time.Since(ts) + c.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds()) + c.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds()) + + if me := errors.FromError(err); me == nil { + sp.Finish() + c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc() + } else { + sp.SetStatus(tracer.SpanStatusError, err.Error()) + c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc() + } + + return err +} + +func (c *Client) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { + return c.funcStream(ctx, req, opts...) +} + +func (c *Client) String() string { + return "http" +} diff --git a/client_helpers.go b/client_helpers.go new file mode 100644 index 0000000..96c2f1d --- /dev/null +++ b/client_helpers.go @@ -0,0 +1,263 @@ +package http + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/codec" + "go.unistack.org/micro/v4/logger" + "go.unistack.org/micro/v4/metadata" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + + "go.unistack.org/micro-client-http/v4/builder" +) + +func buildHTTPRequest( + ctx context.Context, + addr string, + path string, + ct string, + cf codec.Codec, + msg any, + opts client.CallOptions, + log logger.Logger, +) ( + *http.Request, + error, +) { + protoMsg, ok := msg.(proto.Message) + if !ok { + return nil, errors.New("msg must be a proto message type") + } + + var ( + method = http.MethodPost + bodyOpt string + + parameters = map[string]map[string]string{} + ) + + if opts.Context != nil { + if v, ok := methodFromOpts(opts); ok { + method = v + } + if v, ok := pathFromOpts(opts); ok { + path = v + } + if v, ok := bodyFromOpts(opts); ok { + bodyOpt = v + } + if h, ok := headerFromOpts(opts); ok && len(h) > 0 { + m, ok := parameters["header"] + if !ok { + m = make(map[string]string) + parameters["header"] = m + } + for idx := 0; idx+1 < len(h); idx += 2 { + m[h[idx]] = h[idx+1] + } + } + if c, ok := cookieFromOpts(opts); ok && len(c) > 0 { + m, ok := parameters["cookie"] + if !ok { + m = make(map[string]string) + parameters["cookie"] = m + } + for idx := 0; idx+1 < len(c); idx += 2 { + m[c[idx]] = c[idx+1] + } + } + } + + reqBuilder, err := builder.NewRequestBuilder(path, method, bodyOpt, protoMsg) + if err != nil { + return nil, fmt.Errorf("new request builder: %w", err) + } + + resolvedPath, newMsg, err := reqBuilder.Build() + if err != nil { + return nil, fmt.Errorf("build request: %w", err) + } + + resolvedURL := joinURL(addr, resolvedPath) + + u, err := normalizeURL(resolvedURL) + if err != nil { + return nil, fmt.Errorf("normalize url: %w", err) + } + + body, err := marshallMsg(cf, newMsg) + if err != nil { + return nil, fmt.Errorf("marshal msg: %w", err) + } + + var hreq *http.Request + + if len(body) > 0 { + hreq, err = http.NewRequestWithContext(ctx, method, u.String(), io.NopCloser(bytes.NewBuffer(body))) + hreq.ContentLength = int64(len(body)) + } else { + hreq, err = http.NewRequestWithContext(ctx, method, u.String(), nil) + } + + if err != nil { + return nil, fmt.Errorf("new http request: %w", err) + } + + setHeadersAndCookies(ctx, hreq, ct, opts) + if err = validateHeadersAndCookies(hreq, parameters); err != nil { + return nil, fmt.Errorf("validate headers and cookies: %w", err) + } + + if log.V(logger.DebugLevel) { + log.Debug( + ctx, + fmt.Sprintf("request %s to %s with headers %v body %s", method, u.String(), hreq.Header, body), + ) + } + + return hreq, nil +} + +func joinURL(addr, resolvedPath string) string { + if addr == "" { + return resolvedPath + } + if resolvedPath == "" { + return addr + } + + switch { + case strings.HasSuffix(addr, "/") && strings.HasPrefix(resolvedPath, "/"): + return addr + resolvedPath[1:] + case !strings.HasSuffix(addr, "/") && !strings.HasPrefix(resolvedPath, "/"): + return addr + "/" + resolvedPath + default: + return addr + resolvedPath + } +} + +func normalizeURL(raw string) (*url.URL, error) { + if !strings.Contains(raw, "://") { + raw = "http://" + raw + } + + u, err := url.Parse(raw) + if err != nil { + return nil, fmt.Errorf("parse url: %w", err) + } + + if u.Scheme != "http" && u.Scheme != "https" { + return nil, fmt.Errorf("invalid scheme: %q (must be http or https)", u.Scheme) + } + + if u.Host == "" { + return nil, errors.New("missing host in url") + } + + return u, nil +} + +func marshallMsg(cf codec.Codec, msg proto.Message) ([]byte, error) { + if msg == nil { + return nil, nil + } + isEmpty := true + msg.ProtoReflect().Range(func(protoreflect.FieldDescriptor, protoreflect.Value) bool { + isEmpty = false + return false + }) + if isEmpty { + return nil, nil + } + return cf.Marshal(msg) +} + +func setHeadersAndCookies(ctx context.Context, r *http.Request, ct string, opts client.CallOptions) { + r.Header = make(http.Header) + + r.Header.Set(metadata.HeaderContentType, ct) + r.Header.Set("Content-Length", fmt.Sprintf("%d", r.ContentLength)) + + if opts.AuthToken != "" { + r.Header.Set(metadata.HeaderAuthorization, opts.AuthToken) + } + + if opts.StreamTimeout > time.Duration(0) { + r.Header.Set(metadata.HeaderTimeout, fmt.Sprintf("%d", opts.StreamTimeout)) + } + if opts.RequestTimeout > time.Duration(0) { + r.Header.Set(metadata.HeaderTimeout, fmt.Sprintf("%d", opts.RequestTimeout)) + } + + if opts.RequestMetadata != nil { + for k, v := range opts.RequestMetadata { + if k == "Cookie" { + applyCookies(r, v) + continue + } + r.Header[k] = append(r.Header[k], v...) + } + } + + if md, ok := metadata.FromOutgoingContext(ctx); ok { + for k, v := range md { + if k == "Cookie" { + applyCookies(r, v) + continue + } + r.Header[k] = append(r.Header[k], v...) + } + } +} + +func applyCookies(r *http.Request, rawCookies []string) { + if len(rawCookies) == 0 { + return + } + + raw := strings.Join(rawCookies, "; ") + + tmp := http.Request{Header: http.Header{}} + tmp.Header.Set("Cookie", raw) + + for _, c := range tmp.Cookies() { + r.AddCookie(c) + } +} + +func validateHeadersAndCookies(r *http.Request, parameters map[string]map[string]string) error { + if headers, ok := parameters["header"]; ok { + for name, required := range headers { + if required == "true" && r.Header.Get(name) == "" { + return fmt.Errorf("missing required header: %s", name) + } + } + } + + if cookies, ok := parameters["cookie"]; ok { + cookieMap := map[string]string{} + for _, c := range r.Cookies() { + cookieMap[c.Name] = c.Value + } + + for name, required := range cookies { + if required == "true" { + if _, ok := cookieMap[name]; !ok { + return fmt.Errorf("missing required cookie: %s", name) + } + } + } + } + + return nil +} diff --git a/client_helpers_test.go b/client_helpers_test.go new file mode 100644 index 0000000..8ae7c90 --- /dev/null +++ b/client_helpers_test.go @@ -0,0 +1,401 @@ +package http + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + jsoncodec "go.unistack.org/micro-codec-json/v4" + "google.golang.org/protobuf/proto" + + pb "go.unistack.org/micro-client-http/v4/builder/proto" +) + +func TestJoinURL(t *testing.T) { + tests := []struct { + name string + addr string + path string + want string + }{ + { + name: "both without slash", + addr: "http://example.com", + path: "api/v1", + want: "http://example.com/api/v1", + }, + { + name: "addr with slash, path without slash", + addr: "http://example.com/", + path: "api/v1", + want: "http://example.com/api/v1", + }, + { + name: "addr without slash, path with slash", + addr: "http://example.com", + path: "/api/v1", + want: "http://example.com/api/v1", + }, + { + name: "both with slash", + addr: "http://example.com/", + path: "/api/v1", + want: "http://example.com/api/v1", + }, + { + name: "empty addr", + addr: "", + path: "/api/v1", + want: "/api/v1", + }, + { + name: "empty path", + addr: "http://example.com", + path: "", + want: "http://example.com", + }, + { + name: "both empty", + addr: "", + path: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, joinURL(tt.addr, tt.path)) + }) + } +} + +func TestNormalizeURL(t *testing.T) { + tests := []struct { + name string + input string + want string + wantErr bool + }{ + { + name: "host with port", + input: "localhost:8080", + want: "http://localhost:8080", + wantErr: false, + }, + { + name: "http with host", + input: "http://example.com", + want: "http://example.com", + wantErr: false, + }, + { + name: "http with no host", + input: "http://", + want: "", + wantErr: true, + }, + { + name: "https with host", + input: "https://example.com", + want: "https://example.com", + wantErr: false, + }, + { + name: "https with no host", + input: "https://", + want: "", + wantErr: true, + }, + { + name: "invalid scheme", + input: "ftp://example.com", + want: "", + wantErr: true, + }, + { + name: "IPv4 without scheme", + input: "127.0.0.1:9000", + want: "http://127.0.0.1:9000", + wantErr: false, + }, + { + name: "IPv4 with scheme", + input: "http://127.0.0.1:8080", + want: "http://127.0.0.1:8080", + wantErr: false, + }, + { + name: "IPv6 without scheme", + input: "[::1]:8080", + want: "http://[::1]:8080", + wantErr: false, + }, + { + name: "IPv6 with scheme", + input: "https://[::1]:443", + want: "https://[::1]:443", + wantErr: false, + }, + { + name: "hostname only", + input: "my-service", + want: "http://my-service", + wantErr: false, + }, + { + name: "hostname with path", + input: "service.local/api/v1", + want: "http://service.local/api/v1", + wantErr: false, + }, + { + name: "hostname with dash and port", + input: "api-service.local:8080", + want: "http://api-service.local:8080", + wantErr: false, + }, + { + name: "just path", + input: "/api/v1", + want: "", + wantErr: true, + }, + { + name: "empty string", + input: "", + want: "", + wantErr: true, + }, + { + name: "http with query params", + input: "http://example.com?x=1&y=2", + want: "http://example.com?x=1&y=2", + wantErr: false, + }, + { + name: "http with fragment", + input: "http://example.com/path#section1", + want: "http://example.com/path#section1", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := normalizeURL(tt.input) + if tt.wantErr { + require.Error(t, err) + require.Nil(t, result) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, result.String()) + } + }) + } +} + +func TestMarshallMsg(t *testing.T) { + type request = pb.Test_Client_Call_Request + + tests := []struct { + name string + msg proto.Message + expected string + }{ + { + name: "empty", + msg: &request{}, + expected: "", + }, + { + name: "nil", + msg: nil, + expected: "", + }, + { + name: "valid", + msg: &request{UserId: "123", OrderId: 456}, + expected: `{"userId":"123","orderId":456}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := marshallMsg(jsoncodec.NewCodec(), tt.msg) + require.NoError(t, err) + require.Equal(t, tt.expected, string(result)) + }) + } +} + +func TestApplyCookies(t *testing.T) { + tests := []struct { + name string + rawCookies []string + want []*http.Cookie + }{ + { + name: "empty", + rawCookies: []string{}, + want: []*http.Cookie{}, + }, + { + name: "single cookie", + rawCookies: []string{"session=abc123"}, + want: []*http.Cookie{ + {Name: "session", Value: "abc123"}, + }, + }, + { + name: "multiple cookies separate items", + rawCookies: []string{"session=abc123", "user=john"}, + want: []*http.Cookie{ + {Name: "session", Value: "abc123"}, + {Name: "user", Value: "john"}, + }, + }, + { + name: "multiple cookies in one item", + rawCookies: []string{"a=1; b=2"}, + want: []*http.Cookie{ + {Name: "a", Value: "1"}, + {Name: "b", Value: "2"}, + }, + }, + { + name: "mix of combined and separate cookies", + rawCookies: []string{"a=1; b=2", "c=3"}, + want: []*http.Cookie{ + {Name: "a", Value: "1"}, + {Name: "b", Value: "2"}, + {Name: "c", Value: "3"}, + }, + }, + { + name: "duplicate cookies", + rawCookies: []string{"session=abc123", "session=xyz"}, + want: []*http.Cookie{ + {Name: "session", Value: "abc123"}, + {Name: "session", Value: "xyz"}, + }, + }, + { + name: "cookie with spaces", + rawCookies: []string{"token=abc 123"}, + want: []*http.Cookie{ + {Name: "token", Value: "abc 123", Quoted: true}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + applyCookies(req, tt.rawCookies) + require.Equal(t, tt.want, req.Cookies()) + }) + } +} + +func TestValidateHeadersAndCookies(t *testing.T) { + tests := []struct { + name string + prepareRequest func() *http.Request + parameters map[string]map[string]string + wantErr bool + }{ + { + name: "all required headers and cookies present", + prepareRequest: func() *http.Request { + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("My-Header-1", "Header-Value-1") + req.Header.Set("My-Header-2", "Header-Value-2") + req.AddCookie(&http.Cookie{Name: "session-1", Value: "abc-1"}) + req.AddCookie(&http.Cookie{Name: "session-2", Value: "abc-2"}) + return req + }, + parameters: map[string]map[string]string{ + "header": {"My-Header-1": "true", "My-Header-2": "true"}, + "cookie": {"session-1": "true", "session-2": "true"}, + }, + wantErr: false, + }, + { + name: "missing required header", + prepareRequest: func() *http.Request { + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("My-Header-1", "Header-Value-1") + req.AddCookie(&http.Cookie{Name: "session-1", Value: "abc-1"}) + req.AddCookie(&http.Cookie{Name: "session-2", Value: "abc-2"}) + return req + }, + parameters: map[string]map[string]string{ + "header": {"My-Header-1": "true", "My-Header-2": "true"}, + "cookie": {"session-1": "true", "session-2": "true"}, + }, + wantErr: true, + }, + { + name: "missing required cookie", + prepareRequest: func() *http.Request { + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("My-Header-1", "Header-Value-1") + req.Header.Set("My-Header-2", "Header-Value-2") + req.AddCookie(&http.Cookie{Name: "session-1", Value: "abc-1"}) + return req + }, + parameters: map[string]map[string]string{ + "header": {"My-Header-1": "true", "My-Header-2": "true"}, + "cookie": {"session-1": "true", "session-2": "true"}, + }, + wantErr: true, + }, + { + name: "optional header and cookie not provided partially", + prepareRequest: func() *http.Request { + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("My-Header-1", "Header-Value-1") + req.AddCookie(&http.Cookie{Name: "session-1", Value: "abc-1"}) + return req + }, + parameters: map[string]map[string]string{ + "header": {"My-Header-1": "true", "My-Header-2": "false"}, + "cookie": {"session-1": "true", "session-2": "false"}, + }, + wantErr: false, + }, + { + name: "optional header and cookie not provided", + prepareRequest: func() *http.Request { + req := httptest.NewRequest("GET", "/", nil) + return req + }, + parameters: map[string]map[string]string{ + "header": {"My-Header-1": "false", "My-Header-2": "false"}, + "cookie": {"session-1": "false", "session-2": "false"}, + }, + wantErr: false, + }, + { + name: "no headers or cookies required", + prepareRequest: func() *http.Request { + req := httptest.NewRequest("GET", "/", nil) + return req + }, + parameters: map[string]map[string]string{}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateHeadersAndCookies(tt.prepareRequest(), tt.parameters) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/client_stream.go b/client_stream.go new file mode 100644 index 0000000..5896f6b --- /dev/null +++ b/client_stream.go @@ -0,0 +1,13 @@ +package http + +import ( + "context" + + "go.unistack.org/micro/v4/client" +) + +// TODO: Add stream support in the future. + +func (c *Client) fnStream(context.Context, client.Request, ...client.CallOption) (client.Stream, error) { + panic("not implemented") +} diff --git a/client_unary_call.go b/client_unary_call.go new file mode 100644 index 0000000..0507449 --- /dev/null +++ b/client_unary_call.go @@ -0,0 +1,273 @@ +package http + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strings" + "time" + + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/codec" + "go.unistack.org/micro/v4/errors" + "go.unistack.org/micro/v4/logger" + "go.unistack.org/micro/v4/metadata" + "go.unistack.org/micro/v4/selector" +) + +func (c *Client) fnCall(ctx context.Context, req client.Request, rsp any, opts ...client.CallOption) error { + // make a copy of call opts + callOpts := c.opts.CallOptions + for _, opt := range opts { + opt(&callOpts) + } + + // check if we already have a deadline + d, ok := ctx.Deadline() + if !ok { + var cancel context.CancelFunc + // no deadline so we create a new one + ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout) + defer cancel() + } else { + // got a deadline so no need to setup context, + // but we need to set the timeout we pass along + opt := client.WithRequestTimeout(time.Until(d)) + opt(&callOpts) + } + + // should we noop right here? + select { + case <-ctx.Done(): + return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) + default: + } + + // make copy of call method + hcall := c.call + + // use the router passed as a call option, or fallback to the rpc clients router + if callOpts.Router == nil { + callOpts.Router = c.opts.Router + } + + if callOpts.Selector == nil { + callOpts.Selector = c.opts.Selector + } + + // inject proxy address + // TODO: don't even bother using Lookup/Select in this case + if len(c.opts.Proxy) > 0 { + callOpts.Address = []string{c.opts.Proxy} + } + + var next selector.Next + + call := func(i int) error { + // call backoff first. Someone may want an initial start delay + t, err := callOpts.Backoff(ctx, req, i) + if err != nil { + return errors.InternalServerError("go.micro.client", "%+v", err) + } + + // only sleep if greater than 0 + if t.Seconds() > 0 { + time.Sleep(t) + } + + if next == nil { + var routes []string + // lookup the route to send the reques to + // TODO apply any filtering here + routes, err = c.opts.Lookup(ctx, req, callOpts) + if err != nil { + return errors.InternalServerError("go.micro.client", "%+v", err) + } + + // balance the list of nodes + next, err = callOpts.Selector.Select(routes) + if err != nil { + return errors.InternalServerError("go.micro.client", "%+v", err) + } + } + + node := next() + + // make the call + err = hcall(ctx, node, req, rsp, callOpts) + + // record the result of the call to inform future routing decisions + if verr := c.opts.Selector.Record(node, err); verr != nil { + return errors.InternalServerError("go.micro.client", "%+v", verr) + } + + // try and transform the error to micro error + if verr, ok := err.(*errors.Error); ok { + return verr + } + + return err + } + + ch := make(chan error, callOpts.Retries) + var gerr error + + for i := 0; i <= callOpts.Retries; i++ { + go func() { + ch <- call(i) + }() + + select { + case <-ctx.Done(): + return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) + case err := <-ch: + // if the call succeeded lets bail early + if err == nil { + return nil + } + + retry, rerr := callOpts.Retry(ctx, req, i, err) + if rerr != nil { + return rerr + } + + if !retry { + return err + } + + gerr = err + } + } + + return gerr +} + +func (c *Client) call(ctx context.Context, addr string, req client.Request, rsp any, opts client.CallOptions) error { + ct := req.ContentType() + if len(opts.ContentType) > 0 { + ct = opts.ContentType + } + + cf, err := c.newCodec(ct) + if err != nil { + return errors.BadRequest("go.micro.client", "%+v", err) + } + + hreq, err := buildHTTPRequest(ctx, addr, req.Endpoint(), ct, cf, req.Body(), opts, c.opts.Logger) + if err != nil { + return errors.BadRequest("go.micro.client", "%+v", err) + } + + hrsp, err := c.httpClient.Do(hreq) + if err != nil { + switch err := err.(type) { + case *url.Error: + if err, ok := err.Err.(net.Error); ok && err.Timeout() { + return errors.Timeout("go.micro.client", "%+v", err) + } + case net.Error: + if err.Timeout() { + return errors.Timeout("go.micro.client", "%+v", err) + } + } + return errors.InternalServerError("go.micro.client", "%+v", err) + } + + defer hrsp.Body.Close() + + return c.parseRsp(ctx, hrsp, rsp, opts) +} + +func (c *Client) newCodec(ct string) (codec.Codec, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if idx := strings.IndexRune(ct, ';'); idx >= 0 { + ct = ct[:idx] + } + + if cf, ok := c.opts.Codecs[ct]; ok { + return cf, nil + } + + return nil, codec.ErrUnknownContentType +} + +func (c *Client) parseRsp(ctx context.Context, hrsp *http.Response, rsp any, opts client.CallOptions) error { + log := c.opts.Logger + + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + var buf []byte + + if opts.ResponseMetadata != nil { + for k, v := range hrsp.Header { + opts.ResponseMetadata.Set(k, strings.Join(v, ",")) + } + } + + if hrsp.StatusCode == http.StatusNoContent { + return nil + } + + ct := DefaultContentType + if htype := hrsp.Header.Get(metadata.HeaderContentType); htype != "" { + ct = htype + } + + if hrsp.Body != nil { + var err error + buf, err = io.ReadAll(hrsp.Body) + if err != nil { + return errors.InternalServerError("go.micro.client", "read body: %v", err) + } + } + + cf, err := c.newCodec(ct) + if err != nil { + return errors.InternalServerError("go.micro.client", "unknown content-type %s: %v", ct, err) + } + + if log.V(logger.DebugLevel) { + log.Debug(ctx, fmt.Sprintf("response with headers: %v and body: %s", hrsp.Header, buf)) + } + + if hrsp.StatusCode < http.StatusBadRequest { + if err = cf.Unmarshal(buf, rsp); err != nil { + return errors.InternalServerError("go.micro.client", "unmarshal response: %v", err) + } + return nil + } + + var mappedErr any + + errMap, ok := errorMapFromOpts(opts) + if ok && errMap != nil { + mappedErr, ok = errMap[fmt.Sprintf("%d", hrsp.StatusCode)] + if !ok { + mappedErr, ok = errMap["default"] + } + } + + if !ok || mappedErr == nil { + return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) + } + + if err = cf.Unmarshal(buf, mappedErr); err != nil { + return errors.InternalServerError("go.micro.client", "unmarshal response: %v", err) + } + + if v, ok := mappedErr.(error); ok { + return v + } + + // if the error map item does not implement the error interface, wrap it + return &Error{err: mappedErr} +} diff --git a/client_unary_call_test.go b/client_unary_call_test.go new file mode 100644 index 0000000..e0147fc --- /dev/null +++ b/client_unary_call_test.go @@ -0,0 +1,1284 @@ +package http_test + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/require" + jsoncodec "go.unistack.org/micro-codec-json/v4" + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/metadata" + "google.golang.org/protobuf/proto" + + httpcli "go.unistack.org/micro-client-http/v4" + pb "go.unistack.org/micro-client-http/v4/builder/proto" +) + +func TestClient_Call_Get(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + tests := []struct { + name string + method string + path string + req *request + options []client.CallOption + wantPath string + wantReqBody []byte + wantRsp *response + wantErr bool + }{ + { + name: "GET request (query)", + method: http.MethodGet, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/products?order_id=456&user_id=123", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "GET request (path)", + method: http.MethodGet, + path: "/user/{user_id}/order/{order_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/order/456/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "GET request (path + query)", + method: http.MethodGet, + path: "/user/{user_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/products?order_id=456", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "GET request (zero-value query)", + method: http.MethodGet, + path: "/user/products", + req: &request{}, + wantPath: "/user/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "GET request (zero-value path)", + method: http.MethodGet, + path: "/user/{user_id}/products", + req: &request{OrderId: 456}, + wantRsp: nil, + wantErr: true, + }, + { + name: "GET request (with body)", + method: http.MethodGet, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + options: []client.CallOption{httpcli.Body("*")}, + wantRsp: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, tt.method, r.Method) + require.Equal(t, tt.wantPath, r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + require.Equal(t, tt.wantReqBody, buf) + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + if tt.wantRsp != nil { + c := jsoncodec.NewCodec() + buf, err = c.Marshal(tt.wantRsp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + } + })) + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"), + ) + rsp = &response{} + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(tt.method), + httpcli.Path(tt.path), + } + + if len(tt.options) > 0 { + opts = append(opts, tt.options...) + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", tt.req), + rsp, + opts..., + ) + + if tt.wantErr { + require.Error(t, err) + require.Empty(t, rsp) + } else { + require.NoError(t, err) + require.True(t, proto.Equal(tt.wantRsp, rsp)) + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) + } + }) + } +} + +func TestClient_Call_Head(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + tests := []struct { + name string + method string + path string + req *request + options []client.CallOption + wantPath string + wantReqBody []byte + wantRsp *response + wantErr bool + }{ + { + name: "HEAD request (query)", + method: http.MethodHead, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/products?order_id=456&user_id=123", + wantReqBody: []byte{}, + wantRsp: &response{}, + wantErr: false, + }, + { + name: "HEAD request (path)", + method: http.MethodHead, + path: "/user/{user_id}/order/{order_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/order/456/products", + wantReqBody: []byte{}, + wantRsp: &response{}, + wantErr: false, + }, + { + name: "HEAD request (path + query)", + method: http.MethodHead, + path: "/user/{user_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/products?order_id=456", + wantReqBody: []byte{}, + wantRsp: &response{}, + wantErr: false, + }, + { + name: "HEAD request (zero-value query)", + method: http.MethodHead, + path: "/user/products", + req: &request{}, + wantPath: "/user/products", + wantReqBody: []byte{}, + wantRsp: &response{}, + wantErr: false, + }, + { + name: "HEAD request (zero-value path)", + method: http.MethodHead, + path: "/user/{user_id}/products", + req: &request{OrderId: 456}, + wantRsp: nil, + wantErr: true, + }, + { + name: "HEAD request (with body)", + method: http.MethodHead, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + options: []client.CallOption{httpcli.Body("*")}, + wantRsp: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, tt.method, r.Method) + require.Equal(t, tt.wantPath, r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + require.Equal(t, tt.wantReqBody, buf) + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + // used to verify that the HTTP client skips the response body for HEAD method + c := jsoncodec.NewCodec() + buf, err = c.Marshal(map[string]any{"id": "product-id", "name": "product-name"}) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"), + ) + rsp = &response{} + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(tt.method), + httpcli.Path(tt.path), + } + + if len(tt.options) > 0 { + opts = append(opts, tt.options...) + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", tt.req), + rsp, + opts..., + ) + + if tt.wantErr { + require.Error(t, err) + require.Empty(t, rsp) + } else { + require.NoError(t, err) + require.True(t, proto.Equal(tt.wantRsp, rsp)) + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) + } + }) + } +} + +func TestClient_Call_Post(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + tests := []struct { + name string + method string + path string + req *request + options []client.CallOption + wantPath string + wantReqBody []byte + wantRsp *response + wantErr bool + }{ + { + name: "POST request (query)", + method: http.MethodPost, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/products?order_id=456&user_id=123", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (path)", + method: http.MethodPost, + path: "/user/{user_id}/order/{order_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/order/456/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (body)", + method: http.MethodPost, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + options: []client.CallOption{httpcli.Body("*")}, + wantPath: "/user/products", + wantReqBody: []byte(`{"userId":"123","orderId":456}`), + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (path + query)", + method: http.MethodPost, + path: "/user/{user_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/products?order_id=456", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (path + body)", + method: http.MethodPost, + path: "/user/{user_id}/products", + req: &request{UserId: "123", OrderId: 456}, + options: []client.CallOption{httpcli.Body("*")}, + wantPath: "/user/123/products", + wantReqBody: []byte(`{"orderId":456}`), + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (query + body)", + method: http.MethodPost, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + options: []client.CallOption{httpcli.Body("order_id")}, + wantPath: "/user/products?user_id=123", + wantReqBody: []byte(`{"orderId":456}`), + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (zero-value query)", + method: http.MethodPost, + path: "/user/products", + req: &request{}, + wantPath: "/user/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (zero-value body)", + method: http.MethodPost, + path: "/user/products", + req: &request{}, + options: []client.CallOption{httpcli.Body("*")}, + wantPath: "/user/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "POST request (zero-value path)", + method: http.MethodPost, + path: "/user/{user_id}/products", + req: &request{OrderId: 456}, + wantRsp: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, tt.method, r.Method) + require.Equal(t, tt.wantPath, r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + require.Equal(t, tt.wantReqBody, buf) + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + if tt.wantRsp != nil { + c := jsoncodec.NewCodec() + buf, err = c.Marshal(tt.wantRsp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + } + })) + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"), + ) + rsp = &response{} + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(tt.method), + httpcli.Path(tt.path), + } + + if len(tt.options) > 0 { + opts = append(opts, tt.options...) + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", tt.req), + rsp, + opts..., + ) + + if tt.wantErr { + require.Error(t, err) + require.Empty(t, rsp) + } else { + require.NoError(t, err) + require.True(t, proto.Equal(tt.wantRsp, rsp)) + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) + } + }) + } +} + +func TestClient_Call_Delete(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + tests := []struct { + name string + method string + path string + req *request + options []client.CallOption + wantPath string + wantReqBody []byte + wantRsp *response + wantErr bool + }{ + { + name: "DELETE request (query)", + method: http.MethodDelete, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/products?order_id=456&user_id=123", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "DELETE request (path)", + method: http.MethodDelete, + path: "/user/{user_id}/order/{order_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/order/456/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "DELETE request (path + query)", + method: http.MethodDelete, + path: "/user/{user_id}/products", + req: &request{UserId: "123", OrderId: 456}, + wantPath: "/user/123/products?order_id=456", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "DELETE request (zero-value query)", + method: http.MethodDelete, + path: "/user/products", + req: &request{}, + wantPath: "/user/products", + wantReqBody: []byte{}, + wantRsp: &response{Id: "product-id", Name: "product-name"}, + wantErr: false, + }, + { + name: "DELETE request (zero-value path)", + method: http.MethodDelete, + path: "/user/{user_id}/products", + req: &request{OrderId: 456}, + wantRsp: nil, + wantErr: true, + }, + { + name: "DELETE request (with body)", + method: http.MethodDelete, + path: "/user/products", + req: &request{UserId: "123", OrderId: 456}, + options: []client.CallOption{httpcli.Body("*")}, + wantRsp: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, tt.method, r.Method) + require.Equal(t, tt.wantPath, r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + require.Equal(t, tt.wantReqBody, buf) + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + if tt.wantRsp != nil { + c := jsoncodec.NewCodec() + buf, err = c.Marshal(tt.wantRsp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + } + })) + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"), + ) + rsp = &response{} + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(tt.method), + httpcli.Path(tt.path), + } + + if len(tt.options) > 0 { + opts = append(opts, tt.options...) + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", tt.req), + rsp, + opts..., + ) + + if tt.wantErr { + require.Error(t, err) + require.Empty(t, rsp) + } else { + require.NoError(t, err) + require.True(t, proto.Equal(tt.wantRsp, rsp)) + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) + } + }) + } +} + +func TestClient_Call_ErrorsMap(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + defaultError = pb.Test_Client_Call_DefaultError + specialError = pb.Test_Client_Call_SpecialError + ) + + tests := []struct { + name string + serverMock func() *httptest.Server + expectedErr error + }{ + { + name: "default error", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusBadRequest) + + resp := map[string]interface{}{ + "code": "default-error-code", + "msg": "default-error-message", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + expectedErr: &defaultError{Code: "default-error-code", Msg: "default-error-message"}, + }, + { + name: "special error", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusForbidden) + + resp := map[string]interface{}{ + "code": "special-error-code", + "msg": "special-error-message", + "warning": "special-error-warning", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + expectedErr: &specialError{Code: "special-error-code", Msg: "special-error-message", Warning: "special-error-warning"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := tt.serverMock() + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"), + ) + req = &request{UserId: "123", OrderId: 456} + rsp = &response{} + + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(http.MethodPost), + httpcli.Path("/user/products"), + httpcli.Body("*"), + httpcli.ErrorMap(map[string]any{ + "default": &defaultError{}, + "403": &specialError{}, + }), + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", req), + rsp, + opts..., + ) + + require.Equal(t, tt.expectedErr.Error(), err.Error()) + require.Empty(t, rsp) + + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) + }) + } +} + +func TestClient_Call_HeadersAndCookies(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + tests := []struct { + name string + serverMock func() *httptest.Server + prepareMetadata func() metadata.Metadata + headerOption client.CallOption + cookieOption client.CallOption + expectedRsp *response + wantErr bool + }{ + { + name: "with required headers", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + resp := map[string]interface{}{ + "id": "product-id", + "name": "product-name", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + prepareMetadata: func() metadata.Metadata { + return metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value") + }, + headerOption: httpcli.Header("Authorization", "true", "My-Header", "true"), + expectedRsp: &response{Id: "product-id", Name: "product-name"}, + }, + { + name: "without required headers", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + resp := map[string]interface{}{ + "id": "product-id", + "name": "product-name", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + prepareMetadata: func() metadata.Metadata { + return metadata.Pairs("Authorization", "Bearer token") + }, + headerOption: httpcli.Header("Authorization", "true", "My-Header", "true"), + wantErr: true, + }, + { + name: "with required cookies", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "session_id=abc123; theme=dark", r.Header.Get("Cookie")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + resp := map[string]interface{}{ + "id": "product-id", + "name": "product-name", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + prepareMetadata: func() metadata.Metadata { + return metadata.Pairs("Cookie", "session_id=abc123; theme=dark") + }, + cookieOption: httpcli.Cookie("session_id", "true", "theme", "true"), + expectedRsp: &response{Id: "product-id", Name: "product-name"}, + }, + { + name: "without required cookies", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "session_id=abc123; theme=dark", r.Header.Get("Cookie")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + resp := map[string]interface{}{ + "id": "product-id", + "name": "product-name", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + prepareMetadata: func() metadata.Metadata { + return metadata.Pairs("Cookie", "session_id=abc123") + }, + cookieOption: httpcli.Cookie("session_id", "true", "theme", "true"), + wantErr: true, + }, + { + name: "with headers and cookies", + serverMock: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + require.Equal(t, "session_id=abc123; theme=dark", r.Header.Get("Cookie")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusOK) + + resp := map[string]interface{}{ + "id": "product-id", + "name": "product-name", + } + buf, err = c.Marshal(resp) + require.NoError(t, err) + _, err = w.Write(buf) + require.NoError(t, err) + })) + }, + prepareMetadata: func() metadata.Metadata { + return metadata.Pairs( + "Authorization", "Bearer token", + "My-Header", "My-Header-Value", + "Cookie", "session_id=abc123; theme=dark", + ) + }, + headerOption: httpcli.Header("Authorization", "true", "My-Header", "true"), + cookieOption: httpcli.Cookie("session_id", "true", "theme", "true"), + expectedRsp: &response{Id: "product-id", Name: "product-name"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := tt.serverMock() + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext(context.Background(), tt.prepareMetadata()) + req = &request{UserId: "123", OrderId: 456} + rsp = &response{} + + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(http.MethodPost), + httpcli.Path("/user/products"), + httpcli.Body("*"), + } + if tt.headerOption != nil { + opts = append(opts, tt.headerOption) + } + if tt.cookieOption != nil { + opts = append(opts, tt.cookieOption) + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", req), + rsp, + opts..., + ) + + if tt.wantErr { + require.Error(t, err) + require.Empty(t, rsp) + } else { + require.NoError(t, err) + require.True(t, proto.Equal(tt.expectedRsp, rsp)) + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) + } + }) + } +} + +func TestClient_Call_NoContent(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + serverMock := func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate request + require.Equal(t, "POST", r.Method) + require.Equal(t, "/user/products", r.URL.RequestURI()) + + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + require.Equal(t, "Bearer token", r.Header.Get("Authorization")) + require.Equal(t, "My-Header-Value", r.Header.Get("My-Header")) + + buf, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + + c := jsoncodec.NewCodec() + + req := &request{} + err = c.Unmarshal(buf, req) + require.NoError(t, err) + require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req)) + + // Return response + w.Header().Set("Content-Type", "application/json") + w.Header().Set("My-Header", "My-Header-Value") + w.WriteHeader(http.StatusNoContent) + })) + } + + server := serverMock() + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = metadata.NewOutgoingContext( + context.Background(), + metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"), + ) + req = &request{UserId: "123", OrderId: 456} + rsp = &response{} + + respMetadata = metadata.Metadata{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithResponseMetadata(&respMetadata), + httpcli.Method(http.MethodPost), + httpcli.Path("/user/products"), + httpcli.Body("*"), + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", req), + rsp, + opts..., + ) + require.NoError(t, err) + require.Empty(t, rsp) + + require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type")) + require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header")) +} + +func TestClient_Call_RequestTimeoutError(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + serverMock := func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(2 * time.Millisecond) + })) + } + + server := serverMock() + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx = context.Background() + req = &request{UserId: "123", OrderId: 456} + rsp = &response{} + ) + + opts := []client.CallOption{ + client.WithAddress(server.URL), + client.WithRequestTimeout(time.Millisecond), + httpcli.Method(http.MethodPost), + httpcli.Path("/user/products"), + httpcli.Body("*"), + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", req), + rsp, + opts..., + ) + require.Error(t, err) +} + +func TestClient_Call_ContextDeadlineError(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + serverMock := func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(2 * time.Millisecond) + })) + } + + server := serverMock() + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond)) + req = &request{UserId: "123", OrderId: 456} + rsp = &response{} + ) + defer cancel() + + opts := []client.CallOption{ + client.WithAddress(server.URL), + httpcli.Method(http.MethodPost), + httpcli.Path("/user/products"), + httpcli.Body("*"), + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", req), + rsp, + opts..., + ) + require.Error(t, err) +} + +func TestClient_Call_ContextCanceled(t *testing.T) { + type ( + request = pb.Test_Client_Call_Request + response = pb.Test_Client_Call_Response + ) + + serverMock := func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(2 * time.Millisecond) + })) + } + + server := serverMock() + defer server.Close() + + httpClient := httpcli.NewClient( + client.Codec("application/json", jsoncodec.NewCodec()), + ) + + var ( + ctx, cancel = context.WithCancel(context.Background()) + req = &request{UserId: "123", OrderId: 456} + rsp = &response{} + ) + cancel() + + opts := []client.CallOption{ + client.WithAddress(server.URL), + httpcli.Method(http.MethodPost), + httpcli.Path("/user/products"), + httpcli.Body("*"), + } + + err := httpClient.Call( + ctx, + httpClient.NewRequest("test.service", "Test.Call", req), + rsp, + opts..., + ) + require.Error(t, err) +} diff --git a/error.go b/error.go new file mode 100644 index 0000000..d878791 --- /dev/null +++ b/error.go @@ -0,0 +1,12 @@ +package http + +import "fmt" + +// Error is used when items in the error map do not implement the error interface and need to be wrapped. +type Error struct { + err any +} + +func (e *Error) Error() string { + return fmt.Sprintf("%+v", e.err) +} diff --git a/go.mod b/go.mod index 733ad9f..840dd1e 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,22 @@ go 1.23.0 toolchain go1.24.2 -require go.unistack.org/micro/v4 v4.1.7 +require ( + github.com/stretchr/testify v1.11.1 + go.unistack.org/micro-codec-json/v4 v4.1.0 + go.unistack.org/micro/v4 v4.1.19 + google.golang.org/protobuf v1.36.9 +) require ( - github.com/ash3in/uuidv8 v1.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/google/uuid v1.6.0 // indirect github.com/matoous/go-nanoid v1.5.1 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/spf13/cast v1.9.2 // indirect go.unistack.org/micro-proto/v4 v4.1.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.22.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 // indirect - google.golang.org/grpc v1.72.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/sys v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/grpc v1.75.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index aed22b0..076bc7a 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,11 @@ -github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= -github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/ash3in/uuidv8 v1.2.0 h1:2oogGdtCPwaVtyvPPGin4TfZLtOGE5F+W++E880G6SI= -github.com/ash3in/uuidv8 v1.2.0/go.mod h1:BnU0wJBxnzdEKmVg4xckBkD+VZuecTFTUP3M0dWgyY4= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -16,36 +14,32 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4= github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.unistack.org/micro-codec-json/v4 v4.1.0 h1:iydeSkt3ee7IPU0dHHKlGN97lw+YFQasBk9rdv0woYA= +go.unistack.org/micro-codec-json/v4 v4.1.0/go.mod h1:aUg86elSlURSynTAetDAAXj/VzFDwwcg92QNrRzcvrM= go.unistack.org/micro-proto/v4 v4.1.0 h1:qPwL2n/oqh9RE3RTTDgt28XK3QzV597VugQPaw9lKUk= go.unistack.org/micro-proto/v4 v4.1.0/go.mod h1:ArmK7o+uFvxSY3dbJhKBBX4Pm1rhWdLEFf3LxBrMtec= -go.unistack.org/micro/v4 v4.1.2 h1:9SOlPYyPNNFpg1A7BsvhDyQm3gysLH1AhWbDCp1hyoY= -go.unistack.org/micro/v4 v4.1.2/go.mod h1:lr3oYED8Ay1vjK68QqRw30QOtdk/ffpZqMFDasOUhKw= -go.unistack.org/micro/v4 v4.1.7 h1:4/dSEMTqxYoHimn/8wKDohfTXi2zDy6eWXYBCnBaZdc= -go.unistack.org/micro/v4 v4.1.7/go.mod h1:lr3oYED8Ay1vjK68QqRw30QOtdk/ffpZqMFDasOUhKw= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 h1:29cjnHVylHwTzH66WfFZqgSQgnxzvWE+jvBwpZCLRxY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +go.unistack.org/micro/v4 v4.1.19 h1:LKpmSPYvX5B9AkFD7JqMU/U06v5yEWn2bsCG/YKZtZI= +go.unistack.org/micro/v4 v4.1.19/go.mod h1:xleO2M5Yxh4s6I+RUcLrEpUjobefh+71ctrdIfn7TUs= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/http.go b/http.go deleted file mode 100644 index 364d41c..0000000 --- a/http.go +++ /dev/null @@ -1,724 +0,0 @@ -// Package http provides a http client -package http // import "go.unistack.org/micro-client-http/v3" - -import ( - "bufio" - "bytes" - "context" - stderr "errors" - "fmt" - "io" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "go.unistack.org/micro/v4/client" - "go.unistack.org/micro/v4/codec" - "go.unistack.org/micro/v4/errors" - "go.unistack.org/micro/v4/logger" - "go.unistack.org/micro/v4/metadata" - "go.unistack.org/micro/v4/options" - "go.unistack.org/micro/v4/selector" - "go.unistack.org/micro/v4/semconv" - "go.unistack.org/micro/v4/tracer" - rutil "go.unistack.org/micro/v4/util/reflect" -) - -var DefaultContentType = "application/json" - -/* -func filterLabel(r []router.Route) []router.Route { - // selector.FilterLabel("protocol", "http") - return r -} -*/ - -type httpClient struct { - funcCall client.FuncCall - funcStream client.FuncStream - httpcli *http.Client - opts client.Options - mu sync.RWMutex -} - -func newRequest(ctx context.Context, log logger.Logger, addr string, req client.Request, ct string, cf codec.Codec, msg interface{}, opts client.CallOptions) (*http.Request, error) { - var tags []string - var parameters map[string]map[string]string - scheme := "http" - method := http.MethodPost - body := "*" // as like google api http annotation - host := addr - path := req.Endpoint() - - u, err := url.Parse(addr) - if err == nil { - scheme = u.Scheme - path = u.Path - host = u.Host - } else { - u = &url.URL{Scheme: scheme, Path: path, Host: host} - } - - // nolint: nestif - if opts.Context != nil { - if m, ok := opts.Context.Value(methodKey{}).(string); ok { - method = m - } - if p, ok := opts.Context.Value(pathKey{}).(string); ok { - path += p - } - if b, ok := opts.Context.Value(bodyKey{}).(string); ok { - body = b - } - if t, ok := opts.Context.Value(structTagsKey{}).([]string); ok && len(t) > 0 { - tags = t - } - if k, ok := opts.Context.Value(headerKey{}).([]string); ok && len(k) > 0 { - if parameters == nil { - parameters = make(map[string]map[string]string) - } - m, ok := parameters["header"] - if !ok { - m = make(map[string]string) - parameters["header"] = m - } - for idx := 0; idx+1 < len(k); idx += 2 { - m[k[idx]] = k[idx+1] - } - } - if k, ok := opts.Context.Value(cookieKey{}).([]string); ok && len(k) > 0 { - if parameters == nil { - parameters = make(map[string]map[string]string) - } - m, ok := parameters["cookie"] - if !ok { - m = make(map[string]string) - parameters["cookie"] = m - } - for idx := 0; idx+1 < len(k); idx += 2 { - m[k[idx]] = k[idx+1] - } - } - } - - if len(tags) == 0 { - switch ct { - default: - tags = append(tags, "json", "protobuf") - case "text/xml": - tags = append(tags, "xml") - } - } - - if path == "" { - path = req.Endpoint() - } - - u, err = u.Parse(path) - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - - var nmsg interface{} - if len(u.Query()) > 0 { - path, nmsg, err = newPathRequest(u.Path+"?"+u.RawQuery, method, body, msg, tags, parameters) - } else { - path, nmsg, err = newPathRequest(u.Path, method, body, msg, tags, parameters) - } - - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - - u, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, path)) - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - - var cookies []*http.Cookie - header := make(http.Header) - if opts.Context != nil { - if md, ok := opts.Context.Value(metadataKey{}).(metadata.Metadata); ok { - for k, v := range md { - header[k] = append(header[k], v...) - } - } - } - if opts.AuthToken != "" { - header.Set(metadata.HeaderAuthorization, opts.AuthToken) - } - if opts.RequestMetadata != nil { - for k, v := range opts.RequestMetadata { - header[k] = append(header[k], v...) - } - } - - if md, ok := metadata.FromOutgoingContext(ctx); ok { - for k, v := range md { - header[k] = append(header[k], v...) - } - } - - // set timeout in nanoseconds - if opts.StreamTimeout > time.Duration(0) { - header.Set(metadata.HeaderTimeout, fmt.Sprintf("%d", opts.StreamTimeout)) - } - if opts.RequestTimeout > time.Duration(0) { - header.Set(metadata.HeaderTimeout, fmt.Sprintf("%d", opts.RequestTimeout)) - } - - // set the content type for the request - header.Set(metadata.HeaderContentType, ct) - var v interface{} - - for km, vm := range parameters { - for k, required := range vm { - v, err = rutil.StructFieldByPath(msg, k) - if stderr.Is(err, rutil.ErrNotFound) { - // Note: check the `json_name` in the `protobuf` tag for headers with hyphens, - // since struct fields cannot contain hyphens. - v, err = rutil.StructFieldByTag(msg, "protobuf", fmt.Sprintf("json=%s", k)) - } - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - if rutil.IsZero(v) { - if required == "true" { - return nil, errors.BadRequest("go.micro.client", "required field %s not set", k) - } - continue - } - - switch km { - case "header": - header.Set(k, fmt.Sprintf("%v", v)) - case "cookie": - cookies = append(cookies, &http.Cookie{Name: k, Value: fmt.Sprintf("%v", v)}) - } - } - } - - b, err := cf.Marshal(nmsg) - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - - var hreq *http.Request - if len(b) > 0 { - hreq, err = http.NewRequestWithContext(ctx, method, u.String(), io.NopCloser(bytes.NewBuffer(b))) - hreq.ContentLength = int64(len(b)) - header.Set("Content-Length", fmt.Sprintf("%d", hreq.ContentLength)) - } else { - hreq, err = http.NewRequestWithContext(ctx, method, u.String(), nil) - } - - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - - hreq.Header = header - for _, cookie := range cookies { - hreq.AddCookie(cookie) - } - - if log.V(logger.DebugLevel) { - log.Debug(ctx, fmt.Sprintf("request %s to %s with headers %v body %s", method, u.String(), hreq.Header, b)) - } - - return hreq, nil -} - -func (c *httpClient) call(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error { - ct := req.ContentType() - if len(opts.ContentType) > 0 { - ct = opts.ContentType - } - - cf, err := c.newCodec(ct) - if err != nil { - return errors.BadRequest("go.micro.client", "%+v", err) - } - hreq, err := newRequest(ctx, c.opts.Logger, addr, req, ct, cf, req.Body(), opts) - if err != nil { - return err - } - - // make the request - hrsp, err := c.httpcli.Do(hreq) - if err != nil { - switch err := err.(type) { - case *url.Error: - if err, ok := err.Err.(net.Error); ok && err.Timeout() { - return errors.Timeout("go.micro.client", "%+v", err) - } - case net.Error: - if err.Timeout() { - return errors.Timeout("go.micro.client", "%+v", err) - } - } - return errors.InternalServerError("go.micro.client", "%+v", err) - } - - defer hrsp.Body.Close() - - return c.parseRsp(ctx, hrsp, rsp, opts) -} - -func (c *httpClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) { - ct := req.ContentType() - if len(opts.ContentType) > 0 { - ct = opts.ContentType - } - - // get codec - cf, err := c.newCodec(ct) - if err != nil { - return nil, errors.BadRequest("go.micro.client", "%+v", err) - } - - cc, err := (c.httpcli.Transport).(*http.Transport).DialContext(ctx, "tcp", addr) - if err != nil { - return nil, errors.InternalServerError("go.micro.client", "Error dialing: %v", err) - } - - return &httpStream{ - address: addr, - logger: c.opts.Logger, - context: ctx, - closed: make(chan bool), - opts: opts, - conn: cc, - ct: ct, - cf: cf, - reader: bufio.NewReader(cc), - request: req, - }, nil -} - -func (c *httpClient) newCodec(ct string) (codec.Codec, error) { - c.mu.RLock() - - if idx := strings.IndexRune(ct, ';'); idx >= 0 { - ct = ct[:idx] - } - - if cf, ok := c.opts.Codecs[ct]; ok { - c.mu.RUnlock() - return cf, nil - } - - c.mu.RUnlock() - return nil, codec.ErrUnknownContentType -} - -func (c *httpClient) Init(opts ...client.Option) error { - for _, o := range opts { - o(&c.opts) - } - - c.funcCall = c.fnCall - c.funcStream = c.fnStream - - c.opts.Hooks.EachPrev(func(hook options.Hook) { - switch h := hook.(type) { - case client.HookCall: - c.funcCall = h(c.funcCall) - case client.HookStream: - c.funcStream = h(c.funcStream) - } - }) - - return nil -} - -func (c *httpClient) Options() client.Options { - return c.opts -} - -func (c *httpClient) NewRequest(service, method string, req interface{}, opts ...client.RequestOption) client.Request { - return newHTTPRequest(service, method, req, c.opts.ContentType, opts...) -} - -func (c *httpClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { - ts := time.Now() - c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc() - var sp tracer.Span - ctx, sp = c.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client", - tracer.WithSpanKind(tracer.SpanKindClient), - tracer.WithSpanLabels("endpoint", req.Endpoint()), - ) - err := c.funcCall(ctx, req, rsp, opts...) - c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec() - te := time.Since(ts) - c.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds()) - c.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds()) - - if me := errors.FromError(err); me == nil { - sp.Finish() - c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc() - } else { - sp.SetStatus(tracer.SpanStatusError, err.Error()) - c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc() - } - - return err -} - -func (c *httpClient) fnCall(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { - // make a copy of call opts - callOpts := c.opts.CallOptions - for _, opt := range opts { - opt(&callOpts) - } - - // check if we already have a deadline - d, ok := ctx.Deadline() - if !ok { - var cancel context.CancelFunc - // no deadline so we create a new one - ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout) - defer cancel() - } else { - // got a deadline so no need to setup context - // but we need to set the timeout we pass along - opt := client.WithRequestTimeout(time.Until(d)) - opt(&callOpts) - } - - // should we noop right here? - select { - case <-ctx.Done(): - return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) - default: - } - - // make copy of call method - hcall := c.call - - // use the router passed as a call option, or fallback to the rpc clients router - if callOpts.Router == nil { - callOpts.Router = c.opts.Router - } - - if callOpts.Selector == nil { - callOpts.Selector = c.opts.Selector - } - - // inject proxy address - // TODO: don't even bother using Lookup/Select in this case - if len(c.opts.Proxy) > 0 { - callOpts.Address = []string{c.opts.Proxy} - } - - var next selector.Next - - // return errors.New("go.micro.client", "request timeout", 408) - call := func(i int) error { - // call backoff first. Someone may want an initial start delay - t, err := callOpts.Backoff(ctx, req, i) - if err != nil { - return errors.InternalServerError("go.micro.client", "%+v", err) - } - - // only sleep if greater than 0 - if t.Seconds() > 0 { - time.Sleep(t) - } - - if next == nil { - var routes []string - // lookup the route to send the reques to - // TODO apply any filtering here - routes, err = c.opts.Lookup(ctx, req, callOpts) - if err != nil { - return errors.InternalServerError("go.micro.client", "%+v", err) - } - - // balance the list of nodes - next, err = callOpts.Selector.Select(routes) - if err != nil { - return err - } - } - - node := next() - - // make the call - err = hcall(ctx, node, req, rsp, callOpts) - // record the result of the call to inform future routing decisions - if verr := c.opts.Selector.Record(node, err); verr != nil { - return verr - } - - // try and transform the error to a go-micro error - if verr, ok := err.(*errors.Error); ok { - return verr - } - - return err - } - - ch := make(chan error, callOpts.Retries) - var gerr error - - for i := 0; i <= callOpts.Retries; i++ { - go func() { - ch <- call(i) - }() - - select { - case <-ctx.Done(): - return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) - case err := <-ch: - // if the call succeeded lets bail early - if err == nil { - return nil - } - - retry, rerr := callOpts.Retry(ctx, req, i, err) - if rerr != nil { - return rerr - } - - if !retry { - return err - } - - gerr = err - } - } - - return gerr -} - -func (c *httpClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { - ts := time.Now() - c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc() - var sp tracer.Span - ctx, sp = c.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client", - tracer.WithSpanKind(tracer.SpanKindClient), - tracer.WithSpanLabels("endpoint", req.Endpoint()), - ) - stream, err := c.funcStream(ctx, req, opts...) - c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec() - te := time.Since(ts) - c.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds()) - c.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds()) - - if me := errors.FromError(err); me == nil { - sp.Finish() - c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc() - } else { - sp.SetStatus(tracer.SpanStatusError, err.Error()) - c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc() - } - - return stream, err -} - -func (c *httpClient) fnStream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { - var err error - - // make a copy of call opts - callOpts := c.opts.CallOptions - for _, o := range opts { - o(&callOpts) - } - - // check if we already have a deadline - d, ok := ctx.Deadline() - if !ok && callOpts.StreamTimeout > time.Duration(0) { - var cancel context.CancelFunc - // no deadline so we create a new one - ctx, cancel = context.WithTimeout(ctx, callOpts.StreamTimeout) - defer cancel() - } else { - // got a deadline so no need to setup context - // but we need to set the timeout we pass along - o := client.WithStreamTimeout(time.Until(d)) - o(&callOpts) - } - - // should we noop right here? - select { - case <-ctx.Done(): - return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) - default: - } - - /* - // make copy of call method - hstream := h.stream - // wrap the call in reverse - for i := len(callOpts.CallWrappers); i > 0; i-- { - hstream = callOpts.CallWrappers[i-1](hstream) - } - */ - - // use the router passed as a call option, or fallback to the rpc clients router - if callOpts.Router == nil { - callOpts.Router = c.opts.Router - } - - if callOpts.Selector == nil { - callOpts.Selector = c.opts.Selector - } - - // inject proxy address - // TODO: don't even bother using Lookup/Select in this case - if len(c.opts.Proxy) > 0 { - callOpts.Address = []string{c.opts.Proxy} - } - - var next selector.Next - - call := func(i int) (client.Stream, error) { - // call backoff first. Someone may want an initial start delay - t, cerr := callOpts.Backoff(ctx, req, i) - if cerr != nil { - return nil, errors.InternalServerError("go.micro.client", "%+v", cerr) - } - - // only sleep if greater than 0 - if t.Seconds() > 0 { - time.Sleep(t) - } - - if next == nil { - var routes []string - // lookup the route to send the reques to - // TODO apply any filtering here - routes, err = c.opts.Lookup(ctx, req, callOpts) - if err != nil { - return nil, errors.InternalServerError("go.micro.client", "%+v", err) - } - - // balance the list of nodes - next, err = callOpts.Selector.Select(routes) - if err != nil { - return nil, err - } - } - - node := next() - - stream, cerr := c.stream(ctx, node, req, callOpts) - - // record the result of the call to inform future routing decisions - if verr := c.opts.Selector.Record(node, cerr); verr != nil { - return nil, verr - } - - // try and transform the error to a go-micro error - if verr, ok := cerr.(*errors.Error); ok { - return nil, verr - } - - return stream, cerr - } - - type response struct { - stream client.Stream - err error - } - - ch := make(chan response, callOpts.Retries) - var grr error - - for i := 0; i <= callOpts.Retries; i++ { - go func() { - s, cerr := call(i) - ch <- response{s, cerr} - }() - - select { - case <-ctx.Done(): - return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) - case rsp := <-ch: - // if the call succeeded lets bail early - if rsp.err == nil { - return rsp.stream, nil - } - - retry, rerr := callOpts.Retry(ctx, req, i, err) - if rerr != nil { - return nil, rerr - } - - if !retry { - return nil, rsp.err - } - - grr = rsp.err - } - } - - return nil, grr -} - -func (c *httpClient) String() string { - return "http" -} - -func (c *httpClient) Name() string { - return c.opts.Name -} - -func NewClient(opts ...client.Option) *httpClient { - options := client.NewOptions(opts...) - - if len(options.ContentType) == 0 { - options.ContentType = DefaultContentType - } - - c := &httpClient{ - opts: options, - } - - var dialer func(context.Context, string) (net.Conn, error) - if v, ok := options.Context.Value(httpDialerKey{}).(*net.Dialer); ok { - dialer = func(ctx context.Context, addr string) (net.Conn, error) { - return v.DialContext(ctx, "tcp", addr) - } - } - if options.ContextDialer != nil { - dialer = options.ContextDialer - } - if dialer == nil { - dialer = func(ctx context.Context, addr string) (net.Conn, error) { - return (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).DialContext(ctx, "tcp", addr) - } - } - - if httpcli, ok := options.Context.Value(httpClientKey{}).(*http.Client); ok { - c.httpcli = httpcli - } else { - // TODO customTransport := http.DefaultTransport.(*http.Transport).Clone() - tr := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialer(ctx, addr) - }, - ForceAttemptHTTP2: true, - MaxConnsPerHost: 100, - MaxIdleConns: 20, - IdleConnTimeout: 60 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - TLSClientConfig: options.TLSConfig, - } - c.httpcli = &http.Client{Transport: tr} - } - - c.funcCall = c.fnCall - c.funcStream = c.fnStream - - return c -} diff --git a/http_test.go b/http_test.go deleted file mode 100644 index 14c8ea6..0000000 --- a/http_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package http - -import ( - "encoding/json" - "fmt" - "net/url" - "strings" - "testing" -) - -func TestNestedPathPost(t *testing.T) { - req := &request{Name: "first", Field1: "fieldval"} - p, m, err := newPathRequest("/api/v1/xxxx", "POST", "*", req, []string{"json", "protobuf"}, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(p) - if err != nil { - t.Fatal(err) - } - if s := u.String(); s != "/api/v1/xxxx" { - t.Fatalf("nested path error %s", s) - } - _ = m -} - -type request struct { - NestedTest *request `json:"nested_test,omitempty"` - Name string `json:"name,omitempty"` - Field1 string `json:"field1,omitempty"` - ClientID string `json:",omitempty"` - Field2 string `json:",omitempty"` - Field3 int64 `json:",omitempty"` -} - -func TestNestedPath(t *testing.T) { - req := &request{Name: "first", NestedTest: &request{Name: "second"}, Field1: "fieldval"} - p, m, err := newPathRequest("/api/v1/{name}/{nested_test.name}", "PUT", "*", req, []string{"json", "protobuf"}, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(p) - if err != nil { - t.Fatal(err) - } - if s := u.String(); s != "/api/v1/first/second" { - t.Fatalf("nested path error %s", s) - } - b, err := json.Marshal(m) - if err != nil { - t.Fatal(err) - } - fmt.Printf("m %#+v %s\n", m, b) -} - -func TestPathWithHeader(t *testing.T) { - req := &request{Name: "vtolstov", Field1: "field1", ClientID: "1234567890"} - p, m, err := newPathRequest( - "/api/v1/test?Name={name}&Field1={field1}", - "POST", - "*", - req, - nil, - map[string]map[string]string{"header": {"ClientID": "true"}}, - ) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(p) - if err != nil { - t.Fatal(err) - } - if m != nil { - t.Fatal("new struct must be nil") - } - if u.Query().Get("Name") != "vtolstov" || u.Query().Get("Field1") != "field1" { - t.Fatalf("invalid values %v", u.Query()) - } -} - -func TestPathValues(t *testing.T) { - req := &request{Name: "vtolstov", Field1: "field1"} - p, m, err := newPathRequest("/api/v1/test?Name={name}&Field1={field1}", "POST", "*", req, nil, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(p) - if err != nil { - t.Fatal(err) - } - _ = m - if u.Query().Get("Name") != "vtolstov" || u.Query().Get("Field1") != "field1" { - t.Fatalf("invalid values %v", u.Query()) - } -} - -func TestValidPath(t *testing.T) { - req := &request{Name: "vtolstov", Field1: "field1", Field2: "field2", Field3: 10} - p, m, err := newPathRequest("/api/v1/{name}/list", "GET", "", req, nil, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(p) - if err != nil { - t.Fatal(err) - } - _ = m - parts := strings.Split(u.RawQuery, "&") - if len(parts) != 3 { - t.Fatalf("invalid path: %v", parts) - } -} - -func TestInvalidPath(t *testing.T) { - req := &request{Name: "vtolstov", Field1: "field1", Field2: "field2", Field3: 10} - s, _, err := newPathRequest("/api/v1/{xname}/list", "GET", "", req, nil, nil) - if err == nil { - t.Fatalf("path param must not be filled: %s", s) - } -} diff --git a/options.go b/options.go index 3590a05..c0dd465 100644 --- a/options.go +++ b/options.go @@ -1,126 +1,148 @@ package http import ( + "context" + "crypto/tls" "net" "net/http" + "time" "go.unistack.org/micro/v4/client" - "go.unistack.org/micro/v4/metadata" ) -var ( - // DefaultPoolMaxStreams maximum streams on a connectioin - // (20) - DefaultPoolMaxStreams = 20 - - // DefaultPoolMaxIdle maximum idle conns of a pool - // (50) - DefaultPoolMaxIdle = 50 - - // DefaultMaxRecvMsgSize maximum message that client can receive - // (4 MB). - DefaultMaxRecvMsgSize = 1024 * 1024 * 4 - - // DefaultMaxSendMsgSize maximum message that client can send - // (4 MB). - DefaultMaxSendMsgSize = 1024 * 1024 * 4 -) - -type poolMaxStreams struct{} - -// PoolMaxStreams maximum streams on a connectioin -func PoolMaxStreams(n int) client.Option { - return client.SetOption(poolMaxStreams{}, n) -} - -type poolMaxIdle struct{} - -// PoolMaxIdle maximum idle conns of a pool -func PoolMaxIdle(d int) client.Option { - return client.SetOption(poolMaxIdle{}, d) -} - -type maxRecvMsgSizeKey struct{} - -// MaxRecvMsgSize set the maximum size of message that client can receive. -func MaxRecvMsgSize(s int) client.Option { - return client.SetOption(maxRecvMsgSizeKey{}, s) -} - -type maxSendMsgSizeKey struct{} - -// MaxSendMsgSize set the maximum size of message that client can send. -func MaxSendMsgSize(s int) client.Option { - return client.SetOption(maxSendMsgSizeKey{}, s) -} - +// --------------------------------------------- HTTPClient option ----------------------------------------------------- type httpClientKey struct{} -// nolint: golint -// HTTPClient pass http.Client option to client Call func HTTPClient(c *http.Client) client.Option { return client.SetOption(httpClientKey{}, c) } +func httpClientFromOpts(opts client.Options) (*http.Client, bool) { + httpClient, ok := opts.Context.Value(httpClientKey{}).(*http.Client) + return httpClient, ok +} + +func defaultHTTPClient( + dialer func(ctx context.Context, addr string) (net.Conn, error), + tlsConfig *tls.Config, +) *http.Client { + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer(ctx, addr) + }, + ForceAttemptHTTP2: true, + MaxConnsPerHost: 100, + MaxIdleConns: 20, + IdleConnTimeout: 60 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: tlsConfig, + } + return &http.Client{Transport: tr} +} + +// --------------------------------------------- HTTPDialer option ----------------------------------------------------- type httpDialerKey struct{} -// nolint: golint -// HTTPDialer pass net.Dialer option to client func HTTPDialer(d *net.Dialer) client.Option { return client.SetOption(httpDialerKey{}, d) } +func httpDialerFromOpts(opts client.Options) (dialerFunc func(context.Context, string) (net.Conn, error), ok bool) { + var d *net.Dialer + + if d, ok = opts.Context.Value(httpDialerKey{}).(*net.Dialer); ok { + dialerFunc = func(ctx context.Context, addr string) (net.Conn, error) { + return d.DialContext(ctx, "tcp", addr) + } + } + + if opts.ContextDialer != nil { + dialerFunc, ok = opts.ContextDialer, true + } + + return dialerFunc, ok +} + +func defaultHTTPDialer() func(ctx context.Context, addr string) (net.Conn, error) { + return func(ctx context.Context, addr string) (net.Conn, error) { + d := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + } + return d.DialContext(ctx, "tcp", addr) + } +} + +// ----------------------------------------------- Method option ------------------------------------------------------- type methodKey struct{} -// Method pass method option to client Call func Method(m string) client.CallOption { return client.SetCallOption(methodKey{}, m) } +func methodFromOpts(opts client.CallOptions) (string, bool) { + m, ok := opts.Context.Value(methodKey{}).(string) + return m, ok +} + +// ------------------------------------------------ Path option -------------------------------------------------------- type pathKey struct{} -// Path spcecifies path option to client Call func Path(p string) client.CallOption { return client.SetCallOption(pathKey{}, p) } +func pathFromOpts(opts client.CallOptions) (string, bool) { + p, ok := opts.Context.Value(pathKey{}).(string) + return p, ok +} + +// ------------------------------------------------ Body option -------------------------------------------------------- type bodyKey struct{} -// Body specifies body option to client Call func Body(b string) client.CallOption { return client.SetCallOption(bodyKey{}, b) } +func bodyFromOpts(opts client.CallOptions) (string, bool) { + b, ok := opts.Context.Value(bodyKey{}).(string) + return b, ok +} + +// ---------------------------------------------- ErrorMap option ------------------------------------------------------ type errorMapKey struct{} -func ErrorMap(m map[string]interface{}) client.CallOption { +func ErrorMap(m map[string]any) client.CallOption { return client.SetCallOption(errorMapKey{}, m) } -type structTagsKey struct{} - -// StructTags pass tags slice option to client Call -func StructTags(tags []string) client.CallOption { - return client.SetCallOption(structTagsKey{}, tags) -} - -type metadataKey struct{} - -// Metadata pass metadata to client Call -func Metadata(md metadata.Metadata) client.CallOption { - return client.SetCallOption(metadataKey{}, md) +func errorMapFromOpts(opts client.CallOptions) (map[string]any, bool) { + errMap, ok := opts.Context.Value(errorMapKey{}).(map[string]any) + return errMap, ok } +// ------------------------------------------------ Cookie option ------------------------------------------------------ type cookieKey struct{} -// Cookie pass cookie to client Call func Cookie(cookies ...string) client.CallOption { return client.SetCallOption(cookieKey{}, cookies) } +func cookieFromOpts(opts client.CallOptions) ([]string, bool) { + c, ok := opts.Context.Value(cookieKey{}).([]string) + return c, ok +} + +// ------------------------------------------------ Header option ------------------------------------------------------ type headerKey struct{} -// Header pass cookie to client Call func Header(headers ...string) client.CallOption { return client.SetCallOption(headerKey{}, headers) } + +func headerFromOpts(opts client.CallOptions) ([]string, bool) { + h, ok := opts.Context.Value(headerKey{}).([]string) + return h, ok +} diff --git a/request.go b/request.go index 0622422..71257ed 100644 --- a/request.go +++ b/request.go @@ -6,30 +6,10 @@ import ( ) type httpRequest struct { - service string - method string - contentType string - request interface{} - opts client.RequestOptions -} - -func newHTTPRequest(service, method string, request interface{}, contentType string, opts ...client.RequestOption) client.Request { - options := client.NewRequestOptions(opts...) - if len(options.ContentType) == 0 { - options.ContentType = contentType - } - - return &httpRequest{ - service: service, - method: method, - request: request, - contentType: options.ContentType, - opts: options, - } -} - -func (h *httpRequest) ContentType() string { - return h.contentType + service string + method string + request any + opts client.RequestOptions } func (h *httpRequest) Service() string { @@ -44,14 +24,18 @@ func (h *httpRequest) Endpoint() string { return h.method } -func (h *httpRequest) Codec() codec.Codec { - return nil +func (h *httpRequest) ContentType() string { + return h.opts.ContentType } -func (h *httpRequest) Body() interface{} { +func (h *httpRequest) Body() any { return h.request } +func (h *httpRequest) Codec() codec.Codec { + return nil +} + func (h *httpRequest) Stream() bool { return h.opts.Stream } diff --git a/stream.go b/stream.go deleted file mode 100644 index d5b765c..0000000 --- a/stream.go +++ /dev/null @@ -1,175 +0,0 @@ -package http - -import ( - "bufio" - "context" - "fmt" - "io" - "net" - "net/http" - "sync" - - "go.unistack.org/micro/v4/client" - "go.unistack.org/micro/v4/codec" - "go.unistack.org/micro/v4/errors" - "go.unistack.org/micro/v4/logger" -) - -// Implements the streamer interface -type httpStream struct { - err error - conn net.Conn - cf codec.Codec - context context.Context - logger logger.Logger - request client.Request - closed chan bool - reader *bufio.Reader - address string - ct string - opts client.CallOptions - mu sync.RWMutex -} - -var errShutdown = fmt.Errorf("connection is shut down") - -func (h *httpStream) isClosed() bool { - select { - case <-h.closed: - return true - default: - return false - } -} - -func (h *httpStream) Context() context.Context { - return h.context -} - -func (h *httpStream) Request() client.Request { - return h.request -} - -func (h *httpStream) Response() client.Response { - return nil -} - -func (h *httpStream) SendMsg(msg interface{}) error { - return h.Send(msg) -} - -func (h *httpStream) Send(msg interface{}) error { - h.mu.Lock() - defer h.mu.Unlock() - - if h.isClosed() { - h.err = errShutdown - return errShutdown - } - - hreq, err := newRequest(h.context, h.logger, h.address, h.request, h.ct, h.cf, msg, h.opts) - if err != nil { - return err - } - - return hreq.Write(h.conn) -} - -func (h *httpStream) RecvMsg(msg interface{}) error { - return h.Recv(msg) -} - -func (h *httpStream) Recv(msg interface{}) error { - h.mu.Lock() - defer h.mu.Unlock() - - if h.isClosed() { - h.err = errShutdown - return errShutdown - } - - hrsp, err := http.ReadResponse(h.reader, new(http.Request)) - if err != nil { - return errors.InternalServerError("go.micro.client", "%+v", err) - } - defer hrsp.Body.Close() - - return h.parseRsp(h.context, h.logger, hrsp, h.cf, msg, h.opts) -} - -func (h *httpStream) Error() error { - h.mu.RLock() - defer h.mu.RUnlock() - return h.err -} - -func (h *httpStream) CloseSend() error { - return h.Close() -} - -func (h *httpStream) Close() error { - select { - case <-h.closed: - return nil - default: - close(h.closed) - return h.conn.Close() - } -} - -func (h *httpStream) parseRsp(ctx context.Context, log logger.Logger, hrsp *http.Response, cf codec.Codec, rsp interface{}, opts client.CallOptions) error { - var err error - var buf []byte - - // fast path return - if hrsp.StatusCode == http.StatusNoContent { - return nil - } - - select { - case <-ctx.Done(): - err = ctx.Err() - default: - if hrsp.Body != nil { - buf, err = io.ReadAll(hrsp.Body) - if err != nil { - if log.V(logger.ErrorLevel) { - log.Error(ctx, "failed to read body", err) - } - return errors.InternalServerError("go.micro.client", "%s", buf) - } - } - - if log.V(logger.DebugLevel) { - log.Debug(ctx, fmt.Sprintf("response %s with %v", buf, hrsp.Header)) - } - - if hrsp.StatusCode < 400 { - if err = cf.Unmarshal(buf, rsp); err != nil { - return errors.InternalServerError("go.micro.client", "%+v", err) - } - return nil - } - - var rerr interface{} - errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) - if ok && errmap != nil { - if rerr, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok { - rerr, ok = errmap["default"].(error) - } - } - if !ok || rerr == nil { - return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) - } - - if cerr := cf.Unmarshal(buf, rerr); cerr != nil { - return errors.InternalServerError("go.micro.client", "%+v", cerr) - } - - if err, ok = rerr.(error); !ok { - err = &Error{rerr} - } - } - - return err -} diff --git a/util.go b/util.go deleted file mode 100644 index c21a59b..0000000 --- a/util.go +++ /dev/null @@ -1,414 +0,0 @@ -package http - -import ( - "context" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strings" - "sync" - - "go.unistack.org/micro/v4/client" - "go.unistack.org/micro/v4/errors" - "go.unistack.org/micro/v4/logger" - "go.unistack.org/micro/v4/metadata" - rutil "go.unistack.org/micro/v4/util/reflect" -) - -var ( - templateCache = make(map[string][]string) - mu sync.RWMutex -) - -// Error struct holds error -type Error struct { - err interface{} -} - -// Error func for error interface -func (err *Error) Error() string { - return fmt.Sprintf("%v", err.err) -} - -func GetError(err error) interface{} { - if rerr, ok := err.(*Error); ok { - return rerr.err - } - return err -} - -func newPathRequest(path string, method string, body string, msg interface{}, tags []string, parameters map[string]map[string]string) (string, interface{}, error) { - // parse via https://github.com/googleapis/googleapis/blob/master/google/api/http.proto definition - tpl, err := newTemplate(path) - if err != nil { - return "", nil, err - } - - if len(tpl) > 0 && msg == nil { - return "", nil, fmt.Errorf("nil message but path params requested: %v", path) - } - - fieldsmapskip := make(map[string]struct{}) - fieldsmap := make(map[string]string, len(tpl)) - for _, v := range tpl { - var vs, ve int - for i := 0; i < len(v); i++ { - switch v[i] { - case '{': - vs = i + 1 - case '}': - ve = i - } - if ve != 0 { - fieldsmap[v[vs:ve]] = "" - vs = 0 - ve = 0 - } - } - } - - nmsg, err := rutil.Zero(msg) - if err != nil { - return "", nil, err - } - - // we cant switch on message and use proto helpers, to avoid dependency to protobuf - tmsg := reflect.ValueOf(msg) - if tmsg.Kind() == reflect.Ptr { - tmsg = tmsg.Elem() - } - - tnmsg := reflect.ValueOf(nmsg) - if tnmsg.Kind() == reflect.Ptr { - tnmsg = tnmsg.Elem() - } - - values := url.Values{} - // copy cycle - - cleanPath := make(map[string]bool) - for i := 0; i < tmsg.NumField(); i++ { - val := tmsg.Field(i) - if val.IsZero() { - continue - } - fld := tmsg.Type().Field(i) - // Skip unexported fields. - if fld.PkgPath != "" { - continue - } - /* check for empty PkgPath can be replaced with new method IsExported - if !fld.IsExported() { - continue - } - */ - t := &tag{} - for _, tn := range tags { - ts, ok := fld.Tag.Lookup(tn) - if !ok { - continue - } - - tp := strings.Split(ts, ",") - // special - switch tn { - case "protobuf": // special - for _, p := range tp { - if idx := strings.Index(p, "name="); idx > 0 { - t = &tag{key: tn, name: p[idx:]} - } - } - default: - t = &tag{key: tn, name: tp[0]} - } - - if t.name != "" { - break - } - } - - cname := t.name - if cname == "" { - cname = fld.Name - // fallback to lowercase - t.name = strings.ToLower(fld.Name) - } - if _, ok := parameters["header"][cname]; ok { - continue - } - if _, ok := parameters["cookie"][cname]; ok { - continue - } - - if !val.IsValid() || val.IsZero() { - continue - } - - // nolint: gocritic, nestif - if _, ok := fieldsmap[t.name]; ok { - switch val.Type().Kind() { - case reflect.Slice: - for idx := 0; idx < val.Len(); idx++ { - values.Add(t.name, getParam(val.Index(idx))) - } - fieldsmapskip[t.name] = struct{}{} - default: - fieldsmap[t.name] = getParam(val) - } - } else { - for k, v := range fieldsmap { - isSet := false - if v != "" { - continue - } - var clean []string - fld := msg - - parts := strings.Split(k, ".") - - for idx := 0; idx < len(parts); idx++ { - var nfld interface{} - var name string - if tags == nil { - tags = []string{"json"} - } - tagsloop: - for ti := 0; ti < len(tags); ti++ { - name, nfld, err = rutil.StructFieldNameByTag(fld, tags[ti], parts[idx]) - if err == nil { - clean = append(clean, name) - break tagsloop - } - } - if err == nil { - fld = nfld - if len(parts)-1 == idx { - isSet = true - fieldsmap[k] = fmt.Sprintf("%v", fld) - - } - } - } - if isSet { - cleanPath[strings.Join(clean, ".")] = true - } - } - for k := range cleanPath { - if err = rutil.ZeroFieldByPath(nmsg, k); err != nil { - return "", nil, err - } - } - if (body == "*" || body == t.name) && method != http.MethodGet { - if tnmsg.Field(i).CanSet() { - tnmsg.Field(i).Set(val) - } - } else if method == http.MethodGet { - if val.Type().Kind() == reflect.Slice { - for idx := 0; idx < val.Len(); idx++ { - values.Add(t.name, getParam(val.Index(idx))) - } - } else if !rutil.IsEmpty(val) { - values.Add(t.name, getParam(val)) - } - } - - } - } - - // check not filled stuff - for k, v := range fieldsmap { - _, ok := fieldsmapskip[k] - if !ok && v == "" { - return "", nil, fmt.Errorf("path param %s not filled", k) - } - } - - var b strings.Builder - - for _, fld := range tpl { - _, _ = b.WriteRune('/') - // nolint: nestif - var vs, ve, vf int - var pholder bool - for i := 0; i < len(fld); i++ { - switch fld[i] { - case '{': - vs = i + 1 - case '}': - ve = i - } - // nolint: nestif - if vs > 0 && ve != 0 { - if vm, ok := fieldsmap[fld[vs:ve]]; ok { - if vm != "" { - _, _ = b.WriteString(fld[vf : vs-1]) - _, _ = b.WriteString(vm) - vf = ve + 1 - } - } else { - _, _ = b.WriteString(fld) - } - vs = 0 - ve = 0 - pholder = true - } - } - if !pholder { - _, _ = b.WriteString(fld) - } - } - - if len(values) > 0 { - _, _ = b.WriteRune('?') - _, _ = b.WriteString(values.Encode()) - } - - // rutil.ZeroEmpty(tnmsg.Interface()) - - if rutil.IsZero(nmsg) && !isEmptyStruct(nmsg) { - return b.String(), nil, nil - } - - return b.String(), nmsg, nil -} - -func isEmptyStruct(v interface{}) bool { - val := reflect.ValueOf(v) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - return val.Kind() == reflect.Struct && val.NumField() == 0 -} - -func newTemplate(path string) ([]string, error) { - if len(path) == 0 || path[0] != '/' { - return nil, fmt.Errorf("path must starts with /") - } - mu.RLock() - tpl, ok := templateCache[path] - if ok { - mu.RUnlock() - return tpl, nil - } - mu.RUnlock() - - tpl = strings.Split(path[1:], "/") - mu.Lock() - templateCache[path] = tpl - mu.Unlock() - - return tpl, nil -} - -func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp interface{}, opts client.CallOptions) error { - var err error - var buf []byte - - // fast path return - if hrsp.StatusCode == http.StatusNoContent { - return nil - } - - if opts.ResponseMetadata != nil { - *opts.ResponseMetadata = metadata.New(len(hrsp.Header)) - for k, v := range hrsp.Header { - opts.ResponseMetadata.Set(k, strings.Join(v, ",")) - } - } - - select { - case <-ctx.Done(): - err = ctx.Err() - default: - ct := DefaultContentType - - if htype := hrsp.Header.Get("Content-Type"); htype != "" { - ct = htype - } - - if hrsp.Body != nil { - buf, err = io.ReadAll(hrsp.Body) - if err != nil { - if h.opts.Logger.V(logger.ErrorLevel) { - h.opts.Logger.Error(ctx, "failed to read body", err) - } - return errors.InternalServerError("go.micro.client", "%s", buf) - } - } - - cf, cerr := h.newCodec(ct) - if cerr != nil { - if h.opts.Logger.V(logger.DebugLevel) { - h.opts.Logger.Debug(ctx, fmt.Sprintf("response with %v unknown content-type %s %s", hrsp.Header, ct, buf)) - } - return errors.InternalServerError("go.micro.client", "%+v", cerr) - } - - if h.opts.Logger.V(logger.DebugLevel) { - h.opts.Logger.Debug(ctx, fmt.Sprintf("response %s with %v", buf, hrsp.Header)) - } - - // succeseful response - if hrsp.StatusCode < 400 { - if err = cf.Unmarshal(buf, rsp); err != nil { - return errors.InternalServerError("go.micro.client", "%+v", err) - } - return nil - } - - // response with error - var rerr interface{} - errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) - if ok && errmap != nil { - rerr, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)] - if !ok { - rerr, ok = errmap["default"] - } - } - - if !ok || rerr == nil { - return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) - } - - if cerr := cf.Unmarshal(buf, rerr); cerr != nil { - return errors.InternalServerError("go.micro.client", "%+v", cerr) - } - - if err, ok = rerr.(error); !ok { - err = &Error{rerr} - } - - } - - return err -} - -type tag struct { - key string - name string -} - -func getParam(val reflect.Value) string { - var v string - switch val.Kind() { - case reflect.Ptr: - switch reflect.Indirect(val).Type().String() { - case - "wrapperspb.BoolValue", - "wrapperspb.BytesValue", - "wrapperspb.DoubleValue", - "wrapperspb.FloatValue", - "wrapperspb.Int32Value", "wrapperspb.Int64Value", - "wrapperspb.StringValue", - "wrapperspb.UInt32Value", "wrapperspb.UInt64Value": - if eva := reflect.Indirect(val).FieldByName("Value"); eva.IsValid() { - v = getParam(eva) - } - } - default: - v = fmt.Sprintf("%v", val.Interface()) - } - return v -} diff --git a/util_test.go b/util_test.go deleted file mode 100644 index c84e2a2..0000000 --- a/util_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package http - -import ( - "net/http" - "net/url" - "testing" -) - -func TestParsing(t *testing.T) { - type Message struct { - IIN string `protobuf:"bytes,1,opt,name=iin,proto3" json:"iin"` - } - - omsg := &Message{IIN: "5555"} - - for _, m := range []string{"POST"} { - body := "" - path, nmsg, err := newPathRequest("/users/iin/{iin}/push-notifications", m, body, omsg, []string{"protobuf", "json"}, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(path) - if err != nil { - t.Fatal(err) - } - _ = nmsg - if u.Path != "/users/iin/5555/push-notifications" { - t.Fatalf("newPathRequest invalid path %s", u.Path) - } - if nmsg != nil { - t.Fatalf("new message must be nil: %v\n", nmsg) - } - } -} - -func TestNewPathRequest(t *testing.T) { - type Message struct { - Name string `json:"name"` - Val1 string `protobuf:"bytes,1,opt,name=val1,proto3" json:"val1"` - Val3 []string - Val2 int64 - } - - omsg := &Message{Name: "test_name", Val1: "test_val1", Val2: 100, Val3: []string{"slice"}} - - for _, m := range []string{"POST", "PUT", "PATCH", "GET", "DELETE"} { - body := "" - path, nmsg, err := newPathRequest("/v1/test", m, body, omsg, []string{"protobuf", "json"}, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(path) - if err != nil { - t.Fatal(err) - } - switch m { - case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete: - break - case http.MethodGet: - vals := u.Query() - if v, ok := vals["name"]; !ok || v[0] != "test_name" { - t.Fatalf("%s invalid path: %v nmsg: %v", m, path, nmsg) - } - } - - } -} - -func TestNewPathRequestWithEmptyBody(t *testing.T) { - val := struct{}{} - cases := []string{ - "", - "*", - "{}", - "nil", - `{"type": "invalid"}`, - } - - for _, body := range cases { - for _, m := range []string{"POST", "PUT", "PATCH", "GET", "DELETE"} { - path, nmsg, err := newPathRequest("/v1/test", m, body, val, []string{"protobuf", "json"}, nil) - if err != nil { - t.Fatal(err) - } - if nmsg == nil { - t.Fatalf("invalid path: nil nmsg") - } - - u, err := url.Parse(path) - if err != nil { - t.Fatal(err) - } - vals := u.Query() - if len(vals) != 0 { - t.Fatalf("invalid path: %v nmsg: %v", path, nmsg) - } - } - } -} - -func TestNewPathVarRequest(t *testing.T) { - type Message struct { - Name string `json:"name"` - Val1 string `protobuf:"bytes,1,opt,name=val1,proto3" json:"val1"` - Val3 []string - Val2 int64 - } - - omsg := &Message{Name: "test_name", Val1: "test_val1", Val2: 100, Val3: []string{"slice"}} - - for _, m := range []string{"POST", "PUT", "PATCH", "GET", "DELETE"} { - body := "" - if m != "GET" { - body = "*" - } - path, nmsg, err := newPathRequest("/v1/test/{val1}", m, body, omsg, []string{"protobuf", "json"}, nil) - if err != nil { - t.Fatal(err) - } - u, err := url.Parse(path) - if err != nil { - t.Fatal(err) - } - if m != "GET" { - if _, ok := nmsg.(*Message); !ok { - t.Fatalf("invalid nmsg: %#+v\n", nmsg) - } - if nmsg.(*Message).Name != "test_name" { - t.Fatalf("invalid path: %v nmsg: %#+v", path, nmsg) - } - } else { - vals := u.Query() - if v, ok := vals["val2"]; !ok || v[0] != "100" { - t.Fatalf("invalid path: %v nmsg: %v", path, nmsg) - } - } - } -}