mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
52c83a1989 | |||
7ed553866b | |||
fcf0e1c51e | |||
24c2ecfa4b | |||
d23b4ed024 | |||
ff9e4140aa | |||
f7def87d0f | |||
aa9822854d | |||
2ce4218a30 | |||
fd27955e11 | |||
426a7d5f96 | |||
21b679dc91 | |||
e4f468d4d2 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@ -1,3 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
github: dstotijn
|
||||||
|
|
||||||
patreon: dstotijn
|
patreon: dstotijn
|
||||||
|
custom: "https://www.paypal.com/paypalme/dstotijn"
|
||||||
|
4
.github/workflows/build-test.yml
vendored
4
.github/workflows/build-test.yml
vendored
@ -5,7 +5,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: ["1.17", "1.16"]
|
go: ["1.23", "1.22", "1.21"]
|
||||||
name: Go ${{ matrix.go }} - Build
|
name: Go ${{ matrix.go }} - Build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -34,7 +34,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: ["1.17", "1.16"]
|
go: ["1.23", "1.22", "1.21"]
|
||||||
name: Go ${{ matrix.go }} - Test
|
name: Go ${{ matrix.go }} - Test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -35,7 +35,7 @@ linters-settings:
|
|||||||
godot:
|
godot:
|
||||||
capital: true
|
capital: true
|
||||||
ireturn:
|
ireturn:
|
||||||
allow: "error,empty,anon,stdlib,.*(or|er)$,github.com/99designs/gqlgen/graphql.Marshaler,github.com/dstotijn/hetty/pkg/api.QueryResolver,github.com/dstotijn/hetty/pkg/search.Expression"
|
allow: "error,empty,anon,stdlib,.*(or|er)$,github.com/99designs/gqlgen/graphql.Marshaler,github.com/dstotijn/hetty/pkg/api.QueryResolver,github.com/dstotijn/hetty/pkg/filter.Expression"
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
@ -69,6 +69,31 @@ scoop:
|
|||||||
description: An HTTP toolkit for security research.
|
description: An HTTP toolkit for security research.
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|
||||||
|
dockers:
|
||||||
|
- extra_files:
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
- pkg
|
||||||
|
- cmd
|
||||||
|
- admin
|
||||||
|
image_templates:
|
||||||
|
- "ghcr.io/dstotijn/hetty:{{ .Version }}"
|
||||||
|
- "ghcr.io/dstotijn/hetty:{{ .Major }}"
|
||||||
|
- "ghcr.io/dstotijn/hetty:{{ .Major }}.{{ .Minor }}"
|
||||||
|
- "ghcr.io/dstotijn/hetty:latest"
|
||||||
|
- "dstotijn/hetty:{{ .Version }}"
|
||||||
|
- "dstotijn/hetty:{{ .Major }}"
|
||||||
|
- "dstotijn/hetty:{{ .Major }}.{{ .Minor }}"
|
||||||
|
- "dstotijn/hetty:latest"
|
||||||
|
build_flag_templates:
|
||||||
|
- "--pull"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.source=https://github.com/dstotijn/hetty"
|
||||||
|
- "--build-arg=HETTY_VERSION={{.Version}}"
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: "checksums.txt"
|
name_template: "checksums.txt"
|
||||||
|
|
||||||
|
22
README.md
22
README.md
@ -64,8 +64,18 @@ Alternatively, you can [download the latest release from
|
|||||||
GitHub](https://github.com/dstotijn/hetty/releases/latest) for your OS and
|
GitHub](https://github.com/dstotijn/hetty/releases/latest) for your OS and
|
||||||
architecture, and move the binary to a directory in your `$PATH`. If your OS is
|
architecture, and move the binary to a directory in your `$PATH`. If your OS is
|
||||||
not available for one of the package managers or not listed in the GitHub
|
not available for one of the package managers or not listed in the GitHub
|
||||||
releases, you can compile from source _(link coming soon)_ or use a Docker image
|
releases, you can compile from source _(link coming soon)_.
|
||||||
_(link coming soon)_.
|
|
||||||
|
#### Docker
|
||||||
|
|
||||||
|
Docker images are distributed via [GitHub's Container registry](https://github.com/dstotijn/hetty/pkgs/container/hetty)
|
||||||
|
and [Docker Hub](https://hub.docker.com/r/dstotijn/hetty). To run Hetty via with a volume for database and certificate
|
||||||
|
storage, and port 8080 forwarded:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -v $HOME/.hetty:/root/.hetty -p 8080:8080 \
|
||||||
|
ghcr.io/dstotijn/hetty:latest
|
||||||
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@ -91,7 +101,7 @@ Runs an HTTP server with (MITM) proxy, GraphQL service, and a web based admin in
|
|||||||
Options:
|
Options:
|
||||||
--cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem")
|
--cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem")
|
||||||
--key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem")
|
--key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem")
|
||||||
--db Database directory path. (Default: "~/.hetty/db")
|
--db Database file path. Creates file if it doesn't exist. (Default: "~/.hetty/hetty.db")
|
||||||
--addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080")
|
--addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080")
|
||||||
--chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false)
|
--chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false)
|
||||||
--verbose Enable verbose logging.
|
--verbose Enable verbose logging.
|
||||||
@ -136,12 +146,10 @@ Guidelines](CONTRIBUTING.md) for details.
|
|||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
<a href="https://www.tines.com/?utm_source=oss&utm_medium=sponsorship&utm_campaign=hetty">
|
💖 Are you enjoying Hetty? You can [sponsor me](https://github.com/sponsors/dstotijn)!
|
||||||
<img src="https://hetty.xyz/img/tines-sponsorship-badge.png" width="140" alt="Sponsored by Tines">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
||||||
© 2022 Hetty Software
|
© 2019–2025 Hetty Software
|
||||||
|
@ -7,7 +7,7 @@ import { useRouter } from "next/router";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
||||||
import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
|
import { KeyValuePair } from "lib/components/KeyValuePair";
|
||||||
import Link from "lib/components/Link";
|
import Link from "lib/components/Link";
|
||||||
import RequestTabs from "lib/components/RequestTabs";
|
import RequestTabs from "lib/components/RequestTabs";
|
||||||
import ResponseStatus from "lib/components/ResponseStatus";
|
import ResponseStatus from "lib/components/ResponseStatus";
|
||||||
@ -112,11 +112,11 @@ function EditRequest(): JSX.Element {
|
|||||||
newQueryParams.push({ key: "", value: "" });
|
newQueryParams.push({ key: "", value: "" });
|
||||||
setQueryParams(newQueryParams);
|
setQueryParams(newQueryParams);
|
||||||
|
|
||||||
const newReqHeaders = sortKeyValuePairs(interceptedRequest.headers || []);
|
const newReqHeaders = interceptedRequest.headers || [];
|
||||||
setReqHeaders([...newReqHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
setReqHeaders([...newReqHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||||
|
|
||||||
setResBody(interceptedRequest.response?.body || "");
|
setResBody(interceptedRequest.response?.body || "");
|
||||||
const newResHeaders = sortKeyValuePairs(interceptedRequest.response?.headers || []);
|
const newResHeaders = interceptedRequest.response?.headers || [];
|
||||||
setResHeaders([...newResHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
setResHeaders([...newResHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -76,6 +76,7 @@ function Search(): JSX.Element {
|
|||||||
<ClickAwayListener onClickAway={handleClickAway}>
|
<ClickAwayListener onClickAway={handleClickAway}>
|
||||||
<Paper
|
<Paper
|
||||||
component="form"
|
component="form"
|
||||||
|
autoComplete="off"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
ref={filterRef}
|
ref={filterRef}
|
||||||
sx={{
|
sx={{
|
||||||
@ -109,6 +110,8 @@ function Search(): JSX.Element {
|
|||||||
value={searchExpr}
|
value={searchExpr}
|
||||||
onChange={(e) => setSearchExpr(e.target.value)}
|
onChange={(e) => setSearchExpr(e.target.value)}
|
||||||
onFocus={() => setFilterOpen(true)}
|
onFocus={() => setFilterOpen(true)}
|
||||||
|
autoCorrect="false"
|
||||||
|
spellCheck="false"
|
||||||
/>
|
/>
|
||||||
<Tooltip title="Search">
|
<Tooltip title="Search">
|
||||||
<IconButton type="submit" sx={{ padding: 1.25 }}>
|
<IconButton type="submit" sx={{ padding: 1.25 }}>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Alert, Box, Button, Typography } from "@mui/material";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import { Alert, Box, Button, Fab, Tooltip, Typography, useTheme } from "@mui/material";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
|
import { KeyValuePair } from "lib/components/KeyValuePair";
|
||||||
import RequestTabs from "lib/components/RequestTabs";
|
import RequestTabs from "lib/components/RequestTabs";
|
||||||
import Response from "lib/components/Response";
|
import Response from "lib/components/Response";
|
||||||
import SplitPane from "lib/components/SplitPane";
|
import SplitPane from "lib/components/SplitPane";
|
||||||
@ -17,15 +18,21 @@ import { queryParamsFromURL } from "lib/queryParamsFromURL";
|
|||||||
import updateKeyPairItem from "lib/updateKeyPairItem";
|
import updateKeyPairItem from "lib/updateKeyPairItem";
|
||||||
import updateURLQueryParams from "lib/updateURLQueryParams";
|
import updateURLQueryParams from "lib/updateURLQueryParams";
|
||||||
|
|
||||||
|
const defaultMethod = HttpMethod.Get;
|
||||||
|
const defaultProto = HttpProto.Http20;
|
||||||
|
const emptyKeyPair = [{ key: "", value: "" }];
|
||||||
|
|
||||||
function EditRequest(): JSX.Element {
|
function EditRequest(): JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const reqId = router.query.id as string | undefined;
|
const reqId = router.query.id as string | undefined;
|
||||||
|
|
||||||
const [method, setMethod] = useState(HttpMethod.Get);
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [method, setMethod] = useState(defaultMethod);
|
||||||
const [url, setURL] = useState("");
|
const [url, setURL] = useState("");
|
||||||
const [proto, setProto] = useState(HttpProto.Http20);
|
const [proto, setProto] = useState(defaultProto);
|
||||||
const [queryParams, setQueryParams] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
|
const [queryParams, setQueryParams] = useState<KeyValuePair[]>(emptyKeyPair);
|
||||||
const [headers, setHeaders] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
|
const [headers, setHeaders] = useState<KeyValuePair[]>(emptyKeyPair);
|
||||||
const [body, setBody] = useState("");
|
const [body, setBody] = useState("");
|
||||||
|
|
||||||
const handleQueryParamChange = (key: string, value: string, idx: number) => {
|
const handleQueryParamChange = (key: string, value: string, idx: number) => {
|
||||||
@ -83,7 +90,7 @@ function EditRequest(): JSX.Element {
|
|||||||
newQueryParams.push({ key: "", value: "" });
|
newQueryParams.push({ key: "", value: "" });
|
||||||
setQueryParams(newQueryParams);
|
setQueryParams(newQueryParams);
|
||||||
|
|
||||||
const newHeaders = sortKeyValuePairs(senderRequest.headers || []);
|
const newHeaders = senderRequest.headers || [];
|
||||||
setHeaders([...newHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
setHeaders([...newHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||||
setResponse(senderRequest.response);
|
setResponse(senderRequest.response);
|
||||||
},
|
},
|
||||||
@ -131,8 +138,26 @@ function EditRequest(): JSX.Element {
|
|||||||
createOrUpdateRequestAndSend();
|
createOrUpdateRequestAndSend();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNewRequest = () => {
|
||||||
|
setURL("");
|
||||||
|
setMethod(defaultMethod);
|
||||||
|
setProto(defaultProto);
|
||||||
|
setQueryParams(emptyKeyPair);
|
||||||
|
setHeaders(emptyKeyPair);
|
||||||
|
setBody("");
|
||||||
|
setResponse(null);
|
||||||
|
router.push(`/sender`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex" flexDirection="column" height="100%" gap={2}>
|
<Box display="flex" flexDirection="column" height="100%" gap={2}>
|
||||||
|
<Box sx={{ position: "absolute", bottom: theme.spacing(2), right: theme.spacing(2) }}>
|
||||||
|
<Tooltip title="New request">
|
||||||
|
<Fab color="primary" onClick={handleNewRequest}>
|
||||||
|
<AddIcon />
|
||||||
|
</Fab>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
<Box component="form" autoComplete="off" onSubmit={handleFormSubmit}>
|
<Box component="form" autoComplete="off" onSubmit={handleFormSubmit}>
|
||||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||||
<UrlBar
|
<UrlBar
|
||||||
|
@ -184,20 +184,4 @@ export function KeyValuePairTable({ items, onChange, onDelete }: KeyValuePairTab
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortKeyValuePairs(items: KeyValuePair[]): KeyValuePair[] {
|
|
||||||
const sorted = [...items];
|
|
||||||
|
|
||||||
sorted.sort((a, b) => {
|
|
||||||
if (a.key < b.key) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.key > b.key) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default KeyValuePairTable;
|
export default KeyValuePairTable;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
import { sortKeyValuePairs } from "./KeyValuePair";
|
|
||||||
import ResponseTabs from "./ResponseTabs";
|
import ResponseTabs from "./ResponseTabs";
|
||||||
|
|
||||||
import ResponseStatus from "lib/components/ResponseStatus";
|
import ResponseStatus from "lib/components/ResponseStatus";
|
||||||
@ -29,7 +28,7 @@ function Response({ response }: ResponseProps): JSX.Element {
|
|||||||
</Box>
|
</Box>
|
||||||
<ResponseTabs
|
<ResponseTabs
|
||||||
body={response?.body}
|
body={response?.body}
|
||||||
headers={sortKeyValuePairs(response?.headers || [])}
|
headers={response?.headers || []}
|
||||||
hasResponse={response !== undefined && response !== null}
|
hasResponse={response !== undefined && response !== null}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -16,16 +16,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/chromedp/chromedp"
|
"github.com/chromedp/chromedp"
|
||||||
badgerdb "github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/api"
|
"github.com/dstotijn/hetty/pkg/api"
|
||||||
"github.com/dstotijn/hetty/pkg/chrome"
|
"github.com/dstotijn/hetty/pkg/chrome"
|
||||||
"github.com/dstotijn/hetty/pkg/db/badger"
|
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||||
"github.com/dstotijn/hetty/pkg/proj"
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy"
|
"github.com/dstotijn/hetty/pkg/proxy"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||||
@ -51,7 +50,7 @@ Runs an HTTP server with (MITM) proxy, GraphQL service, and a web based admin in
|
|||||||
Options:
|
Options:
|
||||||
--cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem")
|
--cert Path to root CA certificate. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_cert.pem")
|
||||||
--key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem")
|
--key Path to root CA private key. Creates file if it doesn't exist. (Default: "~/.hetty/hetty_key.pem")
|
||||||
--db Database directory path. (Default: "~/.hetty/db")
|
--db Database file path. Creates file if it doesn't exist. (Default: "~/.hetty/hetty.db")
|
||||||
--addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080")
|
--addr TCP address for HTTP server to listen on, in the form \"host:port\". (Default: ":8080")
|
||||||
--chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false)
|
--chrome Launch Chrome with proxy settings applied and certificate errors ignored. (Default: false)
|
||||||
--verbose Enable verbose logging.
|
--verbose Enable verbose logging.
|
||||||
@ -89,7 +88,7 @@ func NewHettyCommand() (*ffcli.Command, *Config) {
|
|||||||
"Path to root CA certificate. Creates a new certificate if file doesn't exist.")
|
"Path to root CA certificate. Creates a new certificate if file doesn't exist.")
|
||||||
fs.StringVar(&cmd.key, "key", "~/.hetty/hetty_key.pem",
|
fs.StringVar(&cmd.key, "key", "~/.hetty/hetty_key.pem",
|
||||||
"Path to root CA private key. Creates a new private key if file doesn't exist.")
|
"Path to root CA private key. Creates a new private key if file doesn't exist.")
|
||||||
fs.StringVar(&cmd.db, "db", "~/.hetty/db", "Database directory path.")
|
fs.StringVar(&cmd.db, "db", "~/.hetty/hetty.db", "Database file path. Creates file if it doesn't exist.")
|
||||||
fs.StringVar(&cmd.addr, "addr", ":8080", "TCP address to listen on, in the form \"host:port\".")
|
fs.StringVar(&cmd.addr, "addr", ":8080", "TCP address to listen on, in the form \"host:port\".")
|
||||||
fs.BoolVar(&cmd.chrome, "chrome", false, "Launch Chrome with proxy settings applied and certificate errors ignored.")
|
fs.BoolVar(&cmd.chrome, "chrome", false, "Launch Chrome with proxy settings applied and certificate errors ignored.")
|
||||||
fs.BoolVar(&cmd.version, "version", false, "Output version.")
|
fs.BoolVar(&cmd.version, "version", false, "Output version.")
|
||||||
@ -154,25 +153,21 @@ func (cmd *HettyCommand) Exec(ctx context.Context, _ []string) error {
|
|||||||
cmd.config.logger.Fatal("Failed to load or create CA key pair.", zap.Error(err))
|
cmd.config.logger.Fatal("Failed to load or create CA key pair.", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BadgerDB logs some verbose entries with `INFO` level, so unless
|
dbLogger := cmd.config.logger.Named("boltdb").Sugar()
|
||||||
// we're running in debug mode, bump the minimal level to `WARN`.
|
boltOpts := *bbolt.DefaultOptions
|
||||||
dbLogger := cmd.config.logger.Named("badgerdb").WithOptions(zap.IncreaseLevel(zapcore.WarnLevel))
|
boltOpts.Logger = &bolt.Logger{SugaredLogger: dbLogger}
|
||||||
|
|
||||||
dbSugaredLogger := dbLogger.Sugar()
|
boltDB, err := bolt.OpenDatabase(dbPath, &boltOpts)
|
||||||
|
|
||||||
badger, err := badger.OpenDatabase(
|
|
||||||
badgerdb.DefaultOptions(dbPath).WithLogger(badger.NewLogger(dbSugaredLogger)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.config.logger.Fatal("Failed to open database.", zap.Error(err))
|
cmd.config.logger.Fatal("Failed to open database.", zap.Error(err))
|
||||||
}
|
}
|
||||||
defer badger.Close()
|
defer boltDB.Close()
|
||||||
|
|
||||||
scope := &scope.Scope{}
|
scope := &scope.Scope{}
|
||||||
|
|
||||||
reqLogService := reqlog.NewService(reqlog.Config{
|
reqLogService := reqlog.NewService(reqlog.Config{
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
Repository: badger,
|
Repository: boltDB,
|
||||||
Logger: cmd.config.logger.Named("reqlog").Sugar(),
|
Logger: cmd.config.logger.Named("reqlog").Sugar(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -181,12 +176,12 @@ func (cmd *HettyCommand) Exec(ctx context.Context, _ []string) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
senderService := sender.NewService(sender.Config{
|
senderService := sender.NewService(sender.Config{
|
||||||
Repository: badger,
|
Repository: boltDB,
|
||||||
ReqLogService: reqLogService,
|
ReqLogService: reqLogService,
|
||||||
})
|
})
|
||||||
|
|
||||||
projService, err := proj.NewService(proj.Config{
|
projService, err := proj.NewService(proj.Config{
|
||||||
Repository: badger,
|
Repository: boltDB,
|
||||||
InterceptService: interceptService,
|
InterceptService: interceptService,
|
||||||
ReqLogService: reqLogService,
|
ReqLogService: reqLogService,
|
||||||
SenderService: senderService,
|
SenderService: senderService,
|
||||||
|
23
go.mod
23
go.mod
@ -1,57 +1,46 @@
|
|||||||
module github.com/dstotijn/hetty
|
module github.com/dstotijn/hetty
|
||||||
|
|
||||||
go 1.17
|
go 1.23
|
||||||
|
|
||||||
|
toolchain go1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.14.0
|
github.com/99designs/gqlgen v0.14.0
|
||||||
github.com/chromedp/chromedp v0.7.8
|
github.com/chromedp/chromedp v0.7.8
|
||||||
github.com/dgraph-io/badger/v3 v3.2103.2
|
|
||||||
github.com/google/go-cmp v0.5.6
|
github.com/google/go-cmp v0.5.6
|
||||||
github.com/gorilla/mux v1.7.4
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/matryer/moq v0.2.5
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/oklog/ulid v1.3.1
|
github.com/oklog/ulid v1.3.1
|
||||||
github.com/peterbourgon/ff/v3 v3.1.2
|
github.com/peterbourgon/ff/v3 v3.1.2
|
||||||
github.com/smallstep/truststore v0.11.0
|
github.com/smallstep/truststore v0.11.0
|
||||||
github.com/vektah/gqlparser/v2 v2.2.0
|
github.com/vektah/gqlparser/v2 v2.2.0
|
||||||
|
go.etcd.io/bbolt v1.4.0-beta.0
|
||||||
go.uber.org/zap v1.21.0
|
go.uber.org/zap v1.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/agnivade/levenshtein v1.1.0 // indirect
|
github.com/agnivade/levenshtein v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
|
||||||
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf // indirect
|
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf // indirect
|
||||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.1.0 // indirect
|
github.com/gobwas/ws v1.1.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
|
|
||||||
github.com/golang/protobuf v1.3.1 // indirect
|
|
||||||
github.com/golang/snappy v0.0.3 // indirect
|
|
||||||
github.com/google/flatbuffers v1.12.1 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/klauspost/compress v1.12.3 // indirect
|
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/matryer/moq v0.2.5 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/urfave/cli/v2 v2.1.1 // indirect
|
github.com/urfave/cli/v2 v2.1.1 // indirect
|
||||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e // indirect
|
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e // indirect
|
||||||
go.opencensus.io v0.22.5 // indirect
|
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
golang.org/x/mod v0.4.2 // indirect
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
|
||||||
golang.org/x/tools v0.1.5 // indirect
|
golang.org/x/tools v0.1.5 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||||
|
99
go.sum
99
go.sum
@ -1,9 +1,6 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI=
|
github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI=
|
||||||
github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE=
|
github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||||
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
|
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
|
||||||
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
||||||
@ -11,63 +8,28 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
|
|||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf h1:1omDWNUsWxn2HpiMiMuyRmzjl9uG7RP3IE6GTlpgJWU=
|
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf h1:1omDWNUsWxn2HpiMiMuyRmzjl9uG7RP3IE6GTlpgJWU=
|
||||||
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
|
github.com/chromedp/cdproto v0.0.0-20220217222649-d8c14a5c6edf/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
|
||||||
github.com/chromedp/chromedp v0.7.8 h1:JFPIFb28LPjcx6l6mUUzLOTD/TgswcTtg7KrDn8S/2I=
|
github.com/chromedp/chromedp v0.7.8 h1:JFPIFb28LPjcx6l6mUUzLOTD/TgswcTtg7KrDn8S/2I=
|
||||||
github.com/chromedp/chromedp v0.7.8/go.mod h1:HcIUFBa5vA+u2QI3+xljiU59llUQ8lgGoLzYSCBfmUA=
|
github.com/chromedp/chromedp v0.7.8/go.mod h1:HcIUFBa5vA+u2QI3+xljiU59llUQ8lgGoLzYSCBfmUA=
|
||||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8=
|
|
||||||
github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M=
|
|
||||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
|
||||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
||||||
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
|
||||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
@ -79,22 +41,17 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
|
||||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||||
@ -114,7 +71,6 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw
|
|||||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
|
||||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM=
|
github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM=
|
||||||
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
|
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
|
||||||
@ -124,8 +80,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
@ -136,36 +90,25 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
|||||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||||
github.com/smallstep/truststore v0.11.0 h1:JUTkQ4oHr40jHTS/A2t0usEhteMWG+45CDD2iJA/dIk=
|
github.com/smallstep/truststore v0.11.0 h1:JUTkQ4oHr40jHTS/A2t0usEhteMWG+45CDD2iJA/dIk=
|
||||||
github.com/smallstep/truststore v0.11.0/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
|
github.com/smallstep/truststore v0.11.0/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
|
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
|
||||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||||
github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
|
github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
|
||||||
github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
|
github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
go.etcd.io/bbolt v1.4.0-beta.0 h1:U7Y9yH6ZojEo5/BDFMXDXD1RNx9L7iKxudzqR68jLaM=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.etcd.io/bbolt v1.4.0-beta.0/go.mod h1:Qv5yHB6jkQESXT/uVfxJgUPMqgAyhL0GLxcQaz9bSec=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||||
@ -174,62 +117,46 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
|||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@ -243,12 +170,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
@ -259,9 +180,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||||
|
@ -49,3 +49,17 @@ func UnmarshalURL(v interface{}) (*url.URL, error) {
|
|||||||
|
|
||||||
return u, nil
|
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]
|
||||||
|
}
|
||||||
|
@ -11,18 +11,19 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/proj"
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy"
|
"github.com/dstotijn/hetty/pkg/proxy"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
"github.com/dstotijn/hetty/pkg/sender"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,10 +40,10 @@ var revHTTPProtocolMap = map[HTTPProtocol]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
ProjectService proj.Service
|
ProjectService *proj.Service
|
||||||
RequestLogService reqlog.Service
|
RequestLogService *reqlog.Service
|
||||||
InterceptService *intercept.Service
|
InterceptService *intercept.Service
|
||||||
SenderService sender.Service
|
SenderService *sender.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -124,6 +125,8 @@ func parseRequestLog(reqLog reqlog.RequestLog) (HTTPRequestLog, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(HTTPHeaders(log.Headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqLog.Response != nil {
|
if reqLog.Response != nil {
|
||||||
@ -172,6 +175,8 @@ func parseResponseLog(resLog reqlog.ResponseLog) (HTTPResponseLog, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(HTTPHeaders(httpResLog.Headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpResLog, nil
|
return httpResLog, nil
|
||||||
@ -634,7 +639,7 @@ func (r *mutationResolver) UpdateInterceptSettings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if input.RequestFilter != nil && *input.RequestFilter != "" {
|
if input.RequestFilter != nil && *input.RequestFilter != "" {
|
||||||
expr, err := search.ParseQuery(*input.RequestFilter)
|
expr, err := filter.ParseQuery(*input.RequestFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not parse request filter: %w", err)
|
return nil, fmt.Errorf("could not parse request filter: %w", err)
|
||||||
}
|
}
|
||||||
@ -643,7 +648,7 @@ func (r *mutationResolver) UpdateInterceptSettings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if input.ResponseFilter != nil && *input.ResponseFilter != "" {
|
if input.ResponseFilter != nil && *input.ResponseFilter != "" {
|
||||||
expr, err := search.ParseQuery(*input.ResponseFilter)
|
expr, err := filter.ParseQuery(*input.ResponseFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not parse response filter: %w", err)
|
return nil, fmt.Errorf("could not parse response filter: %w", err)
|
||||||
}
|
}
|
||||||
@ -710,6 +715,8 @@ func parseSenderRequest(req sender.Request) (SenderRequest, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(HTTPHeaders(senderReq.Headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Body) > 0 {
|
if len(req.Body) > 0 {
|
||||||
@ -765,6 +772,8 @@ func parseHTTPRequest(req *http.Request) (HTTPRequest, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(HTTPHeaders(httpReq.Headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Body != nil {
|
if req.Body != nil {
|
||||||
@ -815,6 +824,8 @@ func parseHTTPResponse(res *http.Response) (HTTPResponse, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(HTTPHeaders(httpRes.Headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Body != nil {
|
if res.Body != nil {
|
||||||
@ -854,7 +865,7 @@ func parseInterceptItem(item intercept.Item) (req HTTPRequest, err error) {
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseProject(projSvc proj.Service, p proj.Project) Project {
|
func parseProject(projSvc *proj.Service, p proj.Project) Project {
|
||||||
project := Project{
|
project := Project{
|
||||||
ID: p.ID,
|
ID: p.ID,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
@ -905,43 +916,43 @@ func scopeToScopeRules(rules []scope.Rule) []ScopeRule {
|
|||||||
return scopeRules
|
return scopeRules
|
||||||
}
|
}
|
||||||
|
|
||||||
func findRequestsFilterFromInput(input *HTTPRequestLogFilterInput) (filter reqlog.FindRequestsFilter, err error) {
|
func findRequestsFilterFromInput(input *HTTPRequestLogFilterInput) (findFilter reqlog.FindRequestsFilter, err error) {
|
||||||
if input == nil {
|
if input == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.OnlyInScope != nil {
|
if input.OnlyInScope != nil {
|
||||||
filter.OnlyInScope = *input.OnlyInScope
|
findFilter.OnlyInScope = *input.OnlyInScope
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.SearchExpression != nil && *input.SearchExpression != "" {
|
if input.SearchExpression != nil && *input.SearchExpression != "" {
|
||||||
expr, err := search.ParseQuery(*input.SearchExpression)
|
expr, err := filter.ParseQuery(*input.SearchExpression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reqlog.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
|
return reqlog.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.SearchExpr = expr
|
findFilter.SearchExpr = expr
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSenderRequestsFilterFromInput(input *SenderRequestFilterInput) (filter sender.FindRequestsFilter, err error) {
|
func findSenderRequestsFilterFromInput(input *SenderRequestFilterInput) (findFilter sender.FindRequestsFilter, err error) {
|
||||||
if input == nil {
|
if input == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.OnlyInScope != nil {
|
if input.OnlyInScope != nil {
|
||||||
filter.OnlyInScope = *input.OnlyInScope
|
findFilter.OnlyInScope = *input.OnlyInScope
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.SearchExpression != nil && *input.SearchExpression != "" {
|
if input.SearchExpression != nil && *input.SearchExpression != "" {
|
||||||
expr, err := search.ParseQuery(*input.SearchExpression)
|
expr, err := filter.ParseQuery(*input.SearchExpression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sender.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
|
return sender.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.SearchExpr = expr
|
findFilter.SearchExpr = expr
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package badger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Key prefixes. Each prefix value should be unique.
|
|
||||||
projectPrefix = 0x00
|
|
||||||
reqLogPrefix = 0x01
|
|
||||||
resLogPrefix = 0x02
|
|
||||||
senderReqPrefix = 0x03
|
|
||||||
|
|
||||||
// Request log indices.
|
|
||||||
reqLogProjectIDIndex = 0x00
|
|
||||||
|
|
||||||
// Sender request indices.
|
|
||||||
senderReqProjectIDIndex = 0x00
|
|
||||||
)
|
|
||||||
|
|
||||||
// Database is used to store and retrieve data from an underlying Badger database.
|
|
||||||
type Database struct {
|
|
||||||
badger *badger.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenDatabase opens a new Badger database.
|
|
||||||
func OpenDatabase(opts badger.Options) (*Database, error) {
|
|
||||||
db, err := badger.Open(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("badger: failed to open database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Database{badger: db}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the underlying Badger database.
|
|
||||||
func (db *Database) Close() error {
|
|
||||||
return db.badger.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatabaseFromBadgerDB returns a Database with `db` set as the underlying
|
|
||||||
// Badger database.
|
|
||||||
func DatabaseFromBadgerDB(db *badger.DB) *Database {
|
|
||||||
return &Database{badger: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func entryKey(prefix, index byte, value []byte) []byte {
|
|
||||||
// Key consists of: | prefix (byte) | index (byte) | value
|
|
||||||
key := make([]byte, 2+len(value))
|
|
||||||
key[0] = prefix
|
|
||||||
key[1] = index
|
|
||||||
copy(key[2:len(value)+2], value)
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package badger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Interface guard.
|
|
||||||
var _ badger.Logger = (*Logger)(nil)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
*zap.SugaredLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogger(l *zap.SugaredLogger) *Logger {
|
|
||||||
return &Logger{l}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Warningf(template string, args ...interface{}) {
|
|
||||||
l.Warnf(template, args)
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
package badger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/proj"
|
|
||||||
)
|
|
||||||
|
|
||||||
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("badger: failed to encode project: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.Update(func(txn *badger.Txn) error {
|
|
||||||
return txn.Set(entryKey(projectPrefix, 0, project.ID[:]), buf.Bytes())
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) FindProjectByID(ctx context.Context, projectID ulid.ULID) (project proj.Project, err error) {
|
|
||||||
err = db.badger.View(func(txn *badger.Txn) error {
|
|
||||||
item, err := txn.Get(entryKey(projectPrefix, 0, projectID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = item.Value(func(rawProject []byte) error {
|
|
||||||
return gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to retrieve or parse project: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
|
||||||
return proj.Project{}, proj.ErrProjectNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return proj.Project{}, fmt.Errorf("badger: failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return project, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
err := db.ClearRequestLogs(ctx, projectID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete project request logs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.DeleteSenderRequests(ctx, projectID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete project sender requests: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.Update(func(txn *badger.Txn) error {
|
|
||||||
return txn.Delete(entryKey(projectPrefix, 0, projectID[:]))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete project item: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) Projects(ctx context.Context) ([]proj.Project, error) {
|
|
||||||
projects := make([]proj.Project, 0)
|
|
||||||
|
|
||||||
err := db.badger.View(func(txn *badger.Txn) error {
|
|
||||||
var rawProject []byte
|
|
||||||
prefix := entryKey(projectPrefix, 0, nil)
|
|
||||||
|
|
||||||
iterator := txn.NewIterator(badger.DefaultIteratorOptions)
|
|
||||||
defer iterator.Close()
|
|
||||||
|
|
||||||
for iterator.Seek(prefix); iterator.ValidForPrefix(prefix); iterator.Next() {
|
|
||||||
rawProject, err := iterator.Item().ValueCopy(rawProject)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to copy value: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var project proj.Project
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode project: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
projects = append(projects, project)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("badger: failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects, nil
|
|
||||||
}
|
|
@ -1,328 +0,0 @@
|
|||||||
package badger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
|
||||||
"math/rand"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
badgerdb "github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/proj"
|
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
|
||||||
|
|
||||||
//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()
|
|
||||||
|
|
||||||
badgerDB, err := badgerdb.Open(badgerdb.DefaultOptions("").WithInMemory(true))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
database := DatabaseFromBadgerDB(badgerDB)
|
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
searchExpr, err := search.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(.*)"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = database.UpsertProject(context.Background(), exp)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error storing project: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawProject []byte
|
|
||||||
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
item, err := txn.Get(entryKey(projectPrefix, 0, exp.ID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawProject, err = item.ValueCopy(nil)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error retrieving project from database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := proj.Project{}
|
|
||||||
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProjectByID(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("existing project", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
badgerDB, err := badgerdb.Open(badgerdb.DefaultOptions("").WithInMemory(true))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
database := DatabaseFromBadgerDB(badgerDB)
|
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
exp := proj.Project{
|
|
||||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
|
||||||
Name: "foobar",
|
|
||||||
Settings: proj.Settings{},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
|
|
||||||
err = gob.NewEncoder(&buf).Encode(exp)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error encoding project: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = badgerDB.Update(func(txn *badgerdb.Txn) error {
|
|
||||||
return txn.Set(entryKey(projectPrefix, 0, exp.ID[:]), buf.Bytes())
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error setting project: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := database.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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("project not found", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
|
||||||
}
|
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
|
||||||
|
|
||||||
_, err = database.FindProjectByID(context.Background(), projectID)
|
|
||||||
if !errors.Is(err, proj.ErrProjectNotFound) {
|
|
||||||
t.Fatalf("expected `proj.ErrProjectNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteProject(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
badgerDB, err := badgerdb.Open(badgerdb.DefaultOptions("").WithInMemory(true))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
database := DatabaseFromBadgerDB(badgerDB)
|
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
// Store fixtures.
|
|
||||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
|
||||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
|
||||||
senderReqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
|
||||||
|
|
||||||
err = badgerDB.Update(func(txn *badgerdb.Txn) error {
|
|
||||||
// Project item.
|
|
||||||
if err := txn.Set(entryKey(projectPrefix, 0, projectID[:]), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sender request items.
|
|
||||||
if err := txn.Set(entryKey(senderReqPrefix, 0, senderReqID[:]), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := txn.Set(entryKey(resLogPrefix, 0, senderReqID[:]), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err := txn.Set(entryKey(senderReqPrefix, senderReqProjectIDIndex, append(projectID[:], senderReqID[:]...)), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request log items.
|
|
||||||
if err := txn.Set(entryKey(reqLogPrefix, 0, reqLogID[:]), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := txn.Set(entryKey(resLogPrefix, 0, reqLogID[:]), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = txn.Set(entryKey(reqLogPrefix, reqLogProjectIDIndex, append(projectID[:], reqLogID[:]...)), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error creating fixtures: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = database.DeleteProject(context.Background(), projectID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error deleting project: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert project key was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(projectPrefix, 0, projectID[:]))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert request log item was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(reqLogPrefix, 0, reqLogID[:]))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert response log item related to request log was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(resLogPrefix, 0, reqLogID[:]))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert request log project ID index key was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(reqLogPrefix, reqLogProjectIDIndex, append(projectID[:], reqLogID[:]...)))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert sender request item was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(senderReqPrefix, 0, senderReqID[:]))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert response log item related to sender request was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(resLogPrefix, 0, senderReqID[:]))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert sender request project ID index key was deleted.
|
|
||||||
err = badgerDB.View(func(txn *badgerdb.Txn) error {
|
|
||||||
_, err := txn.Get(entryKey(senderReqPrefix, senderReqProjectIDIndex, append(projectID[:], senderReqID[:]...)))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if !errors.Is(err, badgerdb.ErrKeyNotFound) {
|
|
||||||
t.Fatalf("expected `badger.ErrKeyNotFound`, got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProjects(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
|
||||||
}
|
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
exp := []proj.Project{
|
|
||||||
{
|
|
||||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
|
||||||
Name: "one",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
|
|
||||||
Name: "two",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store fixtures.
|
|
||||||
for _, project := range exp {
|
|
||||||
err = database.UpsertProject(context.Background(), project)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error creating project fixture: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := database.Projects(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error finding projects: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(exp) != len(got) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
package badger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (db *Database) FindRequestLogs(
|
|
||||||
ctx context.Context,
|
|
||||||
filter reqlog.FindRequestsFilter,
|
|
||||||
scope *scope.Scope) ([]reqlog.RequestLog, error,
|
|
||||||
) {
|
|
||||||
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
|
|
||||||
return nil, reqlog.ErrProjectIDMustBeSet
|
|
||||||
}
|
|
||||||
|
|
||||||
txn := db.badger.NewTransaction(false)
|
|
||||||
defer txn.Discard()
|
|
||||||
|
|
||||||
reqLogIDs, err := findRequestLogIDsByProjectID(txn, filter.ProjectID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("badger: failed to find request log IDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLogs := make([]reqlog.RequestLog, 0, len(reqLogIDs))
|
|
||||||
|
|
||||||
for _, reqLogID := range reqLogIDs {
|
|
||||||
reqLog, err := getRequestLogWithResponse(txn, reqLogID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("badger: failed to get request log (id: %v): %w", reqLogID.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.OnlyInScope {
|
|
||||||
if !reqLog.MatchScope(scope) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by search expression.
|
|
||||||
// TODO: Once pagination is introduced, this filter logic should be done
|
|
||||||
// as items are retrieved (e.g. when using a `badger.Iterator`).
|
|
||||||
if filter.SearchExpr != nil {
|
|
||||||
match, err := reqLog.Matches(filter.SearchExpr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"badger: failed to match search expression for request log (id: %v): %w",
|
|
||||||
reqLogID.String(), err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLogs = append(reqLogs, reqLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqLogs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRequestLogWithResponse(txn *badger.Txn, reqLogID ulid.ULID) (reqlog.RequestLog, error) {
|
|
||||||
item, err := txn.Get(entryKey(reqLogPrefix, 0, reqLogID[:]))
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, badger.ErrKeyNotFound):
|
|
||||||
return reqlog.RequestLog{}, reqlog.ErrRequestNotFound
|
|
||||||
case err != nil:
|
|
||||||
return reqlog.RequestLog{}, fmt.Errorf("failed to lookup request log item: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLog := reqlog.RequestLog{
|
|
||||||
ID: reqLogID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = item.Value(func(rawReqLog []byte) error {
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode request log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return reqlog.RequestLog{}, fmt.Errorf("failed to retrieve or parse request log value: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, err = txn.Get(entryKey(resLogPrefix, 0, reqLogID[:]))
|
|
||||||
|
|
||||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
|
||||||
return reqLog, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return reqlog.RequestLog{}, fmt.Errorf("failed to get response log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = item.Value(func(rawReslog []byte) error {
|
|
||||||
var resLog reqlog.ResponseLog
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(rawReslog)).Decode(&resLog)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode response log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLog.Response = &resLog
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return reqlog.RequestLog{}, fmt.Errorf("failed to retrieve or parse response log value: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqLog, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) FindRequestLogByID(ctx context.Context, reqLogID ulid.ULID) (reqLog reqlog.RequestLog, err error) {
|
|
||||||
txn := db.badger.NewTransaction(false)
|
|
||||||
defer txn.Discard()
|
|
||||||
|
|
||||||
reqLog, err = getRequestLogWithResponse(txn, reqLogID)
|
|
||||||
if err != nil {
|
|
||||||
return reqlog.RequestLog{}, fmt.Errorf("badger: failed to get request log: %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)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to encode request log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries := []*badger.Entry{
|
|
||||||
// Request log itself.
|
|
||||||
{
|
|
||||||
Key: entryKey(reqLogPrefix, 0, reqLog.ID[:]),
|
|
||||||
Value: buf.Bytes(),
|
|
||||||
},
|
|
||||||
// Index by project ID.
|
|
||||||
{
|
|
||||||
Key: entryKey(reqLogPrefix, reqLogProjectIDIndex, append(reqLog.ProjectID[:], reqLog.ID[:]...)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.Update(func(txn *badger.Txn) error {
|
|
||||||
for i := range entries {
|
|
||||||
err := txn.SetEntry(entries[i])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
|
|
||||||
err := gob.NewEncoder(&buf).Encode(resLog)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to encode response log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.Update(func(txn *badger.Txn) error {
|
|
||||||
return txn.SetEntry(&badger.Entry{
|
|
||||||
Key: entryKey(resLogPrefix, 0, reqLogID[:]),
|
|
||||||
Value: buf.Bytes(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
// Note: this transaction is used just for reading; we use the `badger.WriteBatch`
|
|
||||||
// API to bulk delete items.
|
|
||||||
txn := db.badger.NewTransaction(false)
|
|
||||||
defer txn.Discard()
|
|
||||||
|
|
||||||
reqLogIDs, err := findRequestLogIDsByProjectID(txn, projectID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to find request log IDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeBatch := db.badger.NewWriteBatch()
|
|
||||||
defer writeBatch.Cancel()
|
|
||||||
|
|
||||||
for _, reqLogID := range reqLogIDs {
|
|
||||||
// Delete request logs.
|
|
||||||
err := writeBatch.Delete(entryKey(reqLogPrefix, 0, reqLogID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete request log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete related response log.
|
|
||||||
err = writeBatch.Delete(entryKey(resLogPrefix, 0, reqLogID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete request log: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeBatch.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to commit batch write: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.DropPrefix(entryKey(reqLogPrefix, reqLogProjectIDIndex, projectID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to drop request log project ID index items: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findRequestLogIDsByProjectID(txn *badger.Txn, projectID ulid.ULID) ([]ulid.ULID, error) {
|
|
||||||
reqLogIDs := make([]ulid.ULID, 0)
|
|
||||||
opts := badger.DefaultIteratorOptions
|
|
||||||
opts.PrefetchValues = false
|
|
||||||
opts.Reverse = true
|
|
||||||
iterator := txn.NewIterator(opts)
|
|
||||||
defer iterator.Close()
|
|
||||||
|
|
||||||
var projectIndexKey []byte
|
|
||||||
|
|
||||||
prefix := entryKey(reqLogPrefix, reqLogProjectIDIndex, projectID[:])
|
|
||||||
|
|
||||||
for iterator.Seek(append(prefix, 255)); iterator.ValidForPrefix(prefix); iterator.Next() {
|
|
||||||
projectIndexKey = iterator.Item().KeyCopy(projectIndexKey)
|
|
||||||
|
|
||||||
var id ulid.ULID
|
|
||||||
// The request log ID starts *after* the first 2 prefix and index bytes
|
|
||||||
// and the 16 byte project ID.
|
|
||||||
if err := id.UnmarshalBinary(projectIndexKey[18:]); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse request log ID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLogIDs = append(reqLogIDs, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqLogIDs, nil
|
|
||||||
}
|
|
@ -1,240 +0,0 @@
|
|||||||
package badger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (db *Database) StoreSenderRequest(ctx context.Context, req sender.Request) error {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
|
|
||||||
err := gob.NewEncoder(&buf).Encode(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to encode sender request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entries := []*badger.Entry{
|
|
||||||
// Sender request itself.
|
|
||||||
{
|
|
||||||
Key: entryKey(senderReqPrefix, 0, req.ID[:]),
|
|
||||||
Value: buf.Bytes(),
|
|
||||||
},
|
|
||||||
// Index by project ID.
|
|
||||||
{
|
|
||||||
Key: entryKey(senderReqPrefix, senderReqProjectIDIndex, append(req.ProjectID[:], req.ID[:]...)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.Update(func(txn *badger.Txn) error {
|
|
||||||
for i := range entries {
|
|
||||||
err := txn.SetEntry(entries[i])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) FindSenderRequestByID(ctx context.Context, senderReqID ulid.ULID) (sender.Request, error) {
|
|
||||||
txn := db.badger.NewTransaction(false)
|
|
||||||
defer txn.Discard()
|
|
||||||
|
|
||||||
req, err := getSenderRequestWithResponseLog(txn, senderReqID)
|
|
||||||
if err != nil {
|
|
||||||
return sender.Request{}, fmt.Errorf("badger: failed to get sender request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) FindSenderRequests(
|
|
||||||
ctx context.Context,
|
|
||||||
filter sender.FindRequestsFilter,
|
|
||||||
scope *scope.Scope,
|
|
||||||
) ([]sender.Request, error) {
|
|
||||||
if filter.ProjectID.Compare(ulid.ULID{}) == 0 {
|
|
||||||
return nil, sender.ErrProjectIDMustBeSet
|
|
||||||
}
|
|
||||||
|
|
||||||
txn := db.badger.NewTransaction(false)
|
|
||||||
defer txn.Discard()
|
|
||||||
|
|
||||||
senderReqIDs, err := findSenderRequestIDsByProjectID(txn, filter.ProjectID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("badger: failed to find sender request IDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
senderReqs := make([]sender.Request, 0, len(senderReqIDs))
|
|
||||||
|
|
||||||
for _, id := range senderReqIDs {
|
|
||||||
senderReq, err := getSenderRequestWithResponseLog(txn, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("badger: failed to get sender request (id: %v): %w", id.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.OnlyInScope {
|
|
||||||
if !senderReq.MatchScope(scope) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by search expression.
|
|
||||||
// TODO: Once pagination is introduced, this filter logic should be done
|
|
||||||
// as items are retrieved (e.g. when using a `badger.Iterator`).
|
|
||||||
if filter.SearchExpr != nil {
|
|
||||||
match, err := senderReq.Matches(filter.SearchExpr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"badger: failed to match search expression for sender request (id: %v): %w",
|
|
||||||
id.String(), err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
senderReqs = append(senderReqs, senderReq)
|
|
||||||
}
|
|
||||||
|
|
||||||
return senderReqs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
// Note: this transaction is used just for reading; we use the `badger.WriteBatch`
|
|
||||||
// API to bulk delete items.
|
|
||||||
txn := db.badger.NewTransaction(false)
|
|
||||||
defer txn.Discard()
|
|
||||||
|
|
||||||
senderReqIDs, err := findSenderRequestIDsByProjectID(txn, projectID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to find sender request IDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeBatch := db.badger.NewWriteBatch()
|
|
||||||
defer writeBatch.Cancel()
|
|
||||||
|
|
||||||
for _, senderReqID := range senderReqIDs {
|
|
||||||
// Delete sender requests.
|
|
||||||
err := writeBatch.Delete(entryKey(senderReqPrefix, 0, senderReqID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete sender requests: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete related response log.
|
|
||||||
err = writeBatch.Delete(entryKey(resLogPrefix, 0, senderReqID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to delete request log: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writeBatch.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to commit batch write: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.badger.DropPrefix(entryKey(senderReqPrefix, senderReqProjectIDIndex, projectID[:]))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("badger: failed to drop sender request project ID index items: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSenderRequestWithResponseLog(txn *badger.Txn, senderReqID ulid.ULID) (sender.Request, error) {
|
|
||||||
item, err := txn.Get(entryKey(senderReqPrefix, 0, senderReqID[:]))
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, badger.ErrKeyNotFound):
|
|
||||||
return sender.Request{}, sender.ErrRequestNotFound
|
|
||||||
case err != nil:
|
|
||||||
return sender.Request{}, fmt.Errorf("failed to lookup sender request item: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := sender.Request{
|
|
||||||
ID: senderReqID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = item.Value(func(rawSenderReq []byte) error {
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode sender request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return sender.Request{}, fmt.Errorf("failed to retrieve or parse sender request value: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, err = txn.Get(entryKey(resLogPrefix, 0, senderReqID[:]))
|
|
||||||
|
|
||||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return sender.Request{}, fmt.Errorf("failed to get response log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = item.Value(func(rawReslog []byte) error {
|
|
||||||
var resLog reqlog.ResponseLog
|
|
||||||
err = gob.NewDecoder(bytes.NewReader(rawReslog)).Decode(&resLog)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode response log: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Response = &resLog
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return sender.Request{}, fmt.Errorf("failed to retrieve or parse response log value: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSenderRequestIDsByProjectID(txn *badger.Txn, projectID ulid.ULID) ([]ulid.ULID, error) {
|
|
||||||
senderReqIDs := make([]ulid.ULID, 0)
|
|
||||||
opts := badger.DefaultIteratorOptions
|
|
||||||
opts.PrefetchValues = false
|
|
||||||
opts.Reverse = true
|
|
||||||
iterator := txn.NewIterator(opts)
|
|
||||||
defer iterator.Close()
|
|
||||||
|
|
||||||
var projectIndexKey []byte
|
|
||||||
|
|
||||||
prefix := entryKey(senderReqPrefix, senderReqProjectIDIndex, projectID[:])
|
|
||||||
|
|
||||||
for iterator.Seek(append(prefix, 255)); iterator.ValidForPrefix(prefix); iterator.Next() {
|
|
||||||
projectIndexKey = iterator.Item().KeyCopy(projectIndexKey)
|
|
||||||
|
|
||||||
var id ulid.ULID
|
|
||||||
// The request log ID starts *after* the first 2 prefix and index bytes
|
|
||||||
// and the 16 byte project ID.
|
|
||||||
if err := id.UnmarshalBinary(projectIndexKey[18:]); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse sender request ID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
senderReqIDs = append(senderReqIDs, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return senderReqIDs, nil
|
|
||||||
}
|
|
60
pkg/db/bolt/bolt.go
Normal file
60
pkg/db/bolt/bolt.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database is used to store and retrieve data from an underlying Bolt database.
|
||||||
|
type Database struct {
|
||||||
|
bolt *bolt.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenDatabase opens a new Bolt database.
|
||||||
|
func OpenDatabase(path string, opts *bolt.Options) (*Database, error) {
|
||||||
|
db, err := bolt.Open(path, 0o600, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bolt: failed to open database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DatabaseFromBoltDB(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying Bolt database.
|
||||||
|
func (db *Database) Close() error {
|
||||||
|
return db.bolt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseFromBoltDB returns a Database with `db` set as the underlying Bolt
|
||||||
|
// database.
|
||||||
|
func DatabaseFromBoltDB(db *bolt.DB) (*Database, error) {
|
||||||
|
err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
_, err := tx.CreateBucketIfNotExists(projectsBucketName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bolt: failed to create projects bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Database{bolt: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNestedBucket(tx *bolt.Tx, names ...[]byte) (b *bolt.Bucket, err error) {
|
||||||
|
for i, name := range names {
|
||||||
|
if b == nil {
|
||||||
|
b, err = tx.CreateBucketIfNotExists(name)
|
||||||
|
} else {
|
||||||
|
b, err = b.CreateBucketIfNotExists(name)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bolt: failed to create nested bucket %q: %w", names[:i+1], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
23
pkg/db/bolt/logger.go
Normal file
23
pkg/db/bolt/logger.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface guard.
|
||||||
|
var _ bolt.Logger = (*Logger)(nil)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
*zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning implements bbolt.Logger.
|
||||||
|
func (l *Logger) Warning(v ...interface{}) {
|
||||||
|
l.Warn(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf implements bbolt.Logger.
|
||||||
|
func (l *Logger) Warningf(format string, v ...interface{}) {
|
||||||
|
l.Warnf(format, v...)
|
||||||
|
}
|
176
pkg/db/bolt/proj.go
Normal file
176
pkg/db/bolt/proj.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/oklog/ulid"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrProjectsBucketNotFound = errors.New("bolt: projects bucket not found")
|
||||||
|
ErrProjectBucketNotFound = errors.New("bolt: project bucket not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
projectsBucketName = []byte("projects")
|
||||||
|
projectKey = []byte("project")
|
||||||
|
)
|
||||||
|
|
||||||
|
func projectsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||||
|
b := tx.Bucket(projectsBucketName)
|
||||||
|
if b == nil {
|
||||||
|
return nil, ErrProjectsBucketNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectBucket(tx *bolt.Tx, projectID []byte) (*bolt.Bucket, error) {
|
||||||
|
pb, err := projectsBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := pb.Bucket(projectID[:])
|
||||||
|
if b == nil {
|
||||||
|
return nil, ErrProjectBucketNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
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[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to create project bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.Put(projectKey, buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to upsert project: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.CreateBucketIfNotExists(reqLogsBucketName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to create request logs bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.CreateBucketIfNotExists(senderReqsBucketName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to create sender requests bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", 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[:])
|
||||||
|
if errors.Is(err, ErrProjectsBucketNotFound) || errors.Is(err, ErrProjectBucketNotFound) {
|
||||||
|
return proj.ErrProjectNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawProject := bucket.Get(projectKey)
|
||||||
|
if rawProject == nil {
|
||||||
|
return proj.ErrProjectNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode project: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return proj.Project{}, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return project, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
|
||||||
|
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||||
|
pb, err := projectsBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pb.DeleteBucket(projectID[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete project bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pb.ForEachBucket(func(projectID []byte) error {
|
||||||
|
bucket, err := projectBucket(tx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawProject := bucket.Get(projectKey)
|
||||||
|
if rawProject == nil {
|
||||||
|
return proj.ErrProjectNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var project proj.Project
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&project)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to decode project: %w", err)
|
||||||
|
}
|
||||||
|
projects = append(projects, project)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to iterate over projects: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects, nil
|
||||||
|
}
|
267
pkg/db/bolt/proj_test.go
Normal file
267
pkg/db/bolt/proj_test.go
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
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"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
|
)
|
||||||
|
|
||||||
|
//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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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(.*)"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpsertProject(context.Background(), exp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error storing project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawProject []byte
|
||||||
|
|
||||||
|
err = boltDB.View(func(tx *bbolt.Tx) error {
|
||||||
|
rawProject = tx.Bucket([]byte("projects")).Bucket(exp.ID[:]).Get([]byte("project"))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error retrieving project from database: %v", err)
|
||||||
|
}
|
||||||
|
if rawProject == nil {
|
||||||
|
t.Fatalf("expected raw project to be retrieved, got: nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
got := proj.Project{}
|
||||||
|
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(rawProject)).Decode(&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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindProjectByID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("existing project", 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()
|
||||||
|
|
||||||
|
exp := proj.Project{
|
||||||
|
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
err = gob.NewEncoder(&buf).Encode(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[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.Put([]byte("project"), buf.Bytes())
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error setting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("project not found", 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()
|
||||||
|
|
||||||
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
|
||||||
|
_, err = db.FindProjectByID(context.Background(), projectID)
|
||||||
|
if !errors.Is(err, proj.ErrProjectNotFound) {
|
||||||
|
t.Fatalf("expected `proj.ErrProjectNotFound`, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteProject(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()
|
||||||
|
|
||||||
|
// Insert test fixture.
|
||||||
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error storing project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.DeleteProject(context.Background(), projectID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error deleting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var got *bbolt.Bucket
|
||||||
|
err = boltDB.View(func(tx *bbolt.Tx) error {
|
||||||
|
got = tx.Bucket([]byte("projects")).Bucket(projectID[:])
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if got != nil {
|
||||||
|
t.Fatalf("expected bucket to be nil, got: %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjects(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()
|
||||||
|
|
||||||
|
exp := []proj.Project{
|
||||||
|
{
|
||||||
|
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||||
|
Name: "one",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: ulid.MustNew(ulid.Timestamp(time.Now())+100, ulidEntropy),
|
||||||
|
Name: "two",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store fixtures.
|
||||||
|
for _, project := range exp {
|
||||||
|
err = db.UpsertProject(context.Background(), project)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating project fixture: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := db.Projects(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error finding projects: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(exp) != len(got) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
204
pkg/db/bolt/reqlog.go
Normal file
204
pkg/db/bolt/reqlog.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/oklog/ulid"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"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[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := pb.Bucket(reqLogsBucketName)
|
||||||
|
if b == nil {
|
||||||
|
return nil, ErrRequestLogsBucketNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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 err != nil {
|
||||||
|
return fmt.Errorf("failed to match search expression for request log (id: %v): %w", reqLogID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reqLogs = append(reqLogs, reqLog)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bolt: failed to iterate over request logs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse items, so newest requests appear first.
|
||||||
|
for i, j := 0, len(reqLogs)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
reqLogs[i], reqLogs[j] = reqLogs[j], reqLogs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
b, err := requestLogsBucket(tx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to get request logs bucket: %w", err)
|
||||||
|
}
|
||||||
|
rawReqLog := b.Get(reqLogID[:])
|
||||||
|
if rawReqLog == nil {
|
||||||
|
return reqlog.ErrRequestNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode 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 reqLog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
err := gob.NewEncoder(&buf).Encode(reqLog)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to encode request log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.bolt.Update(func(txn *bolt.Tx) error {
|
||||||
|
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())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to put request log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqLog reqlog.RequestLog
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(rawReqLog)).Decode(&reqLog)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode request log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqLog.Response = &resLog
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
err = gob.NewEncoder(&buf).Encode(reqLog)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encode request log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.Put(reqLog.ID[:], buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to put request log: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
|
||||||
|
err := db.bolt.Update(func(txn *bolt.Tx) error {
|
||||||
|
pb, err := projectBucket(txn, projectID[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get project bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb.DeleteBucket(reqLogsBucketName)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package badger
|
package bolt_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -8,10 +8,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
badgerdb "github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||||
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,15 +23,21 @@ func TestFindRequestLogs(t *testing.T) {
|
|||||||
t.Run("without project ID in filter", func(t *testing.T) {
|
t.Run("without project ID in filter", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
path := t.TempDir() + "bolt.db"
|
||||||
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
t.Fatalf("failed to open bolt database: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
|
||||||
|
db, err := bolt.DatabaseFromBoltDB(boltDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
filter := reqlog.FindRequestsFilter{}
|
filter := reqlog.FindRequestsFilter{}
|
||||||
|
|
||||||
_, err = database.FindRequestLogs(context.Background(), filter, nil)
|
_, err = db.FindRequestLogs(context.Background(), filter, nil)
|
||||||
if !errors.Is(err, reqlog.ErrProjectIDMustBeSet) {
|
if !errors.Is(err, reqlog.ErrProjectIDMustBeSet) {
|
||||||
t.Fatalf("expected `reqlog.ErrProjectIDMustBeSet`, got: %v", err)
|
t.Fatalf("expected `reqlog.ErrProjectIDMustBeSet`, got: %v", err)
|
||||||
}
|
}
|
||||||
@ -38,14 +46,27 @@ func TestFindRequestLogs(t *testing.T) {
|
|||||||
t.Run("returns request logs and related response logs", func(t *testing.T) {
|
t.Run("returns request logs and related response logs", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
database, err := OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
path := t.TempDir() + "bolt.db"
|
||||||
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
t.Fatalf("failed to open bolt database: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
|
||||||
|
db, err := bolt.DatabaseFromBoltDB(boltDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
|
||||||
|
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.RequestLog{
|
||||||
{
|
{
|
||||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||||
@ -81,24 +102,17 @@ func TestFindRequestLogs(t *testing.T) {
|
|||||||
|
|
||||||
// Store fixtures.
|
// Store fixtures.
|
||||||
for _, reqLog := range fixtures {
|
for _, reqLog := range fixtures {
|
||||||
err = database.StoreRequestLog(context.Background(), reqLog)
|
err = db.StoreRequestLog(context.Background(), reqLog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating request log fixture: %v", err)
|
t.Fatalf("unexpected error creating request log fixture: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqLog.Response != nil {
|
|
||||||
err = database.StoreResponseLog(context.Background(), reqLog.ID, *reqLog.Response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error creating response log fixture: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := reqlog.FindRequestsFilter{
|
filter := reqlog.FindRequestsFilter{
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := database.FindRequestLogs(context.Background(), filter, nil)
|
got, err := db.FindRequestLogs(context.Background(), filter, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error finding request logs: %v", err)
|
t.Fatalf("unexpected error finding request logs: %v", err)
|
||||||
}
|
}
|
172
pkg/db/bolt/sender.go
Normal file
172
pkg/db/bolt/sender.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/oklog/ulid"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
|
"github.com/dstotijn/hetty/pkg/sender"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrSenderRequestsBucketNotFound = errors.New("bolt: sender requests bucket not found")
|
||||||
|
|
||||||
|
var senderReqsBucketName = []byte("sender_requests")
|
||||||
|
|
||||||
|
func senderReqsBucket(tx *bolt.Tx, projectID ulid.ULID) (*bolt.Bucket, error) {
|
||||||
|
pb, err := projectBucket(tx, projectID[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := pb.Bucket(senderReqsBucketName)
|
||||||
|
if b == nil {
|
||||||
|
return nil, ErrSenderRequestsBucketNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) StoreSenderRequest(ctx context.Context, req sender.Request) error {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
err := gob.NewEncoder(&buf).Encode(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to encode sender request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.bolt.Update(func(tx *bolt.Tx) error {
|
||||||
|
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())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to put sender request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.bolt.View(func(tx *bolt.Tx) error {
|
||||||
|
senderReqsBucket, err := senderReqsBucket(tx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get sender requests bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSenderReq := senderReqsBucket.Get(senderReqID[:])
|
||||||
|
if rawSenderReq == nil {
|
||||||
|
return sender.ErrRequestNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gob.NewDecoder(bytes.NewReader(rawSenderReq)).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode sender request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return sender.Request{}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode 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 err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"bolt: failed to match search expression for sender request (id: %v): %w",
|
||||||
|
senderReqID, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reqs = append(reqs, req)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse items, so newest requests appear first.
|
||||||
|
for i, j := 0, len(reqs)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
reqs[i], reqs[j] = reqs[j], reqs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return reqs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||||
|
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||||
|
senderReqsBucket, err := senderReqsBucket(tx, projectID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get sender requests bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = senderReqsBucket.DeleteBucket(senderReqsBucketName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete sender requests bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bolt: failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,26 +1,23 @@
|
|||||||
package badger_test
|
package bolt_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
badgerdb "github.com/dgraph-io/badger/v3"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/db/badger"
|
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||||
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
"github.com/dstotijn/hetty/pkg/sender"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gosec
|
|
||||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
|
|
||||||
var exampleURL = func() *url.URL {
|
var exampleURL = func() *url.URL {
|
||||||
u, err := url.Parse("https://example.com/foobar")
|
u, err := url.Parse("https://example.com/foobar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,18 +30,35 @@ var exampleURL = func() *url.URL {
|
|||||||
func TestFindRequestByID(t *testing.T) {
|
func TestFindRequestByID(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
database, err := badger.OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
path := t.TempDir() + "bolt.db"
|
||||||
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
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()
|
||||||
|
|
||||||
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error upserting project: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
// See: https://go.dev/blog/subtests#cleaning-up-after-a-group-of-parallel-tests
|
// See: https://go.dev/blog/subtests#cleaning-up-after-a-group-of-parallel-tests
|
||||||
t.Run("group", func(t *testing.T) {
|
t.Run("group", func(t *testing.T) {
|
||||||
t.Run("sender request not found", func(t *testing.T) {
|
t.Run("sender request not found", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := database.FindSenderRequestByID(context.Background(), ulid.ULID{})
|
_, err := db.FindSenderRequestByID(context.Background(), projectID, reqID)
|
||||||
if !errors.Is(err, sender.ErrRequestNotFound) {
|
if !errors.Is(err, sender.ErrRequestNotFound) {
|
||||||
t.Fatalf("expected `sender.ErrRequestNotFound`, got: %v", err)
|
t.Fatalf("expected `sender.ErrRequestNotFound`, got: %v", err)
|
||||||
}
|
}
|
||||||
@ -55,7 +69,7 @@ func TestFindRequestByID(t *testing.T) {
|
|||||||
|
|
||||||
exp := sender.Request{
|
exp := sender.Request{
|
||||||
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||||
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
ProjectID: projectID,
|
||||||
SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
SourceRequestLogID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||||
|
|
||||||
URL: exampleURL,
|
URL: exampleURL,
|
||||||
@ -65,31 +79,23 @@ func TestFindRequestByID(t *testing.T) {
|
|||||||
"X-Foo": []string{"bar"},
|
"X-Foo": []string{"bar"},
|
||||||
},
|
},
|
||||||
Body: []byte("foo"),
|
Body: []byte("foo"),
|
||||||
}
|
Response: &reqlog.ResponseLog{
|
||||||
|
Proto: "HTTP/2.0",
|
||||||
err := database.StoreSenderRequest(context.Background(), exp)
|
Status: "200 OK",
|
||||||
if err != nil {
|
StatusCode: 200,
|
||||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
Header: http.Header{
|
||||||
}
|
"X-Yolo": []string{"swag"},
|
||||||
|
},
|
||||||
resLog := reqlog.ResponseLog{
|
Body: []byte("bar"),
|
||||||
Proto: "HTTP/2.0",
|
|
||||||
Status: "200 OK",
|
|
||||||
StatusCode: 200,
|
|
||||||
Header: http.Header{
|
|
||||||
"X-Yolo": []string{"swag"},
|
|
||||||
},
|
},
|
||||||
Body: []byte("bar"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = database.StoreResponseLog(context.Background(), exp.ID, resLog)
|
err := db.StoreSenderRequest(context.Background(), exp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exp.Response = &resLog
|
got, err := db.FindSenderRequestByID(context.Background(), exp.ProjectID, exp.ID)
|
||||||
|
|
||||||
got, err := database.FindSenderRequestByID(context.Background(), exp.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||||
}
|
}
|
||||||
@ -107,15 +113,22 @@ func TestFindSenderRequests(t *testing.T) {
|
|||||||
t.Run("without project ID in filter", func(t *testing.T) {
|
t.Run("without project ID in filter", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
database, err := badger.OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
path := t.TempDir() + "bolt.db"
|
||||||
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
t.Fatalf("failed to open bolt database: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
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{}
|
filter := sender.FindRequestsFilter{}
|
||||||
|
|
||||||
_, err = database.FindSenderRequests(context.Background(), filter, nil)
|
_, err = db.FindSenderRequests(context.Background(), filter, nil)
|
||||||
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
if !errors.Is(err, sender.ErrProjectIDMustBeSet) {
|
||||||
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
t.Fatalf("expected `sender.ErrProjectIDMustBeSet`, got: %v", err)
|
||||||
}
|
}
|
||||||
@ -124,14 +137,30 @@ func TestFindSenderRequests(t *testing.T) {
|
|||||||
t.Run("returns sender requests and related response logs", func(t *testing.T) {
|
t.Run("returns sender requests and related response logs", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
database, err := badger.OpenDatabase(badgerdb.DefaultOptions("").WithInMemory(true))
|
path := t.TempDir() + "bolt.db"
|
||||||
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open badger database: %v", err)
|
t.Fatalf("failed to open bolt database: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
defer boltDB.Close()
|
||||||
|
|
||||||
|
db, err := bolt.DatabaseFromBoltDB(boltDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
|
Name: "foobar",
|
||||||
|
Settings: proj.Settings{},
|
||||||
|
})
|
||||||
|
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),
|
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
||||||
@ -169,24 +198,17 @@ func TestFindSenderRequests(t *testing.T) {
|
|||||||
|
|
||||||
// Store fixtures.
|
// Store fixtures.
|
||||||
for _, senderReq := range fixtures {
|
for _, senderReq := range fixtures {
|
||||||
err = database.StoreSenderRequest(context.Background(), senderReq)
|
err = db.StoreSenderRequest(context.Background(), senderReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error creating request log fixture: %v", err)
|
t.Fatalf("unexpected error creating request log fixture: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if senderReq.Response != nil {
|
|
||||||
err = database.StoreResponseLog(context.Background(), senderReq.ID, *senderReq.Response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error creating response log fixture: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := sender.FindRequestsFilter{
|
filter := sender.FindRequestsFilter{
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := database.FindSenderRequests(context.Background(), filter, nil)
|
got, err := db.FindSenderRequests(context.Background(), filter, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error finding sender requests: %v", err)
|
t.Fatalf("unexpected error finding sender requests: %v", err)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package search
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
@ -78,8 +78,10 @@ func (rl *RegexpLiteral) UnmarshalBinary(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(PrefixExpression{})
|
// The `filter` package was previously named `search`.
|
||||||
gob.Register(InfixExpression{})
|
// We use the legacy names for backwards compatibility with existing database data.
|
||||||
gob.Register(StringLiteral{})
|
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.PrefixExpression", PrefixExpression{})
|
||||||
gob.Register(RegexpLiteral{})
|
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.InfixExpression", InfixExpression{})
|
||||||
|
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.StringLiteral", StringLiteral{})
|
||||||
|
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.RegexpLiteral", RegexpLiteral{})
|
||||||
}
|
}
|
212
pkg/filter/ast_test.go
Normal file
212
pkg/filter/ast_test.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package filter_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpressionString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expression filter.Expression
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "string literal expression",
|
||||||
|
expression: filter.StringLiteral{Value: "foobar"},
|
||||||
|
expected: `"foobar"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with equal operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpEq,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" = "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with not equal operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpNotEq,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" != "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with greater than operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpGt,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" > "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with less than operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpLt,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" < "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with greater than or equal operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpGtEq,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" >= "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with less than or equal operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpLtEq,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" <= "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with regular expression operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpRe,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.RegexpLiteral{regexp.MustCompile("bar")},
|
||||||
|
},
|
||||||
|
expected: `("foo" =~ "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with not regular expression operator",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpNotRe,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.RegexpLiteral{regexp.MustCompile("bar")},
|
||||||
|
},
|
||||||
|
expected: `("foo" !~ "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with AND, OR and NOT operators",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpOr,
|
||||||
|
Left: filter.StringLiteral{Value: "bar"},
|
||||||
|
Right: filter.PrefixExpression{
|
||||||
|
Operator: filter.TokOpNot,
|
||||||
|
Right: filter.StringLiteral{Value: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `("foo" AND ("bar" OR (NOT "baz")))`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolean expression with nested group",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpOr,
|
||||||
|
Left: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
Right: filter.PrefixExpression{
|
||||||
|
Operator: filter.TokOpNot,
|
||||||
|
Right: filter.StringLiteral{Value: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `(("foo" AND "bar") OR (NOT "baz"))`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implicit boolean expression with string literal operands",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
Right: filter.StringLiteral{Value: "baz"},
|
||||||
|
},
|
||||||
|
expected: `(("foo" AND "bar") AND "baz")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implicit boolean expression nested in group",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: `("foo" AND "bar")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implicit and explicit boolean expression with string literal operands",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpOr,
|
||||||
|
Left: filter.StringLiteral{Value: "bar"},
|
||||||
|
Right: filter.StringLiteral{Value: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: filter.StringLiteral{Value: "yolo"},
|
||||||
|
},
|
||||||
|
expected: `(("foo" AND ("bar" OR "baz")) AND "yolo")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implicit boolean expression with comparison operands",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpAnd,
|
||||||
|
Left: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpEq,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
Right: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpRe,
|
||||||
|
Left: filter.StringLiteral{Value: "baz"},
|
||||||
|
Right: filter.RegexpLiteral{regexp.MustCompile("yolo")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `(("foo" = "bar") AND ("baz" =~ "yolo"))`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eq operator takes precedence over boolean ops",
|
||||||
|
expression: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpOr,
|
||||||
|
Left: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpEq,
|
||||||
|
Left: filter.StringLiteral{Value: "foo"},
|
||||||
|
Right: filter.StringLiteral{Value: "bar"},
|
||||||
|
},
|
||||||
|
Right: filter.InfixExpression{
|
||||||
|
Operator: filter.TokOpEq,
|
||||||
|
Left: filter.StringLiteral{Value: "baz"},
|
||||||
|
Right: filter.StringLiteral{Value: "yolo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `(("foo" = "bar") OR ("baz" = "yolo"))`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := tt.expression.String()
|
||||||
|
if tt.expected != got {
|
||||||
|
t.Errorf("expected: %v, got: %v", tt.expected, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
82
pkg/filter/http.go
Normal file
82
pkg/filter/http.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool, error) {
|
||||||
|
if headers == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case TokOpEq:
|
||||||
|
strLiteral, ok := expr.(StringLiteral)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("filter: expression must be a string literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
case TokOpNotEq:
|
||||||
|
strLiteral, ok := expr.(StringLiteral)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("filter: expression must be a string literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
case TokOpRe:
|
||||||
|
re, ok := expr.(RegexpLiteral)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("filter: expression must be a regular expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
case TokOpNotRe:
|
||||||
|
re, ok := expr.(RegexpLiteral)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("filter: expression must be a regular expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("filter: unsupported operator %q", op.String())
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package search
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package search
|
package filter
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package search
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -88,7 +88,7 @@ func ParseQuery(input string) (expr Expression, err error) {
|
|||||||
p.nextToken()
|
p.nextToken()
|
||||||
|
|
||||||
if p.curTokenIs(TokEOF) {
|
if p.curTokenIs(TokEOF) {
|
||||||
return nil, fmt.Errorf("search: unexpected EOF")
|
return nil, fmt.Errorf("filter: unexpected EOF")
|
||||||
}
|
}
|
||||||
|
|
||||||
for !p.curTokenIs(TokEOF) {
|
for !p.curTokenIs(TokEOF) {
|
||||||
@ -96,7 +96,7 @@ func ParseQuery(input string) (expr Expression, err error) {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, fmt.Errorf("search: could not parse expression: %w", err)
|
return nil, fmt.Errorf("filter: could not parse expression: %w", err)
|
||||||
case expr == nil:
|
case expr == nil:
|
||||||
expr = right
|
expr = right
|
||||||
default:
|
default:
|
@ -1,4 +1,4 @@
|
|||||||
package search
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -20,7 +20,7 @@ func TestParseQuery(t *testing.T) {
|
|||||||
name: "empty query",
|
name: "empty query",
|
||||||
input: "",
|
input: "",
|
||||||
expectedExpression: nil,
|
expectedExpression: nil,
|
||||||
expectedError: errors.New("search: unexpected EOF"),
|
expectedError: errors.New("filter: unexpected EOF"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "string literal expression",
|
name: "string literal expression",
|
@ -11,37 +11,21 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
"github.com/dstotijn/hetty/pkg/sender"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
// Service is used for managing projects.
|
type Service struct {
|
||||||
type Service interface {
|
|
||||||
CreateProject(ctx context.Context, name string) (Project, error)
|
|
||||||
OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error)
|
|
||||||
CloseProject() error
|
|
||||||
DeleteProject(ctx context.Context, projectID ulid.ULID) error
|
|
||||||
ActiveProject(ctx context.Context) (Project, error)
|
|
||||||
IsProjectActive(projectID ulid.ULID) bool
|
|
||||||
Projects(ctx context.Context) ([]Project, error)
|
|
||||||
Scope() *scope.Scope
|
|
||||||
SetScopeRules(ctx context.Context, rules []scope.Rule) error
|
|
||||||
SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error
|
|
||||||
SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error
|
|
||||||
UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
repo Repository
|
repo Repository
|
||||||
interceptSvc *intercept.Service
|
interceptSvc *intercept.Service
|
||||||
reqLogSvc reqlog.Service
|
reqLogSvc *reqlog.Service
|
||||||
senderSvc sender.Service
|
senderSvc *sender.Service
|
||||||
scope *scope.Scope
|
scope *scope.Scope
|
||||||
activeProjectID ulid.ULID
|
activeProjectID ulid.ULID
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -59,17 +43,17 @@ type Settings struct {
|
|||||||
// Request log settings
|
// Request log settings
|
||||||
ReqLogBypassOutOfScope bool
|
ReqLogBypassOutOfScope bool
|
||||||
ReqLogOnlyFindInScope bool
|
ReqLogOnlyFindInScope bool
|
||||||
ReqLogSearchExpr search.Expression
|
ReqLogSearchExpr filter.Expression
|
||||||
|
|
||||||
// Intercept settings
|
// Intercept settings
|
||||||
InterceptRequests bool
|
InterceptRequests bool
|
||||||
InterceptResponses bool
|
InterceptResponses bool
|
||||||
InterceptRequestFilter search.Expression
|
InterceptRequestFilter filter.Expression
|
||||||
InterceptResponseFilter search.Expression
|
InterceptResponseFilter filter.Expression
|
||||||
|
|
||||||
// Sender settings
|
// Sender settings
|
||||||
SenderOnlyFindInScope bool
|
SenderOnlyFindInScope bool
|
||||||
SenderSearchExpr search.Expression
|
SenderSearchExpr filter.Expression
|
||||||
|
|
||||||
// Scope settings
|
// Scope settings
|
||||||
ScopeRules []scope.Rule
|
ScopeRules []scope.Rule
|
||||||
@ -87,14 +71,14 @@ var nameRegexp = regexp.MustCompile(`^[\w\d\s]+$`)
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Repository Repository
|
Repository Repository
|
||||||
InterceptService *intercept.Service
|
InterceptService *intercept.Service
|
||||||
ReqLogService reqlog.Service
|
ReqLogService *reqlog.Service
|
||||||
SenderService sender.Service
|
SenderService *sender.Service
|
||||||
Scope *scope.Scope
|
Scope *scope.Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new Service.
|
// NewService returns a new Service.
|
||||||
func NewService(cfg Config) (Service, error) {
|
func NewService(cfg Config) (*Service, error) {
|
||||||
return &service{
|
return &Service{
|
||||||
repo: cfg.Repository,
|
repo: cfg.Repository,
|
||||||
interceptSvc: cfg.InterceptService,
|
interceptSvc: cfg.InterceptService,
|
||||||
reqLogSvc: cfg.ReqLogService,
|
reqLogSvc: cfg.ReqLogService,
|
||||||
@ -103,7 +87,7 @@ func NewService(cfg Config) (Service, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) CreateProject(ctx context.Context, name string) (Project, error) {
|
func (svc *Service) CreateProject(ctx context.Context, name string) (Project, error) {
|
||||||
if !nameRegexp.MatchString(name) {
|
if !nameRegexp.MatchString(name) {
|
||||||
return Project{}, ErrInvalidName
|
return Project{}, ErrInvalidName
|
||||||
}
|
}
|
||||||
@ -122,7 +106,7 @@ func (svc *service) CreateProject(ctx context.Context, name string) (Project, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CloseProject closes the currently open project (if there is one).
|
// CloseProject closes the currently open project (if there is one).
|
||||||
func (svc *service) CloseProject() error {
|
func (svc *Service) CloseProject() error {
|
||||||
svc.mu.Lock()
|
svc.mu.Lock()
|
||||||
defer svc.mu.Unlock()
|
defer svc.mu.Unlock()
|
||||||
|
|
||||||
@ -148,7 +132,7 @@ func (svc *service) CloseProject() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteProject removes a project from the repository.
|
// DeleteProject removes a project from the repository.
|
||||||
func (svc *service) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
|
func (svc *Service) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
|
||||||
if svc.activeProjectID.Compare(projectID) == 0 {
|
if svc.activeProjectID.Compare(projectID) == 0 {
|
||||||
return fmt.Errorf("proj: project (%v) is active", projectID.String())
|
return fmt.Errorf("proj: project (%v) is active", projectID.String())
|
||||||
}
|
}
|
||||||
@ -161,7 +145,7 @@ func (svc *service) DeleteProject(ctx context.Context, projectID ulid.ULID) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OpenProject sets a project as the currently active project.
|
// 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, projectID ulid.ULID) (Project, error) {
|
||||||
svc.mu.Lock()
|
svc.mu.Lock()
|
||||||
defer svc.mu.Unlock()
|
defer svc.mu.Unlock()
|
||||||
|
|
||||||
@ -203,7 +187,7 @@ func (svc *service) OpenProject(ctx context.Context, projectID ulid.ULID) (Proje
|
|||||||
return project, nil
|
return project, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) ActiveProject(ctx context.Context) (Project, error) {
|
func (svc *Service) ActiveProject(ctx context.Context) (Project, error) {
|
||||||
activeProjectID := svc.activeProjectID
|
activeProjectID := svc.activeProjectID
|
||||||
if activeProjectID.Compare(ulid.ULID{}) == 0 {
|
if activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||||
return Project{}, ErrNoProject
|
return Project{}, ErrNoProject
|
||||||
@ -219,7 +203,7 @@ func (svc *service) ActiveProject(ctx context.Context) (Project, error) {
|
|||||||
return project, nil
|
return project, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) Projects(ctx context.Context) ([]Project, error) {
|
func (svc *Service) Projects(ctx context.Context) ([]Project, error) {
|
||||||
projects, err := svc.repo.Projects(ctx)
|
projects, err := svc.repo.Projects(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("proj: could not get projects: %w", err)
|
return nil, fmt.Errorf("proj: could not get projects: %w", err)
|
||||||
@ -228,11 +212,11 @@ func (svc *service) Projects(ctx context.Context) ([]Project, error) {
|
|||||||
return projects, nil
|
return projects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) Scope() *scope.Scope {
|
func (svc *Service) Scope() *scope.Scope {
|
||||||
return svc.scope
|
return svc.scope
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetScopeRules(ctx context.Context, rules []scope.Rule) error {
|
func (svc *Service) SetScopeRules(ctx context.Context, rules []scope.Rule) error {
|
||||||
project, err := svc.ActiveProject(ctx)
|
project, err := svc.ActiveProject(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -250,7 +234,7 @@ func (svc *service) SetScopeRules(ctx context.Context, rules []scope.Rule) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error {
|
func (svc *Service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error {
|
||||||
project, err := svc.ActiveProject(ctx)
|
project, err := svc.ActiveProject(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -271,7 +255,7 @@ func (svc *service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.F
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error {
|
func (svc *Service) SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error {
|
||||||
project, err := svc.ActiveProject(ctx)
|
project, err := svc.ActiveProject(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -292,11 +276,11 @@ func (svc *service) SetSenderRequestFindFilter(ctx context.Context, filter sende
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) IsProjectActive(projectID ulid.ULID) bool {
|
func (svc *Service) IsProjectActive(projectID ulid.ULID) bool {
|
||||||
return projectID.Compare(svc.activeProjectID) == 0
|
return projectID.Compare(svc.activeProjectID) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error {
|
func (svc *Service) UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error {
|
||||||
project, err := svc.ActiveProject(ctx)
|
project, err := svc.ActiveProject(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:unparam
|
//nolint:unparam
|
||||||
@ -68,22 +68,22 @@ var resFilterKeyFns = map[string]func(res *http.Response) (string, error){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MatchRequestFilter returns true if an HTTP request matches the request filter expression.
|
// MatchRequestFilter returns true if an HTTP request matches the request filter expression.
|
||||||
func MatchRequestFilter(req *http.Request, expr search.Expression) (bool, error) {
|
func MatchRequestFilter(req *http.Request, expr filter.Expression) (bool, error) {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case search.PrefixExpression:
|
case filter.PrefixExpression:
|
||||||
return matchReqPrefixExpr(req, e)
|
return matchReqPrefixExpr(req, e)
|
||||||
case search.InfixExpression:
|
case filter.InfixExpression:
|
||||||
return matchReqInfixExpr(req, e)
|
return matchReqInfixExpr(req, e)
|
||||||
case search.StringLiteral:
|
case filter.StringLiteral:
|
||||||
return matchReqStringLiteral(req, e)
|
return matchReqStringLiteral(req, e)
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchReqPrefixExpr(req *http.Request, expr search.PrefixExpression) (bool, error) {
|
func matchReqPrefixExpr(req *http.Request, expr filter.PrefixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpNot:
|
case filter.TokOpNot:
|
||||||
match, err := MatchRequestFilter(req, expr.Right)
|
match, err := MatchRequestFilter(req, expr.Right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -95,9 +95,9 @@ func matchReqPrefixExpr(req *http.Request, expr search.PrefixExpression) (bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, error) {
|
func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpAnd:
|
case filter.TokOpAnd:
|
||||||
left, err := MatchRequestFilter(req, expr.Left)
|
left, err := MatchRequestFilter(req, expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -109,7 +109,7 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
return left && right, nil
|
return left && right, nil
|
||||||
case search.TokOpOr:
|
case filter.TokOpOr:
|
||||||
left, err := MatchRequestFilter(req, expr.Left)
|
left, err := MatchRequestFilter(req, expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -123,7 +123,7 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
|
|||||||
return left || right, nil
|
return left || right, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
left, ok := expr.Left.(search.StringLiteral)
|
left, ok := expr.Left.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("left operand must be a string literal")
|
return false, errors.New("left operand must be a string literal")
|
||||||
}
|
}
|
||||||
@ -133,21 +133,30 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
|
|||||||
return false, fmt.Errorf("failed to get string literal from request for left operand: %w", err)
|
return false, fmt.Errorf("failed to get string literal from request for left operand: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
if leftVal == "headers" {
|
||||||
right, ok := expr.Right.(search.RegexpLiteral)
|
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||||
|
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a regular expression")
|
return false, errors.New("right operand must be a regular expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpRe:
|
case filter.TokOpRe:
|
||||||
return right.MatchString(leftVal), nil
|
return right.MatchString(leftVal), nil
|
||||||
case search.TokOpNotRe:
|
case filter.TokOpNotRe:
|
||||||
return !right.MatchString(leftVal), nil
|
return !right.MatchString(leftVal), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
right, ok := expr.Right.(search.StringLiteral)
|
right, ok := expr.Right.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a string literal")
|
return false, errors.New("right operand must be a string literal")
|
||||||
}
|
}
|
||||||
@ -158,20 +167,20 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpEq:
|
case filter.TokOpEq:
|
||||||
return leftVal == rightVal, nil
|
return leftVal == rightVal, nil
|
||||||
case search.TokOpNotEq:
|
case filter.TokOpNotEq:
|
||||||
return leftVal != rightVal, nil
|
return leftVal != rightVal, nil
|
||||||
case search.TokOpGt:
|
case filter.TokOpGt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal > rightVal, nil
|
return leftVal > rightVal, nil
|
||||||
case search.TokOpLt:
|
case filter.TokOpLt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal < rightVal, nil
|
return leftVal < rightVal, nil
|
||||||
case search.TokOpGtEq:
|
case filter.TokOpGtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal >= rightVal, nil
|
return leftVal >= rightVal, nil
|
||||||
case search.TokOpLtEq:
|
case filter.TokOpLtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal <= rightVal, nil
|
return leftVal <= rightVal, nil
|
||||||
default:
|
default:
|
||||||
@ -188,7 +197,18 @@ func getMappedStringLiteralFromReq(req *http.Request, s string) (string, error)
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchReqStringLiteral(req *http.Request, strLiteral search.StringLiteral) (bool, error) {
|
func matchReqStringLiteral(req *http.Request, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, fn := range reqFilterKeyFns {
|
for _, fn := range reqFilterKeyFns {
|
||||||
value, err := fn(req)
|
value, err := fn(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -259,22 +279,22 @@ func MatchRequestScope(req *http.Request, s *scope.Scope) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MatchResponseFilter returns true if an HTTP response matches the response filter expression.
|
// MatchResponseFilter returns true if an HTTP response matches the response filter expression.
|
||||||
func MatchResponseFilter(res *http.Response, expr search.Expression) (bool, error) {
|
func MatchResponseFilter(res *http.Response, expr filter.Expression) (bool, error) {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case search.PrefixExpression:
|
case filter.PrefixExpression:
|
||||||
return matchResPrefixExpr(res, e)
|
return matchResPrefixExpr(res, e)
|
||||||
case search.InfixExpression:
|
case filter.InfixExpression:
|
||||||
return matchResInfixExpr(res, e)
|
return matchResInfixExpr(res, e)
|
||||||
case search.StringLiteral:
|
case filter.StringLiteral:
|
||||||
return matchResStringLiteral(res, e)
|
return matchResStringLiteral(res, e)
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchResPrefixExpr(res *http.Response, expr search.PrefixExpression) (bool, error) {
|
func matchResPrefixExpr(res *http.Response, expr filter.PrefixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpNot:
|
case filter.TokOpNot:
|
||||||
match, err := MatchResponseFilter(res, expr.Right)
|
match, err := MatchResponseFilter(res, expr.Right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -286,9 +306,9 @@ func matchResPrefixExpr(res *http.Response, expr search.PrefixExpression) (bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, error) {
|
func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpAnd:
|
case filter.TokOpAnd:
|
||||||
left, err := MatchResponseFilter(res, expr.Left)
|
left, err := MatchResponseFilter(res, expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -300,7 +320,7 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
return left && right, nil
|
return left && right, nil
|
||||||
case search.TokOpOr:
|
case filter.TokOpOr:
|
||||||
left, err := MatchResponseFilter(res, expr.Left)
|
left, err := MatchResponseFilter(res, expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -314,7 +334,7 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
|
|||||||
return left || right, nil
|
return left || right, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
left, ok := expr.Left.(search.StringLiteral)
|
left, ok := expr.Left.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("left operand must be a string literal")
|
return false, errors.New("left operand must be a string literal")
|
||||||
}
|
}
|
||||||
@ -324,21 +344,30 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
|
|||||||
return false, fmt.Errorf("failed to get string literal from response for left operand: %w", err)
|
return false, fmt.Errorf("failed to get string literal from response for left operand: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
if leftVal == "headers" {
|
||||||
right, ok := expr.Right.(search.RegexpLiteral)
|
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, res.Header)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||||
|
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a regular expression")
|
return false, errors.New("right operand must be a regular expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpRe:
|
case filter.TokOpRe:
|
||||||
return right.MatchString(leftVal), nil
|
return right.MatchString(leftVal), nil
|
||||||
case search.TokOpNotRe:
|
case filter.TokOpNotRe:
|
||||||
return !right.MatchString(leftVal), nil
|
return !right.MatchString(leftVal), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
right, ok := expr.Right.(search.StringLiteral)
|
right, ok := expr.Right.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a string literal")
|
return false, errors.New("right operand must be a string literal")
|
||||||
}
|
}
|
||||||
@ -349,20 +378,20 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpEq:
|
case filter.TokOpEq:
|
||||||
return leftVal == rightVal, nil
|
return leftVal == rightVal, nil
|
||||||
case search.TokOpNotEq:
|
case filter.TokOpNotEq:
|
||||||
return leftVal != rightVal, nil
|
return leftVal != rightVal, nil
|
||||||
case search.TokOpGt:
|
case filter.TokOpGt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal > rightVal, nil
|
return leftVal > rightVal, nil
|
||||||
case search.TokOpLt:
|
case filter.TokOpLt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal < rightVal, nil
|
return leftVal < rightVal, nil
|
||||||
case search.TokOpGtEq:
|
case filter.TokOpGtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal >= rightVal, nil
|
return leftVal >= rightVal, nil
|
||||||
case search.TokOpLtEq:
|
case filter.TokOpLtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal <= rightVal, nil
|
return leftVal <= rightVal, nil
|
||||||
default:
|
default:
|
||||||
@ -379,7 +408,18 @@ func getMappedStringLiteralFromRes(res *http.Response, s string) (string, error)
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchResStringLiteral(res *http.Response, strLiteral search.StringLiteral) (bool, error) {
|
func matchResStringLiteral(res *http.Response, strLiteral filter.StringLiteral) (bool, error) {
|
||||||
|
for key, values := range res.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
if strings.Contains(
|
||||||
|
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||||
|
strings.ToLower(strLiteral.Value),
|
||||||
|
) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, fn := range resFilterKeyFns {
|
for _, fn := range resFilterKeyFns {
|
||||||
value, err := fn(res)
|
value, err := fn(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/log"
|
"github.com/dstotijn/hetty/pkg/log"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy"
|
"github.com/dstotijn/hetty/pkg/proxy"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -56,16 +56,16 @@ type Service struct {
|
|||||||
|
|
||||||
requestsEnabled bool
|
requestsEnabled bool
|
||||||
responsesEnabled bool
|
responsesEnabled bool
|
||||||
reqFilter search.Expression
|
reqFilter filter.Expression
|
||||||
resFilter search.Expression
|
resFilter filter.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Logger log.Logger
|
Logger log.Logger
|
||||||
RequestsEnabled bool
|
RequestsEnabled bool
|
||||||
ResponsesEnabled bool
|
ResponsesEnabled bool
|
||||||
RequestFilter search.Expression
|
RequestFilter filter.Expression
|
||||||
ResponseFilter search.Expression
|
ResponseFilter filter.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestIDs implements sort.Interface.
|
// RequestIDs implements sort.Interface.
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package intercept
|
package intercept
|
||||||
|
|
||||||
import "github.com/dstotijn/hetty/pkg/search"
|
import "github.com/dstotijn/hetty/pkg/filter"
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
RequestsEnabled bool
|
RequestsEnabled bool
|
||||||
ResponsesEnabled bool
|
ResponsesEnabled bool
|
||||||
RequestFilter search.Expression
|
RequestFilter filter.Expression
|
||||||
ResponseFilter search.Expression
|
ResponseFilter filter.Expression
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error)
|
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error)
|
||||||
FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error)
|
FindRequestLogByID(ctx context.Context, projectID, id ulid.ULID) (RequestLog, error)
|
||||||
StoreRequestLog(ctx context.Context, reqLog RequestLog) error
|
StoreRequestLog(ctx context.Context, reqLog RequestLog) error
|
||||||
StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog ResponseLog) error
|
StoreResponseLog(ctx context.Context, projectID, reqLogID ulid.ULID, resLog ResponseLog) error
|
||||||
ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error
|
ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error
|
||||||
}
|
}
|
||||||
|
@ -1,291 +0,0 @@
|
|||||||
// Code generated by moq; DO NOT EDIT.
|
|
||||||
// github.com/matryer/moq
|
|
||||||
|
|
||||||
package reqlog_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure, that RepoMock does implement reqlog.Repository.
|
|
||||||
// If this is not the case, regenerate this file with moq.
|
|
||||||
var _ reqlog.Repository = &RepoMock{}
|
|
||||||
|
|
||||||
// RepoMock is a mock implementation of reqlog.Repository.
|
|
||||||
//
|
|
||||||
// func TestSomethingThatUsesRepository(t *testing.T) {
|
|
||||||
//
|
|
||||||
// // make and configure a mocked reqlog.Repository
|
|
||||||
// mockedRepository := &RepoMock{
|
|
||||||
// ClearRequestLogsFunc: func(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
// panic("mock out the ClearRequestLogs method")
|
|
||||||
// },
|
|
||||||
// FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
|
||||||
// panic("mock out the FindRequestLogByID method")
|
|
||||||
// },
|
|
||||||
// FindRequestLogsFunc: func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error) {
|
|
||||||
// panic("mock out the FindRequestLogs method")
|
|
||||||
// },
|
|
||||||
// StoreRequestLogFunc: func(ctx context.Context, reqLog reqlog.RequestLog) error {
|
|
||||||
// panic("mock out the StoreRequestLog method")
|
|
||||||
// },
|
|
||||||
// StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
|
||||||
// panic("mock out the StoreResponseLog method")
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // use mockedRepository in code that requires reqlog.Repository
|
|
||||||
// // and then make assertions.
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
type RepoMock struct {
|
|
||||||
// ClearRequestLogsFunc mocks the ClearRequestLogs method.
|
|
||||||
ClearRequestLogsFunc func(ctx context.Context, projectID ulid.ULID) error
|
|
||||||
|
|
||||||
// FindRequestLogByIDFunc mocks the FindRequestLogByID method.
|
|
||||||
FindRequestLogByIDFunc func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error)
|
|
||||||
|
|
||||||
// FindRequestLogsFunc mocks the FindRequestLogs method.
|
|
||||||
FindRequestLogsFunc func(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error)
|
|
||||||
|
|
||||||
// StoreRequestLogFunc mocks the StoreRequestLog method.
|
|
||||||
StoreRequestLogFunc func(ctx context.Context, reqLog reqlog.RequestLog) error
|
|
||||||
|
|
||||||
// StoreResponseLogFunc mocks the StoreResponseLog method.
|
|
||||||
StoreResponseLogFunc func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
|
|
||||||
|
|
||||||
// calls tracks calls to the methods.
|
|
||||||
calls struct {
|
|
||||||
// ClearRequestLogs holds details about calls to the ClearRequestLogs method.
|
|
||||||
ClearRequestLogs []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ProjectID is the projectID argument value.
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}
|
|
||||||
// FindRequestLogByID holds details about calls to the FindRequestLogByID method.
|
|
||||||
FindRequestLogByID []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ID is the id argument value.
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
// FindRequestLogs holds details about calls to the FindRequestLogs method.
|
|
||||||
FindRequestLogs []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// Filter is the filter argument value.
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
// ScopeMoqParam is the scopeMoqParam argument value.
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
}
|
|
||||||
// StoreRequestLog holds details about calls to the StoreRequestLog method.
|
|
||||||
StoreRequestLog []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ReqLog is the reqLog argument value.
|
|
||||||
ReqLog reqlog.RequestLog
|
|
||||||
}
|
|
||||||
// StoreResponseLog holds details about calls to the StoreResponseLog method.
|
|
||||||
StoreResponseLog []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ReqLogID is the reqLogID argument value.
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
// ResLog is the resLog argument value.
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lockClearRequestLogs sync.RWMutex
|
|
||||||
lockFindRequestLogByID sync.RWMutex
|
|
||||||
lockFindRequestLogs sync.RWMutex
|
|
||||||
lockStoreRequestLog sync.RWMutex
|
|
||||||
lockStoreResponseLog sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearRequestLogs calls ClearRequestLogsFunc.
|
|
||||||
func (mock *RepoMock) ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
if mock.ClearRequestLogsFunc == nil {
|
|
||||||
panic("RepoMock.ClearRequestLogsFunc: method is nil but Repository.ClearRequestLogs was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ProjectID: projectID,
|
|
||||||
}
|
|
||||||
mock.lockClearRequestLogs.Lock()
|
|
||||||
mock.calls.ClearRequestLogs = append(mock.calls.ClearRequestLogs, callInfo)
|
|
||||||
mock.lockClearRequestLogs.Unlock()
|
|
||||||
return mock.ClearRequestLogsFunc(ctx, projectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearRequestLogsCalls gets all the calls that were made to ClearRequestLogs.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.ClearRequestLogsCalls())
|
|
||||||
func (mock *RepoMock) ClearRequestLogsCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockClearRequestLogs.RLock()
|
|
||||||
calls = mock.calls.ClearRequestLogs
|
|
||||||
mock.lockClearRequestLogs.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestLogByID calls FindRequestLogByIDFunc.
|
|
||||||
func (mock *RepoMock) FindRequestLogByID(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
|
||||||
if mock.FindRequestLogByIDFunc == nil {
|
|
||||||
panic("RepoMock.FindRequestLogByIDFunc: method is nil but Repository.FindRequestLogByID was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
mock.lockFindRequestLogByID.Lock()
|
|
||||||
mock.calls.FindRequestLogByID = append(mock.calls.FindRequestLogByID, callInfo)
|
|
||||||
mock.lockFindRequestLogByID.Unlock()
|
|
||||||
return mock.FindRequestLogByIDFunc(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestLogByIDCalls gets all the calls that were made to FindRequestLogByID.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.FindRequestLogByIDCalls())
|
|
||||||
func (mock *RepoMock) FindRequestLogByIDCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockFindRequestLogByID.RLock()
|
|
||||||
calls = mock.calls.FindRequestLogByID
|
|
||||||
mock.lockFindRequestLogByID.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestLogs calls FindRequestLogsFunc.
|
|
||||||
func (mock *RepoMock) FindRequestLogs(ctx context.Context, filter reqlog.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]reqlog.RequestLog, error) {
|
|
||||||
if mock.FindRequestLogsFunc == nil {
|
|
||||||
panic("RepoMock.FindRequestLogsFunc: method is nil but Repository.FindRequestLogs was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
Filter: filter,
|
|
||||||
ScopeMoqParam: scopeMoqParam,
|
|
||||||
}
|
|
||||||
mock.lockFindRequestLogs.Lock()
|
|
||||||
mock.calls.FindRequestLogs = append(mock.calls.FindRequestLogs, callInfo)
|
|
||||||
mock.lockFindRequestLogs.Unlock()
|
|
||||||
return mock.FindRequestLogsFunc(ctx, filter, scopeMoqParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestLogsCalls gets all the calls that were made to FindRequestLogs.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.FindRequestLogsCalls())
|
|
||||||
func (mock *RepoMock) FindRequestLogsCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
}
|
|
||||||
mock.lockFindRequestLogs.RLock()
|
|
||||||
calls = mock.calls.FindRequestLogs
|
|
||||||
mock.lockFindRequestLogs.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreRequestLog calls StoreRequestLogFunc.
|
|
||||||
func (mock *RepoMock) StoreRequestLog(ctx context.Context, reqLog reqlog.RequestLog) error {
|
|
||||||
if mock.StoreRequestLogFunc == nil {
|
|
||||||
panic("RepoMock.StoreRequestLogFunc: method is nil but Repository.StoreRequestLog was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLog reqlog.RequestLog
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ReqLog: reqLog,
|
|
||||||
}
|
|
||||||
mock.lockStoreRequestLog.Lock()
|
|
||||||
mock.calls.StoreRequestLog = append(mock.calls.StoreRequestLog, callInfo)
|
|
||||||
mock.lockStoreRequestLog.Unlock()
|
|
||||||
return mock.StoreRequestLogFunc(ctx, reqLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreRequestLogCalls gets all the calls that were made to StoreRequestLog.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.StoreRequestLogCalls())
|
|
||||||
func (mock *RepoMock) StoreRequestLogCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLog reqlog.RequestLog
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLog reqlog.RequestLog
|
|
||||||
}
|
|
||||||
mock.lockStoreRequestLog.RLock()
|
|
||||||
calls = mock.calls.StoreRequestLog
|
|
||||||
mock.lockStoreRequestLog.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResponseLog calls StoreResponseLogFunc.
|
|
||||||
func (mock *RepoMock) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
|
||||||
if mock.StoreResponseLogFunc == nil {
|
|
||||||
panic("RepoMock.StoreResponseLogFunc: method is nil but Repository.StoreResponseLog was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ReqLogID: reqLogID,
|
|
||||||
ResLog: resLog,
|
|
||||||
}
|
|
||||||
mock.lockStoreResponseLog.Lock()
|
|
||||||
mock.calls.StoreResponseLog = append(mock.calls.StoreResponseLog, callInfo)
|
|
||||||
mock.lockStoreResponseLog.Unlock()
|
|
||||||
return mock.StoreResponseLogFunc(ctx, reqLogID, resLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResponseLogCalls gets all the calls that were made to StoreResponseLog.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.StoreResponseLogCalls())
|
|
||||||
func (mock *RepoMock) StoreResponseLogCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
}
|
|
||||||
mock.lockStoreResponseLog.RLock()
|
|
||||||
calls = mock.calls.StoreResponseLog
|
|
||||||
mock.lockStoreResponseLog.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
@ -12,10 +12,10 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/log"
|
"github.com/dstotijn/hetty/pkg/log"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy"
|
"github.com/dstotijn/hetty/pkg/proxy"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey int
|
type contextKey int
|
||||||
@ -51,21 +51,7 @@ type ResponseLog struct {
|
|||||||
Body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service interface {
|
type Service struct {
|
||||||
FindRequests(ctx context.Context) ([]RequestLog, error)
|
|
||||||
FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error)
|
|
||||||
ClearRequests(ctx context.Context, projectID ulid.ULID) error
|
|
||||||
RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc
|
|
||||||
ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc
|
|
||||||
SetActiveProjectID(id ulid.ULID)
|
|
||||||
ActiveProjectID() ulid.ULID
|
|
||||||
SetBypassOutOfScopeRequests(bool)
|
|
||||||
BypassOutOfScopeRequests() bool
|
|
||||||
SetFindReqsFilter(filter FindRequestsFilter)
|
|
||||||
FindReqsFilter() FindRequestsFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
bypassOutOfScopeRequests bool
|
bypassOutOfScopeRequests bool
|
||||||
findReqsFilter FindRequestsFilter
|
findReqsFilter FindRequestsFilter
|
||||||
activeProjectID ulid.ULID
|
activeProjectID ulid.ULID
|
||||||
@ -77,20 +63,22 @@ type service struct {
|
|||||||
type FindRequestsFilter struct {
|
type FindRequestsFilter struct {
|
||||||
ProjectID ulid.ULID
|
ProjectID ulid.ULID
|
||||||
OnlyInScope bool
|
OnlyInScope bool
|
||||||
SearchExpr search.Expression
|
SearchExpr filter.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Scope *scope.Scope
|
ActiveProjectID ulid.ULID
|
||||||
Repository Repository
|
Scope *scope.Scope
|
||||||
Logger log.Logger
|
Repository Repository
|
||||||
|
Logger log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg Config) Service {
|
func NewService(cfg Config) *Service {
|
||||||
s := &service{
|
s := &Service{
|
||||||
repo: cfg.Repository,
|
activeProjectID: cfg.ActiveProjectID,
|
||||||
scope: cfg.Scope,
|
repo: cfg.Repository,
|
||||||
logger: cfg.Logger,
|
scope: cfg.Scope,
|
||||||
|
logger: cfg.Logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.logger == nil {
|
if s.logger == nil {
|
||||||
@ -100,28 +88,28 @@ func NewService(cfg Config) Service {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) FindRequests(ctx context.Context) ([]RequestLog, error) {
|
func (svc *Service) FindRequests(ctx context.Context) ([]RequestLog, error) {
|
||||||
return svc.repo.FindRequestLogs(ctx, svc.findReqsFilter, svc.scope)
|
return svc.repo.FindRequestLogs(ctx, svc.findReqsFilter, svc.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) {
|
func (svc *Service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) {
|
||||||
return svc.repo.FindRequestLogByID(ctx, id)
|
return svc.repo.FindRequestLogByID(ctx, svc.activeProjectID, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
func (svc *Service) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||||
return svc.repo.ClearRequestLogs(ctx, projectID)
|
return svc.repo.ClearRequestLogs(ctx, projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error {
|
func (svc *Service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error {
|
||||||
resLog, err := ParseHTTPResponse(res)
|
resLog, err := ParseHTTPResponse(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.repo.StoreResponseLog(ctx, reqLogID, resLog)
|
return svc.repo.StoreResponseLog(ctx, svc.activeProjectID, reqLogID, resLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
||||||
return func(req *http.Request) {
|
return func(req *http.Request) {
|
||||||
next(req)
|
next(req)
|
||||||
|
|
||||||
@ -199,7 +187,7 @@ func (svc *service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
||||||
return func(res *http.Response) error {
|
return func(res *http.Response) error {
|
||||||
if err := next(res); err != nil {
|
if err := next(res); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -241,27 +229,27 @@ func (svc *service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetActiveProjectID(id ulid.ULID) {
|
func (svc *Service) SetActiveProjectID(id ulid.ULID) {
|
||||||
svc.activeProjectID = id
|
svc.activeProjectID = id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) ActiveProjectID() ulid.ULID {
|
func (svc *Service) ActiveProjectID() ulid.ULID {
|
||||||
return svc.activeProjectID
|
return svc.activeProjectID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetFindReqsFilter(filter FindRequestsFilter) {
|
func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
|
||||||
svc.findReqsFilter = filter
|
svc.findReqsFilter = filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) FindReqsFilter() FindRequestsFilter {
|
func (svc *Service) FindReqsFilter() FindRequestsFilter {
|
||||||
return svc.findReqsFilter
|
return svc.findReqsFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetBypassOutOfScopeRequests(bypass bool) {
|
func (svc *Service) SetBypassOutOfScopeRequests(bypass bool) {
|
||||||
svc.bypassOutOfScopeRequests = bypass
|
svc.bypassOutOfScopeRequests = bypass
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) BypassOutOfScopeRequests() bool {
|
func (svc *Service) BypassOutOfScopeRequests() bool {
|
||||||
return svc.bypassOutOfScopeRequests
|
return svc.bypassOutOfScopeRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package reqlog_test
|
package reqlog_test
|
||||||
|
|
||||||
//go:generate go run github.com/matryer/moq -out repo_mock_test.go -pkg reqlog_test . Repository:RepoMock
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
@ -14,7 +12,10 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||||
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
"github.com/dstotijn/hetty/pkg/proxy"
|
"github.com/dstotijn/hetty/pkg/proxy"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
@ -25,16 +26,32 @@ var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|||||||
|
|
||||||
//nolint:paralleltest
|
//nolint:paralleltest
|
||||||
func TestRequestModifier(t *testing.T) {
|
func TestRequestModifier(t *testing.T) {
|
||||||
repoMock := &RepoMock{
|
path := t.TempDir() + "bolt.db"
|
||||||
StoreRequestLogFunc: func(_ context.Context, _ reqlog.RequestLog) error {
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
return 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()
|
||||||
|
|
||||||
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error upserting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
svc := reqlog.NewService(reqlog.Config{
|
svc := reqlog.NewService(reqlog.Config{
|
||||||
Repository: repoMock,
|
Repository: db,
|
||||||
Scope: &scope.Scope{},
|
Scope: &scope.Scope{},
|
||||||
})
|
})
|
||||||
svc.SetActiveProjectID(ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy))
|
svc.SetActiveProjectID(projectID)
|
||||||
|
|
||||||
next := func(req *http.Request) {
|
next := func(req *http.Request) {
|
||||||
req.Body = io.NopCloser(strings.NewReader("modified body"))
|
req.Body = io.NopCloser(strings.NewReader("modified body"))
|
||||||
@ -47,13 +64,8 @@ func TestRequestModifier(t *testing.T) {
|
|||||||
reqModFn(req)
|
reqModFn(req)
|
||||||
|
|
||||||
t.Run("request log was stored in repository", func(t *testing.T) {
|
t.Run("request log was stored in repository", func(t *testing.T) {
|
||||||
gotCount := len(repoMock.StoreRequestLogCalls())
|
|
||||||
if expCount := 1; expCount != gotCount {
|
|
||||||
t.Fatalf("incorrect `proj.Service.AddRequestLog` calls (expected: %v, got: %v)", expCount, gotCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := reqlog.RequestLog{
|
exp := reqlog.RequestLog{
|
||||||
ID: ulid.ULID{}, // Empty value
|
ID: reqID,
|
||||||
ProjectID: svc.ActiveProjectID(),
|
ProjectID: svc.ActiveProjectID(),
|
||||||
Method: req.Method,
|
Method: req.Method,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
@ -61,8 +73,11 @@ func TestRequestModifier(t *testing.T) {
|
|||||||
Header: req.Header,
|
Header: req.Header,
|
||||||
Body: []byte("modified body"),
|
Body: []byte("modified body"),
|
||||||
}
|
}
|
||||||
got := repoMock.StoreRequestLogCalls()[0].ReqLog
|
|
||||||
got.ID = ulid.ULID{} // Override to empty value so we can compare against expected value.
|
got, err := svc.FindRequestLogByID(context.Background(), reqID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to find request by id: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if diff := cmp.Diff(exp, got); diff != "" {
|
if diff := cmp.Diff(exp, got); diff != "" {
|
||||||
t.Fatalf("request log not equal (-exp, +got):\n%v", diff)
|
t.Fatalf("request log not equal (-exp, +got):\n%v", diff)
|
||||||
@ -72,15 +87,31 @@ func TestRequestModifier(t *testing.T) {
|
|||||||
|
|
||||||
//nolint:paralleltest
|
//nolint:paralleltest
|
||||||
func TestResponseModifier(t *testing.T) {
|
func TestResponseModifier(t *testing.T) {
|
||||||
repoMock := &RepoMock{
|
path := t.TempDir() + "bolt.db"
|
||||||
StoreResponseLogFunc: func(_ context.Context, _ ulid.ULID, _ reqlog.ResponseLog) error {
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
return nil
|
if err != nil {
|
||||||
},
|
t.Fatalf("failed to open bolt database: %v", err)
|
||||||
}
|
}
|
||||||
svc := reqlog.NewService(reqlog.Config{
|
defer boltDB.Close()
|
||||||
Repository: repoMock,
|
|
||||||
|
db, err := bolt.DatabaseFromBoltDB(boltDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
})
|
})
|
||||||
svc.SetActiveProjectID(ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy))
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error upserting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := reqlog.NewService(reqlog.Config{
|
||||||
|
Repository: db,
|
||||||
|
})
|
||||||
|
svc.SetActiveProjectID(projectID)
|
||||||
|
|
||||||
next := func(res *http.Response) error {
|
next := func(res *http.Response) error {
|
||||||
res.Body = io.NopCloser(strings.NewReader("modified body"))
|
res.Body = io.NopCloser(strings.NewReader("modified body"))
|
||||||
@ -92,6 +123,14 @@ func TestResponseModifier(t *testing.T) {
|
|||||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
req = req.WithContext(context.WithValue(req.Context(), reqlog.ReqLogIDKey, reqLogID))
|
req = req.WithContext(context.WithValue(req.Context(), reqlog.ReqLogIDKey, reqLogID))
|
||||||
|
|
||||||
|
err = db.StoreRequestLog(context.Background(), reqlog.RequestLog{
|
||||||
|
ID: reqLogID,
|
||||||
|
ProjectID: projectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to store request log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
res := &http.Response{
|
res := &http.Response{
|
||||||
Request: req,
|
Request: req,
|
||||||
Body: io.NopCloser(strings.NewReader("bar")),
|
Body: io.NopCloser(strings.NewReader("bar")),
|
||||||
@ -104,23 +143,15 @@ func TestResponseModifier(t *testing.T) {
|
|||||||
t.Run("request log was stored in repository", func(t *testing.T) {
|
t.Run("request log was stored in repository", func(t *testing.T) {
|
||||||
// Dirty (but simple) wait for other goroutine to finish calling repository.
|
// Dirty (but simple) wait for other goroutine to finish calling repository.
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
got := len(repoMock.StoreResponseLogCalls())
|
|
||||||
if exp := 1; exp != got {
|
got, err := svc.FindRequestLogByID(context.Background(), reqLogID)
|
||||||
t.Fatalf("incorrect `proj.Service.AddResponseLog` calls (expected: %v, got: %v)", exp, got)
|
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) {
|
t.Run("ran next modifier first, before calling repository", func(t *testing.T) {
|
||||||
got := repoMock.StoreResponseLogCalls()[0].ResLog.Body
|
if exp := "modified body"; exp != string(got.Response.Body) {
|
||||||
if exp := "modified body"; exp != string(got) {
|
t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got.Response.Body))
|
||||||
t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("called repository with request log id", func(t *testing.T) {
|
|
||||||
got := repoMock.StoreResponseLogCalls()[0].ReqLogID
|
|
||||||
if exp := reqLogID; exp.Compare(got) != 0 {
|
|
||||||
t.Fatalf("incorrect `reqLogID` argument for `Repository.AddResponseLogCalls` (expected: %v, got: %v)",
|
|
||||||
exp.String(), got.String())
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
|
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
|
||||||
@ -36,22 +36,22 @@ var ResLogSearchKeyFns = map[string]func(rl ResponseLog) string{
|
|||||||
// TODO: Request and response headers search key functions.
|
// TODO: Request and response headers search key functions.
|
||||||
|
|
||||||
// Matches returns true if the supplied search expression evaluates to true.
|
// Matches returns true if the supplied search expression evaluates to true.
|
||||||
func (reqLog RequestLog) Matches(expr search.Expression) (bool, error) {
|
func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case search.PrefixExpression:
|
case filter.PrefixExpression:
|
||||||
return reqLog.matchPrefixExpr(e)
|
return reqLog.matchPrefixExpr(e)
|
||||||
case search.InfixExpression:
|
case filter.InfixExpression:
|
||||||
return reqLog.matchInfixExpr(e)
|
return reqLog.matchInfixExpr(e)
|
||||||
case search.StringLiteral:
|
case filter.StringLiteral:
|
||||||
return reqLog.matchStringLiteral(e)
|
return reqLog.matchStringLiteral(e)
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reqLog RequestLog) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpNot:
|
case filter.TokOpNot:
|
||||||
match, err := reqLog.Matches(expr.Right)
|
match, err := reqLog.Matches(expr.Right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -63,9 +63,9 @@ func (reqLog RequestLog) matchPrefixExpr(expr search.PrefixExpression) (bool, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpAnd:
|
case filter.TokOpAnd:
|
||||||
left, err := reqLog.Matches(expr.Left)
|
left, err := reqLog.Matches(expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -77,7 +77,7 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
return left && right, nil
|
return left && right, nil
|
||||||
case search.TokOpOr:
|
case filter.TokOpOr:
|
||||||
left, err := reqLog.Matches(expr.Left)
|
left, err := reqLog.Matches(expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -91,28 +91,46 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
|
|||||||
return left || right, nil
|
return left || right, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
left, ok := expr.Left.(search.StringLiteral)
|
left, ok := expr.Left.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("left operand must be a string literal")
|
return false, errors.New("left operand must be a string literal")
|
||||||
}
|
}
|
||||||
|
|
||||||
leftVal := reqLog.getMappedStringLiteral(left.Value)
|
leftVal := reqLog.getMappedStringLiteral(left.Value)
|
||||||
|
|
||||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
if leftVal == "req.headers" {
|
||||||
right, ok := expr.Right.(search.RegexpLiteral)
|
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftVal == "res.headers" && reqLog.Response != nil {
|
||||||
|
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||||
|
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a regular expression")
|
return false, errors.New("right operand must be a regular expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpRe:
|
case filter.TokOpRe:
|
||||||
return right.MatchString(leftVal), nil
|
return right.MatchString(leftVal), nil
|
||||||
case search.TokOpNotRe:
|
case filter.TokOpNotRe:
|
||||||
return !right.MatchString(leftVal), nil
|
return !right.MatchString(leftVal), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
right, ok := expr.Right.(search.StringLiteral)
|
right, ok := expr.Right.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a string literal")
|
return false, errors.New("right operand must be a string literal")
|
||||||
}
|
}
|
||||||
@ -120,20 +138,20 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
|
|||||||
rightVal := reqLog.getMappedStringLiteral(right.Value)
|
rightVal := reqLog.getMappedStringLiteral(right.Value)
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpEq:
|
case filter.TokOpEq:
|
||||||
return leftVal == rightVal, nil
|
return leftVal == rightVal, nil
|
||||||
case search.TokOpNotEq:
|
case filter.TokOpNotEq:
|
||||||
return leftVal != rightVal, nil
|
return leftVal != rightVal, nil
|
||||||
case search.TokOpGt:
|
case filter.TokOpGt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal > rightVal, nil
|
return leftVal > rightVal, nil
|
||||||
case search.TokOpLt:
|
case filter.TokOpLt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal < rightVal, nil
|
return leftVal < rightVal, nil
|
||||||
case search.TokOpGtEq:
|
case filter.TokOpGtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal >= rightVal, nil
|
return leftVal >= rightVal, nil
|
||||||
case search.TokOpLtEq:
|
case filter.TokOpLtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal <= rightVal, nil
|
return leftVal <= rightVal, nil
|
||||||
default:
|
default:
|
||||||
@ -162,7 +180,18 @@ func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reqLog RequestLog) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, fn := range reqLogSearchKeyFns {
|
for _, fn := range reqLogSearchKeyFns {
|
||||||
if strings.Contains(
|
if strings.Contains(
|
||||||
strings.ToLower(fn(reqLog)),
|
strings.ToLower(fn(reqLog)),
|
||||||
@ -173,6 +202,17 @@ func (reqLog RequestLog) matchStringLiteral(strLiteral search.StringLiteral) (bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqLog.Response != nil {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, fn := range ResLogSearchKeyFns {
|
for _, fn := range ResLogSearchKeyFns {
|
||||||
if strings.Contains(
|
if strings.Contains(
|
||||||
strings.ToLower(fn(*reqLog.Response)),
|
strings.ToLower(fn(*reqLog.Response)),
|
||||||
|
@ -3,8 +3,8 @@ package reqlog_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequestLogMatch(t *testing.T) {
|
func TestRequestLogMatch(t *testing.T) {
|
||||||
@ -176,7 +176,7 @@ func TestRequestLogMatch(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
searchExpr, err := search.ParseQuery(tt.query)
|
searchExpr, err := filter.ParseQuery(tt.query)
|
||||||
assertError(t, nil, err)
|
assertError(t, nil, err)
|
||||||
|
|
||||||
got, err := tt.requestLog.Matches(searchExpr)
|
got, err := tt.requestLog.Matches(searchExpr)
|
||||||
|
@ -1,212 +0,0 @@
|
|||||||
package search_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExpressionString(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expression search.Expression
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "string literal expression",
|
|
||||||
expression: search.StringLiteral{Value: "foobar"},
|
|
||||||
expected: `"foobar"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with equal operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpEq,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" = "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with not equal operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpNotEq,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" != "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with greater than operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpGt,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" > "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with less than operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpLt,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" < "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with greater than or equal operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpGtEq,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" >= "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with less than or equal operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpLtEq,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" <= "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with regular expression operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpRe,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.RegexpLiteral{regexp.MustCompile("bar")},
|
|
||||||
},
|
|
||||||
expected: `("foo" =~ "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with not regular expression operator",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpNotRe,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.RegexpLiteral{regexp.MustCompile("bar")},
|
|
||||||
},
|
|
||||||
expected: `("foo" !~ "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with AND, OR and NOT operators",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.InfixExpression{
|
|
||||||
Operator: search.TokOpOr,
|
|
||||||
Left: search.StringLiteral{Value: "bar"},
|
|
||||||
Right: search.PrefixExpression{
|
|
||||||
Operator: search.TokOpNot,
|
|
||||||
Right: search.StringLiteral{Value: "baz"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `("foo" AND ("bar" OR (NOT "baz")))`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "boolean expression with nested group",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpOr,
|
|
||||||
Left: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
Right: search.PrefixExpression{
|
|
||||||
Operator: search.TokOpNot,
|
|
||||||
Right: search.StringLiteral{Value: "baz"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `(("foo" AND "bar") OR (NOT "baz"))`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "implicit boolean expression with string literal operands",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
Right: search.StringLiteral{Value: "baz"},
|
|
||||||
},
|
|
||||||
expected: `(("foo" AND "bar") AND "baz")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "implicit boolean expression nested in group",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
expected: `("foo" AND "bar")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "implicit and explicit boolean expression with string literal operands",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.InfixExpression{
|
|
||||||
Operator: search.TokOpOr,
|
|
||||||
Left: search.StringLiteral{Value: "bar"},
|
|
||||||
Right: search.StringLiteral{Value: "baz"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Right: search.StringLiteral{Value: "yolo"},
|
|
||||||
},
|
|
||||||
expected: `(("foo" AND ("bar" OR "baz")) AND "yolo")`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "implicit boolean expression with comparison operands",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpAnd,
|
|
||||||
Left: search.InfixExpression{
|
|
||||||
Operator: search.TokOpEq,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
Right: search.InfixExpression{
|
|
||||||
Operator: search.TokOpRe,
|
|
||||||
Left: search.StringLiteral{Value: "baz"},
|
|
||||||
Right: search.RegexpLiteral{regexp.MustCompile("yolo")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `(("foo" = "bar") AND ("baz" =~ "yolo"))`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "eq operator takes precedence over boolean ops",
|
|
||||||
expression: search.InfixExpression{
|
|
||||||
Operator: search.TokOpOr,
|
|
||||||
Left: search.InfixExpression{
|
|
||||||
Operator: search.TokOpEq,
|
|
||||||
Left: search.StringLiteral{Value: "foo"},
|
|
||||||
Right: search.StringLiteral{Value: "bar"},
|
|
||||||
},
|
|
||||||
Right: search.InfixExpression{
|
|
||||||
Operator: search.TokOpEq,
|
|
||||||
Left: search.StringLiteral{Value: "baz"},
|
|
||||||
Right: search.StringLiteral{Value: "yolo"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `(("foo" = "bar") OR ("baz" = "yolo"))`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
got := tt.expression.String()
|
|
||||||
if tt.expected != got {
|
|
||||||
t.Errorf("expected: %v, got: %v", tt.expected, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,14 +5,12 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
FindSenderRequestByID(ctx context.Context, id ulid.ULID) (Request, error)
|
FindSenderRequestByID(ctx context.Context, projectID, id ulid.ULID) (Request, error)
|
||||||
FindSenderRequests(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error)
|
FindSenderRequests(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]Request, error)
|
||||||
StoreSenderRequest(ctx context.Context, req Request) error
|
StoreSenderRequest(ctx context.Context, req Request) error
|
||||||
StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
|
|
||||||
DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error
|
DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error
|
||||||
}
|
}
|
||||||
|
@ -1,292 +0,0 @@
|
|||||||
// Code generated by moq; DO NOT EDIT.
|
|
||||||
// github.com/matryer/moq
|
|
||||||
|
|
||||||
package sender_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure, that RepoMock does implement sender.Repository.
|
|
||||||
// If this is not the case, regenerate this file with moq.
|
|
||||||
var _ sender.Repository = &RepoMock{}
|
|
||||||
|
|
||||||
// RepoMock is a mock implementation of sender.Repository.
|
|
||||||
//
|
|
||||||
// func TestSomethingThatUsesRepository(t *testing.T) {
|
|
||||||
//
|
|
||||||
// // make and configure a mocked sender.Repository
|
|
||||||
// mockedRepository := &RepoMock{
|
|
||||||
// DeleteSenderRequestsFunc: func(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
// panic("mock out the DeleteSenderRequests method")
|
|
||||||
// },
|
|
||||||
// FindSenderRequestByIDFunc: func(ctx context.Context, id ulid.ULID) (sender.Request, error) {
|
|
||||||
// panic("mock out the FindSenderRequestByID method")
|
|
||||||
// },
|
|
||||||
// FindSenderRequestsFunc: func(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error) {
|
|
||||||
// panic("mock out the FindSenderRequests method")
|
|
||||||
// },
|
|
||||||
// StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
|
||||||
// panic("mock out the StoreResponseLog method")
|
|
||||||
// },
|
|
||||||
// StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
|
|
||||||
// panic("mock out the StoreSenderRequest method")
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // use mockedRepository in code that requires sender.Repository
|
|
||||||
// // and then make assertions.
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
type RepoMock struct {
|
|
||||||
// DeleteSenderRequestsFunc mocks the DeleteSenderRequests method.
|
|
||||||
DeleteSenderRequestsFunc func(ctx context.Context, projectID ulid.ULID) error
|
|
||||||
|
|
||||||
// FindSenderRequestByIDFunc mocks the FindSenderRequestByID method.
|
|
||||||
FindSenderRequestByIDFunc func(ctx context.Context, id ulid.ULID) (sender.Request, error)
|
|
||||||
|
|
||||||
// FindSenderRequestsFunc mocks the FindSenderRequests method.
|
|
||||||
FindSenderRequestsFunc func(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error)
|
|
||||||
|
|
||||||
// StoreResponseLogFunc mocks the StoreResponseLog method.
|
|
||||||
StoreResponseLogFunc func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error
|
|
||||||
|
|
||||||
// StoreSenderRequestFunc mocks the StoreSenderRequest method.
|
|
||||||
StoreSenderRequestFunc func(ctx context.Context, req sender.Request) error
|
|
||||||
|
|
||||||
// calls tracks calls to the methods.
|
|
||||||
calls struct {
|
|
||||||
// DeleteSenderRequests holds details about calls to the DeleteSenderRequests method.
|
|
||||||
DeleteSenderRequests []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ProjectID is the projectID argument value.
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}
|
|
||||||
// FindSenderRequestByID holds details about calls to the FindSenderRequestByID method.
|
|
||||||
FindSenderRequestByID []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ID is the id argument value.
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
// FindSenderRequests holds details about calls to the FindSenderRequests method.
|
|
||||||
FindSenderRequests []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// Filter is the filter argument value.
|
|
||||||
Filter sender.FindRequestsFilter
|
|
||||||
// ScopeMoqParam is the scopeMoqParam argument value.
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
}
|
|
||||||
// StoreResponseLog holds details about calls to the StoreResponseLog method.
|
|
||||||
StoreResponseLog []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ReqLogID is the reqLogID argument value.
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
// ResLog is the resLog argument value.
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
}
|
|
||||||
// StoreSenderRequest holds details about calls to the StoreSenderRequest method.
|
|
||||||
StoreSenderRequest []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// Req is the req argument value.
|
|
||||||
Req sender.Request
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lockDeleteSenderRequests sync.RWMutex
|
|
||||||
lockFindSenderRequestByID sync.RWMutex
|
|
||||||
lockFindSenderRequests sync.RWMutex
|
|
||||||
lockStoreResponseLog sync.RWMutex
|
|
||||||
lockStoreSenderRequest sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSenderRequests calls DeleteSenderRequestsFunc.
|
|
||||||
func (mock *RepoMock) DeleteSenderRequests(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
if mock.DeleteSenderRequestsFunc == nil {
|
|
||||||
panic("RepoMock.DeleteSenderRequestsFunc: method is nil but Repository.DeleteSenderRequests was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ProjectID: projectID,
|
|
||||||
}
|
|
||||||
mock.lockDeleteSenderRequests.Lock()
|
|
||||||
mock.calls.DeleteSenderRequests = append(mock.calls.DeleteSenderRequests, callInfo)
|
|
||||||
mock.lockDeleteSenderRequests.Unlock()
|
|
||||||
return mock.DeleteSenderRequestsFunc(ctx, projectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSenderRequestsCalls gets all the calls that were made to DeleteSenderRequests.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.DeleteSenderRequestsCalls())
|
|
||||||
func (mock *RepoMock) DeleteSenderRequestsCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockDeleteSenderRequests.RLock()
|
|
||||||
calls = mock.calls.DeleteSenderRequests
|
|
||||||
mock.lockDeleteSenderRequests.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindSenderRequestByID calls FindSenderRequestByIDFunc.
|
|
||||||
func (mock *RepoMock) FindSenderRequestByID(ctx context.Context, id ulid.ULID) (sender.Request, error) {
|
|
||||||
if mock.FindSenderRequestByIDFunc == nil {
|
|
||||||
panic("RepoMock.FindSenderRequestByIDFunc: method is nil but Repository.FindSenderRequestByID was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
mock.lockFindSenderRequestByID.Lock()
|
|
||||||
mock.calls.FindSenderRequestByID = append(mock.calls.FindSenderRequestByID, callInfo)
|
|
||||||
mock.lockFindSenderRequestByID.Unlock()
|
|
||||||
return mock.FindSenderRequestByIDFunc(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindSenderRequestByIDCalls gets all the calls that were made to FindSenderRequestByID.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.FindSenderRequestByIDCalls())
|
|
||||||
func (mock *RepoMock) FindSenderRequestByIDCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockFindSenderRequestByID.RLock()
|
|
||||||
calls = mock.calls.FindSenderRequestByID
|
|
||||||
mock.lockFindSenderRequestByID.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindSenderRequests calls FindSenderRequestsFunc.
|
|
||||||
func (mock *RepoMock) FindSenderRequests(ctx context.Context, filter sender.FindRequestsFilter, scopeMoqParam *scope.Scope) ([]sender.Request, error) {
|
|
||||||
if mock.FindSenderRequestsFunc == nil {
|
|
||||||
panic("RepoMock.FindSenderRequestsFunc: method is nil but Repository.FindSenderRequests was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Filter sender.FindRequestsFilter
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
Filter: filter,
|
|
||||||
ScopeMoqParam: scopeMoqParam,
|
|
||||||
}
|
|
||||||
mock.lockFindSenderRequests.Lock()
|
|
||||||
mock.calls.FindSenderRequests = append(mock.calls.FindSenderRequests, callInfo)
|
|
||||||
mock.lockFindSenderRequests.Unlock()
|
|
||||||
return mock.FindSenderRequestsFunc(ctx, filter, scopeMoqParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindSenderRequestsCalls gets all the calls that were made to FindSenderRequests.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.FindSenderRequestsCalls())
|
|
||||||
func (mock *RepoMock) FindSenderRequestsCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Filter sender.FindRequestsFilter
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Filter sender.FindRequestsFilter
|
|
||||||
ScopeMoqParam *scope.Scope
|
|
||||||
}
|
|
||||||
mock.lockFindSenderRequests.RLock()
|
|
||||||
calls = mock.calls.FindSenderRequests
|
|
||||||
mock.lockFindSenderRequests.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResponseLog calls StoreResponseLogFunc.
|
|
||||||
func (mock *RepoMock) StoreResponseLog(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
|
||||||
if mock.StoreResponseLogFunc == nil {
|
|
||||||
panic("RepoMock.StoreResponseLogFunc: method is nil but Repository.StoreResponseLog was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ReqLogID: reqLogID,
|
|
||||||
ResLog: resLog,
|
|
||||||
}
|
|
||||||
mock.lockStoreResponseLog.Lock()
|
|
||||||
mock.calls.StoreResponseLog = append(mock.calls.StoreResponseLog, callInfo)
|
|
||||||
mock.lockStoreResponseLog.Unlock()
|
|
||||||
return mock.StoreResponseLogFunc(ctx, reqLogID, resLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResponseLogCalls gets all the calls that were made to StoreResponseLog.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.StoreResponseLogCalls())
|
|
||||||
func (mock *RepoMock) StoreResponseLogCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ReqLogID ulid.ULID
|
|
||||||
ResLog reqlog.ResponseLog
|
|
||||||
}
|
|
||||||
mock.lockStoreResponseLog.RLock()
|
|
||||||
calls = mock.calls.StoreResponseLog
|
|
||||||
mock.lockStoreResponseLog.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreSenderRequest calls StoreSenderRequestFunc.
|
|
||||||
func (mock *RepoMock) StoreSenderRequest(ctx context.Context, req sender.Request) error {
|
|
||||||
if mock.StoreSenderRequestFunc == nil {
|
|
||||||
panic("RepoMock.StoreSenderRequestFunc: method is nil but Repository.StoreSenderRequest was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Req sender.Request
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
Req: req,
|
|
||||||
}
|
|
||||||
mock.lockStoreSenderRequest.Lock()
|
|
||||||
mock.calls.StoreSenderRequest = append(mock.calls.StoreSenderRequest, callInfo)
|
|
||||||
mock.lockStoreSenderRequest.Unlock()
|
|
||||||
return mock.StoreSenderRequestFunc(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreSenderRequestCalls gets all the calls that were made to StoreSenderRequest.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedRepository.StoreSenderRequestCalls())
|
|
||||||
func (mock *RepoMock) StoreSenderRequestCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Req sender.Request
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Req sender.Request
|
|
||||||
}
|
|
||||||
mock.lockStoreSenderRequest.RLock()
|
|
||||||
calls = mock.calls.StoreSenderRequest
|
|
||||||
mock.lockStoreSenderRequest.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
@ -1,498 +0,0 @@
|
|||||||
// Code generated by moq; DO NOT EDIT.
|
|
||||||
// github.com/matryer/moq
|
|
||||||
|
|
||||||
package sender_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/dstotijn/hetty/pkg/proxy"
|
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
|
||||||
"github.com/oklog/ulid"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure, that ReqLogServiceMock does implement reqlog.Service.
|
|
||||||
// If this is not the case, regenerate this file with moq.
|
|
||||||
var _ reqlog.Service = &ReqLogServiceMock{}
|
|
||||||
|
|
||||||
// ReqLogServiceMock is a mock implementation of reqlog.Service.
|
|
||||||
//
|
|
||||||
// func TestSomethingThatUsesService(t *testing.T) {
|
|
||||||
//
|
|
||||||
// // make and configure a mocked reqlog.Service
|
|
||||||
// mockedService := &ReqLogServiceMock{
|
|
||||||
// ActiveProjectIDFunc: func() ulid.ULID {
|
|
||||||
// panic("mock out the ActiveProjectID method")
|
|
||||||
// },
|
|
||||||
// BypassOutOfScopeRequestsFunc: func() bool {
|
|
||||||
// panic("mock out the BypassOutOfScopeRequests method")
|
|
||||||
// },
|
|
||||||
// ClearRequestsFunc: func(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
// panic("mock out the ClearRequests method")
|
|
||||||
// },
|
|
||||||
// FindReqsFilterFunc: func() reqlog.FindRequestsFilter {
|
|
||||||
// panic("mock out the FindReqsFilter method")
|
|
||||||
// },
|
|
||||||
// FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
|
||||||
// panic("mock out the FindRequestLogByID method")
|
|
||||||
// },
|
|
||||||
// FindRequestsFunc: func(ctx context.Context) ([]reqlog.RequestLog, error) {
|
|
||||||
// panic("mock out the FindRequests method")
|
|
||||||
// },
|
|
||||||
// RequestModifierFunc: func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
|
||||||
// panic("mock out the RequestModifier method")
|
|
||||||
// },
|
|
||||||
// ResponseModifierFunc: func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
|
||||||
// panic("mock out the ResponseModifier method")
|
|
||||||
// },
|
|
||||||
// SetActiveProjectIDFunc: func(id ulid.ULID) {
|
|
||||||
// panic("mock out the SetActiveProjectID method")
|
|
||||||
// },
|
|
||||||
// SetBypassOutOfScopeRequestsFunc: func(b bool) {
|
|
||||||
// panic("mock out the SetBypassOutOfScopeRequests method")
|
|
||||||
// },
|
|
||||||
// SetFindReqsFilterFunc: func(filter reqlog.FindRequestsFilter) {
|
|
||||||
// panic("mock out the SetFindReqsFilter method")
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // use mockedService in code that requires reqlog.Service
|
|
||||||
// // and then make assertions.
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
type ReqLogServiceMock struct {
|
|
||||||
// ActiveProjectIDFunc mocks the ActiveProjectID method.
|
|
||||||
ActiveProjectIDFunc func() ulid.ULID
|
|
||||||
|
|
||||||
// BypassOutOfScopeRequestsFunc mocks the BypassOutOfScopeRequests method.
|
|
||||||
BypassOutOfScopeRequestsFunc func() bool
|
|
||||||
|
|
||||||
// ClearRequestsFunc mocks the ClearRequests method.
|
|
||||||
ClearRequestsFunc func(ctx context.Context, projectID ulid.ULID) error
|
|
||||||
|
|
||||||
// FindReqsFilterFunc mocks the FindReqsFilter method.
|
|
||||||
FindReqsFilterFunc func() reqlog.FindRequestsFilter
|
|
||||||
|
|
||||||
// FindRequestLogByIDFunc mocks the FindRequestLogByID method.
|
|
||||||
FindRequestLogByIDFunc func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error)
|
|
||||||
|
|
||||||
// FindRequestsFunc mocks the FindRequests method.
|
|
||||||
FindRequestsFunc func(ctx context.Context) ([]reqlog.RequestLog, error)
|
|
||||||
|
|
||||||
// RequestModifierFunc mocks the RequestModifier method.
|
|
||||||
RequestModifierFunc func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc
|
|
||||||
|
|
||||||
// ResponseModifierFunc mocks the ResponseModifier method.
|
|
||||||
ResponseModifierFunc func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc
|
|
||||||
|
|
||||||
// SetActiveProjectIDFunc mocks the SetActiveProjectID method.
|
|
||||||
SetActiveProjectIDFunc func(id ulid.ULID)
|
|
||||||
|
|
||||||
// SetBypassOutOfScopeRequestsFunc mocks the SetBypassOutOfScopeRequests method.
|
|
||||||
SetBypassOutOfScopeRequestsFunc func(b bool)
|
|
||||||
|
|
||||||
// SetFindReqsFilterFunc mocks the SetFindReqsFilter method.
|
|
||||||
SetFindReqsFilterFunc func(filter reqlog.FindRequestsFilter)
|
|
||||||
|
|
||||||
// calls tracks calls to the methods.
|
|
||||||
calls struct {
|
|
||||||
// ActiveProjectID holds details about calls to the ActiveProjectID method.
|
|
||||||
ActiveProjectID []struct {
|
|
||||||
}
|
|
||||||
// BypassOutOfScopeRequests holds details about calls to the BypassOutOfScopeRequests method.
|
|
||||||
BypassOutOfScopeRequests []struct {
|
|
||||||
}
|
|
||||||
// ClearRequests holds details about calls to the ClearRequests method.
|
|
||||||
ClearRequests []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ProjectID is the projectID argument value.
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}
|
|
||||||
// FindReqsFilter holds details about calls to the FindReqsFilter method.
|
|
||||||
FindReqsFilter []struct {
|
|
||||||
}
|
|
||||||
// FindRequestLogByID holds details about calls to the FindRequestLogByID method.
|
|
||||||
FindRequestLogByID []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// ID is the id argument value.
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
// FindRequests holds details about calls to the FindRequests method.
|
|
||||||
FindRequests []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
}
|
|
||||||
// RequestModifier holds details about calls to the RequestModifier method.
|
|
||||||
RequestModifier []struct {
|
|
||||||
// Next is the next argument value.
|
|
||||||
Next proxy.RequestModifyFunc
|
|
||||||
}
|
|
||||||
// ResponseModifier holds details about calls to the ResponseModifier method.
|
|
||||||
ResponseModifier []struct {
|
|
||||||
// Next is the next argument value.
|
|
||||||
Next proxy.ResponseModifyFunc
|
|
||||||
}
|
|
||||||
// SetActiveProjectID holds details about calls to the SetActiveProjectID method.
|
|
||||||
SetActiveProjectID []struct {
|
|
||||||
// ID is the id argument value.
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
// SetBypassOutOfScopeRequests holds details about calls to the SetBypassOutOfScopeRequests method.
|
|
||||||
SetBypassOutOfScopeRequests []struct {
|
|
||||||
// B is the b argument value.
|
|
||||||
B bool
|
|
||||||
}
|
|
||||||
// SetFindReqsFilter holds details about calls to the SetFindReqsFilter method.
|
|
||||||
SetFindReqsFilter []struct {
|
|
||||||
// Filter is the filter argument value.
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lockActiveProjectID sync.RWMutex
|
|
||||||
lockBypassOutOfScopeRequests sync.RWMutex
|
|
||||||
lockClearRequests sync.RWMutex
|
|
||||||
lockFindReqsFilter sync.RWMutex
|
|
||||||
lockFindRequestLogByID sync.RWMutex
|
|
||||||
lockFindRequests sync.RWMutex
|
|
||||||
lockRequestModifier sync.RWMutex
|
|
||||||
lockResponseModifier sync.RWMutex
|
|
||||||
lockSetActiveProjectID sync.RWMutex
|
|
||||||
lockSetBypassOutOfScopeRequests sync.RWMutex
|
|
||||||
lockSetFindReqsFilter sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActiveProjectID calls ActiveProjectIDFunc.
|
|
||||||
func (mock *ReqLogServiceMock) ActiveProjectID() ulid.ULID {
|
|
||||||
if mock.ActiveProjectIDFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.ActiveProjectIDFunc: method is nil but Service.ActiveProjectID was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
}{}
|
|
||||||
mock.lockActiveProjectID.Lock()
|
|
||||||
mock.calls.ActiveProjectID = append(mock.calls.ActiveProjectID, callInfo)
|
|
||||||
mock.lockActiveProjectID.Unlock()
|
|
||||||
return mock.ActiveProjectIDFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActiveProjectIDCalls gets all the calls that were made to ActiveProjectID.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.ActiveProjectIDCalls())
|
|
||||||
func (mock *ReqLogServiceMock) ActiveProjectIDCalls() []struct {
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
}
|
|
||||||
mock.lockActiveProjectID.RLock()
|
|
||||||
calls = mock.calls.ActiveProjectID
|
|
||||||
mock.lockActiveProjectID.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// BypassOutOfScopeRequests calls BypassOutOfScopeRequestsFunc.
|
|
||||||
func (mock *ReqLogServiceMock) BypassOutOfScopeRequests() bool {
|
|
||||||
if mock.BypassOutOfScopeRequestsFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.BypassOutOfScopeRequestsFunc: method is nil but Service.BypassOutOfScopeRequests was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
}{}
|
|
||||||
mock.lockBypassOutOfScopeRequests.Lock()
|
|
||||||
mock.calls.BypassOutOfScopeRequests = append(mock.calls.BypassOutOfScopeRequests, callInfo)
|
|
||||||
mock.lockBypassOutOfScopeRequests.Unlock()
|
|
||||||
return mock.BypassOutOfScopeRequestsFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BypassOutOfScopeRequestsCalls gets all the calls that were made to BypassOutOfScopeRequests.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.BypassOutOfScopeRequestsCalls())
|
|
||||||
func (mock *ReqLogServiceMock) BypassOutOfScopeRequestsCalls() []struct {
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
}
|
|
||||||
mock.lockBypassOutOfScopeRequests.RLock()
|
|
||||||
calls = mock.calls.BypassOutOfScopeRequests
|
|
||||||
mock.lockBypassOutOfScopeRequests.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearRequests calls ClearRequestsFunc.
|
|
||||||
func (mock *ReqLogServiceMock) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
|
||||||
if mock.ClearRequestsFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.ClearRequestsFunc: method is nil but Service.ClearRequests was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ProjectID: projectID,
|
|
||||||
}
|
|
||||||
mock.lockClearRequests.Lock()
|
|
||||||
mock.calls.ClearRequests = append(mock.calls.ClearRequests, callInfo)
|
|
||||||
mock.lockClearRequests.Unlock()
|
|
||||||
return mock.ClearRequestsFunc(ctx, projectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearRequestsCalls gets all the calls that were made to ClearRequests.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.ClearRequestsCalls())
|
|
||||||
func (mock *ReqLogServiceMock) ClearRequestsCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ProjectID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockClearRequests.RLock()
|
|
||||||
calls = mock.calls.ClearRequests
|
|
||||||
mock.lockClearRequests.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindReqsFilter calls FindReqsFilterFunc.
|
|
||||||
func (mock *ReqLogServiceMock) FindReqsFilter() reqlog.FindRequestsFilter {
|
|
||||||
if mock.FindReqsFilterFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.FindReqsFilterFunc: method is nil but Service.FindReqsFilter was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
}{}
|
|
||||||
mock.lockFindReqsFilter.Lock()
|
|
||||||
mock.calls.FindReqsFilter = append(mock.calls.FindReqsFilter, callInfo)
|
|
||||||
mock.lockFindReqsFilter.Unlock()
|
|
||||||
return mock.FindReqsFilterFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindReqsFilterCalls gets all the calls that were made to FindReqsFilter.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.FindReqsFilterCalls())
|
|
||||||
func (mock *ReqLogServiceMock) FindReqsFilterCalls() []struct {
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
}
|
|
||||||
mock.lockFindReqsFilter.RLock()
|
|
||||||
calls = mock.calls.FindReqsFilter
|
|
||||||
mock.lockFindReqsFilter.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestLogByID calls FindRequestLogByIDFunc.
|
|
||||||
func (mock *ReqLogServiceMock) FindRequestLogByID(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
|
||||||
if mock.FindRequestLogByIDFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.FindRequestLogByIDFunc: method is nil but Service.FindRequestLogByID was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
mock.lockFindRequestLogByID.Lock()
|
|
||||||
mock.calls.FindRequestLogByID = append(mock.calls.FindRequestLogByID, callInfo)
|
|
||||||
mock.lockFindRequestLogByID.Unlock()
|
|
||||||
return mock.FindRequestLogByIDFunc(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestLogByIDCalls gets all the calls that were made to FindRequestLogByID.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.FindRequestLogByIDCalls())
|
|
||||||
func (mock *ReqLogServiceMock) FindRequestLogByIDCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockFindRequestLogByID.RLock()
|
|
||||||
calls = mock.calls.FindRequestLogByID
|
|
||||||
mock.lockFindRequestLogByID.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequests calls FindRequestsFunc.
|
|
||||||
func (mock *ReqLogServiceMock) FindRequests(ctx context.Context) ([]reqlog.RequestLog, error) {
|
|
||||||
if mock.FindRequestsFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.FindRequestsFunc: method is nil but Service.FindRequests was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
}
|
|
||||||
mock.lockFindRequests.Lock()
|
|
||||||
mock.calls.FindRequests = append(mock.calls.FindRequests, callInfo)
|
|
||||||
mock.lockFindRequests.Unlock()
|
|
||||||
return mock.FindRequestsFunc(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRequestsCalls gets all the calls that were made to FindRequests.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.FindRequestsCalls())
|
|
||||||
func (mock *ReqLogServiceMock) FindRequestsCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
}
|
|
||||||
mock.lockFindRequests.RLock()
|
|
||||||
calls = mock.calls.FindRequests
|
|
||||||
mock.lockFindRequests.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestModifier calls RequestModifierFunc.
|
|
||||||
func (mock *ReqLogServiceMock) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
|
||||||
if mock.RequestModifierFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.RequestModifierFunc: method is nil but Service.RequestModifier was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Next proxy.RequestModifyFunc
|
|
||||||
}{
|
|
||||||
Next: next,
|
|
||||||
}
|
|
||||||
mock.lockRequestModifier.Lock()
|
|
||||||
mock.calls.RequestModifier = append(mock.calls.RequestModifier, callInfo)
|
|
||||||
mock.lockRequestModifier.Unlock()
|
|
||||||
return mock.RequestModifierFunc(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestModifierCalls gets all the calls that were made to RequestModifier.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.RequestModifierCalls())
|
|
||||||
func (mock *ReqLogServiceMock) RequestModifierCalls() []struct {
|
|
||||||
Next proxy.RequestModifyFunc
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Next proxy.RequestModifyFunc
|
|
||||||
}
|
|
||||||
mock.lockRequestModifier.RLock()
|
|
||||||
calls = mock.calls.RequestModifier
|
|
||||||
mock.lockRequestModifier.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseModifier calls ResponseModifierFunc.
|
|
||||||
func (mock *ReqLogServiceMock) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
|
|
||||||
if mock.ResponseModifierFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.ResponseModifierFunc: method is nil but Service.ResponseModifier was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Next proxy.ResponseModifyFunc
|
|
||||||
}{
|
|
||||||
Next: next,
|
|
||||||
}
|
|
||||||
mock.lockResponseModifier.Lock()
|
|
||||||
mock.calls.ResponseModifier = append(mock.calls.ResponseModifier, callInfo)
|
|
||||||
mock.lockResponseModifier.Unlock()
|
|
||||||
return mock.ResponseModifierFunc(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseModifierCalls gets all the calls that were made to ResponseModifier.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.ResponseModifierCalls())
|
|
||||||
func (mock *ReqLogServiceMock) ResponseModifierCalls() []struct {
|
|
||||||
Next proxy.ResponseModifyFunc
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Next proxy.ResponseModifyFunc
|
|
||||||
}
|
|
||||||
mock.lockResponseModifier.RLock()
|
|
||||||
calls = mock.calls.ResponseModifier
|
|
||||||
mock.lockResponseModifier.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetActiveProjectID calls SetActiveProjectIDFunc.
|
|
||||||
func (mock *ReqLogServiceMock) SetActiveProjectID(id ulid.ULID) {
|
|
||||||
if mock.SetActiveProjectIDFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.SetActiveProjectIDFunc: method is nil but Service.SetActiveProjectID was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
ID ulid.ULID
|
|
||||||
}{
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
mock.lockSetActiveProjectID.Lock()
|
|
||||||
mock.calls.SetActiveProjectID = append(mock.calls.SetActiveProjectID, callInfo)
|
|
||||||
mock.lockSetActiveProjectID.Unlock()
|
|
||||||
mock.SetActiveProjectIDFunc(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetActiveProjectIDCalls gets all the calls that were made to SetActiveProjectID.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.SetActiveProjectIDCalls())
|
|
||||||
func (mock *ReqLogServiceMock) SetActiveProjectIDCalls() []struct {
|
|
||||||
ID ulid.ULID
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
ID ulid.ULID
|
|
||||||
}
|
|
||||||
mock.lockSetActiveProjectID.RLock()
|
|
||||||
calls = mock.calls.SetActiveProjectID
|
|
||||||
mock.lockSetActiveProjectID.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBypassOutOfScopeRequests calls SetBypassOutOfScopeRequestsFunc.
|
|
||||||
func (mock *ReqLogServiceMock) SetBypassOutOfScopeRequests(b bool) {
|
|
||||||
if mock.SetBypassOutOfScopeRequestsFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.SetBypassOutOfScopeRequestsFunc: method is nil but Service.SetBypassOutOfScopeRequests was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
B bool
|
|
||||||
}{
|
|
||||||
B: b,
|
|
||||||
}
|
|
||||||
mock.lockSetBypassOutOfScopeRequests.Lock()
|
|
||||||
mock.calls.SetBypassOutOfScopeRequests = append(mock.calls.SetBypassOutOfScopeRequests, callInfo)
|
|
||||||
mock.lockSetBypassOutOfScopeRequests.Unlock()
|
|
||||||
mock.SetBypassOutOfScopeRequestsFunc(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBypassOutOfScopeRequestsCalls gets all the calls that were made to SetBypassOutOfScopeRequests.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.SetBypassOutOfScopeRequestsCalls())
|
|
||||||
func (mock *ReqLogServiceMock) SetBypassOutOfScopeRequestsCalls() []struct {
|
|
||||||
B bool
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
B bool
|
|
||||||
}
|
|
||||||
mock.lockSetBypassOutOfScopeRequests.RLock()
|
|
||||||
calls = mock.calls.SetBypassOutOfScopeRequests
|
|
||||||
mock.lockSetBypassOutOfScopeRequests.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFindReqsFilter calls SetFindReqsFilterFunc.
|
|
||||||
func (mock *ReqLogServiceMock) SetFindReqsFilter(filter reqlog.FindRequestsFilter) {
|
|
||||||
if mock.SetFindReqsFilterFunc == nil {
|
|
||||||
panic("ReqLogServiceMock.SetFindReqsFilterFunc: method is nil but Service.SetFindReqsFilter was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
}{
|
|
||||||
Filter: filter,
|
|
||||||
}
|
|
||||||
mock.lockSetFindReqsFilter.Lock()
|
|
||||||
mock.calls.SetFindReqsFilter = append(mock.calls.SetFindReqsFilter, callInfo)
|
|
||||||
mock.lockSetFindReqsFilter.Unlock()
|
|
||||||
mock.SetFindReqsFilterFunc(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFindReqsFilterCalls gets all the calls that were made to SetFindReqsFilter.
|
|
||||||
// Check the length with:
|
|
||||||
// len(mockedService.SetFindReqsFilterCalls())
|
|
||||||
func (mock *ReqLogServiceMock) SetFindReqsFilterCalls() []struct {
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Filter reqlog.FindRequestsFilter
|
|
||||||
}
|
|
||||||
mock.lockSetFindReqsFilter.RLock()
|
|
||||||
calls = mock.calls.SetFindReqsFilter
|
|
||||||
mock.lockSetFindReqsFilter.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
@ -7,9 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var senderReqSearchKeyFns = map[string]func(req Request) string{
|
var senderReqSearchKeyFns = map[string]func(req Request) string{
|
||||||
@ -29,22 +29,22 @@ var senderReqSearchKeyFns = map[string]func(req Request) string{
|
|||||||
// TODO: Request and response headers search key functions.
|
// TODO: Request and response headers search key functions.
|
||||||
|
|
||||||
// Matches returns true if the supplied search expression evaluates to true.
|
// Matches returns true if the supplied search expression evaluates to true.
|
||||||
func (req Request) Matches(expr search.Expression) (bool, error) {
|
func (req Request) Matches(expr filter.Expression) (bool, error) {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case search.PrefixExpression:
|
case filter.PrefixExpression:
|
||||||
return req.matchPrefixExpr(e)
|
return req.matchPrefixExpr(e)
|
||||||
case search.InfixExpression:
|
case filter.InfixExpression:
|
||||||
return req.matchInfixExpr(e)
|
return req.matchInfixExpr(e)
|
||||||
case search.StringLiteral:
|
case filter.StringLiteral:
|
||||||
return req.matchStringLiteral(e)
|
return req.matchStringLiteral(e)
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req Request) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
func (req Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpNot:
|
case filter.TokOpNot:
|
||||||
match, err := req.Matches(expr.Right)
|
match, err := req.Matches(expr.Right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -56,9 +56,9 @@ func (req Request) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpAnd:
|
case filter.TokOpAnd:
|
||||||
left, err := req.Matches(expr.Left)
|
left, err := req.Matches(expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -70,7 +70,7 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return left && right, nil
|
return left && right, nil
|
||||||
case search.TokOpOr:
|
case filter.TokOpOr:
|
||||||
left, err := req.Matches(expr.Left)
|
left, err := req.Matches(expr.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -84,28 +84,46 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
|||||||
return left || right, nil
|
return left || right, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
left, ok := expr.Left.(search.StringLiteral)
|
left, ok := expr.Left.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("left operand must be a string literal")
|
return false, errors.New("left operand must be a string literal")
|
||||||
}
|
}
|
||||||
|
|
||||||
leftVal := req.getMappedStringLiteral(left.Value)
|
leftVal := req.getMappedStringLiteral(left.Value)
|
||||||
|
|
||||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
if leftVal == "req.headers" {
|
||||||
right, ok := expr.Right.(search.RegexpLiteral)
|
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftVal == "res.headers" && req.Response != nil {
|
||||||
|
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Response.Header)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||||
|
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a regular expression")
|
return false, errors.New("right operand must be a regular expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpRe:
|
case filter.TokOpRe:
|
||||||
return right.MatchString(leftVal), nil
|
return right.MatchString(leftVal), nil
|
||||||
case search.TokOpNotRe:
|
case filter.TokOpNotRe:
|
||||||
return !right.MatchString(leftVal), nil
|
return !right.MatchString(leftVal), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
right, ok := expr.Right.(search.StringLiteral)
|
right, ok := expr.Right.(filter.StringLiteral)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, errors.New("right operand must be a string literal")
|
return false, errors.New("right operand must be a string literal")
|
||||||
}
|
}
|
||||||
@ -113,20 +131,20 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
|||||||
rightVal := req.getMappedStringLiteral(right.Value)
|
rightVal := req.getMappedStringLiteral(right.Value)
|
||||||
|
|
||||||
switch expr.Operator {
|
switch expr.Operator {
|
||||||
case search.TokOpEq:
|
case filter.TokOpEq:
|
||||||
return leftVal == rightVal, nil
|
return leftVal == rightVal, nil
|
||||||
case search.TokOpNotEq:
|
case filter.TokOpNotEq:
|
||||||
return leftVal != rightVal, nil
|
return leftVal != rightVal, nil
|
||||||
case search.TokOpGt:
|
case filter.TokOpGt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal > rightVal, nil
|
return leftVal > rightVal, nil
|
||||||
case search.TokOpLt:
|
case filter.TokOpLt:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal < rightVal, nil
|
return leftVal < rightVal, nil
|
||||||
case search.TokOpGtEq:
|
case filter.TokOpGtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal >= rightVal, nil
|
return leftVal >= rightVal, nil
|
||||||
case search.TokOpLtEq:
|
case filter.TokOpLtEq:
|
||||||
// TODO(?) attempt to parse as int.
|
// TODO(?) attempt to parse as int.
|
||||||
return leftVal <= rightVal, nil
|
return leftVal <= rightVal, nil
|
||||||
default:
|
default:
|
||||||
@ -155,7 +173,18 @@ func (req Request) getMappedStringLiteral(s string) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (req Request) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, fn := range senderReqSearchKeyFns {
|
for _, fn := range senderReqSearchKeyFns {
|
||||||
if strings.Contains(
|
if strings.Contains(
|
||||||
strings.ToLower(fn(req)),
|
strings.ToLower(fn(req)),
|
||||||
@ -166,6 +195,17 @@ func (req Request) matchStringLiteral(strLiteral search.StringLiteral) (bool, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Response != nil {
|
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 _, fn := range reqlog.ResLogSearchKeyFns {
|
for _, fn := range reqlog.ResLogSearchKeyFns {
|
||||||
if strings.Contains(
|
if strings.Contains(
|
||||||
strings.ToLower(fn(*req.Response)),
|
strings.ToLower(fn(*req.Response)),
|
||||||
|
@ -3,8 +3,8 @@ package sender_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
"github.com/dstotijn/hetty/pkg/sender"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ func TestRequestLogMatch(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
searchExpr, err := search.ParseQuery(tt.query)
|
searchExpr, err := filter.ParseQuery(tt.query)
|
||||||
assertError(t, nil, err)
|
assertError(t, nil, err)
|
||||||
|
|
||||||
got, err := tt.senderReq.Matches(searchExpr)
|
got, err := tt.senderReq.Matches(searchExpr)
|
||||||
|
@ -12,9 +12,9 @@ import (
|
|||||||
|
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/filter"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/scope"
|
"github.com/dstotijn/hetty/pkg/scope"
|
||||||
"github.com/dstotijn/hetty/pkg/search"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
@ -30,37 +30,25 @@ var (
|
|||||||
ErrRequestNotFound = errors.New("sender: request not found")
|
ErrRequestNotFound = errors.New("sender: request not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service struct {
|
||||||
FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error)
|
|
||||||
FindRequests(ctx context.Context) ([]Request, error)
|
|
||||||
CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error)
|
|
||||||
CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error)
|
|
||||||
DeleteRequests(ctx context.Context, projectID ulid.ULID) error
|
|
||||||
SendRequest(ctx context.Context, id ulid.ULID) (Request, error)
|
|
||||||
SetActiveProjectID(ulid.ULID)
|
|
||||||
SetFindReqsFilter(filter FindRequestsFilter)
|
|
||||||
FindReqsFilter() FindRequestsFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
activeProjectID ulid.ULID
|
activeProjectID ulid.ULID
|
||||||
findReqsFilter FindRequestsFilter
|
findReqsFilter FindRequestsFilter
|
||||||
scope *scope.Scope
|
scope *scope.Scope
|
||||||
repo Repository
|
repo Repository
|
||||||
reqLogSvc reqlog.Service
|
reqLogSvc *reqlog.Service
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type FindRequestsFilter struct {
|
type FindRequestsFilter struct {
|
||||||
ProjectID ulid.ULID
|
ProjectID ulid.ULID
|
||||||
OnlyInScope bool
|
OnlyInScope bool
|
||||||
SearchExpr search.Expression
|
SearchExpr filter.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Scope *scope.Scope
|
Scope *scope.Scope
|
||||||
Repository Repository
|
Repository Repository
|
||||||
ReqLogService reqlog.Service
|
ReqLogService *reqlog.Service
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,8 +56,8 @@ type SendError struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg Config) Service {
|
func NewService(cfg Config) *Service {
|
||||||
svc := &service{
|
svc := &Service{
|
||||||
repo: cfg.Repository,
|
repo: cfg.Repository,
|
||||||
reqLogSvc: cfg.ReqLogService,
|
reqLogSvc: cfg.ReqLogService,
|
||||||
httpClient: defaultHTTPClient,
|
httpClient: defaultHTTPClient,
|
||||||
@ -97,8 +85,8 @@ type Request struct {
|
|||||||
Response *reqlog.ResponseLog
|
Response *reqlog.ResponseLog
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error) {
|
func (svc *Service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request, error) {
|
||||||
req, err := svc.repo.FindSenderRequestByID(ctx, id)
|
req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
||||||
}
|
}
|
||||||
@ -106,11 +94,11 @@ func (svc *service) FindRequestByID(ctx context.Context, id ulid.ULID) (Request,
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) FindRequests(ctx context.Context) ([]Request, error) {
|
func (svc *Service) FindRequests(ctx context.Context) ([]Request, error) {
|
||||||
return svc.repo.FindSenderRequests(ctx, svc.findReqsFilter, svc.scope)
|
return svc.repo.FindSenderRequests(ctx, svc.findReqsFilter, svc.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error) {
|
func (svc *Service) CreateOrUpdateRequest(ctx context.Context, req Request) (Request, error) {
|
||||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||||
return Request{}, ErrProjectIDMustBeSet
|
return Request{}, ErrProjectIDMustBeSet
|
||||||
}
|
}
|
||||||
@ -141,7 +129,7 @@ func (svc *service) CreateOrUpdateRequest(ctx context.Context, req Request) (Req
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error) {
|
func (svc *Service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID) (Request, error) {
|
||||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||||
return Request{}, ErrProjectIDMustBeSet
|
return Request{}, ErrProjectIDMustBeSet
|
||||||
}
|
}
|
||||||
@ -170,16 +158,16 @@ func (svc *service) CloneFromRequestLog(ctx context.Context, reqLogID ulid.ULID)
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetFindReqsFilter(filter FindRequestsFilter) {
|
func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
|
||||||
svc.findReqsFilter = filter
|
svc.findReqsFilter = filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) FindReqsFilter() FindRequestsFilter {
|
func (svc *Service) FindReqsFilter() FindRequestsFilter {
|
||||||
return svc.findReqsFilter
|
return svc.findReqsFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SendRequest(ctx context.Context, id ulid.ULID) (Request, error) {
|
func (svc *Service) SendRequest(ctx context.Context, id ulid.ULID) (Request, error) {
|
||||||
req, err := svc.repo.FindSenderRequestByID(ctx, id)
|
req, err := svc.repo.FindSenderRequestByID(ctx, svc.activeProjectID, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
return Request{}, fmt.Errorf("sender: failed to find request: %w", err)
|
||||||
}
|
}
|
||||||
@ -194,7 +182,9 @@ func (svc *service) SendRequest(ctx context.Context, id ulid.ULID) (Request, err
|
|||||||
return Request{}, fmt.Errorf("sender: could not send HTTP request: %w", err)
|
return Request{}, fmt.Errorf("sender: could not send HTTP request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svc.repo.StoreResponseLog(ctx, id, resLog)
|
req.Response = &resLog
|
||||||
|
|
||||||
|
err = svc.repo.StoreSenderRequest(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Request{}, fmt.Errorf("sender: failed to store sender response log: %w", err)
|
return Request{}, fmt.Errorf("sender: failed to store sender response log: %w", err)
|
||||||
}
|
}
|
||||||
@ -219,7 +209,7 @@ func parseHTTPRequest(ctx context.Context, req Request) (*http.Request, error) {
|
|||||||
return httpReq, nil
|
return httpReq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog, error) {
|
func (svc *Service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog, error) {
|
||||||
res, err := svc.httpClient.Do(httpReq)
|
res, err := svc.httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reqlog.ResponseLog{}, &SendError{err}
|
return reqlog.ResponseLog{}, &SendError{err}
|
||||||
@ -234,11 +224,11 @@ func (svc *service) sendHTTPRequest(httpReq *http.Request) (reqlog.ResponseLog,
|
|||||||
return resLog, err
|
return resLog, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) SetActiveProjectID(id ulid.ULID) {
|
func (svc *Service) SetActiveProjectID(id ulid.ULID) {
|
||||||
svc.activeProjectID = id
|
svc.activeProjectID = id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) DeleteRequests(ctx context.Context, projectID ulid.ULID) error {
|
func (svc *Service) DeleteRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||||
return svc.repo.DeleteSenderRequests(ctx, projectID)
|
return svc.repo.DeleteSenderRequests(ctx, projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package sender_test
|
package sender_test
|
||||||
|
|
||||||
//go:generate go run github.com/matryer/moq -out reqlog_mock_test.go -pkg sender_test ../reqlog Service:ReqLogServiceMock
|
|
||||||
//go:generate go run github.com/matryer/moq -out repo_mock_test.go -pkg sender_test . Repository:RepoMock
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
@ -15,8 +12,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/oklog/ulid"
|
"github.com/oklog/ulid"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
|
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||||
|
"github.com/dstotijn/hetty/pkg/proj"
|
||||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||||
"github.com/dstotijn/hetty/pkg/sender"
|
"github.com/dstotijn/hetty/pkg/sender"
|
||||||
)
|
)
|
||||||
@ -54,16 +55,34 @@ func TestStoreRequest(t *testing.T) {
|
|||||||
t.Run("with active project", func(t *testing.T) {
|
t.Run("with active project", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
repoMock := &RepoMock{
|
path := t.TempDir() + "bolt.db"
|
||||||
StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
|
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||||
return 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()
|
||||||
|
|
||||||
svc := sender.NewService(sender.Config{
|
svc := sender.NewService(sender.Config{
|
||||||
Repository: repoMock,
|
Repository: db,
|
||||||
})
|
})
|
||||||
|
|
||||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
|
Name: "foobar",
|
||||||
|
Settings: proj.Settings{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error upserting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.SetActiveProjectID(projectID)
|
||||||
svc.SetActiveProjectID(projectID)
|
svc.SetActiveProjectID(projectID)
|
||||||
|
|
||||||
exp := sender.Request{
|
exp := sender.Request{
|
||||||
@ -94,18 +113,18 @@ func TestStoreRequest(t *testing.T) {
|
|||||||
t.Fatal("expected request ID to be non-empty value")
|
t.Fatal("expected request ID to be non-empty value")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repoMock.StoreSenderRequestCalls()) != 1 {
|
diff := cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||||
t.Fatal("expected `svc.repo.StoreSenderRequest()` to have been called 1 time")
|
if diff != "" {
|
||||||
|
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff := cmp.Diff(got, repoMock.StoreSenderRequestCalls()[0].Req); diff != "" {
|
got, err = db.FindSenderRequestByID(context.Background(), projectID, got.ID)
|
||||||
t.Fatalf("repo call arg not equal (-exp, +got):\n%v", diff)
|
if err != nil {
|
||||||
|
t.Fatalf("failed to find request by ID: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset ID to make comparison with expected easier.
|
diff = cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||||
got.ID = ulid.ULID{}
|
if diff != "" {
|
||||||
|
|
||||||
if diff := cmp.Diff(exp, got); diff != "" {
|
|
||||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -130,9 +149,30 @@ func TestCloneFromRequestLog(t *testing.T) {
|
|||||||
t.Run("with active project", func(t *testing.T) {
|
t.Run("with active project", func(t *testing.T) {
|
||||||
t.Parallel()
|
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()
|
||||||
|
|
||||||
|
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
|
err = db.UpsertProject(context.Background(), proj.Project{
|
||||||
|
ID: projectID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error upserting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
reqLog := reqlog.RequestLog{
|
reqLog := reqlog.RequestLog{
|
||||||
ID: reqLogID,
|
ID: reqLogID,
|
||||||
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
ProjectID: projectID,
|
||||||
URL: exampleURL,
|
URL: exampleURL,
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Proto: "HTTP/1.1",
|
Proto: "HTTP/1.1",
|
||||||
@ -142,22 +182,18 @@ func TestCloneFromRequestLog(t *testing.T) {
|
|||||||
Body: []byte("foobar"),
|
Body: []byte("foobar"),
|
||||||
}
|
}
|
||||||
|
|
||||||
reqLogMock := &ReqLogServiceMock{
|
if err := db.StoreRequestLog(context.Background(), reqLog); err != nil {
|
||||||
FindRequestLogByIDFunc: func(ctx context.Context, id ulid.ULID) (reqlog.RequestLog, error) {
|
t.Fatalf("failed to store request log: %v", err)
|
||||||
return reqLog, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
repoMock := &RepoMock{
|
|
||||||
StoreSenderRequestFunc: func(ctx context.Context, req sender.Request) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := sender.NewService(sender.Config{
|
svc := sender.NewService(sender.Config{
|
||||||
ReqLogService: reqLogMock,
|
ReqLogService: reqlog.NewService(reqlog.Config{
|
||||||
Repository: repoMock,
|
ActiveProjectID: projectID,
|
||||||
|
Repository: db,
|
||||||
|
}),
|
||||||
|
Repository: db,
|
||||||
})
|
})
|
||||||
|
|
||||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
|
||||||
svc.SetActiveProjectID(projectID)
|
svc.SetActiveProjectID(projectID)
|
||||||
|
|
||||||
exp := sender.Request{
|
exp := sender.Request{
|
||||||
@ -177,30 +213,8 @@ func TestCloneFromRequestLog(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error cloning from request log: %v", err)
|
t.Fatalf("unexpected error cloning from request log: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reqLogMock.FindRequestLogByIDCalls()) != 1 {
|
diff := cmp.Diff(exp, got, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||||
t.Fatal("expected `svc.reqLogSvc.FindRequestLogByID()` to have been called 1 time")
|
if diff != "" {
|
||||||
}
|
|
||||||
|
|
||||||
if got := reqLogMock.FindRequestLogByIDCalls()[0].ID; reqLogID.Compare(got) != 0 {
|
|
||||||
t.Fatalf("reqlog service call arg `id` not equal (expected: %q, got: %q)", reqLogID, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got.ID.Compare(ulid.ULID{}) == 0 {
|
|
||||||
t.Fatal("expected request ID to be non-empty value")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(repoMock.StoreSenderRequestCalls()) != 1 {
|
|
||||||
t.Fatal("expected `svc.repo.StoreSenderRequest()` to have been called 1 time")
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(got, repoMock.StoreSenderRequestCalls()[0].Req); diff != "" {
|
|
||||||
t.Fatalf("repo call arg not equal (-exp, +got):\n%v", diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset ID to make comparison with expected easier.
|
|
||||||
got.ID = ulid.ULID{}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(exp, got); diff != "" {
|
|
||||||
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -209,6 +223,19 @@ func TestCloneFromRequestLog(t *testing.T) {
|
|||||||
func TestSendRequest(t *testing.T) {
|
func TestSendRequest(t *testing.T) {
|
||||||
t.Parallel()
|
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()
|
||||||
|
|
||||||
date := time.Now().Format(http.TimeFormat)
|
date := time.Now().Format(http.TimeFormat)
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -220,10 +247,19 @@ func TestSendRequest(t *testing.T) {
|
|||||||
|
|
||||||
tsURL, _ := url.Parse(ts.URL)
|
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{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error upserting project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||||
req := sender.Request{
|
req := sender.Request{
|
||||||
ID: reqID,
|
ID: reqID,
|
||||||
ProjectID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
|
ProjectID: projectID,
|
||||||
URL: tsURL,
|
URL: tsURL,
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Proto: "HTTP/1.1",
|
Proto: "HTTP/1.1",
|
||||||
@ -233,19 +269,19 @@ func TestSendRequest(t *testing.T) {
|
|||||||
Body: []byte("foobar"),
|
Body: []byte("foobar"),
|
||||||
}
|
}
|
||||||
|
|
||||||
repoMock := &RepoMock{
|
if err := db.StoreSenderRequest(context.Background(), req); err != nil {
|
||||||
FindSenderRequestByIDFunc: func(ctx context.Context, id ulid.ULID) (sender.Request, error) {
|
t.Fatalf("failed to store request: %v", err)
|
||||||
return req, nil
|
|
||||||
},
|
|
||||||
StoreResponseLogFunc: func(ctx context.Context, reqLogID ulid.ULID, resLog reqlog.ResponseLog) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
svc := sender.NewService(sender.Config{
|
|
||||||
Repository: repoMock,
|
|
||||||
})
|
|
||||||
|
|
||||||
exp := reqlog.ResponseLog{
|
svc := sender.NewService(sender.Config{
|
||||||
|
ReqLogService: reqlog.NewService(reqlog.Config{
|
||||||
|
Repository: db,
|
||||||
|
}),
|
||||||
|
Repository: db,
|
||||||
|
})
|
||||||
|
svc.SetActiveProjectID(projectID)
|
||||||
|
|
||||||
|
exp := &reqlog.ResponseLog{
|
||||||
Proto: "HTTP/1.1",
|
Proto: "HTTP/1.1",
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Status: "200 OK",
|
Status: "200 OK",
|
||||||
@ -263,27 +299,8 @@ func TestSendRequest(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error sending request: %v", err)
|
t.Fatalf("unexpected error sending request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repoMock.FindSenderRequestByIDCalls()) != 1 {
|
diff := cmp.Diff(exp, got.Response, cmpopts.IgnoreFields(sender.Request{}, "ID"))
|
||||||
t.Fatal("expected `svc.repo.FindSenderRequestByID()` to have been called 1 time")
|
if diff != "" {
|
||||||
}
|
t.Fatalf("request not equal (-exp, +got):\n%v", diff)
|
||||||
|
|
||||||
if diff := cmp.Diff(reqID, repoMock.FindSenderRequestByIDCalls()[0].ID); diff != "" {
|
|
||||||
t.Fatalf("call arg `id` for `svc.repo.FindSenderRequestByID()` not equal (-exp, +got):\n%v", diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(repoMock.StoreResponseLogCalls()) != 1 {
|
|
||||||
t.Fatal("expected `svc.repo.StoreResponseLog()` to have been called 1 time")
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(reqID, repoMock.StoreResponseLogCalls()[0].ReqLogID); diff != "" {
|
|
||||||
t.Fatalf("call arg `reqLogID` for `svc.repo.StoreResponseLog()` not equal (-exp, +got):\n%v", diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(exp, repoMock.StoreResponseLogCalls()[0].ResLog); diff != "" {
|
|
||||||
t.Fatalf("call arg `resLog` for `svc.repo.StoreResponseLog()` not equal (-exp, +got):\n%v", diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(repoMock.StoreResponseLogCalls()[0].ResLog, *got.Response); diff != "" {
|
|
||||||
t.Fatalf("returned response log value and persisted value not equal (-exp, +got):\n%v", diff)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user