mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Replace GraphQL server with Connect RPC
This commit is contained in:
8995
pkg/api/generated.go
8995
pkg/api/generated.go
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func HTTPHandler(resolver *Resolver, gqlEndpoint string) http.Handler {
|
||||
router := mux.NewRouter().SkipClean(true)
|
||||
router.Methods("POST").Handler(
|
||||
handler.NewDefaultServer(NewExecutableSchema(Config{
|
||||
Resolvers: resolver,
|
||||
})),
|
||||
)
|
||||
router.Methods("GET").Handler(playground.Handler("GraphQL Playground", gqlEndpoint))
|
||||
|
||||
return router
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/oklog/ulid"
|
||||
)
|
||||
|
||||
func MarshalULID(u ulid.ULID) graphql.Marshaler {
|
||||
return graphql.WriterFunc(func(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(u.String()))
|
||||
})
|
||||
}
|
||||
|
||||
func UnmarshalULID(v interface{}) (ulid.ULID, error) {
|
||||
rawULID, ok := v.(string)
|
||||
if !ok {
|
||||
return ulid.ULID{}, fmt.Errorf("ulid must be a string")
|
||||
}
|
||||
|
||||
u, err := ulid.Parse(rawULID)
|
||||
if err != nil {
|
||||
return ulid.ULID{}, fmt.Errorf("failed to parse ULID: %w", err)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func MarshalURL(u *url.URL) graphql.Marshaler {
|
||||
return graphql.WriterFunc(func(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(u.String()))
|
||||
})
|
||||
}
|
||||
|
||||
func UnmarshalURL(v interface{}) (*url.URL, error) {
|
||||
rawURL, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("url must be a string")
|
||||
}
|
||||
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
type HTTPHeaders []HTTPHeader
|
||||
|
||||
func (h HTTPHeaders) Len() int {
|
||||
return len(h)
|
||||
}
|
||||
|
||||
func (h HTTPHeaders) Less(i, j int) bool {
|
||||
return h[i].Key < h[j].Key
|
||||
}
|
||||
|
||||
func (h HTTPHeaders) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
)
|
||||
|
||||
type CancelRequestResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type CancelResponseResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type ClearHTTPRequestLogResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type CloseProjectResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type DeleteProjectResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type DeleteSenderRequestsResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type HTTPHeader struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type HTTPHeaderInput struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type HTTPRequest struct {
|
||||
ID ulid.ULID `json:"id"`
|
||||
URL *url.URL `json:"url"`
|
||||
Method HTTPMethod `json:"method"`
|
||||
Proto HTTPProtocol `json:"proto"`
|
||||
Headers []HTTPHeader `json:"headers"`
|
||||
Body *string `json:"body"`
|
||||
Response *HTTPResponse `json:"response"`
|
||||
}
|
||||
|
||||
type HTTPRequestLog struct {
|
||||
ID ulid.ULID `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Method HTTPMethod `json:"method"`
|
||||
Proto string `json:"proto"`
|
||||
Headers []HTTPHeader `json:"headers"`
|
||||
Body *string `json:"body"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Response *HTTPResponseLog `json:"response"`
|
||||
}
|
||||
|
||||
type HTTPRequestLogFilter struct {
|
||||
OnlyInScope bool `json:"onlyInScope"`
|
||||
SearchExpression *string `json:"searchExpression"`
|
||||
}
|
||||
|
||||
type HTTPRequestLogFilterInput struct {
|
||||
OnlyInScope *bool `json:"onlyInScope"`
|
||||
SearchExpression *string `json:"searchExpression"`
|
||||
}
|
||||
|
||||
type HTTPResponse struct {
|
||||
// Will be the same ID as its related request ID.
|
||||
ID ulid.ULID `json:"id"`
|
||||
Proto HTTPProtocol `json:"proto"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
StatusReason string `json:"statusReason"`
|
||||
Body *string `json:"body"`
|
||||
Headers []HTTPHeader `json:"headers"`
|
||||
}
|
||||
|
||||
type HTTPResponseLog struct {
|
||||
// Will be the same ID as its related request ID.
|
||||
ID ulid.ULID `json:"id"`
|
||||
Proto HTTPProtocol `json:"proto"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
StatusReason string `json:"statusReason"`
|
||||
Body *string `json:"body"`
|
||||
Headers []HTTPHeader `json:"headers"`
|
||||
}
|
||||
|
||||
type InterceptSettings struct {
|
||||
RequestsEnabled bool `json:"requestsEnabled"`
|
||||
ResponsesEnabled bool `json:"responsesEnabled"`
|
||||
RequestFilter *string `json:"requestFilter"`
|
||||
ResponseFilter *string `json:"responseFilter"`
|
||||
}
|
||||
|
||||
type ModifyRequestInput struct {
|
||||
ID ulid.ULID `json:"id"`
|
||||
URL *url.URL `json:"url"`
|
||||
Method HTTPMethod `json:"method"`
|
||||
Proto HTTPProtocol `json:"proto"`
|
||||
Headers []HTTPHeaderInput `json:"headers"`
|
||||
Body *string `json:"body"`
|
||||
ModifyResponse *bool `json:"modifyResponse"`
|
||||
}
|
||||
|
||||
type ModifyRequestResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type ModifyResponseInput struct {
|
||||
RequestID ulid.ULID `json:"requestID"`
|
||||
Proto HTTPProtocol `json:"proto"`
|
||||
Headers []HTTPHeaderInput `json:"headers"`
|
||||
Body *string `json:"body"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
StatusReason string `json:"statusReason"`
|
||||
}
|
||||
|
||||
type ModifyResponseResult struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID ulid.ULID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsActive bool `json:"isActive"`
|
||||
Settings *ProjectSettings `json:"settings"`
|
||||
}
|
||||
|
||||
type ProjectSettings struct {
|
||||
Intercept *InterceptSettings `json:"intercept"`
|
||||
}
|
||||
|
||||
type ScopeHeader struct {
|
||||
Key *string `json:"key"`
|
||||
Value *string `json:"value"`
|
||||
}
|
||||
|
||||
type ScopeHeaderInput struct {
|
||||
Key *string `json:"key"`
|
||||
Value *string `json:"value"`
|
||||
}
|
||||
|
||||
type ScopeRule struct {
|
||||
URL *string `json:"url"`
|
||||
Header *ScopeHeader `json:"header"`
|
||||
Body *string `json:"body"`
|
||||
}
|
||||
|
||||
type ScopeRuleInput struct {
|
||||
URL *string `json:"url"`
|
||||
Header *ScopeHeaderInput `json:"header"`
|
||||
Body *string `json:"body"`
|
||||
}
|
||||
|
||||
type SenderRequest struct {
|
||||
ID ulid.ULID `json:"id"`
|
||||
SourceRequestLogID *ulid.ULID `json:"sourceRequestLogID"`
|
||||
URL *url.URL `json:"url"`
|
||||
Method HTTPMethod `json:"method"`
|
||||
Proto HTTPProtocol `json:"proto"`
|
||||
Headers []HTTPHeader `json:"headers"`
|
||||
Body *string `json:"body"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Response *HTTPResponseLog `json:"response"`
|
||||
}
|
||||
|
||||
type SenderRequestFilter struct {
|
||||
OnlyInScope bool `json:"onlyInScope"`
|
||||
SearchExpression *string `json:"searchExpression"`
|
||||
}
|
||||
|
||||
type SenderRequestFilterInput struct {
|
||||
OnlyInScope *bool `json:"onlyInScope"`
|
||||
SearchExpression *string `json:"searchExpression"`
|
||||
}
|
||||
|
||||
type SenderRequestInput struct {
|
||||
ID *ulid.ULID `json:"id"`
|
||||
URL *url.URL `json:"url"`
|
||||
Method *HTTPMethod `json:"method"`
|
||||
Proto *HTTPProtocol `json:"proto"`
|
||||
Headers []HTTPHeaderInput `json:"headers"`
|
||||
Body *string `json:"body"`
|
||||
}
|
||||
|
||||
type UpdateInterceptSettingsInput struct {
|
||||
RequestsEnabled bool `json:"requestsEnabled"`
|
||||
ResponsesEnabled bool `json:"responsesEnabled"`
|
||||
RequestFilter *string `json:"requestFilter"`
|
||||
ResponseFilter *string `json:"responseFilter"`
|
||||
}
|
||||
|
||||
type HTTPMethod string
|
||||
|
||||
const (
|
||||
HTTPMethodGet HTTPMethod = "GET"
|
||||
HTTPMethodHead HTTPMethod = "HEAD"
|
||||
HTTPMethodPost HTTPMethod = "POST"
|
||||
HTTPMethodPut HTTPMethod = "PUT"
|
||||
HTTPMethodDelete HTTPMethod = "DELETE"
|
||||
HTTPMethodConnect HTTPMethod = "CONNECT"
|
||||
HTTPMethodOptions HTTPMethod = "OPTIONS"
|
||||
HTTPMethodTrace HTTPMethod = "TRACE"
|
||||
HTTPMethodPatch HTTPMethod = "PATCH"
|
||||
)
|
||||
|
||||
var AllHTTPMethod = []HTTPMethod{
|
||||
HTTPMethodGet,
|
||||
HTTPMethodHead,
|
||||
HTTPMethodPost,
|
||||
HTTPMethodPut,
|
||||
HTTPMethodDelete,
|
||||
HTTPMethodConnect,
|
||||
HTTPMethodOptions,
|
||||
HTTPMethodTrace,
|
||||
HTTPMethodPatch,
|
||||
}
|
||||
|
||||
func (e HTTPMethod) IsValid() bool {
|
||||
switch e {
|
||||
case HTTPMethodGet, HTTPMethodHead, HTTPMethodPost, HTTPMethodPut, HTTPMethodDelete, HTTPMethodConnect, HTTPMethodOptions, HTTPMethodTrace, HTTPMethodPatch:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e HTTPMethod) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *HTTPMethod) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = HTTPMethod(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid HttpMethod", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e HTTPMethod) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type HTTPProtocol string
|
||||
|
||||
const (
|
||||
HTTPProtocolHTTP10 HTTPProtocol = "HTTP10"
|
||||
HTTPProtocolHTTP11 HTTPProtocol = "HTTP11"
|
||||
HTTPProtocolHTTP20 HTTPProtocol = "HTTP20"
|
||||
)
|
||||
|
||||
var AllHTTPProtocol = []HTTPProtocol{
|
||||
HTTPProtocolHTTP10,
|
||||
HTTPProtocolHTTP11,
|
||||
HTTPProtocolHTTP20,
|
||||
}
|
||||
|
||||
func (e HTTPProtocol) IsValid() bool {
|
||||
switch e {
|
||||
case HTTPProtocolHTTP10, HTTPProtocolHTTP11, HTTPProtocolHTTP20:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e HTTPProtocol) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *HTTPProtocol) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = HTTPProtocol(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid HttpProtocol", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e HTTPProtocol) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
1005
pkg/api/resolvers.go
1005
pkg/api/resolvers.go
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,12 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
)
|
||||
@ -32,13 +30,13 @@ func projectsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func projectBucket(tx *bolt.Tx, projectID []byte) (*bolt.Bucket, error) {
|
||||
func projectBucket(tx *bolt.Tx, projectID string) (*bolt.Bucket, error) {
|
||||
pb, err := projectsBucket(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := pb.Bucket(projectID[:])
|
||||
b := pb.Bucket([]byte(projectID))
|
||||
if b == nil {
|
||||
return nil, ErrProjectBucketNotFound
|
||||
}
|
||||
@ -46,21 +44,19 @@ func projectBucket(tx *bolt.Tx, projectID []byte) (*bolt.Bucket, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (db *Database) UpsertProject(ctx context.Context, project proj.Project) error {
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
err := gob.NewEncoder(&buf).Encode(project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to encode project: %w", err)
|
||||
}
|
||||
|
||||
err = db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
b, err := createNestedBucket(tx, projectsBucketName, project.ID[:])
|
||||
func (db *Database) UpsertProject(ctx context.Context, project *proj.Project) error {
|
||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
b, err := createNestedBucket(tx, projectsBucketName, []byte(project.Id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to create project bucket: %w", err)
|
||||
}
|
||||
|
||||
err = b.Put(projectKey, buf.Bytes())
|
||||
buf, err := proto.Marshal(project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to marshal project: %w", err)
|
||||
}
|
||||
|
||||
err = b.Put(projectKey, buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to upsert project: %w", err)
|
||||
}
|
||||
@ -84,9 +80,11 @@ func (db *Database) UpsertProject(ctx context.Context, project proj.Project) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) FindProjectByID(ctx context.Context, projectID ulid.ULID) (project proj.Project, err error) {
|
||||
err = db.bolt.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := projectBucket(tx, projectID[:])
|
||||
func (db *Database) FindProjectByID(ctx context.Context, projectID string) (*proj.Project, error) {
|
||||
project := &proj.Project{}
|
||||
|
||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := projectBucket(tx, projectID)
|
||||
if errors.Is(err, ErrProjectsBucketNotFound) || errors.Is(err, ErrProjectBucketNotFound) {
|
||||
return proj.ErrProjectNotFound
|
||||
}
|
||||
@ -99,28 +97,28 @@ func (db *Database) FindProjectByID(ctx context.Context, projectID ulid.ULID) (p
|
||||
return proj.ErrProjectNotFound
|
||||
}
|
||||
|
||||
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
|
||||
err = proto.Unmarshal(rawProject, project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode project: %w", err)
|
||||
return fmt.Errorf("failed to unmarshal project: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return proj.Project{}, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||
return nil, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
|
||||
func (db *Database) DeleteProject(ctx context.Context, projectID string) error {
|
||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
pb, err := projectsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pb.DeleteBucket(projectID[:])
|
||||
err = pb.DeleteBucket([]byte(projectID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete project bucket: %w", err)
|
||||
}
|
||||
@ -134,8 +132,8 @@ func (db *Database) DeleteProject(ctx context.Context, projectID ulid.ULID) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) Projects(ctx context.Context) ([]proj.Project, error) {
|
||||
projects := make([]proj.Project, 0)
|
||||
func (db *Database) Projects(ctx context.Context) ([]*proj.Project, error) {
|
||||
projects := make([]*proj.Project, 0)
|
||||
|
||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||
pb, err := projectsBucket(tx)
|
||||
@ -144,7 +142,7 @@ func (db *Database) Projects(ctx context.Context) ([]proj.Project, error) {
|
||||
}
|
||||
|
||||
err = pb.ForEachBucket(func(projectID []byte) error {
|
||||
bucket, err := projectBucket(tx, projectID)
|
||||
bucket, err := projectBucket(tx, string(projectID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -154,16 +152,16 @@ func (db *Database) Projects(ctx context.Context) ([]proj.Project, error) {
|
||||
return proj.ErrProjectNotFound
|
||||
}
|
||||
|
||||
var project proj.Project
|
||||
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
|
||||
project := &proj.Project{}
|
||||
err = proto.Unmarshal(rawProject, project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to decode project: %w", err)
|
||||
return fmt.Errorf("failed to unmarshal project: %w", err)
|
||||
}
|
||||
projects = append(projects, project)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to iterate over projects: %w", err)
|
||||
return fmt.Errorf("failed to iterate over projects: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -1,40 +1,21 @@
|
||||
package bolt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.etcd.io/bbolt"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/testutil"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var regexpCompareOpt = cmp.Comparer(func(x, y *regexp.Regexp) bool {
|
||||
switch {
|
||||
case x == nil && y == nil:
|
||||
return true
|
||||
case x == nil || y == nil:
|
||||
return false
|
||||
default:
|
||||
return x.String() == y.String()
|
||||
}
|
||||
})
|
||||
|
||||
func TestUpsertProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -50,27 +31,20 @@ func TestUpsertProject(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
searchExpr, err := filter.ParseQuery("foo AND bar OR NOT baz")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
||||
exp := proj.Project{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
Name: "foobar",
|
||||
Settings: proj.Settings{
|
||||
ReqLogBypassOutOfScope: true,
|
||||
ReqLogOnlyFindInScope: true,
|
||||
ReqLogSearchExpr: searchExpr,
|
||||
ScopeRules: []scope.Rule{
|
||||
{
|
||||
URL: regexp.MustCompile("^https://(.*)example.com(.*)$"),
|
||||
Header: scope.Header{
|
||||
Key: regexp.MustCompile("^X-Foo(.*)$"),
|
||||
Value: regexp.MustCompile("^foo(.*)$"),
|
||||
},
|
||||
Body: regexp.MustCompile("^foo(.*)"),
|
||||
},
|
||||
exp := &proj.Project{
|
||||
Id: "foobar-project-id",
|
||||
Name: "foobar",
|
||||
ReqLogBypassOutOfScope: true,
|
||||
ReqLogFilter: &reqlog.RequestLogsFilter{
|
||||
OnlyInScope: true,
|
||||
SearchExpr: "foo AND bar OR NOT baz",
|
||||
},
|
||||
ScopeRules: []*scope.ScopeRule{
|
||||
{
|
||||
UrlRegexp: "^https://(.*)example.com(.*)$",
|
||||
HeaderKeyRegexp: "^X-Foo(.*)$",
|
||||
HeaderValueRegexp: "^foo(.*)$",
|
||||
BodyRegexp: "^foo(.*)",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -83,7 +57,7 @@ func TestUpsertProject(t *testing.T) {
|
||||
var rawProject []byte
|
||||
|
||||
err = boltDB.View(func(tx *bbolt.Tx) error {
|
||||
rawProject = tx.Bucket([]byte("projects")).Bucket(exp.ID[:]).Get([]byte("project"))
|
||||
rawProject = tx.Bucket([]byte("projects")).Bucket([]byte(exp.Id)).Get([]byte("project"))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -93,16 +67,14 @@ func TestUpsertProject(t *testing.T) {
|
||||
t.Fatalf("expected raw project to be retrieved, got: nil")
|
||||
}
|
||||
|
||||
got := proj.Project{}
|
||||
got := &proj.Project{}
|
||||
|
||||
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&got)
|
||||
err = proto.Unmarshal(rawProject, got)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error decoding project: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got, regexpCompareOpt, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
|
||||
t.Fatalf("project not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "project not equal", exp, got, "id")
|
||||
}
|
||||
|
||||
func TestFindProjectByID(t *testing.T) {
|
||||
@ -123,36 +95,32 @@ func TestFindProjectByID(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
exp := proj.Project{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
exp := &proj.Project{
|
||||
Id: ulid.Make().String(),
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
err = gob.NewEncoder(&buf).Encode(exp)
|
||||
buf, err := proto.Marshal(exp)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error encoding project: %v", err)
|
||||
}
|
||||
|
||||
err = boltDB.Update(func(tx *bbolt.Tx) error {
|
||||
b, err := tx.Bucket([]byte("projects")).CreateBucket(exp.ID[:])
|
||||
b, err := tx.Bucket([]byte("projects")).CreateBucket([]byte(exp.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte("project"), buf.Bytes())
|
||||
return b.Put([]byte("project"), buf)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error setting project: %v", err)
|
||||
}
|
||||
|
||||
got, err := db.FindProjectByID(context.Background(), exp.ID)
|
||||
got, err := db.FindProjectByID(context.Background(), exp.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error finding project: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
|
||||
t.Fatalf("project not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "project not equal", exp, got)
|
||||
})
|
||||
|
||||
t.Run("project not found", func(t *testing.T) {
|
||||
@ -170,7 +138,7 @@ func TestFindProjectByID(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
projectID := ulid.Make().String()
|
||||
|
||||
_, err = db.FindProjectByID(context.Background(), projectID)
|
||||
if !errors.Is(err, proj.ErrProjectNotFound) {
|
||||
@ -195,9 +163,9 @@ func TestDeleteProject(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
// Insert test fixture.
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
projectID := ulid.Make().String()
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing project: %v", err)
|
||||
@ -209,8 +177,8 @@ func TestDeleteProject(t *testing.T) {
|
||||
}
|
||||
|
||||
var got *bbolt.Bucket
|
||||
err = boltDB.View(func(tx *bbolt.Tx) error {
|
||||
got = tx.Bucket([]byte("projects")).Bucket(projectID[:])
|
||||
_ = boltDB.View(func(tx *bbolt.Tx) error {
|
||||
got = tx.Bucket([]byte("projects")).Bucket([]byte(projectID))
|
||||
return nil
|
||||
})
|
||||
if got != nil {
|
||||
@ -233,13 +201,13 @@ func TestProjects(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
exp := []proj.Project{
|
||||
exp := []*proj.Project{
|
||||
{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
Id: ulid.Make().String(),
|
||||
Name: "one",
|
||||
},
|
||||
{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
|
||||
Id: ulid.Make().String(),
|
||||
Name: "two",
|
||||
},
|
||||
}
|
||||
@ -261,7 +229,5 @@ func TestProjects(t *testing.T) {
|
||||
t.Fatalf("expected %v projects, got: %v", len(exp), len(got))
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got, cmpopts.IgnoreUnexported(proj.Project{})); diff != "" {
|
||||
t.Fatalf("projects not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoSlicesDiff(t, "projects not equal", exp, got)
|
||||
}
|
||||
|
@ -1,25 +1,23 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
var ErrRequestLogsBucketNotFound = errors.New("bolt: request logs bucket not found")
|
||||
|
||||
var reqLogsBucketName = []byte("request_logs")
|
||||
|
||||
func requestLogsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
|
||||
pb, err := projectBucket(tx, projectID[:])
|
||||
func requestLogsBucket(tx *bolt.Tx, projectID string) (*bolt.Bucket, error) {
|
||||
pb, err := projectBucket(tx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -32,47 +30,36 @@ func requestLogsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (db *Database) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scope *scope.Scope) (reqLogs []reqlog.RequestLog, err error) {
|
||||
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return nil, reqlog.ErrProjectIDMustBeSet
|
||||
}
|
||||
|
||||
func (db *Database) FindRequestLogs(ctx context.Context, projectID string, filterFn func(*reqlog.HttpRequestLog) (bool, error)) (reqLogs []*reqlog.HttpRequestLog, err error) {
|
||||
tx, err := db.bolt.Begin(false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bolt: failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
b, err := requestLogsBucket(tx, filter.ProjectID)
|
||||
b, err := requestLogsBucket(tx, projectID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bolt: failed to get request logs bucket: %w", err)
|
||||
}
|
||||
|
||||
err = b.ForEach(func(reqLogID, rawReqLog []byte) error {
|
||||
var reqLog reqlog.RequestLog
|
||||
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
|
||||
var reqLog reqlog.HttpRequestLog
|
||||
err = proto.Unmarshal(rawReqLog, &reqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode request log: %w", err)
|
||||
}
|
||||
|
||||
if filter.OnlyInScope && !reqLog.MatchScope(scope) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter by search expression. TODO: Once pagination is introduced,
|
||||
// this filter logic should be done as items are retrieved.
|
||||
if filter.SearchExpr != nil {
|
||||
match, err := reqLog.Matches(filter.SearchExpr)
|
||||
if filterFn != nil {
|
||||
match, err := filterFn(&reqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to match search expression for request log (id: %v): %w", reqLogID, err)
|
||||
return fmt.Errorf("failed to filter request log: %w", err)
|
||||
}
|
||||
|
||||
if !match {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
reqLogs = append(reqLogs, reqLog)
|
||||
reqLogs = append(reqLogs, &reqLog)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -87,46 +74,45 @@ func (db *Database) FindRequestLogs(ctx context.Context, filter reqlog.FindReque
|
||||
return reqLogs, nil
|
||||
}
|
||||
|
||||
func (db *Database) FindRequestLogByID(ctx context.Context, projectID, reqLogID ulid.ULID) (reqLog reqlog.RequestLog, err error) {
|
||||
err = db.bolt.View(func(tx *bolt.Tx) error {
|
||||
func (db *Database) FindRequestLogByID(ctx context.Context, projectID, reqLogID string) (*reqlog.HttpRequestLog, error) {
|
||||
reqLog := &reqlog.HttpRequestLog{}
|
||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||
b, err := requestLogsBucket(tx, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to get request logs bucket: %w", err)
|
||||
}
|
||||
rawReqLog := b.Get(reqLogID[:])
|
||||
rawReqLog := b.Get([]byte(reqLogID))
|
||||
if rawReqLog == nil {
|
||||
return reqlog.ErrRequestNotFound
|
||||
return reqlog.ErrRequestLogNotFound
|
||||
}
|
||||
|
||||
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
|
||||
err = proto.Unmarshal(rawReqLog, reqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode request log: %w", err)
|
||||
return fmt.Errorf("failed to unmarshal request log: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return reqlog.RequestLog{}, fmt.Errorf("bolt: failed to find request log by ID: %w", err)
|
||||
return nil, fmt.Errorf("bolt: failed to find request log by ID: %w", err)
|
||||
}
|
||||
|
||||
return reqLog, nil
|
||||
}
|
||||
|
||||
func (db *Database) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
err := gob.NewEncoder(&buf).Encode(reqLog)
|
||||
func (db *Database) StoreRequestLog(ctx context.Context, reqLog *reqlog.HttpRequestLog) error {
|
||||
encReqLog, err := proto.Marshal(reqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to encode request log: %w", err)
|
||||
return fmt.Errorf("bolt: failed to marshal request log: %w", err)
|
||||
}
|
||||
|
||||
err = db.bolt.Update(func(txn *bolt.Tx) error {
|
||||
b, err := requestLogsBucket(txn, reqLog.ProjectID)
|
||||
b, err := requestLogsBucket(txn, reqLog.ProjectId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get request logs bucket: %w", err)
|
||||
}
|
||||
|
||||
err = b.Put(reqLog.ID[:], buf.Bytes())
|
||||
err = b.Put([]byte(reqLog.Id), encReqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put request log: %w", err)
|
||||
}
|
||||
@ -140,40 +126,32 @@ func (db *Database) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) StoreResponseLog(ctx context.Context, projectID, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
err := gob.NewEncoder(&buf).Encode(resLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to encode response log: %w", err)
|
||||
}
|
||||
|
||||
err = db.bolt.Update(func(txn *bolt.Tx) error {
|
||||
func (db *Database) StoreResponseLog(ctx context.Context, projectID, reqLogID string, resLog *http.Response) error {
|
||||
err := db.bolt.Update(func(txn *bolt.Tx) error {
|
||||
b, err := requestLogsBucket(txn, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get request logs bucket: %w", err)
|
||||
}
|
||||
|
||||
rawReqLog := b.Get(reqLogID[:])
|
||||
if rawReqLog == nil {
|
||||
return reqlog.ErrRequestNotFound
|
||||
encReqLog := b.Get([]byte(reqLogID))
|
||||
if encReqLog == nil {
|
||||
return reqlog.ErrRequestLogNotFound
|
||||
}
|
||||
|
||||
var reqLog reqlog.RequestLog
|
||||
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
|
||||
var reqLog reqlog.HttpRequestLog
|
||||
err = proto.Unmarshal(encReqLog, &reqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode request log: %w", err)
|
||||
}
|
||||
|
||||
reqLog.Response = &resLog
|
||||
reqLog.Response = resLog
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
err = gob.NewEncoder(&buf).Encode(reqLog)
|
||||
encReqLog, err = proto.Marshal(&reqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode request log: %w", err)
|
||||
}
|
||||
|
||||
err = b.Put(reqLog.ID[:], buf.Bytes())
|
||||
err = b.Put([]byte(reqLogID), encReqLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put request log: %w", err)
|
||||
}
|
||||
@ -187,9 +165,9 @@ func (db *Database) StoreResponseLog(ctx context.Context, projectID, reqLogID ul
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
|
||||
func (db *Database) ClearRequestLogs(ctx context.Context, projectID string) error {
|
||||
err := db.bolt.Update(func(txn *bolt.Tx) error {
|
||||
pb, err := projectBucket(txn, projectID[:])
|
||||
pb, err := projectBucket(txn, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get project bucket: %w", err)
|
||||
}
|
||||
|
@ -2,47 +2,21 @@ package bolt_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/testutil"
|
||||
)
|
||||
|
||||
func TestFindRequestLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("without project ID in filter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := t.TempDir() + "bolt.db"
|
||||
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open bolt database: %v", err)
|
||||
}
|
||||
|
||||
db, err := bolt.DatabaseFromBoltDB(boltDB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
filter := reqlog.FindRequestsFilter{}
|
||||
|
||||
_, err = db.FindRequestLogs(context.Background(), filter, nil)
|
||||
if !errors.Is(err, reqlog.ErrProjectIDMustBeSet) {
|
||||
t.Fatalf("expected `reqlog.ErrProjectIDMustBeSet`, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("returns request logs and related response logs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -58,44 +32,56 @@ func TestFindRequestLogs(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
projectID := ulid.Make().String()
|
||||
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
}
|
||||
|
||||
fixtures := []reqlog.RequestLog{
|
||||
fixtures := []*reqlog.HttpRequestLog{
|
||||
{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
ProjectID: projectID,
|
||||
URL: mustParseURL(t, "https://example.com/foobar"),
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"baz"},
|
||||
Id: ulid.Make().String(),
|
||||
ProjectId: projectID,
|
||||
Request: &http.Request{
|
||||
Url: "https://example.com/foobar",
|
||||
Method: http.Method_METHOD_POST,
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP11,
|
||||
Headers: []*http.Header{
|
||||
{Key: "X-Foo", Value: "baz"},
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
Proto: "HTTP/1.1",
|
||||
Response: &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Header: http.Header{
|
||||
"X-Yolo": []string{"swag"},
|
||||
Headers: []*http.Header{
|
||||
{Key: "X-Yolo", Value: "swag"},
|
||||
},
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
|
||||
ProjectID: projectID,
|
||||
URL: mustParseURL(t, "https://example.com/foo?bar=baz"),
|
||||
Method: http.MethodGet,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"baz"},
|
||||
Id: ulid.Make().String(),
|
||||
ProjectId: projectID,
|
||||
Request: &http.Request{
|
||||
Url: "https://example.com/foo?bar=baz",
|
||||
Method: http.Method_METHOD_GET,
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP11,
|
||||
Headers: []*http.Header{
|
||||
{Key: "X-Foo", Value: "baz"},
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
Response: &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Headers: []*http.Header{
|
||||
{Key: "X-Yolo", Value: "swag"},
|
||||
},
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -108,34 +94,17 @@ func TestFindRequestLogs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
filter := reqlog.FindRequestsFilter{
|
||||
ProjectID: projectID,
|
||||
}
|
||||
|
||||
got, err := db.FindRequestLogs(context.Background(), filter, nil)
|
||||
got, err := db.FindRequestLogs(context.Background(), projectID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error finding request logs: %v", err)
|
||||
}
|
||||
|
||||
// We expect the found request logs are *reversed*, e.g. newest first.
|
||||
exp := make([]reqlog.RequestLog, len(fixtures))
|
||||
exp := make([]*reqlog.HttpRequestLog, len(fixtures))
|
||||
for i, j := 0, len(fixtures)-1; i < j; i, j = i+1, j-1 {
|
||||
exp[i], exp[j] = fixtures[j], fixtures[i]
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("request logs not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoSlicesDiff(t, "request logs not equal", exp, got)
|
||||
})
|
||||
}
|
||||
|
||||
func mustParseURL(t *testing.T, s string) *url.URL {
|
||||
t.Helper()
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
@ -1,16 +1,13 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
@ -18,8 +15,8 @@ var ErrSenderRequestsBucketNotFound = errors.New("bolt: sender requests bucket n
|
||||
|
||||
var senderReqsBucketName = []byte("sender_requests")
|
||||
|
||||
func senderReqsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
|
||||
pb, err := projectBucket(tx, projectID[:])
|
||||
func senderReqsBucket(tx *bolt.Tx, projectID string) (*bolt.Bucket, error) {
|
||||
pb, err := projectBucket(tx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -32,21 +29,19 @@ func senderReqsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (db *Database) StoreSenderRequest(ctx context.Context, req sender.Request) error {
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
err := gob.NewEncoder(&buf).Encode(req)
|
||||
func (db *Database) StoreSenderRequest(ctx context.Context, req *sender.Request) error {
|
||||
rawReq, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bolt: failed to encode sender request: %w", err)
|
||||
return fmt.Errorf("bolt: failed to marshal sender request: %w", err)
|
||||
}
|
||||
|
||||
err = db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
senderReqsBucket, err := senderReqsBucket(tx, req.ProjectID)
|
||||
senderReqsBucket, err := senderReqsBucket(tx, req.ProjectId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sender requests bucket: %w", err)
|
||||
}
|
||||
|
||||
err = senderReqsBucket.Put(req.ID[:], buf.Bytes())
|
||||
err = senderReqsBucket.Put([]byte(req.Id), rawReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put sender request: %w", err)
|
||||
}
|
||||
@ -60,9 +55,9 @@ func (db *Database) StoreSenderRequest(ctx context.Context, req sender.Request)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) FindSenderRequestByID(ctx context.Context, projectID, senderReqID ulid.ULID) (req sender.Request, err error) {
|
||||
if projectID.Compare(ulid.ULID{}) == 0 {
|
||||
return sender.Request{}, sender.ErrProjectIDMustBeSet
|
||||
func (db *Database) FindSenderRequestByID(ctx context.Context, projectID, senderReqID string) (req *sender.Request, err error) {
|
||||
if projectID == "" {
|
||||
return nil, sender.ErrProjectIDMustBeSet
|
||||
}
|
||||
|
||||
err = db.bolt.View(func(tx *bolt.Tx) error {
|
||||
@ -71,63 +66,49 @@ func (db *Database) FindSenderRequestByID(ctx context.Context, projectID, sender
|
||||
return fmt.Errorf("failed to get sender requests bucket: %w", err)
|
||||
}
|
||||
|
||||
rawSenderReq := senderReqsBucket.Get(senderReqID[:])
|
||||
rawSenderReq := senderReqsBucket.Get([]byte(senderReqID))
|
||||
if rawSenderReq == nil {
|
||||
return sender.ErrRequestNotFound
|
||||
}
|
||||
|
||||
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
|
||||
req = &sender.Request{}
|
||||
err = proto.Unmarshal(rawSenderReq, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode sender request: %w", err)
|
||||
return fmt.Errorf("failed to unmarshal sender request: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return sender.Request{}, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||
return nil, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (db *Database) FindSenderRequests(ctx context.Context, filter sender.FindRequestsFilter, scope *scope.Scope) (reqs []sender.Request, err error) {
|
||||
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return nil, sender.ErrProjectIDMustBeSet
|
||||
}
|
||||
|
||||
func (db *Database) FindSenderRequests(ctx context.Context, projectID string, filterFn func(req *sender.Request) (bool, error)) (reqs []*sender.Request, err error) {
|
||||
tx, err := db.bolt.Begin(false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bolt: failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
b, err := senderReqsBucket(tx, filter.ProjectID)
|
||||
b, err := senderReqsBucket(tx, projectID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get sender requests bucket: %w", err)
|
||||
}
|
||||
|
||||
err = b.ForEach(func(senderReqID, rawSenderReq []byte) error {
|
||||
var req sender.Request
|
||||
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
|
||||
req := &sender.Request{}
|
||||
err = proto.Unmarshal(rawSenderReq, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode sender request: %w", err)
|
||||
return fmt.Errorf("failed to unmarshal sender request: %w", err)
|
||||
}
|
||||
|
||||
if filter.OnlyInScope {
|
||||
if !req.MatchScope(scope) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by search expression. TODO: Once pagination is introduced,
|
||||
// this filter logic should be done as items are retrieved.
|
||||
if filter.SearchExpr != nil {
|
||||
match, err := req.Matches(filter.SearchExpr)
|
||||
if filterFn != nil {
|
||||
match, err := filterFn(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"bolt: failed to match search expression for sender request (id: %v): %w",
|
||||
senderReqID, err,
|
||||
)
|
||||
return fmt.Errorf("failed to filter sender request: %w", err)
|
||||
}
|
||||
|
||||
if !match {
|
||||
@ -150,7 +131,7 @@ func (db *Database) FindSenderRequests(ctx context.Context, filter sender.FindRe
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
func (db *Database) DeleteSenderRequests(ctx context.Context, projectID string) error {
|
||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||
senderReqsBucket, err := senderReqsBucket(tx, projectID)
|
||||
if err != nil {
|
||||
|
@ -3,19 +3,17 @@ package bolt_test
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
"github.com/dstotijn/hetty/pkg/testutil"
|
||||
)
|
||||
|
||||
var exampleURL = func() *url.URL {
|
||||
@ -43,11 +41,11 @@ func TestFindRequestByID(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
projectID := "foobar-project-id"
|
||||
reqID := "foobar-req-id"
|
||||
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
@ -67,24 +65,31 @@ func TestFindRequestByID(t *testing.T) {
|
||||
t.Run("sender request found", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
exp := sender.Request{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
ProjectID: projectID,
|
||||
SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
|
||||
URL: exampleURL,
|
||||
Method: http.MethodGet,
|
||||
Proto: sender.HTTPProto20,
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
exp := &sender.Request{
|
||||
Id: "foobar-sender-req-id",
|
||||
ProjectId: projectID,
|
||||
SourceRequestLogId: "foobar-req-log-id",
|
||||
HttpRequest: &http.Request{
|
||||
Url: exampleURL.String(),
|
||||
Method: http.Method_METHOD_GET,
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP20,
|
||||
Headers: []*http.Header{
|
||||
{
|
||||
Key: "X-Foo",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
Proto: "HTTP/2.0",
|
||||
HttpResponse: &http.Response{
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP20,
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Header: http.Header{
|
||||
"X-Yolo": []string{"swag"},
|
||||
Headers: []*http.Header{
|
||||
{
|
||||
Key: "X-Yolo",
|
||||
Value: "swag",
|
||||
},
|
||||
},
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
@ -95,14 +100,12 @@ func TestFindRequestByID(t *testing.T) {
|
||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
||||
got, err := db.FindSenderRequestByID(context.Background(), exp.ProjectID, exp.ID)
|
||||
got, err := db.FindSenderRequestByID(context.Background(), projectID, exp.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("sender request not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "sender request not equal", exp, got, "id")
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -110,30 +113,6 @@ func TestFindRequestByID(t *testing.T) {
|
||||
func TestFindSenderRequests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("without project ID in filter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := t.TempDir() + "bolt.db"
|
||||
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open bolt database: %v", err)
|
||||
}
|
||||
defer boltDB.Close()
|
||||
|
||||
db, err := bolt.DatabaseFromBoltDB(boltDB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
filter := sender.FindRequestsFilter{}
|
||||
|
||||
_, err = db.FindSenderRequests(context.Background(), filter, nil)
|
||||
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
||||
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("returns sender requests and related response logs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -150,48 +129,61 @@ func TestFindSenderRequests(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
projectID := "foobar-project-id"
|
||||
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
Name: "foobar",
|
||||
Settings: proj.Settings{},
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
Name: "foobar",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating project (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
||||
fixtures := []sender.Request{
|
||||
fixtures := []*sender.Request{
|
||||
{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
ProjectID: projectID,
|
||||
SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"baz"},
|
||||
Id: ulid.Make().String(),
|
||||
ProjectId: projectID,
|
||||
SourceRequestLogId: "foobar-req-log-id-1",
|
||||
HttpRequest: &http.Request{
|
||||
Url: exampleURL.String(),
|
||||
Method: http.Method_METHOD_POST,
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP11,
|
||||
Headers: []*http.Header{
|
||||
{
|
||||
Key: "X-Foo",
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
Proto: "HTTP/1.1",
|
||||
HttpResponse: &http.Response{
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP11,
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Header: http.Header{
|
||||
"X-Yolo": []string{"swag"},
|
||||
Headers: []*http.Header{
|
||||
{
|
||||
Key: "X-Yolo",
|
||||
Value: "swag",
|
||||
},
|
||||
},
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
|
||||
ProjectID: projectID,
|
||||
SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
URL: exampleURL,
|
||||
Method: http.MethodGet,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"baz"},
|
||||
Id: ulid.Make().String(),
|
||||
ProjectId: projectID,
|
||||
SourceRequestLogId: "foobar-req-log-id-2",
|
||||
HttpRequest: &http.Request{
|
||||
Url: exampleURL.String(),
|
||||
Method: http.Method_METHOD_GET,
|
||||
Protocol: http.Protocol_PROTOCOL_HTTP11,
|
||||
Headers: []*http.Header{
|
||||
{
|
||||
Key: "X-Foo",
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -204,23 +196,17 @@ func TestFindSenderRequests(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
filter := sender.FindRequestsFilter{
|
||||
ProjectID: projectID,
|
||||
}
|
||||
|
||||
got, err := db.FindSenderRequests(context.Background(), filter, nil)
|
||||
got, err := db.FindSenderRequests(context.Background(), projectID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error finding sender requests: %v", err)
|
||||
}
|
||||
|
||||
// We expect the found sender requests are *reversed*, e.g. newest first.
|
||||
exp := make([]sender.Request, len(fixtures))
|
||||
exp := make([]*sender.Request, len(fixtures))
|
||||
for i, j := 0, len(fixtures)-1; i < j; i, j = i+1, j-1 {
|
||||
exp[i], exp[j] = fixtures[j], fixtures[i]
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("sender requests not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoSlicesDiff(t, "sender requests not equal", exp, got)
|
||||
})
|
||||
}
|
||||
|
@ -3,10 +3,11 @@ package filter
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
)
|
||||
|
||||
func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool, error) {
|
||||
func MatchHTTPHeaders(op TokenType, expr Expression, headers []*http.Header) (bool, error) {
|
||||
if headers == nil {
|
||||
return false, nil
|
||||
}
|
||||
@ -19,11 +20,9 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
|
||||
}
|
||||
|
||||
// Return `true` if at least one header (<key>: <value>) is equal to the string literal.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if strLiteral.Value == fmt.Sprintf("%v: %v", key, value) {
|
||||
return true, nil
|
||||
}
|
||||
for _, header := range headers {
|
||||
if strLiteral.Value == fmt.Sprintf("%v: %v", header.Key, header.Value) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,11 +34,9 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
|
||||
}
|
||||
|
||||
// Return `true` if none of the headers (<key>: <value>) are equal to the string literal.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if strLiteral.Value == fmt.Sprintf("%v: %v", key, value) {
|
||||
return false, nil
|
||||
}
|
||||
for _, header := range headers {
|
||||
if strLiteral.Value == fmt.Sprintf("%v: %v", header.Key, header.Value) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,11 +48,9 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
|
||||
}
|
||||
|
||||
// Return `true` if at least one header (<key>: <value>) matches the regular expression.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if re.MatchString(fmt.Sprintf("%v: %v", key, value)) {
|
||||
return true, nil
|
||||
}
|
||||
for _, header := range headers {
|
||||
if re.Regexp.MatchString(fmt.Sprintf("%v: %v", header.Key, header.Value)) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,11 +62,9 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
|
||||
}
|
||||
|
||||
// Return `true` if none of the headers (<key>: <value>) match the regular expression.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if re.MatchString(fmt.Sprintf("%v: %v", key, value)) {
|
||||
return false, nil
|
||||
}
|
||||
for _, header := range headers {
|
||||
if re.Regexp.MatchString(fmt.Sprintf("%v: %v", header.Key, header.Value)) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
79
pkg/http/http.go
Normal file
79
pkg/http/http.go
Normal file
@ -0,0 +1,79 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
nethttp "net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ProtoMap = map[string]Protocol{
|
||||
"HTTP/1.0": Protocol_PROTOCOL_HTTP10,
|
||||
"HTTP/1.1": Protocol_PROTOCOL_HTTP11,
|
||||
"HTTP/2.0": Protocol_PROTOCOL_HTTP20,
|
||||
}
|
||||
|
||||
var MethodMap = map[string]Method{
|
||||
"GET": Method_METHOD_GET,
|
||||
"POST": Method_METHOD_POST,
|
||||
"PUT": Method_METHOD_PUT,
|
||||
"DELETE": Method_METHOD_DELETE,
|
||||
"CONNECT": Method_METHOD_CONNECT,
|
||||
"OPTIONS": Method_METHOD_OPTIONS,
|
||||
"TRACE": Method_METHOD_TRACE,
|
||||
"PATCH": Method_METHOD_PATCH,
|
||||
}
|
||||
|
||||
func ParseHeader(header nethttp.Header) []*Header {
|
||||
headers := []*Header{}
|
||||
|
||||
for key, values := range header {
|
||||
for _, value := range values {
|
||||
headers = append(headers, &Header{Key: key, Value: value})
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
var ResponseSearchKeyFns = map[string]func(rl *Response) string{
|
||||
"res.proto": func(rl *Response) string { return rl.GetProtocol().String() },
|
||||
"res.status": func(rl *Response) string { return rl.GetStatus() },
|
||||
"res.statusCode": func(rl *Response) string { return strconv.Itoa(int(rl.GetStatusCode())) },
|
||||
"res.statusReason": func(rl *Response) string {
|
||||
statusReasonSubs := strings.SplitN(rl.GetStatus(), " ", 2)
|
||||
if len(statusReasonSubs) != 2 {
|
||||
return ""
|
||||
}
|
||||
return statusReasonSubs[1]
|
||||
},
|
||||
"res.body": func(rl *Response) string { return string(rl.GetBody()) },
|
||||
}
|
||||
|
||||
func ParseHTTPResponse(res *nethttp.Response) (*Response, error) {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reqlog: could not read body: %w", err)
|
||||
}
|
||||
|
||||
headers := []*Header{}
|
||||
for k, v := range res.Header {
|
||||
for _, vv := range v {
|
||||
headers = append(headers, &Header{Key: k, Value: vv})
|
||||
}
|
||||
}
|
||||
|
||||
protocol, ok := ProtoMap[res.Proto]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("reqlog: invalid protocol %q", res.Proto)
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Protocol: protocol,
|
||||
Status: res.Status,
|
||||
StatusCode: int32(res.StatusCode),
|
||||
Headers: headers,
|
||||
Body: body,
|
||||
}, nil
|
||||
}
|
476
pkg/http/http.pb.go
Normal file
476
pkg/http/http.pb.go
Normal file
@ -0,0 +1,476 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc (unknown)
|
||||
// source: http/http.proto
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
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 Method int32
|
||||
|
||||
const (
|
||||
Method_METHOD_UNSPECIFIED Method = 0
|
||||
Method_METHOD_GET Method = 1
|
||||
Method_METHOD_HEAD Method = 2
|
||||
Method_METHOD_POST Method = 3
|
||||
Method_METHOD_PUT Method = 4
|
||||
Method_METHOD_DELETE Method = 5
|
||||
Method_METHOD_CONNECT Method = 6
|
||||
Method_METHOD_OPTIONS Method = 7
|
||||
Method_METHOD_TRACE Method = 8
|
||||
Method_METHOD_PATCH Method = 9
|
||||
)
|
||||
|
||||
// Enum value maps for Method.
|
||||
var (
|
||||
Method_name = map[int32]string{
|
||||
0: "METHOD_UNSPECIFIED",
|
||||
1: "METHOD_GET",
|
||||
2: "METHOD_HEAD",
|
||||
3: "METHOD_POST",
|
||||
4: "METHOD_PUT",
|
||||
5: "METHOD_DELETE",
|
||||
6: "METHOD_CONNECT",
|
||||
7: "METHOD_OPTIONS",
|
||||
8: "METHOD_TRACE",
|
||||
9: "METHOD_PATCH",
|
||||
}
|
||||
Method_value = map[string]int32{
|
||||
"METHOD_UNSPECIFIED": 0,
|
||||
"METHOD_GET": 1,
|
||||
"METHOD_HEAD": 2,
|
||||
"METHOD_POST": 3,
|
||||
"METHOD_PUT": 4,
|
||||
"METHOD_DELETE": 5,
|
||||
"METHOD_CONNECT": 6,
|
||||
"METHOD_OPTIONS": 7,
|
||||
"METHOD_TRACE": 8,
|
||||
"METHOD_PATCH": 9,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Method) Enum() *Method {
|
||||
p := new(Method)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Method) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Method) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_http_http_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Method) Type() protoreflect.EnumType {
|
||||
return &file_http_http_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Method) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Method.Descriptor instead.
|
||||
func (Method) EnumDescriptor() ([]byte, []int) {
|
||||
return file_http_http_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type Protocol int32
|
||||
|
||||
const (
|
||||
Protocol_PROTOCOL_UNSPECIFIED Protocol = 0
|
||||
Protocol_PROTOCOL_HTTP10 Protocol = 1
|
||||
Protocol_PROTOCOL_HTTP11 Protocol = 2
|
||||
Protocol_PROTOCOL_HTTP20 Protocol = 3
|
||||
)
|
||||
|
||||
// Enum value maps for Protocol.
|
||||
var (
|
||||
Protocol_name = map[int32]string{
|
||||
0: "PROTOCOL_UNSPECIFIED",
|
||||
1: "PROTOCOL_HTTP10",
|
||||
2: "PROTOCOL_HTTP11",
|
||||
3: "PROTOCOL_HTTP20",
|
||||
}
|
||||
Protocol_value = map[string]int32{
|
||||
"PROTOCOL_UNSPECIFIED": 0,
|
||||
"PROTOCOL_HTTP10": 1,
|
||||
"PROTOCOL_HTTP11": 2,
|
||||
"PROTOCOL_HTTP20": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Protocol) Enum() *Protocol {
|
||||
p := new(Protocol)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Protocol) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Protocol) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_http_http_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (Protocol) Type() protoreflect.EnumType {
|
||||
return &file_http_http_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x Protocol) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Protocol.Descriptor instead.
|
||||
func (Protocol) EnumDescriptor() ([]byte, []int) {
|
||||
return file_http_http_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Method Method `protobuf:"varint,1,opt,name=method,proto3,enum=hetty.http.v1.Method" json:"method,omitempty"`
|
||||
Protocol Protocol `protobuf:"varint,2,opt,name=protocol,proto3,enum=hetty.http.v1.Protocol" json:"protocol,omitempty"`
|
||||
Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Headers []*Header `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty"`
|
||||
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
|
||||
Response *Response `protobuf:"bytes,6,opt,name=response,proto3" json:"response,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Request) Reset() {
|
||||
*x = Request{}
|
||||
mi := &file_http_http_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Request) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Request) ProtoMessage() {}
|
||||
|
||||
func (x *Request) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_http_http_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 Request.ProtoReflect.Descriptor instead.
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return file_http_http_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Request) GetMethod() Method {
|
||||
if x != nil {
|
||||
return x.Method
|
||||
}
|
||||
return Method_METHOD_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *Request) GetProtocol() Protocol {
|
||||
if x != nil {
|
||||
return x.Protocol
|
||||
}
|
||||
return Protocol_PROTOCOL_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *Request) GetUrl() string {
|
||||
if x != nil {
|
||||
return x.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Request) GetHeaders() []*Header {
|
||||
if x != nil {
|
||||
return x.Headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Request) GetBody() []byte {
|
||||
if x != nil {
|
||||
return x.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Request) GetResponse() *Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Protocol Protocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=hetty.http.v1.Protocol" json:"protocol,omitempty"`
|
||||
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
|
||||
StatusCode int32 `protobuf:"varint,3,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
|
||||
Headers []*Header `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty"`
|
||||
Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Response) Reset() {
|
||||
*x = Response{}
|
||||
mi := &file_http_http_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Response) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Response) ProtoMessage() {}
|
||||
|
||||
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_http_http_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 Response.ProtoReflect.Descriptor instead.
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return file_http_http_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Response) GetProtocol() Protocol {
|
||||
if x != nil {
|
||||
return x.Protocol
|
||||
}
|
||||
return Protocol_PROTOCOL_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *Response) GetStatus() string {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Response) GetStatusCode() int32 {
|
||||
if x != nil {
|
||||
return x.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Response) GetHeaders() []*Header {
|
||||
if x != nil {
|
||||
return x.Headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Response) GetBody() []byte {
|
||||
if x != nil {
|
||||
return x.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Header) Reset() {
|
||||
*x = Header{}
|
||||
mi := &file_http_http_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Header) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Header) ProtoMessage() {}
|
||||
|
||||
func (x *Header) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_http_http_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 Header.ProtoReflect.Descriptor instead.
|
||||
func (*Header) Descriptor() ([]byte, []int) {
|
||||
return file_http_http_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Header) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Header) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_http_http_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_http_http_proto_rawDesc = []byte{
|
||||
0x0a, 0x0f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x12, 0x0d, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31,
|
||||
0x22, 0xf9, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06,
|
||||
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x68,
|
||||
0x65, 0x74, 0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74,
|
||||
0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x33, 0x0a, 0x08, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e,
|
||||
0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
|
||||
0x72, 0x6c, 0x12, 0x2f, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64,
|
||||
0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x33, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x74, 0x74,
|
||||
0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbd, 0x01, 0x0a,
|
||||
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x68, 0x65,
|
||||
0x74, 0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65,
|
||||
0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79,
|
||||
0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52,
|
||||
0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79,
|
||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x30, 0x0a, 0x06,
|
||||
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0xc1,
|
||||
0x01, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x54,
|
||||
0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
|
||||
0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x47, 0x45, 0x54, 0x10,
|
||||
0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x48, 0x45, 0x41, 0x44,
|
||||
0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x50, 0x4f, 0x53,
|
||||
0x54, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x50, 0x55,
|
||||
0x54, 0x10, 0x04, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x44, 0x45,
|
||||
0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44,
|
||||
0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45,
|
||||
0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x07, 0x12, 0x10,
|
||||
0x0a, 0x0c, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x08,
|
||||
0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x50, 0x41, 0x54, 0x43, 0x48,
|
||||
0x10, 0x09, 0x2a, 0x63, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x18,
|
||||
0x0a, 0x14, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
|
||||
0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x52, 0x4f, 0x54,
|
||||
0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x31, 0x30, 0x10, 0x01, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x31, 0x31,
|
||||
0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48,
|
||||
0x54, 0x54, 0x50, 0x32, 0x30, 0x10, 0x03, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x73, 0x74, 0x6f, 0x74, 0x69, 0x6a, 0x6e, 0x2f, 0x68,
|
||||
0x65, 0x74, 0x74, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_http_http_proto_rawDescOnce sync.Once
|
||||
file_http_http_proto_rawDescData = file_http_http_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_http_http_proto_rawDescGZIP() []byte {
|
||||
file_http_http_proto_rawDescOnce.Do(func() {
|
||||
file_http_http_proto_rawDescData = protoimpl.X.CompressGZIP(file_http_http_proto_rawDescData)
|
||||
})
|
||||
return file_http_http_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_http_http_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_http_http_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_http_http_proto_goTypes = []any{
|
||||
(Method)(0), // 0: hetty.http.v1.Method
|
||||
(Protocol)(0), // 1: hetty.http.v1.Protocol
|
||||
(*Request)(nil), // 2: hetty.http.v1.Request
|
||||
(*Response)(nil), // 3: hetty.http.v1.Response
|
||||
(*Header)(nil), // 4: hetty.http.v1.Header
|
||||
}
|
||||
var file_http_http_proto_depIdxs = []int32{
|
||||
0, // 0: hetty.http.v1.Request.method:type_name -> hetty.http.v1.Method
|
||||
1, // 1: hetty.http.v1.Request.protocol:type_name -> hetty.http.v1.Protocol
|
||||
4, // 2: hetty.http.v1.Request.headers:type_name -> hetty.http.v1.Header
|
||||
3, // 3: hetty.http.v1.Request.response:type_name -> hetty.http.v1.Response
|
||||
1, // 4: hetty.http.v1.Response.protocol:type_name -> hetty.http.v1.Protocol
|
||||
4, // 5: hetty.http.v1.Response.headers:type_name -> hetty.http.v1.Header
|
||||
6, // [6:6] is the sub-list for method output_type
|
||||
6, // [6:6] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_http_http_proto_init() }
|
||||
func file_http_http_proto_init() {
|
||||
if File_http_http_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_http_http_proto_rawDesc,
|
||||
NumEnums: 2,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_http_http_proto_goTypes,
|
||||
DependencyIndexes: file_http_http_proto_depIdxs,
|
||||
EnumInfos: file_http_http_proto_enumTypes,
|
||||
MessageInfos: file_http_http_proto_msgTypes,
|
||||
}.Build()
|
||||
File_http_http_proto = out.File
|
||||
file_http_http_proto_rawDesc = nil
|
||||
file_http_http_proto_goTypes = nil
|
||||
file_http_http_proto_depIdxs = nil
|
||||
}
|
340
pkg/proj/proj.connect.go
Normal file
340
pkg/proj/proj.connect.go
Normal file
@ -0,0 +1,340 @@
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: proj/proj.proto
|
||||
|
||||
package proj
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// ProjectServiceName is the fully-qualified name of the ProjectService service.
|
||||
ProjectServiceName = "hetty.proj.v1.ProjectService"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// ProjectServiceCreateProjectProcedure is the fully-qualified name of the ProjectService's
|
||||
// CreateProject RPC.
|
||||
ProjectServiceCreateProjectProcedure = "/hetty.proj.v1.ProjectService/CreateProject"
|
||||
// ProjectServiceOpenProjectProcedure is the fully-qualified name of the ProjectService's
|
||||
// OpenProject RPC.
|
||||
ProjectServiceOpenProjectProcedure = "/hetty.proj.v1.ProjectService/OpenProject"
|
||||
// ProjectServiceCloseProjectProcedure is the fully-qualified name of the ProjectService's
|
||||
// CloseProject RPC.
|
||||
ProjectServiceCloseProjectProcedure = "/hetty.proj.v1.ProjectService/CloseProject"
|
||||
// ProjectServiceDeleteProjectProcedure is the fully-qualified name of the ProjectService's
|
||||
// DeleteProject RPC.
|
||||
ProjectServiceDeleteProjectProcedure = "/hetty.proj.v1.ProjectService/DeleteProject"
|
||||
// ProjectServiceGetActiveProjectProcedure is the fully-qualified name of the ProjectService's
|
||||
// GetActiveProject RPC.
|
||||
ProjectServiceGetActiveProjectProcedure = "/hetty.proj.v1.ProjectService/GetActiveProject"
|
||||
// ProjectServiceListProjectsProcedure is the fully-qualified name of the ProjectService's
|
||||
// ListProjects RPC.
|
||||
ProjectServiceListProjectsProcedure = "/hetty.proj.v1.ProjectService/ListProjects"
|
||||
// ProjectServiceUpdateInterceptSettingsProcedure is the fully-qualified name of the
|
||||
// ProjectService's UpdateInterceptSettings RPC.
|
||||
ProjectServiceUpdateInterceptSettingsProcedure = "/hetty.proj.v1.ProjectService/UpdateInterceptSettings"
|
||||
// ProjectServiceSetScopeRulesProcedure is the fully-qualified name of the ProjectService's
|
||||
// SetScopeRules RPC.
|
||||
ProjectServiceSetScopeRulesProcedure = "/hetty.proj.v1.ProjectService/SetScopeRules"
|
||||
// ProjectServiceSetRequestLogsFilterProcedure is the fully-qualified name of the ProjectService's
|
||||
// SetRequestLogsFilter RPC.
|
||||
ProjectServiceSetRequestLogsFilterProcedure = "/hetty.proj.v1.ProjectService/SetRequestLogsFilter"
|
||||
)
|
||||
|
||||
// ProjectServiceClient is a client for the hetty.proj.v1.ProjectService service.
|
||||
type ProjectServiceClient interface {
|
||||
CreateProject(context.Context, *connect.Request[CreateProjectRequest]) (*connect.Response[CreateProjectResponse], error)
|
||||
OpenProject(context.Context, *connect.Request[OpenProjectRequest]) (*connect.Response[OpenProjectResponse], error)
|
||||
CloseProject(context.Context, *connect.Request[CloseProjectRequest]) (*connect.Response[CloseProjectResponse], error)
|
||||
DeleteProject(context.Context, *connect.Request[DeleteProjectRequest]) (*connect.Response[DeleteProjectResponse], error)
|
||||
GetActiveProject(context.Context, *connect.Request[GetActiveProjectRequest]) (*connect.Response[GetActiveProjectResponse], error)
|
||||
ListProjects(context.Context, *connect.Request[ListProjectsRequest]) (*connect.Response[ListProjectsResponse], error)
|
||||
UpdateInterceptSettings(context.Context, *connect.Request[UpdateInterceptSettingsRequest]) (*connect.Response[UpdateInterceptSettingsResponse], error)
|
||||
SetScopeRules(context.Context, *connect.Request[SetScopeRulesRequest]) (*connect.Response[SetScopeRulesResponse], error)
|
||||
SetRequestLogsFilter(context.Context, *connect.Request[SetRequestLogsFilterRequest]) (*connect.Response[SetRequestLogsFilterResponse], error)
|
||||
}
|
||||
|
||||
// NewProjectServiceClient constructs a client for the hetty.proj.v1.ProjectService service. By
|
||||
// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
|
||||
// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
|
||||
// connect.WithGRPC() or connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewProjectServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ProjectServiceClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
projectServiceMethods := File_proj_proj_proto.Services().ByName("ProjectService").Methods()
|
||||
return &projectServiceClient{
|
||||
createProject: connect.NewClient[CreateProjectRequest, CreateProjectResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceCreateProjectProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("CreateProject")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
openProject: connect.NewClient[OpenProjectRequest, OpenProjectResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceOpenProjectProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("OpenProject")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
closeProject: connect.NewClient[CloseProjectRequest, CloseProjectResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceCloseProjectProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("CloseProject")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
deleteProject: connect.NewClient[DeleteProjectRequest, DeleteProjectResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceDeleteProjectProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("DeleteProject")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
getActiveProject: connect.NewClient[GetActiveProjectRequest, GetActiveProjectResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceGetActiveProjectProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("GetActiveProject")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listProjects: connect.NewClient[ListProjectsRequest, ListProjectsResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceListProjectsProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("ListProjects")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
updateInterceptSettings: connect.NewClient[UpdateInterceptSettingsRequest, UpdateInterceptSettingsResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceUpdateInterceptSettingsProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("UpdateInterceptSettings")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
setScopeRules: connect.NewClient[SetScopeRulesRequest, SetScopeRulesResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceSetScopeRulesProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("SetScopeRules")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
setRequestLogsFilter: connect.NewClient[SetRequestLogsFilterRequest, SetRequestLogsFilterResponse](
|
||||
httpClient,
|
||||
baseURL+ProjectServiceSetRequestLogsFilterProcedure,
|
||||
connect.WithSchema(projectServiceMethods.ByName("SetRequestLogsFilter")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// projectServiceClient implements ProjectServiceClient.
|
||||
type projectServiceClient struct {
|
||||
createProject *connect.Client[CreateProjectRequest, CreateProjectResponse]
|
||||
openProject *connect.Client[OpenProjectRequest, OpenProjectResponse]
|
||||
closeProject *connect.Client[CloseProjectRequest, CloseProjectResponse]
|
||||
deleteProject *connect.Client[DeleteProjectRequest, DeleteProjectResponse]
|
||||
getActiveProject *connect.Client[GetActiveProjectRequest, GetActiveProjectResponse]
|
||||
listProjects *connect.Client[ListProjectsRequest, ListProjectsResponse]
|
||||
updateInterceptSettings *connect.Client[UpdateInterceptSettingsRequest, UpdateInterceptSettingsResponse]
|
||||
setScopeRules *connect.Client[SetScopeRulesRequest, SetScopeRulesResponse]
|
||||
setRequestLogsFilter *connect.Client[SetRequestLogsFilterRequest, SetRequestLogsFilterResponse]
|
||||
}
|
||||
|
||||
// CreateProject calls hetty.proj.v1.ProjectService.CreateProject.
|
||||
func (c *projectServiceClient) CreateProject(ctx context.Context, req *connect.Request[CreateProjectRequest]) (*connect.Response[CreateProjectResponse], error) {
|
||||
return c.createProject.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// OpenProject calls hetty.proj.v1.ProjectService.OpenProject.
|
||||
func (c *projectServiceClient) OpenProject(ctx context.Context, req *connect.Request[OpenProjectRequest]) (*connect.Response[OpenProjectResponse], error) {
|
||||
return c.openProject.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// CloseProject calls hetty.proj.v1.ProjectService.CloseProject.
|
||||
func (c *projectServiceClient) CloseProject(ctx context.Context, req *connect.Request[CloseProjectRequest]) (*connect.Response[CloseProjectResponse], error) {
|
||||
return c.closeProject.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// DeleteProject calls hetty.proj.v1.ProjectService.DeleteProject.
|
||||
func (c *projectServiceClient) DeleteProject(ctx context.Context, req *connect.Request[DeleteProjectRequest]) (*connect.Response[DeleteProjectResponse], error) {
|
||||
return c.deleteProject.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// GetActiveProject calls hetty.proj.v1.ProjectService.GetActiveProject.
|
||||
func (c *projectServiceClient) GetActiveProject(ctx context.Context, req *connect.Request[GetActiveProjectRequest]) (*connect.Response[GetActiveProjectResponse], error) {
|
||||
return c.getActiveProject.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListProjects calls hetty.proj.v1.ProjectService.ListProjects.
|
||||
func (c *projectServiceClient) ListProjects(ctx context.Context, req *connect.Request[ListProjectsRequest]) (*connect.Response[ListProjectsResponse], error) {
|
||||
return c.listProjects.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// UpdateInterceptSettings calls hetty.proj.v1.ProjectService.UpdateInterceptSettings.
|
||||
func (c *projectServiceClient) UpdateInterceptSettings(ctx context.Context, req *connect.Request[UpdateInterceptSettingsRequest]) (*connect.Response[UpdateInterceptSettingsResponse], error) {
|
||||
return c.updateInterceptSettings.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SetScopeRules calls hetty.proj.v1.ProjectService.SetScopeRules.
|
||||
func (c *projectServiceClient) SetScopeRules(ctx context.Context, req *connect.Request[SetScopeRulesRequest]) (*connect.Response[SetScopeRulesResponse], error) {
|
||||
return c.setScopeRules.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SetRequestLogsFilter calls hetty.proj.v1.ProjectService.SetRequestLogsFilter.
|
||||
func (c *projectServiceClient) SetRequestLogsFilter(ctx context.Context, req *connect.Request[SetRequestLogsFilterRequest]) (*connect.Response[SetRequestLogsFilterResponse], error) {
|
||||
return c.setRequestLogsFilter.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ProjectServiceHandler is an implementation of the hetty.proj.v1.ProjectService service.
|
||||
type ProjectServiceHandler interface {
|
||||
CreateProject(context.Context, *connect.Request[CreateProjectRequest]) (*connect.Response[CreateProjectResponse], error)
|
||||
OpenProject(context.Context, *connect.Request[OpenProjectRequest]) (*connect.Response[OpenProjectResponse], error)
|
||||
CloseProject(context.Context, *connect.Request[CloseProjectRequest]) (*connect.Response[CloseProjectResponse], error)
|
||||
DeleteProject(context.Context, *connect.Request[DeleteProjectRequest]) (*connect.Response[DeleteProjectResponse], error)
|
||||
GetActiveProject(context.Context, *connect.Request[GetActiveProjectRequest]) (*connect.Response[GetActiveProjectResponse], error)
|
||||
ListProjects(context.Context, *connect.Request[ListProjectsRequest]) (*connect.Response[ListProjectsResponse], error)
|
||||
UpdateInterceptSettings(context.Context, *connect.Request[UpdateInterceptSettingsRequest]) (*connect.Response[UpdateInterceptSettingsResponse], error)
|
||||
SetScopeRules(context.Context, *connect.Request[SetScopeRulesRequest]) (*connect.Response[SetScopeRulesResponse], error)
|
||||
SetRequestLogsFilter(context.Context, *connect.Request[SetRequestLogsFilterRequest]) (*connect.Response[SetRequestLogsFilterResponse], error)
|
||||
}
|
||||
|
||||
// NewProjectServiceHandler builds an HTTP handler from the service implementation. It returns the
|
||||
// path on which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewProjectServiceHandler(svc ProjectServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
projectServiceMethods := File_proj_proj_proto.Services().ByName("ProjectService").Methods()
|
||||
projectServiceCreateProjectHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceCreateProjectProcedure,
|
||||
svc.CreateProject,
|
||||
connect.WithSchema(projectServiceMethods.ByName("CreateProject")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceOpenProjectHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceOpenProjectProcedure,
|
||||
svc.OpenProject,
|
||||
connect.WithSchema(projectServiceMethods.ByName("OpenProject")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceCloseProjectHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceCloseProjectProcedure,
|
||||
svc.CloseProject,
|
||||
connect.WithSchema(projectServiceMethods.ByName("CloseProject")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceDeleteProjectHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceDeleteProjectProcedure,
|
||||
svc.DeleteProject,
|
||||
connect.WithSchema(projectServiceMethods.ByName("DeleteProject")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceGetActiveProjectHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceGetActiveProjectProcedure,
|
||||
svc.GetActiveProject,
|
||||
connect.WithSchema(projectServiceMethods.ByName("GetActiveProject")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceListProjectsHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceListProjectsProcedure,
|
||||
svc.ListProjects,
|
||||
connect.WithSchema(projectServiceMethods.ByName("ListProjects")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceUpdateInterceptSettingsHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceUpdateInterceptSettingsProcedure,
|
||||
svc.UpdateInterceptSettings,
|
||||
connect.WithSchema(projectServiceMethods.ByName("UpdateInterceptSettings")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceSetScopeRulesHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceSetScopeRulesProcedure,
|
||||
svc.SetScopeRules,
|
||||
connect.WithSchema(projectServiceMethods.ByName("SetScopeRules")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
projectServiceSetRequestLogsFilterHandler := connect.NewUnaryHandler(
|
||||
ProjectServiceSetRequestLogsFilterProcedure,
|
||||
svc.SetRequestLogsFilter,
|
||||
connect.WithSchema(projectServiceMethods.ByName("SetRequestLogsFilter")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/hetty.proj.v1.ProjectService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case ProjectServiceCreateProjectProcedure:
|
||||
projectServiceCreateProjectHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceOpenProjectProcedure:
|
||||
projectServiceOpenProjectHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceCloseProjectProcedure:
|
||||
projectServiceCloseProjectHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceDeleteProjectProcedure:
|
||||
projectServiceDeleteProjectHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceGetActiveProjectProcedure:
|
||||
projectServiceGetActiveProjectHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceListProjectsProcedure:
|
||||
projectServiceListProjectsHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceUpdateInterceptSettingsProcedure:
|
||||
projectServiceUpdateInterceptSettingsHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceSetScopeRulesProcedure:
|
||||
projectServiceSetScopeRulesHandler.ServeHTTP(w, r)
|
||||
case ProjectServiceSetRequestLogsFilterProcedure:
|
||||
projectServiceSetRequestLogsFilterHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedProjectServiceHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedProjectServiceHandler struct{}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) CreateProject(context.Context, *connect.Request[CreateProjectRequest]) (*connect.Response[CreateProjectResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.CreateProject is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) OpenProject(context.Context, *connect.Request[OpenProjectRequest]) (*connect.Response[OpenProjectResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.OpenProject is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) CloseProject(context.Context, *connect.Request[CloseProjectRequest]) (*connect.Response[CloseProjectResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.CloseProject is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) DeleteProject(context.Context, *connect.Request[DeleteProjectRequest]) (*connect.Response[DeleteProjectResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.DeleteProject is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) GetActiveProject(context.Context, *connect.Request[GetActiveProjectRequest]) (*connect.Response[GetActiveProjectResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.GetActiveProject is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) ListProjects(context.Context, *connect.Request[ListProjectsRequest]) (*connect.Response[ListProjectsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.ListProjects is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) UpdateInterceptSettings(context.Context, *connect.Request[UpdateInterceptSettingsRequest]) (*connect.Response[UpdateInterceptSettingsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.UpdateInterceptSettings is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) SetScopeRules(context.Context, *connect.Request[SetScopeRulesRequest]) (*connect.Response[SetScopeRulesResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.SetScopeRules is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedProjectServiceHandler) SetRequestLogsFilter(context.Context, *connect.Request[SetRequestLogsFilterRequest]) (*connect.Response[SetRequestLogsFilterResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.proj.v1.ProjectService.SetRequestLogsFilter is not implemented"))
|
||||
}
|
356
pkg/proj/proj.go
356
pkg/proj/proj.go
@ -4,12 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
connect "connectrpc.com/connect"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||
@ -18,27 +17,16 @@ import (
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
type Service struct {
|
||||
repo Repository
|
||||
interceptSvc *intercept.Service
|
||||
reqLogSvc *reqlog.Service
|
||||
senderSvc *sender.Service
|
||||
scope *scope.Scope
|
||||
activeProjectID ulid.ULID
|
||||
activeProjectID string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID ulid.ULID
|
||||
Name string
|
||||
Settings Settings
|
||||
|
||||
isActive bool
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
// Request log settings
|
||||
ReqLogBypassOutOfScope bool
|
||||
@ -87,216 +75,324 @@ func NewService(cfg Config) (*Service, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreateProject(ctx context.Context, name string) (Project, error) {
|
||||
if !nameRegexp.MatchString(name) {
|
||||
return Project{}, ErrInvalidName
|
||||
func (svc *Service) CreateProject(ctx context.Context, req *connect.Request[CreateProjectRequest]) (*connect.Response[CreateProjectResponse], error) {
|
||||
if !nameRegexp.MatchString(req.Msg.Name) {
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
|
||||
project := Project{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
Name: name,
|
||||
project := &Project{
|
||||
Id: ulid.Make().String(),
|
||||
Name: req.Msg.Name,
|
||||
}
|
||||
|
||||
err := svc.repo.UpsertProject(ctx, project)
|
||||
if err != nil {
|
||||
return Project{}, fmt.Errorf("proj: could not create project: %w", err)
|
||||
return nil, fmt.Errorf("proj: could not create project: %w", err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
return &connect.Response[CreateProjectResponse]{
|
||||
Msg: &CreateProjectResponse{
|
||||
Project: project,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CloseProject closes the currently open project (if there is one).
|
||||
func (svc *Service) CloseProject() error {
|
||||
func (svc *Service) CloseProject(ctx context.Context, _ *connect.Request[CloseProjectRequest]) (*connect.Response[CloseProjectResponse], error) {
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return nil
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrNoProject)
|
||||
}
|
||||
|
||||
svc.activeProjectID = ulid.ULID{}
|
||||
svc.reqLogSvc.SetActiveProjectID(ulid.ULID{})
|
||||
svc.activeProjectID = ""
|
||||
svc.reqLogSvc.SetActiveProjectID("")
|
||||
svc.reqLogSvc.SetBypassOutOfScopeRequests(false)
|
||||
svc.reqLogSvc.SetFindReqsFilter(reqlog.FindRequestsFilter{})
|
||||
svc.reqLogSvc.SetRequestLogsFilter(nil)
|
||||
svc.interceptSvc.UpdateSettings(intercept.Settings{
|
||||
RequestsEnabled: false,
|
||||
ResponsesEnabled: false,
|
||||
RequestFilter: nil,
|
||||
ResponseFilter: nil,
|
||||
})
|
||||
svc.senderSvc.SetActiveProjectID(ulid.ULID{})
|
||||
svc.senderSvc.SetFindReqsFilter(sender.FindRequestsFilter{})
|
||||
svc.senderSvc.SetActiveProjectID("")
|
||||
svc.scope.SetRules(nil)
|
||||
|
||||
return nil
|
||||
return &connect.Response[CloseProjectResponse]{}, nil
|
||||
}
|
||||
|
||||
// DeleteProject removes a project from the repository.
|
||||
func (svc *Service) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
|
||||
if svc.activeProjectID.Compare(projectID) == 0 {
|
||||
return fmt.Errorf("proj: project (%v) is active", projectID.String())
|
||||
func (svc *Service) DeleteProject(ctx context.Context, req *connect.Request[DeleteProjectRequest]) (*connect.Response[DeleteProjectResponse], error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrNoProject)
|
||||
}
|
||||
|
||||
if err := svc.repo.DeleteProject(ctx, projectID); err != nil {
|
||||
return fmt.Errorf("proj: could not delete project: %w", err)
|
||||
if err := svc.repo.DeleteProject(ctx, req.Msg.ProjectId); err != nil {
|
||||
return nil, fmt.Errorf("proj: could not delete project: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return &connect.Response[DeleteProjectResponse]{}, nil
|
||||
}
|
||||
|
||||
// OpenProject sets a project as the currently active project.
|
||||
func (svc *Service) OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error) {
|
||||
func (svc *Service) OpenProject(ctx context.Context, req *connect.Request[OpenProjectRequest]) (*connect.Response[OpenProjectResponse], error) {
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
project, err := svc.repo.FindProjectByID(ctx, projectID)
|
||||
p, err := svc.repo.FindProjectByID(ctx, req.Msg.ProjectId)
|
||||
if errors.Is(err, ErrProjectNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, ErrProjectNotFound)
|
||||
}
|
||||
if err != nil {
|
||||
return Project{}, fmt.Errorf("proj: failed to get project: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("proj: failed to find project: %w", err))
|
||||
}
|
||||
|
||||
svc.activeProjectID = project.ID
|
||||
svc.activeProjectID = p.Id
|
||||
|
||||
interceptSettings := intercept.Settings{
|
||||
RequestsEnabled: p.InterceptRequests,
|
||||
ResponsesEnabled: p.InterceptResponses,
|
||||
}
|
||||
|
||||
if p.InterceptRequestFilterExpr != "" {
|
||||
expr, err := filter.ParseQuery(p.InterceptRequestFilterExpr)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("proj: failed to parse intercept request filter: %w", err))
|
||||
}
|
||||
interceptSettings.RequestFilter = expr
|
||||
}
|
||||
|
||||
if p.InterceptResponseFilterExpr != "" {
|
||||
expr, err := filter.ParseQuery(p.InterceptResponseFilterExpr)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("proj: failed to parse intercept response filter: %w", err))
|
||||
}
|
||||
interceptSettings.ResponseFilter = expr
|
||||
}
|
||||
|
||||
// Request log settings.
|
||||
svc.reqLogSvc.SetFindReqsFilter(reqlog.FindRequestsFilter{
|
||||
ProjectID: project.ID,
|
||||
OnlyInScope: project.Settings.ReqLogOnlyFindInScope,
|
||||
SearchExpr: project.Settings.ReqLogSearchExpr,
|
||||
})
|
||||
svc.reqLogSvc.SetBypassOutOfScopeRequests(project.Settings.ReqLogBypassOutOfScope)
|
||||
svc.reqLogSvc.SetActiveProjectID(project.ID)
|
||||
svc.reqLogSvc.SetActiveProjectID(p.Id)
|
||||
svc.reqLogSvc.SetBypassOutOfScopeRequests(p.ReqLogBypassOutOfScope)
|
||||
svc.reqLogSvc.SetRequestLogsFilter(p.ReqLogFilter)
|
||||
|
||||
// Intercept settings.
|
||||
svc.interceptSvc.UpdateSettings(intercept.Settings{
|
||||
RequestsEnabled: project.Settings.InterceptRequests,
|
||||
ResponsesEnabled: project.Settings.InterceptResponses,
|
||||
RequestFilter: project.Settings.InterceptRequestFilter,
|
||||
ResponseFilter: project.Settings.InterceptResponseFilter,
|
||||
})
|
||||
svc.interceptSvc.UpdateSettings(interceptSettings)
|
||||
|
||||
// Sender settings.
|
||||
svc.senderSvc.SetActiveProjectID(project.ID)
|
||||
svc.senderSvc.SetFindReqsFilter(sender.FindRequestsFilter{
|
||||
ProjectID: project.ID,
|
||||
OnlyInScope: project.Settings.SenderOnlyFindInScope,
|
||||
SearchExpr: project.Settings.SenderSearchExpr,
|
||||
svc.senderSvc.SetActiveProjectID(p.Id)
|
||||
svc.senderSvc.SetRequestsFilter(&sender.RequestsFilter{
|
||||
OnlyInScope: p.SenderOnlyFindInScope,
|
||||
SearchExpr: p.SenderSearchExpr,
|
||||
})
|
||||
|
||||
// Scope settings.
|
||||
svc.scope.SetRules(project.Settings.ScopeRules)
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ActiveProject(ctx context.Context) (Project, error) {
|
||||
activeProjectID := svc.activeProjectID
|
||||
if activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return Project{}, ErrNoProject
|
||||
}
|
||||
|
||||
project, err := svc.repo.FindProjectByID(ctx, activeProjectID)
|
||||
scopeRules, err := p.ParseScopeRules()
|
||||
if err != nil {
|
||||
return Project{}, fmt.Errorf("proj: failed to get active project: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("proj: failed to parse scope rules: %w", err))
|
||||
}
|
||||
svc.scope.SetRules(scopeRules)
|
||||
|
||||
p.IsActive = true
|
||||
|
||||
return &connect.Response[OpenProjectResponse]{
|
||||
Msg: &OpenProjectResponse{
|
||||
Project: p,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetActiveProject(ctx context.Context, _ *connect.Request[GetActiveProjectRequest]) (*connect.Response[GetActiveProjectResponse], error) {
|
||||
project, err := svc.activeProject(ctx)
|
||||
if errors.Is(err, ErrNoProject) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, err)
|
||||
}
|
||||
|
||||
project.isActive = true
|
||||
project.IsActive = true
|
||||
|
||||
return &connect.Response[GetActiveProjectResponse]{
|
||||
Msg: &GetActiveProjectResponse{
|
||||
Project: project,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) activeProject(ctx context.Context) (*Project, error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, ErrNoProject
|
||||
}
|
||||
|
||||
project, err := svc.repo.FindProjectByID(ctx, svc.activeProjectID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proj: failed to get active project: %w", err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (svc *Service) Projects(ctx context.Context) ([]Project, error) {
|
||||
func (svc *Service) ListProjects(ctx context.Context, _ *connect.Request[ListProjectsRequest]) (*connect.Response[ListProjectsResponse], error) {
|
||||
projects, err := svc.repo.Projects(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proj: could not get projects: %w", err)
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
for _, project := range projects {
|
||||
if svc.IsProjectActive(project.Id) {
|
||||
project.IsActive = true
|
||||
}
|
||||
}
|
||||
|
||||
return &connect.Response[ListProjectsResponse]{
|
||||
Msg: &ListProjectsResponse{
|
||||
Projects: projects,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) Scope() *scope.Scope {
|
||||
return svc.scope
|
||||
}
|
||||
|
||||
func (svc *Service) SetScopeRules(ctx context.Context, rules []scope.Rule) error {
|
||||
project, err := svc.ActiveProject(ctx)
|
||||
func (svc *Service) SetScopeRules(ctx context.Context, req *connect.Request[SetScopeRulesRequest]) (*connect.Response[SetScopeRulesResponse], error) {
|
||||
p, err := svc.activeProject(ctx)
|
||||
if errors.Is(err, ErrNoProject) {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, err)
|
||||
}
|
||||
|
||||
p.ScopeRules = req.Msg.Rules
|
||||
|
||||
err = svc.repo.UpsertProject(ctx, p)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("proj: failed to update project: %w", err))
|
||||
}
|
||||
|
||||
scopeRules, err := p.ParseScopeRules()
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("proj: failed to parse scope rules: %w", err))
|
||||
}
|
||||
|
||||
svc.scope.SetRules(scopeRules)
|
||||
|
||||
return &connect.Response[SetScopeRulesResponse]{}, nil
|
||||
}
|
||||
|
||||
func (p *Project) ParseScopeRules() ([]scope.Rule, error) {
|
||||
var err error
|
||||
scopeRules := make([]scope.Rule, len(p.ScopeRules))
|
||||
|
||||
for i, rule := range p.ScopeRules {
|
||||
scopeRules[i] = scope.Rule{}
|
||||
if rule.UrlRegexp != "" {
|
||||
scopeRules[i].URL, err = regexp.Compile(rule.UrlRegexp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse scope rule's URL field: %w", err)
|
||||
}
|
||||
}
|
||||
if rule.HeaderKeyRegexp != "" {
|
||||
scopeRules[i].Header.Key, err = regexp.Compile(rule.HeaderKeyRegexp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse scope rule's header key field: %w", err)
|
||||
}
|
||||
}
|
||||
if rule.HeaderValueRegexp != "" {
|
||||
scopeRules[i].Header.Value, err = regexp.Compile(rule.HeaderValueRegexp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse scope rule's header value field: %w", err)
|
||||
}
|
||||
}
|
||||
if rule.BodyRegexp != "" {
|
||||
scopeRules[i].Body, err = regexp.Compile(rule.BodyRegexp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse scope rule's body field: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scopeRules, nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetRequestLogsFilter(ctx context.Context, req *connect.Request[SetRequestLogsFilterRequest]) (*connect.Response[SetRequestLogsFilterResponse], error) {
|
||||
project, err := svc.activeProject(ctx)
|
||||
if errors.Is(err, ErrNoProject) {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, err)
|
||||
}
|
||||
|
||||
project.ReqLogFilter = req.Msg.Filter
|
||||
|
||||
err = svc.repo.UpsertProject(ctx, project)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("proj: failed to update project: %w", err))
|
||||
}
|
||||
|
||||
svc.reqLogSvc.SetRequestLogsFilter(req.Msg.Filter)
|
||||
|
||||
return &connect.Response[SetRequestLogsFilterResponse]{}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetSenderRequestFindFilter(ctx context.Context, filter *sender.RequestsFilter) error {
|
||||
project, err := svc.activeProject(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project.Settings.ScopeRules = rules
|
||||
project.SenderOnlyFindInScope = filter.OnlyInScope
|
||||
project.SenderSearchExpr = filter.SearchExpr
|
||||
|
||||
err = svc.repo.UpsertProject(ctx, project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proj: failed to update project: %w", err)
|
||||
}
|
||||
|
||||
svc.scope.SetRules(rules)
|
||||
svc.senderSvc.SetRequestsFilter(filter)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error {
|
||||
project, err := svc.ActiveProject(ctx)
|
||||
func (svc *Service) IsProjectActive(projectID string) bool {
|
||||
return projectID == svc.activeProjectID
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateInterceptSettings(ctx context.Context, req *connect.Request[UpdateInterceptSettingsRequest]) (*connect.Response[UpdateInterceptSettingsResponse], error) {
|
||||
project, err := svc.activeProject(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter.ProjectID = project.ID
|
||||
|
||||
project.Settings.ReqLogOnlyFindInScope = filter.OnlyInScope
|
||||
project.Settings.ReqLogSearchExpr = filter.SearchExpr
|
||||
project.InterceptRequests = req.Msg.RequestsEnabled
|
||||
project.InterceptResponses = req.Msg.ResponsesEnabled
|
||||
project.InterceptRequestFilterExpr = req.Msg.RequestFilterExpr
|
||||
project.InterceptResponseFilterExpr = req.Msg.ResponseFilterExpr
|
||||
|
||||
err = svc.repo.UpsertProject(ctx, project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proj: failed to update project: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("proj: failed to update project: %w", err))
|
||||
}
|
||||
|
||||
svc.reqLogSvc.SetFindReqsFilter(filter)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error {
|
||||
project, err := svc.ActiveProject(ctx)
|
||||
reqFilterExpr, err := filter.ParseQuery(req.Msg.RequestFilterExpr)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("proj: failed to parse intercept request filter: %w", err))
|
||||
}
|
||||
|
||||
filter.ProjectID = project.ID
|
||||
|
||||
project.Settings.SenderOnlyFindInScope = filter.OnlyInScope
|
||||
project.Settings.SenderSearchExpr = filter.SearchExpr
|
||||
|
||||
err = svc.repo.UpsertProject(ctx, project)
|
||||
respFilterExpr, err := filter.ParseQuery(req.Msg.ResponseFilterExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proj: failed to update project: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("proj: failed to parse intercept response filter: %w", err))
|
||||
}
|
||||
|
||||
svc.senderSvc.SetFindReqsFilter(filter)
|
||||
svc.interceptSvc.UpdateSettings(intercept.Settings{
|
||||
RequestsEnabled: req.Msg.RequestsEnabled,
|
||||
ResponsesEnabled: req.Msg.ResponsesEnabled,
|
||||
RequestFilter: reqFilterExpr,
|
||||
ResponseFilter: respFilterExpr,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) IsProjectActive(projectID ulid.ULID) bool {
|
||||
return projectID.Compare(svc.activeProjectID) == 0
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error {
|
||||
project, err := svc.ActiveProject(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project.Settings.InterceptRequests = settings.RequestsEnabled
|
||||
project.Settings.InterceptResponses = settings.ResponsesEnabled
|
||||
project.Settings.InterceptRequestFilter = settings.RequestFilter
|
||||
project.Settings.InterceptResponseFilter = settings.ResponseFilter
|
||||
|
||||
err = svc.repo.UpsertProject(ctx, project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proj: failed to update project: %w", err)
|
||||
}
|
||||
|
||||
svc.interceptSvc.UpdateSettings(settings)
|
||||
|
||||
return nil
|
||||
return &connect.Response[UpdateInterceptSettingsResponse]{}, nil
|
||||
}
|
||||
|
1181
pkg/proj/proj.pb.go
Normal file
1181
pkg/proj/proj.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,14 +2,12 @@ package proj
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindProjectByID(ctx context.Context, id ulid.ULID) (Project, error)
|
||||
UpsertProject(ctx context.Context, project Project) error
|
||||
DeleteProject(ctx context.Context, id ulid.ULID) error
|
||||
Projects(ctx context.Context) ([]Project, error)
|
||||
FindProjectByID(ctx context.Context, id string) (*Project, error)
|
||||
UpsertProject(ctx context.Context, project *Project) error
|
||||
DeleteProject(ctx context.Context, id string) error
|
||||
Projects(ctx context.Context) ([]*Project, error)
|
||||
Close() error
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
@ -34,7 +34,7 @@ var reqFilterKeyFns = map[string]func(req *http.Request) (string, error){
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
return string(body), nil
|
||||
},
|
||||
}
|
||||
@ -61,7 +61,7 @@ var resFilterKeyFns = map[string]func(res *http.Response) (string, error){
|
||||
return "", err
|
||||
}
|
||||
|
||||
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
|
||||
return string(body), nil
|
||||
},
|
||||
@ -134,7 +134,7 @@ func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, er
|
||||
}
|
||||
|
||||
if leftVal == "headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, httppb.ParseHeader(req.Header))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
@ -267,7 +267,7 @@ func MatchRequestScope(req *http.Request, s *scope.Scope) (bool, error) {
|
||||
return false, fmt.Errorf("failed to read request body: %w", err)
|
||||
}
|
||||
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
|
||||
if matches := rule.Body.Match(body); matches {
|
||||
return true, nil
|
||||
@ -345,7 +345,7 @@ func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, e
|
||||
}
|
||||
|
||||
if leftVal == "headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, res.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, httppb.ParseHeader(res.Header))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
|
@ -3,23 +3,19 @@ package intercept_test
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
func TestRequestModifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -33,7 +29,7 @@ func TestRequestModifier(t *testing.T) {
|
||||
ResponsesEnabled: false,
|
||||
})
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
|
||||
err := svc.ModifyRequest(reqID, nil, nil)
|
||||
if !errors.Is(err, intercept.ErrRequestNotFound) {
|
||||
@ -55,7 +51,7 @@ func TestRequestModifier(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
req := httptest.NewRequest("GET", "https://example.com/foo", nil)
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
*req = *req.WithContext(ctx)
|
||||
*req = *req.WithContext(proxy.WithRequestID(req.Context(), reqID))
|
||||
|
||||
@ -82,7 +78,7 @@ func TestRequestModifier(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "https://example.com/foo", nil)
|
||||
req.Header.Set("X-Foo", "foo")
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
*req = *req.WithContext(proxy.WithRequestID(req.Context(), reqID))
|
||||
|
||||
modReq := req.Clone(context.Background())
|
||||
@ -143,7 +139,7 @@ func TestResponseModifier(t *testing.T) {
|
||||
ResponsesEnabled: true,
|
||||
})
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
|
||||
err := svc.ModifyResponse(reqID, nil)
|
||||
if !errors.Is(err, intercept.ErrRequestNotFound) {
|
||||
@ -165,7 +161,7 @@ func TestResponseModifier(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
req := httptest.NewRequest("GET", "https://example.com/foo", nil)
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
*req = *req.WithContext(ctx)
|
||||
*req = *req.WithContext(proxy.WithRequestID(req.Context(), reqID))
|
||||
|
||||
@ -212,7 +208,7 @@ func TestResponseModifier(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "https://example.com/foo", nil)
|
||||
req.Header.Set("X-Foo", "foo")
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
*req = *req.WithContext(proxy.WithRequestID(req.Context(), reqID))
|
||||
|
||||
res := &http.Response{
|
||||
|
@ -7,21 +7,17 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
type contextKey int
|
||||
|
||||
const reqIDKey contextKey = 0
|
||||
@ -95,7 +91,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqID := ulid.Make()
|
||||
ctx := context.WithValue(r.Context(), reqIDKey, reqID)
|
||||
*r = *r.WithContext(ctx)
|
||||
|
||||
|
@ -3,15 +3,13 @@ package reqlog
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error)
|
||||
FindRequestLogByID(ctx context.Context, projectID, id ulid.ULID) (RequestLog, error)
|
||||
StoreRequestLog(ctx context.Context, reqLog RequestLog) error
|
||||
StoreResponseLog(ctx context.Context, projectID, reqLogID ulid.ULID, resLog ResponseLog) error
|
||||
ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error
|
||||
FindRequestLogs(ctx context.Context, projectID string, filterFn func(*HttpRequestLog) (bool, error)) ([]*HttpRequestLog, error)
|
||||
FindRequestLogByID(ctx context.Context, projectID, id string) (*HttpRequestLog, error)
|
||||
StoreRequestLog(ctx context.Context, reqLog *HttpRequestLog) error
|
||||
StoreResponseLog(ctx context.Context, projectID, reqLogID string, resLog *httppb.Response) error
|
||||
ClearRequestLogs(ctx context.Context, projectID string) error
|
||||
}
|
||||
|
167
pkg/reqlog/reqlog.connect.go
Normal file
167
pkg/reqlog/reqlog.connect.go
Normal file
@ -0,0 +1,167 @@
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: reqlog/reqlog.proto
|
||||
|
||||
package reqlog
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// HttpRequestLogServiceName is the fully-qualified name of the HttpRequestLogService service.
|
||||
HttpRequestLogServiceName = "hetty.reqlog.v1.HttpRequestLogService"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// HttpRequestLogServiceGetHttpRequestLogProcedure is the fully-qualified name of the
|
||||
// HttpRequestLogService's GetHttpRequestLog RPC.
|
||||
HttpRequestLogServiceGetHttpRequestLogProcedure = "/hetty.reqlog.v1.HttpRequestLogService/GetHttpRequestLog"
|
||||
// HttpRequestLogServiceListHttpRequestLogsProcedure is the fully-qualified name of the
|
||||
// HttpRequestLogService's ListHttpRequestLogs RPC.
|
||||
HttpRequestLogServiceListHttpRequestLogsProcedure = "/hetty.reqlog.v1.HttpRequestLogService/ListHttpRequestLogs"
|
||||
// HttpRequestLogServiceClearHttpRequestLogsProcedure is the fully-qualified name of the
|
||||
// HttpRequestLogService's ClearHttpRequestLogs RPC.
|
||||
HttpRequestLogServiceClearHttpRequestLogsProcedure = "/hetty.reqlog.v1.HttpRequestLogService/ClearHttpRequestLogs"
|
||||
)
|
||||
|
||||
// HttpRequestLogServiceClient is a client for the hetty.reqlog.v1.HttpRequestLogService service.
|
||||
type HttpRequestLogServiceClient interface {
|
||||
GetHttpRequestLog(context.Context, *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error)
|
||||
ListHttpRequestLogs(context.Context, *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error)
|
||||
ClearHttpRequestLogs(context.Context, *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error)
|
||||
}
|
||||
|
||||
// NewHttpRequestLogServiceClient constructs a client for the hetty.reqlog.v1.HttpRequestLogService
|
||||
// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
|
||||
// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
|
||||
// the connect.WithGRPC() or connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewHttpRequestLogServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) HttpRequestLogServiceClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
httpRequestLogServiceMethods := File_reqlog_reqlog_proto.Services().ByName("HttpRequestLogService").Methods()
|
||||
return &httpRequestLogServiceClient{
|
||||
getHttpRequestLog: connect.NewClient[GetHttpRequestLogRequest, GetHttpRequestLogResponse](
|
||||
httpClient,
|
||||
baseURL+HttpRequestLogServiceGetHttpRequestLogProcedure,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("GetHttpRequestLog")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listHttpRequestLogs: connect.NewClient[ListHttpRequestLogsRequest, ListHttpRequestLogsResponse](
|
||||
httpClient,
|
||||
baseURL+HttpRequestLogServiceListHttpRequestLogsProcedure,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ListHttpRequestLogs")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
clearHttpRequestLogs: connect.NewClient[ClearHttpRequestLogsRequest, ClearHttpRequestLogsResponse](
|
||||
httpClient,
|
||||
baseURL+HttpRequestLogServiceClearHttpRequestLogsProcedure,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ClearHttpRequestLogs")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// httpRequestLogServiceClient implements HttpRequestLogServiceClient.
|
||||
type httpRequestLogServiceClient struct {
|
||||
getHttpRequestLog *connect.Client[GetHttpRequestLogRequest, GetHttpRequestLogResponse]
|
||||
listHttpRequestLogs *connect.Client[ListHttpRequestLogsRequest, ListHttpRequestLogsResponse]
|
||||
clearHttpRequestLogs *connect.Client[ClearHttpRequestLogsRequest, ClearHttpRequestLogsResponse]
|
||||
}
|
||||
|
||||
// GetHttpRequestLog calls hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog.
|
||||
func (c *httpRequestLogServiceClient) GetHttpRequestLog(ctx context.Context, req *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error) {
|
||||
return c.getHttpRequestLog.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListHttpRequestLogs calls hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs.
|
||||
func (c *httpRequestLogServiceClient) ListHttpRequestLogs(ctx context.Context, req *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error) {
|
||||
return c.listHttpRequestLogs.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ClearHttpRequestLogs calls hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs.
|
||||
func (c *httpRequestLogServiceClient) ClearHttpRequestLogs(ctx context.Context, req *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error) {
|
||||
return c.clearHttpRequestLogs.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// HttpRequestLogServiceHandler is an implementation of the hetty.reqlog.v1.HttpRequestLogService
|
||||
// service.
|
||||
type HttpRequestLogServiceHandler interface {
|
||||
GetHttpRequestLog(context.Context, *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error)
|
||||
ListHttpRequestLogs(context.Context, *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error)
|
||||
ClearHttpRequestLogs(context.Context, *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error)
|
||||
}
|
||||
|
||||
// NewHttpRequestLogServiceHandler builds an HTTP handler from the service implementation. It
|
||||
// returns the path on which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewHttpRequestLogServiceHandler(svc HttpRequestLogServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
httpRequestLogServiceMethods := File_reqlog_reqlog_proto.Services().ByName("HttpRequestLogService").Methods()
|
||||
httpRequestLogServiceGetHttpRequestLogHandler := connect.NewUnaryHandler(
|
||||
HttpRequestLogServiceGetHttpRequestLogProcedure,
|
||||
svc.GetHttpRequestLog,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("GetHttpRequestLog")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
httpRequestLogServiceListHttpRequestLogsHandler := connect.NewUnaryHandler(
|
||||
HttpRequestLogServiceListHttpRequestLogsProcedure,
|
||||
svc.ListHttpRequestLogs,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ListHttpRequestLogs")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
httpRequestLogServiceClearHttpRequestLogsHandler := connect.NewUnaryHandler(
|
||||
HttpRequestLogServiceClearHttpRequestLogsProcedure,
|
||||
svc.ClearHttpRequestLogs,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ClearHttpRequestLogs")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/hetty.reqlog.v1.HttpRequestLogService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case HttpRequestLogServiceGetHttpRequestLogProcedure:
|
||||
httpRequestLogServiceGetHttpRequestLogHandler.ServeHTTP(w, r)
|
||||
case HttpRequestLogServiceListHttpRequestLogsProcedure:
|
||||
httpRequestLogServiceListHttpRequestLogsHandler.ServeHTTP(w, r)
|
||||
case HttpRequestLogServiceClearHttpRequestLogsProcedure:
|
||||
httpRequestLogServiceClearHttpRequestLogsHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedHttpRequestLogServiceHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedHttpRequestLogServiceHandler struct{}
|
||||
|
||||
func (UnimplementedHttpRequestLogServiceHandler) GetHttpRequestLog(context.Context, *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedHttpRequestLogServiceHandler) ListHttpRequestLogs(context.Context, *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedHttpRequestLogServiceHandler) ClearHttpRequestLogs(context.Context, *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs is not implemented"))
|
||||
}
|
@ -6,13 +6,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"connectrpc.com/connect"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
@ -26,48 +26,21 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRequestNotFound = errors.New("reqlog: request not found")
|
||||
ErrRequestLogNotFound = errors.New("reqlog: request not found")
|
||||
ErrProjectIDMustBeSet = errors.New("reqlog: project ID must be set")
|
||||
)
|
||||
|
||||
type RequestLog struct {
|
||||
ID ulid.ULID
|
||||
ProjectID ulid.ULID
|
||||
|
||||
URL *url.URL
|
||||
Method string
|
||||
Proto string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
|
||||
Response *ResponseLog
|
||||
}
|
||||
|
||||
type ResponseLog struct {
|
||||
Proto string
|
||||
StatusCode int
|
||||
Status string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
bypassOutOfScopeRequests bool
|
||||
findReqsFilter FindRequestsFilter
|
||||
activeProjectID ulid.ULID
|
||||
reqsFilter *RequestLogsFilter
|
||||
activeProjectID string
|
||||
scope *scope.Scope
|
||||
repo Repository
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
type FindRequestsFilter struct {
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr filter.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ActiveProjectID ulid.ULID
|
||||
ActiveProjectID string
|
||||
Scope *scope.Scope
|
||||
Repository Repository
|
||||
Logger log.Logger
|
||||
@ -88,25 +61,92 @@ func NewService(cfg Config) *Service {
|
||||
return s
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequests(ctx context.Context) ([]RequestLog, error) {
|
||||
return svc.repo.FindRequestLogs(ctx, svc.findReqsFilter, svc.scope)
|
||||
func (svc *Service) ListHttpRequestLogs(ctx context.Context, req *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error) {
|
||||
projectID := svc.activeProjectID
|
||||
if projectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
reqLogs, err := svc.repo.FindRequestLogs(ctx, projectID, svc.filterRequestLog)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("reqlog: failed to find request logs: %w", err))
|
||||
}
|
||||
|
||||
return connect.NewResponse(&ListHttpRequestLogsResponse{
|
||||
HttpRequestLogs: reqLogs,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) {
|
||||
func (svc *Service) filterRequestLog(reqLog *HttpRequestLog) (bool, error) {
|
||||
if svc.reqsFilter.GetOnlyInScope() && svc.scope != nil && !reqLog.MatchScope(svc.scope) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var f filter.Expression
|
||||
var err error
|
||||
if expr := svc.reqsFilter.GetSearchExpr(); expr != "" {
|
||||
f, err = filter.ParseQuery(expr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse search expression: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
match, err := reqLog.Matches(f)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match search expression for request log (id: %v): %w", reqLog.Id, err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequestLogByID(ctx context.Context, id string) (*HttpRequestLog, error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
return svc.repo.FindRequestLogByID(ctx, svc.activeProjectID, id)
|
||||
}
|
||||
|
||||
func (svc *Service) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
return svc.repo.ClearRequestLogs(ctx, projectID)
|
||||
// GetHttpRequestLog implements HttpRequestLogServiceHandler.
|
||||
func (svc *Service) GetHttpRequestLog(ctx context.Context, req *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error) {
|
||||
id, err := ulid.Parse(req.Msg.Id)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||
}
|
||||
|
||||
reqLog, err := svc.repo.FindRequestLogByID(ctx, svc.activeProjectID, id.String())
|
||||
if errors.Is(err, ErrRequestLogNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&GetHttpRequestLogResponse{
|
||||
HttpRequestLog: reqLog,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (svc *Service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error {
|
||||
resLog, err := ParseHTTPResponse(res)
|
||||
func (svc *Service) ClearHttpRequestLogs(ctx context.Context, req *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error) {
|
||||
err := svc.repo.ClearRequestLogs(ctx, svc.activeProjectID)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("reqlog: failed to clear request logs: %w", err))
|
||||
}
|
||||
|
||||
return connect.NewResponse(&ClearHttpRequestLogsResponse{}), nil
|
||||
}
|
||||
|
||||
func (svc *Service) storeResponse(ctx context.Context, reqLogID string, res *http.Response) error {
|
||||
respb, err := httppb.ParseHTTPResponse(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.repo.StoreResponseLog(ctx, svc.activeProjectID, reqLogID, resLog)
|
||||
return svc.repo.StoreResponseLog(ctx, svc.activeProjectID, reqLogID, respb)
|
||||
}
|
||||
|
||||
func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
||||
@ -121,19 +161,19 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
// TODO: Use io.LimitReader.
|
||||
var err error
|
||||
|
||||
body, err = ioutil.ReadAll(req.Body)
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
svc.logger.Errorw("Failed to read request body for logging.",
|
||||
"error", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
clone.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
clone.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
// Bypass logging if no project is active.
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
if svc.activeProjectID == "" {
|
||||
ctx := context.WithValue(req.Context(), LogBypassedKey, true)
|
||||
*req = *req.WithContext(ctx)
|
||||
|
||||
@ -161,14 +201,37 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
return
|
||||
}
|
||||
|
||||
reqLog := RequestLog{
|
||||
ID: reqID,
|
||||
ProjectID: svc.activeProjectID,
|
||||
Method: clone.Method,
|
||||
URL: clone.URL,
|
||||
Proto: clone.Proto,
|
||||
Header: clone.Header,
|
||||
Body: body,
|
||||
proto, ok := httppb.ProtoMap[clone.Proto]
|
||||
if !ok {
|
||||
svc.logger.Errorw("Bypassed logging: request has an invalid protocol.",
|
||||
"proto", clone.Proto)
|
||||
return
|
||||
}
|
||||
|
||||
method, ok := httppb.MethodMap[clone.Method]
|
||||
if !ok {
|
||||
svc.logger.Errorw("Bypassed logging: request has an invalid method.",
|
||||
"method", clone.Method)
|
||||
return
|
||||
}
|
||||
|
||||
headers := []*httppb.Header{}
|
||||
for k, v := range clone.Header {
|
||||
for _, vv := range v {
|
||||
headers = append(headers, &httppb.Header{Key: k, Value: vv})
|
||||
}
|
||||
}
|
||||
|
||||
reqLog := &HttpRequestLog{
|
||||
Id: reqID.String(),
|
||||
ProjectId: svc.activeProjectID,
|
||||
Request: &httppb.Request{
|
||||
Url: clone.URL.String(),
|
||||
Method: method,
|
||||
Protocol: proto,
|
||||
Headers: headers,
|
||||
Body: body,
|
||||
},
|
||||
}
|
||||
|
||||
err := svc.repo.StoreRequestLog(req.Context(), reqLog)
|
||||
@ -179,10 +242,10 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
}
|
||||
|
||||
svc.logger.Debugw("Stored request log.",
|
||||
"reqLogID", reqLog.ID.String(),
|
||||
"url", reqLog.URL.String())
|
||||
|
||||
ctx := context.WithValue(req.Context(), ReqLogIDKey, reqLog.ID)
|
||||
"reqLogID", reqLog.Id,
|
||||
"url", reqLog.Request.Url,
|
||||
)
|
||||
ctx := context.WithValue(req.Context(), ReqLogIDKey, reqID)
|
||||
*req = *req.WithContext(ctx)
|
||||
}
|
||||
}
|
||||
@ -216,7 +279,7 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := svc.storeResponse(context.Background(), reqLogID, &clone); err != nil {
|
||||
if err := svc.storeResponse(context.Background(), reqLogID.String(), &clone); err != nil {
|
||||
svc.logger.Errorw("Failed to store response log.",
|
||||
"error", err)
|
||||
} else {
|
||||
@ -229,20 +292,16 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) SetActiveProjectID(id ulid.ULID) {
|
||||
func (svc *Service) SetActiveProjectID(id string) {
|
||||
svc.activeProjectID = id
|
||||
}
|
||||
|
||||
func (svc *Service) ActiveProjectID() ulid.ULID {
|
||||
func (svc *Service) ActiveProjectID() string {
|
||||
return svc.activeProjectID
|
||||
}
|
||||
|
||||
func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
|
||||
svc.findReqsFilter = filter
|
||||
}
|
||||
|
||||
func (svc *Service) FindReqsFilter() FindRequestsFilter {
|
||||
return svc.findReqsFilter
|
||||
func (svc *Service) SetRequestLogsFilter(filter *RequestLogsFilter) {
|
||||
svc.reqsFilter = filter
|
||||
}
|
||||
|
||||
func (svc *Service) SetBypassOutOfScopeRequests(bypass bool) {
|
||||
@ -252,18 +311,3 @@ func (svc *Service) SetBypassOutOfScopeRequests(bypass bool) {
|
||||
func (svc *Service) BypassOutOfScopeRequests() bool {
|
||||
return svc.bypassOutOfScopeRequests
|
||||
}
|
||||
|
||||
func ParseHTTPResponse(res *http.Response) (ResponseLog, error) {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return ResponseLog{}, fmt.Errorf("reqlog: could not read body: %w", err)
|
||||
}
|
||||
|
||||
return ResponseLog{
|
||||
Proto: res.Proto,
|
||||
StatusCode: res.StatusCode,
|
||||
Status: res.Status,
|
||||
Header: res.Header,
|
||||
Body: body,
|
||||
}, nil
|
||||
}
|
||||
|
533
pkg/reqlog/reqlog.pb.go
Normal file
533
pkg/reqlog/reqlog.pb.go
Normal file
@ -0,0 +1,533 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc (unknown)
|
||||
// source: reqlog/reqlog.proto
|
||||
|
||||
package reqlog
|
||||
|
||||
import (
|
||||
http "github.com/dstotijn/hetty/pkg/http"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
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 HttpRequestLog struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ProjectId string `protobuf:"bytes,2,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"`
|
||||
RemoteIp string `protobuf:"bytes,3,opt,name=remote_ip,json=remoteIp,proto3" json:"remote_ip,omitempty"`
|
||||
Request *http.Request `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"`
|
||||
Response *http.Response `protobuf:"bytes,5,opt,name=response,proto3" json:"response,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) Reset() {
|
||||
*x = HttpRequestLog{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*HttpRequestLog) ProtoMessage() {}
|
||||
|
||||
func (x *HttpRequestLog) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 HttpRequestLog.ProtoReflect.Descriptor instead.
|
||||
func (*HttpRequestLog) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetProjectId() string {
|
||||
if x != nil {
|
||||
return x.ProjectId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetRemoteIp() string {
|
||||
if x != nil {
|
||||
return x.RemoteIp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetRequest() *http.Request {
|
||||
if x != nil {
|
||||
return x.Request
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetResponse() *http.Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetHttpRequestLogRequest 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 *GetHttpRequestLogRequest) Reset() {
|
||||
*x = GetHttpRequestLogRequest{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetHttpRequestLogRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 GetHttpRequestLogRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetHttpRequestLogRequest) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetHttpRequestLogResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
HttpRequestLog *HttpRequestLog `protobuf:"bytes,1,opt,name=http_request_log,json=httpRequestLog,proto3" json:"http_request_log,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) Reset() {
|
||||
*x = GetHttpRequestLogResponse{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetHttpRequestLogResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 GetHttpRequestLogResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetHttpRequestLogResponse) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) GetHttpRequestLog() *HttpRequestLog {
|
||||
if x != nil {
|
||||
return x.HttpRequestLog
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListHttpRequestLogsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsRequest) Reset() {
|
||||
*x = ListHttpRequestLogsRequest{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListHttpRequestLogsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListHttpRequestLogsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 ListHttpRequestLogsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListHttpRequestLogsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
type ListHttpRequestLogsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
HttpRequestLogs []*HttpRequestLog `protobuf:"bytes,1,rep,name=http_request_logs,json=httpRequestLogs,proto3" json:"http_request_logs,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) Reset() {
|
||||
*x = ListHttpRequestLogsResponse{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListHttpRequestLogsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 ListHttpRequestLogsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListHttpRequestLogsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) GetHttpRequestLogs() []*HttpRequestLog {
|
||||
if x != nil {
|
||||
return x.HttpRequestLogs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RequestLogsFilter struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
OnlyInScope bool `protobuf:"varint,1,opt,name=only_in_scope,json=onlyInScope,proto3" json:"only_in_scope,omitempty"`
|
||||
SearchExpr string `protobuf:"bytes,2,opt,name=search_expr,json=searchExpr,proto3" json:"search_expr,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) Reset() {
|
||||
*x = RequestLogsFilter{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RequestLogsFilter) ProtoMessage() {}
|
||||
|
||||
func (x *RequestLogsFilter) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 RequestLogsFilter.ProtoReflect.Descriptor instead.
|
||||
func (*RequestLogsFilter) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) GetOnlyInScope() bool {
|
||||
if x != nil {
|
||||
return x.OnlyInScope
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) GetSearchExpr() string {
|
||||
if x != nil {
|
||||
return x.SearchExpr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ClearHttpRequestLogsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsRequest) Reset() {
|
||||
*x = ClearHttpRequestLogsRequest{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ClearHttpRequestLogsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ClearHttpRequestLogsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 ClearHttpRequestLogsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ClearHttpRequestLogsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
type ClearHttpRequestLogsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsResponse) Reset() {
|
||||
*x = ClearHttpRequestLogsResponse{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ClearHttpRequestLogsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ClearHttpRequestLogsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_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 ClearHttpRequestLogsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ClearHttpRequestLogsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
var File_reqlog_reqlog_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_reqlog_reqlog_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2f, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71,
|
||||
0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x1a, 0x0f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x68, 0x74, 0x74,
|
||||
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x74, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72,
|
||||
0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||
0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x6d,
|
||||
0x6f, 0x74, 0x65, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
|
||||
0x6d, 0x6f, 0x74, 0x65, 0x49, 0x70, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e,
|
||||
0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52,
|
||||
0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x74,
|
||||
0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x0a,
|
||||
0x18, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c,
|
||||
0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x66, 0x0a, 0x19, 0x47, 0x65, 0x74,
|
||||
0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1f, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f,
|
||||
0x67, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f,
|
||||
0x67, 0x22, 0x1c, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
|
||||
0x6a, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b,
|
||||
0x0a, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6c,
|
||||
0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x65, 0x74, 0x74,
|
||||
0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x0f, 0x68, 0x74, 0x74, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0x58, 0x0a, 0x11, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72,
|
||||
0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x79, 0x49, 0x6e, 0x53,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x65,
|
||||
0x78, 0x70, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x72, 0x63,
|
||||
0x68, 0x45, 0x78, 0x70, 0x72, 0x22, 0x1d, 0x0a, 0x1b, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74,
|
||||
0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x22, 0x1e, 0x0a, 0x1c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74, 0x74,
|
||||
0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf0, 0x02, 0x0a, 0x15, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6c,
|
||||
0x0a, 0x11, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x4c, 0x6f, 0x67, 0x12, 0x29, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c,
|
||||
0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a,
|
||||
0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c,
|
||||
0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x72, 0x0a, 0x13,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c,
|
||||
0x6f, 0x67, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c,
|
||||
0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x2c, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x75, 0x0a, 0x14, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79,
|
||||
0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72,
|
||||
0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74,
|
||||
0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x73, 0x74, 0x6f, 0x74, 0x69, 0x6a, 0x6e, 0x2f, 0x68,
|
||||
0x65, 0x74, 0x74, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_reqlog_reqlog_proto_rawDescOnce sync.Once
|
||||
file_reqlog_reqlog_proto_rawDescData = file_reqlog_reqlog_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_reqlog_reqlog_proto_rawDescGZIP() []byte {
|
||||
file_reqlog_reqlog_proto_rawDescOnce.Do(func() {
|
||||
file_reqlog_reqlog_proto_rawDescData = protoimpl.X.CompressGZIP(file_reqlog_reqlog_proto_rawDescData)
|
||||
})
|
||||
return file_reqlog_reqlog_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_reqlog_reqlog_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_reqlog_reqlog_proto_goTypes = []any{
|
||||
(*HttpRequestLog)(nil), // 0: hetty.reqlog.v1.HttpRequestLog
|
||||
(*GetHttpRequestLogRequest)(nil), // 1: hetty.reqlog.v1.GetHttpRequestLogRequest
|
||||
(*GetHttpRequestLogResponse)(nil), // 2: hetty.reqlog.v1.GetHttpRequestLogResponse
|
||||
(*ListHttpRequestLogsRequest)(nil), // 3: hetty.reqlog.v1.ListHttpRequestLogsRequest
|
||||
(*ListHttpRequestLogsResponse)(nil), // 4: hetty.reqlog.v1.ListHttpRequestLogsResponse
|
||||
(*RequestLogsFilter)(nil), // 5: hetty.reqlog.v1.RequestLogsFilter
|
||||
(*ClearHttpRequestLogsRequest)(nil), // 6: hetty.reqlog.v1.ClearHttpRequestLogsRequest
|
||||
(*ClearHttpRequestLogsResponse)(nil), // 7: hetty.reqlog.v1.ClearHttpRequestLogsResponse
|
||||
(*http.Request)(nil), // 8: hetty.http.v1.Request
|
||||
(*http.Response)(nil), // 9: hetty.http.v1.Response
|
||||
}
|
||||
var file_reqlog_reqlog_proto_depIdxs = []int32{
|
||||
8, // 0: hetty.reqlog.v1.HttpRequestLog.request:type_name -> hetty.http.v1.Request
|
||||
9, // 1: hetty.reqlog.v1.HttpRequestLog.response:type_name -> hetty.http.v1.Response
|
||||
0, // 2: hetty.reqlog.v1.GetHttpRequestLogResponse.http_request_log:type_name -> hetty.reqlog.v1.HttpRequestLog
|
||||
0, // 3: hetty.reqlog.v1.ListHttpRequestLogsResponse.http_request_logs:type_name -> hetty.reqlog.v1.HttpRequestLog
|
||||
1, // 4: hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog:input_type -> hetty.reqlog.v1.GetHttpRequestLogRequest
|
||||
3, // 5: hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs:input_type -> hetty.reqlog.v1.ListHttpRequestLogsRequest
|
||||
6, // 6: hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs:input_type -> hetty.reqlog.v1.ClearHttpRequestLogsRequest
|
||||
2, // 7: hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog:output_type -> hetty.reqlog.v1.GetHttpRequestLogResponse
|
||||
4, // 8: hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs:output_type -> hetty.reqlog.v1.ListHttpRequestLogsResponse
|
||||
7, // 9: hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs:output_type -> hetty.reqlog.v1.ClearHttpRequestLogsResponse
|
||||
7, // [7:10] is the sub-list for method output_type
|
||||
4, // [4:7] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_reqlog_reqlog_proto_init() }
|
||||
func file_reqlog_reqlog_proto_init() {
|
||||
if File_reqlog_reqlog_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_reqlog_reqlog_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_reqlog_reqlog_proto_goTypes,
|
||||
DependencyIndexes: file_reqlog_reqlog_proto_depIdxs,
|
||||
MessageInfos: file_reqlog_reqlog_proto_msgTypes,
|
||||
}.Build()
|
||||
File_reqlog_reqlog_proto = out.File
|
||||
file_reqlog_reqlog_proto_rawDesc = nil
|
||||
file_reqlog_reqlog_proto_goTypes = nil
|
||||
file_reqlog_reqlog_proto_depIdxs = nil
|
||||
}
|
@ -3,27 +3,24 @@ package reqlog_test
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/testutil"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
//nolint:paralleltest
|
||||
func TestRequestModifier(t *testing.T) {
|
||||
path := t.TempDir() + "bolt.db"
|
||||
@ -39,9 +36,9 @@ func TestRequestModifier(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
projectID := ulid.Make().String()
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
@ -58,34 +55,39 @@ func TestRequestModifier(t *testing.T) {
|
||||
}
|
||||
reqModFn := svc.RequestModifier(next)
|
||||
req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar"))
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
req.Header.Add("X-Yolo", "swag")
|
||||
reqID := ulid.Make()
|
||||
req = req.WithContext(proxy.WithRequestID(req.Context(), reqID))
|
||||
|
||||
reqModFn(req)
|
||||
|
||||
t.Run("request log was stored in repository", func(t *testing.T) {
|
||||
exp := reqlog.RequestLog{
|
||||
ID: reqID,
|
||||
ProjectID: svc.ActiveProjectID(),
|
||||
Method: req.Method,
|
||||
URL: req.URL,
|
||||
Proto: req.Proto,
|
||||
Header: req.Header,
|
||||
Body: []byte("modified body"),
|
||||
exp := &reqlog.HttpRequestLog{
|
||||
Id: reqID.String(),
|
||||
ProjectId: svc.ActiveProjectID(),
|
||||
Request: &httppb.Request{
|
||||
Url: "https://example.com/",
|
||||
Method: httppb.Method_METHOD_GET,
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP11,
|
||||
Headers: []*httppb.Header{
|
||||
{
|
||||
Key: "X-Yolo",
|
||||
Value: "swag",
|
||||
},
|
||||
},
|
||||
Body: []byte("modified body"),
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.FindRequestLogByID(context.Background(), reqID)
|
||||
got, err := db.FindRequestLogByID(context.Background(), svc.ActiveProjectID(), reqID.String())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by id: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("request log not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "request log not equal", exp, got)
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:paralleltest
|
||||
func TestResponseModifier(t *testing.T) {
|
||||
path := t.TempDir() + "bolt.db"
|
||||
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||
@ -100,9 +102,9 @@ func TestResponseModifier(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
projectID := "foobar-project-id"
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
@ -120,39 +122,44 @@ func TestResponseModifier(t *testing.T) {
|
||||
resModFn := svc.ResponseModifier(next)
|
||||
|
||||
req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar"))
|
||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqLogID := ulid.Make()
|
||||
req = req.WithContext(context.WithValue(req.Context(), reqlog.ReqLogIDKey, reqLogID))
|
||||
|
||||
err = db.StoreRequestLog(context.Background(), reqlog.RequestLog{
|
||||
ID: reqLogID,
|
||||
ProjectID: projectID,
|
||||
err = db.StoreRequestLog(context.Background(), &reqlog.HttpRequestLog{
|
||||
Id: reqLogID.String(),
|
||||
ProjectId: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to store request log: %v", err)
|
||||
}
|
||||
|
||||
res := &http.Response{
|
||||
Request: req,
|
||||
Body: io.NopCloser(strings.NewReader("bar")),
|
||||
Request: req,
|
||||
Proto: "HTTP/1.1",
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(strings.NewReader("bar")),
|
||||
}
|
||||
|
||||
if err := resModFn(res); err != nil {
|
||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
||||
t.Run("request log was stored in repository", func(t *testing.T) {
|
||||
// Dirty (but simple) wait for other goroutine to finish calling repository.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Dirty (but simple) wait for other goroutine to finish calling repository.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
got, err := svc.FindRequestLogByID(context.Background(), reqLogID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by id: %v", err)
|
||||
}
|
||||
got, err := db.FindRequestLogByID(context.Background(), svc.ActiveProjectID(), reqLogID.String())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by id: %v", err)
|
||||
}
|
||||
|
||||
t.Run("ran next modifier first, before calling repository", func(t *testing.T) {
|
||||
if exp := "modified body"; exp != string(got.Response.Body) {
|
||||
t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got.Response.Body))
|
||||
}
|
||||
})
|
||||
})
|
||||
exp := &httppb.Response{
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP11,
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Headers: []*httppb.Header{},
|
||||
Body: []byte("modified body"),
|
||||
}
|
||||
|
||||
testutil.ProtoDiff(t, "response not equal", exp, got.GetResponse())
|
||||
}
|
||||
|
@ -3,40 +3,34 @@ package reqlog
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
|
||||
"req.id": func(rl RequestLog) string { return rl.ID.String() },
|
||||
"req.proto": func(rl RequestLog) string { return rl.Proto },
|
||||
"req.url": func(rl RequestLog) string {
|
||||
if rl.URL == nil {
|
||||
var reqLogSearchKeyFns = map[string]func(rl *HttpRequestLog) string{
|
||||
"req.id": func(rl *HttpRequestLog) string { return rl.GetId() },
|
||||
"req.proto": func(rl *HttpRequestLog) string { return rl.GetRequest().GetProtocol().String() },
|
||||
"req.url": func(rl *HttpRequestLog) string { return rl.GetRequest().GetUrl() },
|
||||
"req.method": func(rl *HttpRequestLog) string { return rl.GetRequest().GetMethod().String() },
|
||||
"req.body": func(rl *HttpRequestLog) string { return string(rl.GetRequest().GetBody()) },
|
||||
"req.timestamp": func(rl *HttpRequestLog) string {
|
||||
id, err := ulid.Parse(rl.GetId())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return rl.URL.String()
|
||||
return ulid.Time(id.Time()).String()
|
||||
},
|
||||
"req.method": func(rl RequestLog) string { return rl.Method },
|
||||
"req.body": func(rl RequestLog) string { return string(rl.Body) },
|
||||
"req.timestamp": func(rl RequestLog) string { return ulid.Time(rl.ID.Time()).String() },
|
||||
}
|
||||
|
||||
var ResLogSearchKeyFns = map[string]func(rl ResponseLog) string{
|
||||
"res.proto": func(rl ResponseLog) string { return rl.Proto },
|
||||
"res.statusCode": func(rl ResponseLog) string { return strconv.Itoa(rl.StatusCode) },
|
||||
"res.statusReason": func(rl ResponseLog) string { return rl.Status },
|
||||
"res.body": func(rl ResponseLog) string { return string(rl.Body) },
|
||||
}
|
||||
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
func (reqLog *HttpRequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
return reqLog.matchPrefixExpr(e)
|
||||
@ -49,7 +43,7 @@ func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
func (reqLog *HttpRequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
match, err := reqLog.Matches(expr.Right)
|
||||
@ -63,7 +57,7 @@ func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, er
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
func (reqLog *HttpRequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
left, err := reqLog.Matches(expr.Left)
|
||||
@ -99,7 +93,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
leftVal := reqLog.getMappedStringLiteral(left.Value)
|
||||
|
||||
if leftVal == "req.headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Request.Headers)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
@ -108,7 +102,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
}
|
||||
|
||||
if leftVal == "res.headers" && reqLog.Response != nil {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Headers)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||
}
|
||||
@ -159,7 +153,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
||||
func (reqLog *HttpRequestLog) getMappedStringLiteral(s string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "req."):
|
||||
fn, ok := reqLogSearchKeyFns[s]
|
||||
@ -167,28 +161,22 @@ func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
||||
return fn(reqLog)
|
||||
}
|
||||
case strings.HasPrefix(s, "res."):
|
||||
if reqLog.Response == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fn, ok := ResLogSearchKeyFns[s]
|
||||
fn, ok := http.ResponseSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(*reqLog.Response)
|
||||
return fn(reqLog.GetResponse())
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range reqLog.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
func (reqLog *HttpRequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for _, header := range reqLog.GetRequest().GetHeaders() {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", header.Key, header.Value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,21 +189,19 @@ func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bo
|
||||
}
|
||||
}
|
||||
|
||||
if reqLog.Response != nil {
|
||||
for key, values := range reqLog.Response.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
if res := reqLog.GetResponse(); res != nil {
|
||||
for _, header := range res.Headers {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", header.Key, header.Value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range ResLogSearchKeyFns {
|
||||
for _, fn := range http.ResponseSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*reqLog.Response)),
|
||||
strings.ToLower(fn(reqLog.GetResponse())),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
@ -226,29 +212,25 @@ func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bo
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) MatchScope(s *scope.Scope) bool {
|
||||
func (reqLog *HttpRequestLog) MatchScope(s *scope.Scope) bool {
|
||||
for _, rule := range s.Rules() {
|
||||
if rule.URL != nil && reqLog.URL != nil {
|
||||
if matches := rule.URL.MatchString(reqLog.URL.String()); matches {
|
||||
if rule.URL != nil {
|
||||
if matches := rule.URL.MatchString(reqLog.GetRequest().GetUrl()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range reqLog.Header {
|
||||
for _, header := range reqLog.GetRequest().GetHeaders() {
|
||||
var keyMatches, valueMatches bool
|
||||
|
||||
if rule.Header.Key != nil {
|
||||
if matches := rule.Header.Key.MatchString(key); matches {
|
||||
keyMatches = true
|
||||
}
|
||||
if matches := rule.Header.Key.MatchString(header.Key); matches {
|
||||
keyMatches = true
|
||||
}
|
||||
|
||||
if rule.Header.Value != nil {
|
||||
for _, value := range values {
|
||||
if matches := rule.Header.Value.MatchString(value); matches {
|
||||
valueMatches = true
|
||||
break
|
||||
}
|
||||
if matches := rule.Header.Value.MatchString(header.Value); matches {
|
||||
valueMatches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// When only key or value is set, match on whatever is set.
|
||||
@ -264,7 +246,7 @@ func (reqLog RequestLog) MatchScope(s *scope.Scope) bool {
|
||||
}
|
||||
|
||||
if rule.Body != nil {
|
||||
if matches := rule.Body.Match(reqLog.Body); matches {
|
||||
if matches := rule.Body.Match(reqLog.GetRequest().GetBody()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
)
|
||||
|
||||
@ -13,15 +14,17 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
requestLog reqlog.RequestLog
|
||||
requestLog *reqlog.HttpRequestLog
|
||||
expectedMatch bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "infix expression, equal operator, match",
|
||||
query: "req.body = foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -29,8 +32,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, not equal operator, match",
|
||||
query: "req.body != bar",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -38,8 +43,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than operator, match",
|
||||
query: "req.body > a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -47,8 +54,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than operator, match",
|
||||
query: "req.body < b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -56,8 +65,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match greater than",
|
||||
query: "req.body >= a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -65,8 +76,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match equal",
|
||||
query: "req.body >= a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -74,8 +87,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match less than",
|
||||
query: "req.body <= b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -83,8 +98,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match equal",
|
||||
query: "req.body <= b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -92,8 +109,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, regular expression operator, match",
|
||||
query: `req.body =~ "^foo(.*)$"`,
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foobar"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -101,8 +120,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, negate regular expression operator, match",
|
||||
query: `req.body !~ "^foo(.*)$"`,
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("xoobar"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("xoobar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -110,9 +131,11 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, and operator, match",
|
||||
query: "req.body = bar AND res.body = yolo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("bar"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
Response: &http.Response{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
@ -122,9 +145,11 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, or operator, match",
|
||||
query: "req.body = bar OR res.body = yolo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
Response: &http.Response{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
@ -134,8 +159,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "prefix expression, not operator, match",
|
||||
query: "NOT (req.body = bar)",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -143,8 +170,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, match in request log",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -152,8 +181,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, no match",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("bar"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: false,
|
||||
expectedError: nil,
|
||||
@ -161,8 +192,8 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, match in response log",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Response: &reqlog.ResponseLog{
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Response: &http.Response{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
|
159
pkg/scope/scope.pb.go
Normal file
159
pkg/scope/scope.pb.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc (unknown)
|
||||
// source: scope/scope.proto
|
||||
|
||||
package scope
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
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 ScopeRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UrlRegexp string `protobuf:"bytes,1,opt,name=url_regexp,json=urlRegexp,proto3" json:"url_regexp,omitempty"`
|
||||
HeaderKeyRegexp string `protobuf:"bytes,2,opt,name=header_key_regexp,json=headerKeyRegexp,proto3" json:"header_key_regexp,omitempty"`
|
||||
HeaderValueRegexp string `protobuf:"bytes,3,opt,name=header_value_regexp,json=headerValueRegexp,proto3" json:"header_value_regexp,omitempty"`
|
||||
BodyRegexp string `protobuf:"bytes,4,opt,name=body_regexp,json=bodyRegexp,proto3" json:"body_regexp,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ScopeRule) Reset() {
|
||||
*x = ScopeRule{}
|
||||
mi := &file_scope_scope_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ScopeRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ScopeRule) ProtoMessage() {}
|
||||
|
||||
func (x *ScopeRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_scope_scope_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 ScopeRule.ProtoReflect.Descriptor instead.
|
||||
func (*ScopeRule) Descriptor() ([]byte, []int) {
|
||||
return file_scope_scope_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ScopeRule) GetUrlRegexp() string {
|
||||
if x != nil {
|
||||
return x.UrlRegexp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScopeRule) GetHeaderKeyRegexp() string {
|
||||
if x != nil {
|
||||
return x.HeaderKeyRegexp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScopeRule) GetHeaderValueRegexp() string {
|
||||
if x != nil {
|
||||
return x.HeaderValueRegexp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ScopeRule) GetBodyRegexp() string {
|
||||
if x != nil {
|
||||
return x.BodyRegexp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_scope_scope_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_scope_scope_proto_rawDesc = []byte{
|
||||
0x0a, 0x11, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65,
|
||||
0x2e, 0x76, 0x31, 0x22, 0xa7, 0x01, 0x0a, 0x09, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x75, 0x6c,
|
||||
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x72, 0x6c, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x72, 0x6c, 0x52, 0x65, 0x67, 0x65, 0x78, 0x70,
|
||||
0x12, 0x2a, 0x0a, 0x11, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x72,
|
||||
0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x65, 0x61,
|
||||
0x64, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x2e, 0x0a, 0x13,
|
||||
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x72, 0x65, 0x67,
|
||||
0x65, 0x78, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x68, 0x65, 0x61, 0x64, 0x65,
|
||||
0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x1f, 0x0a, 0x0b,
|
||||
0x62, 0x6f, 0x64, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0a, 0x62, 0x6f, 0x64, 0x79, 0x52, 0x65, 0x67, 0x65, 0x78, 0x70, 0x42, 0x25, 0x5a,
|
||||
0x23, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x73, 0x74, 0x6f,
|
||||
0x74, 0x69, 0x6a, 0x6e, 0x2f, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_scope_scope_proto_rawDescOnce sync.Once
|
||||
file_scope_scope_proto_rawDescData = file_scope_scope_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_scope_scope_proto_rawDescGZIP() []byte {
|
||||
file_scope_scope_proto_rawDescOnce.Do(func() {
|
||||
file_scope_scope_proto_rawDescData = protoimpl.X.CompressGZIP(file_scope_scope_proto_rawDescData)
|
||||
})
|
||||
return file_scope_scope_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_scope_scope_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_scope_scope_proto_goTypes = []any{
|
||||
(*ScopeRule)(nil), // 0: hetty.scope.v1.ScopeRule
|
||||
}
|
||||
var file_scope_scope_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_scope_scope_proto_init() }
|
||||
func file_scope_scope_proto_init() {
|
||||
if File_scope_scope_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_scope_scope_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_scope_scope_proto_goTypes,
|
||||
DependencyIndexes: file_scope_scope_proto_depIdxs,
|
||||
MessageInfos: file_scope_scope_proto_msgTypes,
|
||||
}.Build()
|
||||
File_scope_scope_proto = out.File
|
||||
file_scope_scope_proto_rawDesc = nil
|
||||
file_scope_scope_proto_goTypes = nil
|
||||
file_scope_scope_proto_depIdxs = nil
|
||||
}
|
@ -2,15 +2,11 @@ package sender
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindSenderRequestByID(ctx context.Context, projectID, id ulid.ULID) (Request, error)
|
||||
FindSenderRequests(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error)
|
||||
StoreSenderRequest(ctx context.Context, req Request) error
|
||||
DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error
|
||||
FindSenderRequestByID(ctx context.Context, projectID, id string) (*Request, error)
|
||||
FindSenderRequests(ctx context.Context, projectID string, filterFn func(*Request) (bool, error)) ([]*Request, error)
|
||||
StoreSenderRequest(ctx context.Context, req *Request) error
|
||||
DeleteSenderRequests(ctx context.Context, projectID string) error
|
||||
}
|
||||
|
@ -5,31 +5,32 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
var senderReqSearchKeyFns = map[string]func(req Request) string{
|
||||
"req.id": func(req Request) string { return req.ID.String() },
|
||||
"req.proto": func(req Request) string { return req.Proto },
|
||||
"req.url": func(req Request) string {
|
||||
if req.URL == nil {
|
||||
var senderReqSearchKeyFns = map[string]func(req *Request) string{
|
||||
"req.id": func(req *Request) string { return req.Id },
|
||||
"req.proto": func(req *Request) string { return req.GetHttpRequest().GetProtocol().String() },
|
||||
"req.url": func(req *Request) string { return req.GetHttpRequest().GetUrl() },
|
||||
"req.method": func(req *Request) string { return req.GetHttpRequest().GetMethod().String() },
|
||||
"req.body": func(req *Request) string { return string(req.GetHttpRequest().GetBody()) },
|
||||
"req.timestamp": func(req *Request) string {
|
||||
id, err := ulid.Parse(req.Id)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return req.URL.String()
|
||||
return ulid.Time(id.Time()).String()
|
||||
},
|
||||
"req.method": func(req Request) string { return req.Method },
|
||||
"req.body": func(req Request) string { return string(req.Body) },
|
||||
"req.timestamp": func(req Request) string { return ulid.Time(req.ID.Time()).String() },
|
||||
}
|
||||
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (req Request) Matches(expr filter.Expression) (bool, error) {
|
||||
func (req *Request) Matches(expr filter.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
return req.matchPrefixExpr(e)
|
||||
@ -42,7 +43,7 @@ func (req Request) Matches(expr filter.Expression) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
func (req *Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
match, err := req.Matches(expr.Right)
|
||||
@ -56,7 +57,7 @@ func (req Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
func (req *Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
left, err := req.Matches(expr.Left)
|
||||
@ -92,7 +93,7 @@ func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
leftVal := req.getMappedStringLiteral(left.Value)
|
||||
|
||||
if leftVal == "req.headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.GetHttpRequest().GetHeaders())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
@ -100,8 +101,8 @@ func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if leftVal == "res.headers" && req.Response != nil {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Response.Header)
|
||||
if leftVal == "res.headers" && req.GetHttpResponse() != nil {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.GetHttpResponse().GetHeaders())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||
}
|
||||
@ -152,7 +153,7 @@ func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) getMappedStringLiteral(s string) string {
|
||||
func (req *Request) getMappedStringLiteral(s string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "req."):
|
||||
fn, ok := senderReqSearchKeyFns[s]
|
||||
@ -160,28 +161,22 @@ func (req Request) getMappedStringLiteral(s string) string {
|
||||
return fn(req)
|
||||
}
|
||||
case strings.HasPrefix(s, "res."):
|
||||
if req.Response == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fn, ok := reqlog.ResLogSearchKeyFns[s]
|
||||
fn, ok := http.ResponseSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(*req.Response)
|
||||
return fn(req.GetHttpResponse())
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (req Request) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range req.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
func (req *Request) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for _, header := range req.GetHttpRequest().GetHeaders() {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", header.Key, header.Value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,54 +189,47 @@ func (req Request) matchStringLiteral(strLiteral filter.StringLiteral) (bool, er
|
||||
}
|
||||
}
|
||||
|
||||
if req.Response != nil {
|
||||
for key, values := range req.Response.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
for _, header := range req.GetHttpResponse().GetHeaders() {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", header.Key, header.Value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range reqlog.ResLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*req.Response)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
for _, fn := range http.ResponseSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(req.GetHttpResponse())),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (req Request) MatchScope(s *scope.Scope) bool {
|
||||
func (req *Request) MatchScope(s *scope.Scope) bool {
|
||||
for _, rule := range s.Rules() {
|
||||
if rule.URL != nil && req.URL != nil {
|
||||
if matches := rule.URL.MatchString(req.URL.String()); matches {
|
||||
if url := req.GetHttpRequest().GetUrl(); rule.URL != nil && url != "" {
|
||||
if matches := rule.URL.MatchString(url); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range req.Header {
|
||||
for _, headers := range req.GetHttpRequest().GetHeaders() {
|
||||
var keyMatches, valueMatches bool
|
||||
|
||||
if rule.Header.Key != nil {
|
||||
if matches := rule.Header.Key.MatchString(key); matches {
|
||||
if matches := rule.Header.Key.MatchString(headers.Key); matches {
|
||||
keyMatches = true
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Header.Value != nil {
|
||||
for _, value := range values {
|
||||
if matches := rule.Header.Value.MatchString(value); matches {
|
||||
valueMatches = true
|
||||
break
|
||||
}
|
||||
if matches := rule.Header.Value.MatchString(headers.Value); matches {
|
||||
valueMatches = true
|
||||
}
|
||||
}
|
||||
// When only key or value is set, match on whatever is set.
|
||||
@ -257,7 +245,7 @@ func (req Request) MatchScope(s *scope.Scope) bool {
|
||||
}
|
||||
|
||||
if rule.Body != nil {
|
||||
if matches := rule.Body.Match(req.Body); matches {
|
||||
if matches := rule.Body.Match(req.GetHttpRequest().GetBody()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
@ -14,15 +14,17 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
senderReq sender.Request
|
||||
senderReq *sender.Request
|
||||
expectedMatch bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "infix expression, equal operator, match",
|
||||
query: "req.body = foo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -30,8 +32,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, not equal operator, match",
|
||||
query: "req.body != bar",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -39,8 +43,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than operator, match",
|
||||
query: "req.body > a",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("b"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -48,8 +54,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than operator, match",
|
||||
query: "req.body < b",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("a"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -57,8 +65,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match greater than",
|
||||
query: "req.body >= a",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("b"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -66,8 +76,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match equal",
|
||||
query: "req.body >= a",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("a"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -75,8 +87,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match less than",
|
||||
query: "req.body <= b",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("a"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -84,8 +98,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match equal",
|
||||
query: "req.body <= b",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("b"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -93,8 +109,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, regular expression operator, match",
|
||||
query: `req.body =~ "^foo(.*)$"`,
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foobar"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -102,8 +120,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, negate regular expression operator, match",
|
||||
query: `req.body !~ "^foo(.*)$"`,
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("xoobar"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("xoobar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -111,9 +131,11 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, and operator, match",
|
||||
query: "req.body = bar AND res.body = yolo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("bar"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
HttpResponse: &http.Response{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
@ -123,9 +145,11 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, or operator, match",
|
||||
query: "req.body = bar OR res.body = yolo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
HttpResponse: &http.Response{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
@ -135,8 +159,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "prefix expression, not operator, match",
|
||||
query: "NOT (req.body = bar)",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -144,8 +170,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, match in request log",
|
||||
query: "foo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("foo"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -153,8 +181,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, no match",
|
||||
query: "foo",
|
||||
senderReq: sender.Request{
|
||||
Body: []byte("bar"),
|
||||
senderReq: &sender.Request{
|
||||
HttpRequest: &http.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: false,
|
||||
expectedError: nil,
|
||||
@ -162,8 +192,8 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, match in response log",
|
||||
query: "foo",
|
||||
senderReq: sender.Request{
|
||||
Response: &reqlog.ResponseLog{
|
||||
senderReq: &sender.Request{
|
||||
HttpResponse: &http.Response{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
|
311
pkg/sender/sender.connect.go
Normal file
311
pkg/sender/sender.connect.go
Normal file
@ -0,0 +1,311 @@
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: sender/sender.proto
|
||||
|
||||
package sender
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// SenderServiceName is the fully-qualified name of the SenderService service.
|
||||
SenderServiceName = "sender.SenderService"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// SenderServiceGetRequestByIDProcedure is the fully-qualified name of the SenderService's
|
||||
// GetRequestByID RPC.
|
||||
SenderServiceGetRequestByIDProcedure = "/sender.SenderService/GetRequestByID"
|
||||
// SenderServiceListRequestsProcedure is the fully-qualified name of the SenderService's
|
||||
// ListRequests RPC.
|
||||
SenderServiceListRequestsProcedure = "/sender.SenderService/ListRequests"
|
||||
// SenderServiceSetRequestsFilterProcedure is the fully-qualified name of the SenderService's
|
||||
// SetRequestsFilter RPC.
|
||||
SenderServiceSetRequestsFilterProcedure = "/sender.SenderService/SetRequestsFilter"
|
||||
// SenderServiceGetRequestsFilterProcedure is the fully-qualified name of the SenderService's
|
||||
// GetRequestsFilter RPC.
|
||||
SenderServiceGetRequestsFilterProcedure = "/sender.SenderService/GetRequestsFilter"
|
||||
// SenderServiceCreateOrUpdateRequestProcedure is the fully-qualified name of the SenderService's
|
||||
// CreateOrUpdateRequest RPC.
|
||||
SenderServiceCreateOrUpdateRequestProcedure = "/sender.SenderService/CreateOrUpdateRequest"
|
||||
// SenderServiceCloneFromRequestLogProcedure is the fully-qualified name of the SenderService's
|
||||
// CloneFromRequestLog RPC.
|
||||
SenderServiceCloneFromRequestLogProcedure = "/sender.SenderService/CloneFromRequestLog"
|
||||
// SenderServiceSendRequestProcedure is the fully-qualified name of the SenderService's SendRequest
|
||||
// RPC.
|
||||
SenderServiceSendRequestProcedure = "/sender.SenderService/SendRequest"
|
||||
// SenderServiceDeleteRequestsProcedure is the fully-qualified name of the SenderService's
|
||||
// DeleteRequests RPC.
|
||||
SenderServiceDeleteRequestsProcedure = "/sender.SenderService/DeleteRequests"
|
||||
)
|
||||
|
||||
// SenderServiceClient is a client for the sender.SenderService service.
|
||||
type SenderServiceClient interface {
|
||||
GetRequestByID(context.Context, *connect.Request[GetRequestByIDRequest]) (*connect.Response[GetRequestByIDResponse], error)
|
||||
ListRequests(context.Context, *connect.Request[ListRequestsRequest]) (*connect.Response[ListRequestsResponse], error)
|
||||
SetRequestsFilter(context.Context, *connect.Request[SetRequestsFilterRequest]) (*connect.Response[SetRequestsFilterResponse], error)
|
||||
GetRequestsFilter(context.Context, *connect.Request[GetRequestsFilterRequest]) (*connect.Response[GetRequestsFilterResponse], error)
|
||||
CreateOrUpdateRequest(context.Context, *connect.Request[CreateOrUpdateRequestRequest]) (*connect.Response[CreateOrUpdateRequestResponse], error)
|
||||
CloneFromRequestLog(context.Context, *connect.Request[CloneFromRequestLogRequest]) (*connect.Response[CloneFromRequestLogResponse], error)
|
||||
SendRequest(context.Context, *connect.Request[SendRequestRequest]) (*connect.Response[SendRequestResponse], error)
|
||||
DeleteRequests(context.Context, *connect.Request[DeleteRequestsRequest]) (*connect.Response[DeleteRequestsResponse], error)
|
||||
}
|
||||
|
||||
// NewSenderServiceClient constructs a client for the sender.SenderService service. By default, it
|
||||
// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
|
||||
// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
|
||||
// connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewSenderServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SenderServiceClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
senderServiceMethods := File_sender_sender_proto.Services().ByName("SenderService").Methods()
|
||||
return &senderServiceClient{
|
||||
getRequestByID: connect.NewClient[GetRequestByIDRequest, GetRequestByIDResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceGetRequestByIDProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("GetRequestByID")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listRequests: connect.NewClient[ListRequestsRequest, ListRequestsResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceListRequestsProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("ListRequests")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
setRequestsFilter: connect.NewClient[SetRequestsFilterRequest, SetRequestsFilterResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceSetRequestsFilterProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("SetRequestsFilter")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
getRequestsFilter: connect.NewClient[GetRequestsFilterRequest, GetRequestsFilterResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceGetRequestsFilterProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("GetRequestsFilter")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
createOrUpdateRequest: connect.NewClient[CreateOrUpdateRequestRequest, CreateOrUpdateRequestResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceCreateOrUpdateRequestProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("CreateOrUpdateRequest")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
cloneFromRequestLog: connect.NewClient[CloneFromRequestLogRequest, CloneFromRequestLogResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceCloneFromRequestLogProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("CloneFromRequestLog")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
sendRequest: connect.NewClient[SendRequestRequest, SendRequestResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceSendRequestProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("SendRequest")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
deleteRequests: connect.NewClient[DeleteRequestsRequest, DeleteRequestsResponse](
|
||||
httpClient,
|
||||
baseURL+SenderServiceDeleteRequestsProcedure,
|
||||
connect.WithSchema(senderServiceMethods.ByName("DeleteRequests")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// senderServiceClient implements SenderServiceClient.
|
||||
type senderServiceClient struct {
|
||||
getRequestByID *connect.Client[GetRequestByIDRequest, GetRequestByIDResponse]
|
||||
listRequests *connect.Client[ListRequestsRequest, ListRequestsResponse]
|
||||
setRequestsFilter *connect.Client[SetRequestsFilterRequest, SetRequestsFilterResponse]
|
||||
getRequestsFilter *connect.Client[GetRequestsFilterRequest, GetRequestsFilterResponse]
|
||||
createOrUpdateRequest *connect.Client[CreateOrUpdateRequestRequest, CreateOrUpdateRequestResponse]
|
||||
cloneFromRequestLog *connect.Client[CloneFromRequestLogRequest, CloneFromRequestLogResponse]
|
||||
sendRequest *connect.Client[SendRequestRequest, SendRequestResponse]
|
||||
deleteRequests *connect.Client[DeleteRequestsRequest, DeleteRequestsResponse]
|
||||
}
|
||||
|
||||
// GetRequestByID calls sender.SenderService.GetRequestByID.
|
||||
func (c *senderServiceClient) GetRequestByID(ctx context.Context, req *connect.Request[GetRequestByIDRequest]) (*connect.Response[GetRequestByIDResponse], error) {
|
||||
return c.getRequestByID.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListRequests calls sender.SenderService.ListRequests.
|
||||
func (c *senderServiceClient) ListRequests(ctx context.Context, req *connect.Request[ListRequestsRequest]) (*connect.Response[ListRequestsResponse], error) {
|
||||
return c.listRequests.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SetRequestsFilter calls sender.SenderService.SetRequestsFilter.
|
||||
func (c *senderServiceClient) SetRequestsFilter(ctx context.Context, req *connect.Request[SetRequestsFilterRequest]) (*connect.Response[SetRequestsFilterResponse], error) {
|
||||
return c.setRequestsFilter.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// GetRequestsFilter calls sender.SenderService.GetRequestsFilter.
|
||||
func (c *senderServiceClient) GetRequestsFilter(ctx context.Context, req *connect.Request[GetRequestsFilterRequest]) (*connect.Response[GetRequestsFilterResponse], error) {
|
||||
return c.getRequestsFilter.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// CreateOrUpdateRequest calls sender.SenderService.CreateOrUpdateRequest.
|
||||
func (c *senderServiceClient) CreateOrUpdateRequest(ctx context.Context, req *connect.Request[CreateOrUpdateRequestRequest]) (*connect.Response[CreateOrUpdateRequestResponse], error) {
|
||||
return c.createOrUpdateRequest.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// CloneFromRequestLog calls sender.SenderService.CloneFromRequestLog.
|
||||
func (c *senderServiceClient) CloneFromRequestLog(ctx context.Context, req *connect.Request[CloneFromRequestLogRequest]) (*connect.Response[CloneFromRequestLogResponse], error) {
|
||||
return c.cloneFromRequestLog.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SendRequest calls sender.SenderService.SendRequest.
|
||||
func (c *senderServiceClient) SendRequest(ctx context.Context, req *connect.Request[SendRequestRequest]) (*connect.Response[SendRequestResponse], error) {
|
||||
return c.sendRequest.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// DeleteRequests calls sender.SenderService.DeleteRequests.
|
||||
func (c *senderServiceClient) DeleteRequests(ctx context.Context, req *connect.Request[DeleteRequestsRequest]) (*connect.Response[DeleteRequestsResponse], error) {
|
||||
return c.deleteRequests.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SenderServiceHandler is an implementation of the sender.SenderService service.
|
||||
type SenderServiceHandler interface {
|
||||
GetRequestByID(context.Context, *connect.Request[GetRequestByIDRequest]) (*connect.Response[GetRequestByIDResponse], error)
|
||||
ListRequests(context.Context, *connect.Request[ListRequestsRequest]) (*connect.Response[ListRequestsResponse], error)
|
||||
SetRequestsFilter(context.Context, *connect.Request[SetRequestsFilterRequest]) (*connect.Response[SetRequestsFilterResponse], error)
|
||||
GetRequestsFilter(context.Context, *connect.Request[GetRequestsFilterRequest]) (*connect.Response[GetRequestsFilterResponse], error)
|
||||
CreateOrUpdateRequest(context.Context, *connect.Request[CreateOrUpdateRequestRequest]) (*connect.Response[CreateOrUpdateRequestResponse], error)
|
||||
CloneFromRequestLog(context.Context, *connect.Request[CloneFromRequestLogRequest]) (*connect.Response[CloneFromRequestLogResponse], error)
|
||||
SendRequest(context.Context, *connect.Request[SendRequestRequest]) (*connect.Response[SendRequestResponse], error)
|
||||
DeleteRequests(context.Context, *connect.Request[DeleteRequestsRequest]) (*connect.Response[DeleteRequestsResponse], error)
|
||||
}
|
||||
|
||||
// NewSenderServiceHandler builds an HTTP handler from the service implementation. It returns the
|
||||
// path on which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewSenderServiceHandler(svc SenderServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
senderServiceMethods := File_sender_sender_proto.Services().ByName("SenderService").Methods()
|
||||
senderServiceGetRequestByIDHandler := connect.NewUnaryHandler(
|
||||
SenderServiceGetRequestByIDProcedure,
|
||||
svc.GetRequestByID,
|
||||
connect.WithSchema(senderServiceMethods.ByName("GetRequestByID")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceListRequestsHandler := connect.NewUnaryHandler(
|
||||
SenderServiceListRequestsProcedure,
|
||||
svc.ListRequests,
|
||||
connect.WithSchema(senderServiceMethods.ByName("ListRequests")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceSetRequestsFilterHandler := connect.NewUnaryHandler(
|
||||
SenderServiceSetRequestsFilterProcedure,
|
||||
svc.SetRequestsFilter,
|
||||
connect.WithSchema(senderServiceMethods.ByName("SetRequestsFilter")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceGetRequestsFilterHandler := connect.NewUnaryHandler(
|
||||
SenderServiceGetRequestsFilterProcedure,
|
||||
svc.GetRequestsFilter,
|
||||
connect.WithSchema(senderServiceMethods.ByName("GetRequestsFilter")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceCreateOrUpdateRequestHandler := connect.NewUnaryHandler(
|
||||
SenderServiceCreateOrUpdateRequestProcedure,
|
||||
svc.CreateOrUpdateRequest,
|
||||
connect.WithSchema(senderServiceMethods.ByName("CreateOrUpdateRequest")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceCloneFromRequestLogHandler := connect.NewUnaryHandler(
|
||||
SenderServiceCloneFromRequestLogProcedure,
|
||||
svc.CloneFromRequestLog,
|
||||
connect.WithSchema(senderServiceMethods.ByName("CloneFromRequestLog")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceSendRequestHandler := connect.NewUnaryHandler(
|
||||
SenderServiceSendRequestProcedure,
|
||||
svc.SendRequest,
|
||||
connect.WithSchema(senderServiceMethods.ByName("SendRequest")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
senderServiceDeleteRequestsHandler := connect.NewUnaryHandler(
|
||||
SenderServiceDeleteRequestsProcedure,
|
||||
svc.DeleteRequests,
|
||||
connect.WithSchema(senderServiceMethods.ByName("DeleteRequests")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/sender.SenderService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case SenderServiceGetRequestByIDProcedure:
|
||||
senderServiceGetRequestByIDHandler.ServeHTTP(w, r)
|
||||
case SenderServiceListRequestsProcedure:
|
||||
senderServiceListRequestsHandler.ServeHTTP(w, r)
|
||||
case SenderServiceSetRequestsFilterProcedure:
|
||||
senderServiceSetRequestsFilterHandler.ServeHTTP(w, r)
|
||||
case SenderServiceGetRequestsFilterProcedure:
|
||||
senderServiceGetRequestsFilterHandler.ServeHTTP(w, r)
|
||||
case SenderServiceCreateOrUpdateRequestProcedure:
|
||||
senderServiceCreateOrUpdateRequestHandler.ServeHTTP(w, r)
|
||||
case SenderServiceCloneFromRequestLogProcedure:
|
||||
senderServiceCloneFromRequestLogHandler.ServeHTTP(w, r)
|
||||
case SenderServiceSendRequestProcedure:
|
||||
senderServiceSendRequestHandler.ServeHTTP(w, r)
|
||||
case SenderServiceDeleteRequestsProcedure:
|
||||
senderServiceDeleteRequestsHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedSenderServiceHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedSenderServiceHandler struct{}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) GetRequestByID(context.Context, *connect.Request[GetRequestByIDRequest]) (*connect.Response[GetRequestByIDResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.GetRequestByID is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) ListRequests(context.Context, *connect.Request[ListRequestsRequest]) (*connect.Response[ListRequestsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.ListRequests is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) SetRequestsFilter(context.Context, *connect.Request[SetRequestsFilterRequest]) (*connect.Response[SetRequestsFilterResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.SetRequestsFilter is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) GetRequestsFilter(context.Context, *connect.Request[GetRequestsFilterRequest]) (*connect.Response[GetRequestsFilterResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.GetRequestsFilter is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) CreateOrUpdateRequest(context.Context, *connect.Request[CreateOrUpdateRequestRequest]) (*connect.Response[CreateOrUpdateRequestResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.CreateOrUpdateRequest is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) CloneFromRequestLog(context.Context, *connect.Request[CloneFromRequestLogRequest]) (*connect.Response[CloneFromRequestLogResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.CloneFromRequestLog is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) SendRequest(context.Context, *connect.Request[SendRequestRequest]) (*connect.Response[SendRequestResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.SendRequest is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedSenderServiceHandler) DeleteRequests(context.Context, *connect.Request[DeleteRequestsRequest]) (*connect.Response[DeleteRequestsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sender.SenderService.DeleteRequests is not implemented"))
|
||||
}
|
@ -5,21 +5,19 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
connect "connectrpc.com/connect"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var defaultHTTPClient = &http.Client{
|
||||
Transport: &HTTPTransport{},
|
||||
Timeout: 30 * time.Second,
|
||||
@ -31,20 +29,14 @@ var (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
activeProjectID ulid.ULID
|
||||
findReqsFilter FindRequestsFilter
|
||||
activeProjectID string
|
||||
reqsFilter *RequestsFilter
|
||||
scope *scope.Scope
|
||||
repo Repository
|
||||
reqLogSvc *reqlog.Service
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type FindRequestsFilter struct {
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr filter.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Scope *scope.Scope
|
||||
Repository Repository
|
||||
@ -71,165 +63,215 @@ func NewService(cfg Config) *Service {
|
||||
return svc
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ID ulid.ULID
|
||||
ProjectID ulid.ULID
|
||||
SourceRequestLogID ulid.ULID
|
||||
func (svc *Service) GetRequestByID(ctx context.Context, req *connect.Request[GetRequestByIDRequest]) (*connect.Response[GetRequestByIDResponse], error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
URL *url.URL
|
||||
Method string
|
||||
Proto string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
|
||||
Response *reqlog.ResponseLog
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error) {
|
||||
req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, id)
|
||||
senderReq, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, req.Msg.RequestId)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("sender: failed to find request: %w", err))
|
||||
}
|
||||
|
||||
return req, nil
|
||||
return &connect.Response[GetRequestByIDResponse]{
|
||||
Msg: &GetRequestByIDResponse{Request: senderReq},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequests(ctx context.Context) ([]Request, error) {
|
||||
return svc.repo.FindSenderRequests(ctx, svc.findReqsFilter, svc.scope)
|
||||
}
|
||||
|
||||
func (svc *Service) CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error) {
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return Request{}, ErrProjectIDMustBeSet
|
||||
}
|
||||
|
||||
if req.ID.Compare(ulid.ULID{}) == 0 {
|
||||
req.ID = ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
}
|
||||
|
||||
req.ProjectID = svc.activeProjectID
|
||||
|
||||
if req.Method == "" {
|
||||
req.Method = http.MethodGet
|
||||
}
|
||||
|
||||
if req.Proto == "" {
|
||||
req.Proto = HTTPProto20
|
||||
}
|
||||
|
||||
if !isValidProto(req.Proto) {
|
||||
return Request{}, fmt.Errorf("sender: unsupported HTTP protocol: %v", req.Proto)
|
||||
}
|
||||
|
||||
err := svc.repo.StoreSenderRequest(ctx, req)
|
||||
func (svc *Service) ListRequests(ctx context.Context, req *connect.Request[ListRequestsRequest]) (*connect.Response[ListRequestsResponse], error) {
|
||||
reqs, err := svc.repo.FindSenderRequests(ctx, svc.activeProjectID, svc.filterRequest)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to store request: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to find requests: %w", err))
|
||||
}
|
||||
|
||||
return req, nil
|
||||
return &connect.Response[ListRequestsResponse]{
|
||||
Msg: &ListRequestsResponse{Requests: reqs},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error) {
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
return Request{}, ErrProjectIDMustBeSet
|
||||
func (svc *Service) filterRequest(req *Request) (bool, error) {
|
||||
if svc.reqsFilter.OnlyInScope {
|
||||
if svc.scope != nil && !req.MatchScope(svc.scope) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
reqLog, err := svc.reqLogSvc.FindRequestLogByID(ctx, reqLogID)
|
||||
if svc.reqsFilter.SearchExpr == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
expr, err := filter.ParseQuery(svc.reqsFilter.SearchExpr)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to find request log: %w", err)
|
||||
return false, fmt.Errorf("failed to parse search expression: %w", err)
|
||||
}
|
||||
|
||||
req := Request{
|
||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||
ProjectID: svc.activeProjectID,
|
||||
SourceRequestLogID: reqLogID,
|
||||
Method: reqLog.Method,
|
||||
URL: reqLog.URL,
|
||||
Proto: HTTPProto20, // Attempt HTTP/2.
|
||||
Header: reqLog.Header,
|
||||
Body: reqLog.Body,
|
||||
}
|
||||
|
||||
err = svc.repo.StoreSenderRequest(ctx, req)
|
||||
match, err := req.Matches(expr)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to store request: %w", err)
|
||||
return false, fmt.Errorf("failed to match search expression for sender request (id: %v): %w",
|
||||
req.Id, err,
|
||||
)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
return match, nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
|
||||
svc.findReqsFilter = filter
|
||||
}
|
||||
func (svc *Service) CreateOrUpdateRequest(ctx context.Context, req *connect.Request[CreateOrUpdateRequestRequest]) (*connect.Response[CreateOrUpdateRequestResponse], error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
func (svc *Service) FindReqsFilter() FindRequestsFilter {
|
||||
return svc.findReqsFilter
|
||||
}
|
||||
r := proto.Clone(req.Msg.Request).(*Request)
|
||||
|
||||
func (svc *Service) SendRequest(ctx context.Context, id ulid.ULID) (Request, error) {
|
||||
req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, id)
|
||||
if r == nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("sender: request is nil"))
|
||||
}
|
||||
|
||||
if r.HttpRequest == nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("sender: request.http_request is nil"))
|
||||
}
|
||||
|
||||
if r.Id == "" {
|
||||
r.Id = ulid.Make().String()
|
||||
}
|
||||
|
||||
r.ProjectId = svc.activeProjectID
|
||||
|
||||
if r.HttpRequest.Method == httppb.Method_METHOD_UNSPECIFIED {
|
||||
r.HttpRequest.Method = httppb.Method_METHOD_GET
|
||||
}
|
||||
|
||||
if r.HttpRequest.Protocol == httppb.Protocol_PROTOCOL_UNSPECIFIED {
|
||||
r.HttpRequest.Protocol = httppb.Protocol_PROTOCOL_HTTP20
|
||||
}
|
||||
|
||||
err := svc.repo.StoreSenderRequest(ctx, r)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to store request: %w", err))
|
||||
}
|
||||
|
||||
return &connect.Response[CreateOrUpdateRequestResponse]{
|
||||
Msg: &CreateOrUpdateRequestResponse{
|
||||
Request: r,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CloneFromRequestLog(ctx context.Context, req *connect.Request[CloneFromRequestLogRequest]) (*connect.Response[CloneFromRequestLogResponse], error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
reqLog, err := svc.reqLogSvc.FindRequestLogByID(ctx, req.Msg.RequestLogId)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to find request log: %w", err))
|
||||
}
|
||||
|
||||
clonedReqLog := proto.Clone(reqLog).(*reqlog.HttpRequestLog)
|
||||
|
||||
senderReq := &Request{
|
||||
Id: ulid.Make().String(),
|
||||
ProjectId: svc.activeProjectID,
|
||||
SourceRequestLogId: clonedReqLog.Id,
|
||||
HttpRequest: clonedReqLog.Request,
|
||||
}
|
||||
|
||||
err = svc.repo.StoreSenderRequest(ctx, senderReq)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to store request: %w", err))
|
||||
}
|
||||
|
||||
return &connect.Response[CloneFromRequestLogResponse]{Msg: &CloneFromRequestLogResponse{
|
||||
Request: senderReq,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetRequestsFilter(filter *RequestsFilter) {
|
||||
svc.reqsFilter = filter
|
||||
}
|
||||
|
||||
func (svc *Service) RequestsFilter() *RequestsFilter {
|
||||
return svc.reqsFilter
|
||||
}
|
||||
|
||||
func (svc *Service) SendRequest(ctx context.Context, connReq *connect.Request[SendRequestRequest]) (*connect.Response[SendRequestResponse], error) {
|
||||
req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, connReq.Msg.RequestId)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("sender: failed to find request: %w", err))
|
||||
}
|
||||
|
||||
httpReq, err := parseHTTPRequest(ctx, req)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to parse HTTP request: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to parse HTTP request: %w", err))
|
||||
}
|
||||
|
||||
resLog, err := svc.sendHTTPRequest(httpReq)
|
||||
httpRes, err := svc.sendHTTPRequest(httpReq)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: could not send HTTP request: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: could not send HTTP request: %w", err))
|
||||
}
|
||||
|
||||
req.Response = &resLog
|
||||
req.HttpResponse = httpRes
|
||||
|
||||
err = svc.repo.StoreSenderRequest(ctx, req)
|
||||
if err != nil {
|
||||
return Request{}, fmt.Errorf("sender: failed to store sender response log: %w", err)
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to store sender response log: %w", err))
|
||||
}
|
||||
|
||||
req.Response = &resLog
|
||||
|
||||
return req, nil
|
||||
return &connect.Response[SendRequestResponse]{
|
||||
Msg: &SendRequestResponse{
|
||||
Request: req,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseHTTPRequest(ctx context.Context, req Request) (*http.Request, error) {
|
||||
ctx = context.WithValue(ctx, protoCtxKey{}, req.Proto)
|
||||
func parseHTTPRequest(ctx context.Context, req *Request) (*http.Request, error) {
|
||||
ctx = context.WithValue(ctx, protoCtxKey{}, req.GetHttpRequest().GetProtocol())
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, req.Method, req.URL.String(), bytes.NewReader(req.Body))
|
||||
httpReq, err := http.NewRequestWithContext(ctx,
|
||||
req.GetHttpRequest().GetMethod().String(),
|
||||
req.GetHttpRequest().GetUrl(),
|
||||
bytes.NewReader(req.GetHttpRequest().GetBody()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct HTTP request: %w", err)
|
||||
}
|
||||
|
||||
if req.Header != nil {
|
||||
httpReq.Header = req.Header
|
||||
for _, header := range req.GetHttpRequest().GetHeaders() {
|
||||
httpReq.Header.Add(header.Key, header.Value)
|
||||
}
|
||||
|
||||
return httpReq, nil
|
||||
}
|
||||
|
||||
func (svc *Service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog, error) {
|
||||
func (svc *Service) sendHTTPRequest(httpReq *http.Request) (*httppb.Response, error) {
|
||||
res, err := svc.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return reqlog.ResponseLog{}, &SendError{err}
|
||||
return nil, &SendError{err}
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
resLog, err := reqlog.ParseHTTPResponse(res)
|
||||
resLog, err := httppb.ParseHTTPResponse(res)
|
||||
if err != nil {
|
||||
return reqlog.ResponseLog{}, fmt.Errorf("failed to parse http response: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse http response: %w", err)
|
||||
}
|
||||
|
||||
return resLog, err
|
||||
}
|
||||
|
||||
func (svc *Service) SetActiveProjectID(id ulid.ULID) {
|
||||
func (svc *Service) SetActiveProjectID(id string) {
|
||||
svc.activeProjectID = id
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
return svc.repo.DeleteSenderRequests(ctx, projectID)
|
||||
func (svc *Service) DeleteRequests(ctx context.Context, req *connect.Request[DeleteRequestsRequest]) (*connect.Response[DeleteRequestsResponse], error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
err := svc.repo.DeleteSenderRequests(ctx, svc.activeProjectID)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("sender: failed to delete requests: %w", err))
|
||||
}
|
||||
|
||||
return &connect.Response[DeleteRequestsResponse]{}, nil
|
||||
}
|
||||
|
||||
func (e SendError) Error() string {
|
||||
|
1040
pkg/sender/sender.pb.go
Normal file
1040
pkg/sender/sender.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,36 +4,24 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
http "net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/oklog/ulid"
|
||||
connect "connectrpc.com/connect"
|
||||
"go.etcd.io/bbolt"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
"github.com/dstotijn/hetty/pkg/testutil"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var exampleURL = func() *url.URL {
|
||||
u, err := url.Parse("https://example.com/foobar")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return u
|
||||
}()
|
||||
|
||||
func TestStoreRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -42,10 +30,16 @@ func TestStoreRequest(t *testing.T) {
|
||||
|
||||
svc := sender.NewService(sender.Config{})
|
||||
|
||||
_, err := svc.CreateOrUpdateRequest(context.Background(), sender.Request{
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Body: []byte("foobar"),
|
||||
_, err := svc.CreateOrUpdateRequest(context.Background(), &connect.Request[sender.CreateOrUpdateRequestRequest]{
|
||||
Msg: &sender.CreateOrUpdateRequestRequest{
|
||||
Request: &sender.Request{
|
||||
HttpRequest: &httppb.Request{
|
||||
Url: "https://example.com/foobar",
|
||||
Method: httppb.Method_METHOD_POST,
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
||||
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
||||
@ -72,75 +66,69 @@ func TestStoreRequest(t *testing.T) {
|
||||
Repository: db,
|
||||
})
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
Name: "foobar",
|
||||
Settings: proj.Settings{},
|
||||
projectID := "foobar-project-id"
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
Name: "foobar",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
}
|
||||
|
||||
svc.SetActiveProjectID(projectID)
|
||||
svc.SetActiveProjectID(projectID)
|
||||
|
||||
exp := sender.Request{
|
||||
ProjectID: projectID,
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
exp := &sender.Request{
|
||||
ProjectId: projectID,
|
||||
HttpRequest: &httppb.Request{
|
||||
Method: httppb.Method_METHOD_POST,
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP20,
|
||||
Url: "https://example.com/foobar",
|
||||
Headers: []*httppb.Header{
|
||||
{Key: "X-Foo", Value: "bar"},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
got, err := svc.CreateOrUpdateRequest(context.Background(), sender.Request{
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
createRes, err := svc.CreateOrUpdateRequest(context.Background(), &connect.Request[sender.CreateOrUpdateRequestRequest]{
|
||||
Msg: &sender.CreateOrUpdateRequestRequest{
|
||||
Request: exp,
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error storing request: %v", err)
|
||||
}
|
||||
|
||||
if got.ID.Compare(ulid.ULID{}) == 0 {
|
||||
if createRes.Msg.Request.Id == "" {
|
||||
t.Fatal("expected request ID to be non-empty value")
|
||||
}
|
||||
|
||||
diff := cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||
if diff != "" {
|
||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "request not equal", exp, createRes.Msg.Request, "id")
|
||||
|
||||
got, err = db.FindSenderRequestByID(context.Background(), projectID, got.ID)
|
||||
got, err := db.FindSenderRequestByID(context.Background(), projectID, createRes.Msg.Request.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by ID: %v", err)
|
||||
}
|
||||
|
||||
diff = cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||
if diff != "" {
|
||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "request not equal", exp, got, "id")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloneFromRequestLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqLogID := "foobar-req-log-id"
|
||||
|
||||
t.Run("without active project", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc := sender.NewService(sender.Config{})
|
||||
|
||||
_, err := svc.CloneFromRequestLog(context.Background(), reqLogID)
|
||||
_, err := svc.CloneFromRequestLog(context.Background(), &connect.Request[sender.CloneFromRequestLogRequest]{
|
||||
Msg: &sender.CloneFromRequestLogRequest{
|
||||
RequestLogId: reqLogID,
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
||||
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
||||
}
|
||||
@ -162,24 +150,32 @@ func TestCloneFromRequestLog(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
projectID := "foobar-project-id"
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
Name: "foobar",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
}
|
||||
|
||||
reqLog := reqlog.RequestLog{
|
||||
ID: reqLogID,
|
||||
ProjectID: projectID,
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
reqLog := &reqlog.HttpRequestLog{
|
||||
Id: reqLogID,
|
||||
ProjectId: projectID,
|
||||
Request: &httppb.Request{
|
||||
Url: "https://example.com/foobar",
|
||||
Method: httppb.Method_METHOD_POST,
|
||||
Body: []byte("foobar"),
|
||||
Headers: []*httppb.Header{
|
||||
{Key: "X-Foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
Response: &httppb.Response{
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP20,
|
||||
StatusCode: 200,
|
||||
Status: "200 OK",
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
if err := db.StoreRequestLog(context.Background(), reqLog); err != nil {
|
||||
@ -196,27 +192,29 @@ func TestCloneFromRequestLog(t *testing.T) {
|
||||
|
||||
svc.SetActiveProjectID(projectID)
|
||||
|
||||
exp := sender.Request{
|
||||
SourceRequestLogID: reqLogID,
|
||||
ProjectID: projectID,
|
||||
URL: exampleURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: sender.HTTPProto20,
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
exp := &sender.Request{
|
||||
SourceRequestLogId: reqLogID,
|
||||
ProjectId: projectID,
|
||||
HttpRequest: &httppb.Request{
|
||||
Url: "https://example.com/foobar",
|
||||
Method: httppb.Method_METHOD_POST,
|
||||
Body: []byte("foobar"),
|
||||
Headers: []*httppb.Header{
|
||||
{Key: "X-Foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
got, err := svc.CloneFromRequestLog(context.Background(), reqLogID)
|
||||
got, err := svc.CloneFromRequestLog(context.Background(), &connect.Request[sender.CloneFromRequestLogRequest]{
|
||||
Msg: &sender.CloneFromRequestLogRequest{
|
||||
RequestLogId: reqLogID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error cloning from request log: %v", err)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||
if diff != "" {
|
||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "request not equal", exp, got.Msg.Request, "id")
|
||||
})
|
||||
}
|
||||
|
||||
@ -245,28 +243,27 @@ func TestSendRequest(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
tsURL, _ := url.Parse(ts.URL)
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
Settings: proj.Settings{},
|
||||
projectID := "foobar-project-id"
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
Name: "foobar",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
}
|
||||
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
req := sender.Request{
|
||||
ID: reqID,
|
||||
ProjectID: projectID,
|
||||
URL: tsURL,
|
||||
Method: http.MethodPost,
|
||||
Proto: "HTTP/1.1",
|
||||
Header: http.Header{
|
||||
"X-Foo": []string{"bar"},
|
||||
reqID := "foobar-req-id"
|
||||
req := &sender.Request{
|
||||
Id: reqID,
|
||||
ProjectId: projectID,
|
||||
HttpRequest: &httppb.Request{
|
||||
Url: ts.URL,
|
||||
Method: httppb.Method_METHOD_POST,
|
||||
Body: []byte("foobar"),
|
||||
Headers: []*httppb.Header{
|
||||
{Key: "X-Foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
Body: []byte("foobar"),
|
||||
}
|
||||
|
||||
if err := db.StoreSenderRequest(context.Background(), req); err != nil {
|
||||
@ -281,26 +278,38 @@ func TestSendRequest(t *testing.T) {
|
||||
})
|
||||
svc.SetActiveProjectID(projectID)
|
||||
|
||||
exp := &reqlog.ResponseLog{
|
||||
Proto: "HTTP/1.1",
|
||||
StatusCode: http.StatusOK,
|
||||
exp := &httppb.Response{
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP11,
|
||||
StatusCode: 200,
|
||||
Status: "200 OK",
|
||||
Header: http.Header{
|
||||
"Content-Length": []string{"3"},
|
||||
"Content-Type": []string{"text/plain; charset=utf-8"},
|
||||
"Date": []string{date},
|
||||
"Foobar": []string{"baz"},
|
||||
Headers: []*httppb.Header{
|
||||
{Key: "Date", Value: date},
|
||||
{Key: "Foobar", Value: "baz"},
|
||||
{Key: "Content-Length", Value: "3"},
|
||||
{Key: "Content-Type", Value: "text/plain; charset=utf-8"},
|
||||
},
|
||||
Body: []byte("baz"),
|
||||
}
|
||||
|
||||
got, err := svc.SendRequest(context.Background(), reqID)
|
||||
got, err := svc.SendRequest(context.Background(), &connect.Request[sender.SendRequestRequest]{
|
||||
Msg: &sender.SendRequestRequest{
|
||||
RequestId: reqID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error sending request: %v", err)
|
||||
}
|
||||
|
||||
diff := cmp.Diff(exp, got.Response, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||
if diff != "" {
|
||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||
opts := []cmp.Option{
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b *httppb.Header) bool {
|
||||
if a.Key != b.Key {
|
||||
return a.Key < b.Key
|
||||
}
|
||||
return a.Value < b.Value
|
||||
}),
|
||||
}
|
||||
if diff := cmp.Diff(exp, got.Msg.Request.HttpResponse, opts...); diff != "" {
|
||||
t.Fatalf("response not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,3 @@ func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func isValidProto(proto string) bool {
|
||||
return proto == HTTPProto10 || proto == HTTPProto11 || proto == HTTPProto20
|
||||
}
|
||||
|
54
pkg/testutil/testutil.go
Normal file
54
pkg/testutil/testutil.go
Normal file
@ -0,0 +1,54 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func ProtoDiff[M proto.Message](t *testing.T, msg string, exp, got M, ignoreFields ...protoreflect.Name) {
|
||||
t.Helper()
|
||||
|
||||
opts := []cmp.Option{
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(exp, ignoreFields...),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got, opts...); diff != "" {
|
||||
t.Fatalf("%v (-exp, +got):\n%v", msg, diff)
|
||||
}
|
||||
}
|
||||
|
||||
func ProtoSlicesDiff[M proto.Message](t *testing.T, msg string, exp, got []M, ignoreFields ...protoreflect.Name) {
|
||||
t.Helper()
|
||||
|
||||
opts := []cmp.Option{
|
||||
protocmp.Transform(),
|
||||
}
|
||||
if len(exp) > 0 {
|
||||
opts = append(opts, protocmp.IgnoreFields(exp[0], ignoreFields...))
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got, opts...); diff != "" {
|
||||
t.Fatalf("%v (-exp, +got):\n%v", msg, diff)
|
||||
}
|
||||
}
|
||||
|
||||
type testLogger struct {
|
||||
log.NopLogger
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (l *testLogger) Errorw(msg string, v ...interface{}) {
|
||||
l.t.Helper()
|
||||
l.t.Fatalf(msg+": %v", v...)
|
||||
}
|
||||
|
||||
func NewLogger(t *testing.T) log.Logger {
|
||||
t.Helper()
|
||||
return &testLogger{t: t}
|
||||
}
|
Reference in New Issue
Block a user