{"id":15074493,"url":"https://github.com/semior001/groxy","last_synced_at":"2025-12-24T17:35:34.480Z","repository":{"id":226770756,"uuid":"769605528","full_name":"Semior001/groxy","owner":"Semior001","description":"simple gRPC mocking server","archived":false,"fork":false,"pushed_at":"2024-12-13T15:17:56.000Z","size":226,"stargazers_count":8,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T16:35:29.954Z","etag":null,"topics":["grpc","grpc-go","grpc-mock-server","mock","mock-server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Semior001.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-09T14:51:50.000Z","updated_at":"2025-03-03T13:00:41.000Z","dependencies_parsed_at":"2024-06-21T12:59:31.796Z","dependency_job_id":"4e054067-528a-4da5-a19a-9a7c7fd4346f","html_url":"https://github.com/Semior001/groxy","commit_stats":{"total_commits":31,"total_committers":1,"mean_commits":31.0,"dds":0.0,"last_synced_commit":"abd9627db8be742aa130878f433e0405f2f953d6"},"previous_names":["semior001/groxy"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Semior001%2Fgroxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Semior001%2Fgroxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Semior001%2Fgroxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Semior001%2Fgroxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Semior001","download_url":"https://codeload.github.com/Semior001/groxy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248271925,"owners_count":21075800,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grpc","grpc-go","grpc-mock-server","mock","mock-server"],"created_at":"2024-09-25T03:33:48.538Z","updated_at":"2025-12-24T17:35:34.474Z","avatar_url":"https://github.com/Semior001.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg class=\"logo\" src=\".github/logo.png\" width=\"334px\" height=\"108px\" alt=\"gRoxy | gRPC mocking server\"/\u003e\n\n[![build](https://github.com/Semior001/groxy/actions/workflows/.go.yaml/badge.svg)](https://github.com/Semior001/groxy/actions/workflows/.go.yaml)\u0026nbsp;[![Coverage Status](https://coveralls.io/repos/github/Semior001/groxy/badge.svg?branch=master)](https://coveralls.io/github/Semior001/groxy?branch=master)\u0026nbsp;[![Go Report Card](https://goreportcard.com/badge/github.com/Semior001/groxy)](https://goreportcard.com/report/github.com/Semior001/groxy)\u0026nbsp;[![Go Reference](https://pkg.go.dev/badge/github.com/Semior001/groxy.svg)](https://pkg.go.dev/github.com/Semior001/groxy)\u0026nbsp;[![GitHub release](https://img.shields.io/github/release/Semior001/groxy.svg)](https://github.com/Semior001/groxy/releases)\n\n\u003c/div\u003e\n\ngRoxy is a gRPC mocking server that allows you to mock gRPC services and responses easily by specifying the message content alongside the message definition. gRoxy is designed to be used in development and testing environments to help you test your gRPC clients and services without having to rely on the actual gRPC server.\n\n* * *\n\n- [todos](#todos)\n- [installation](#installation)\n- [usage](#usage)\n  - [example](#example)\n  - [configuration](#configuration)\n  - [gRPC reflection](#grpc-reflection)\n  - [groxypb](#groxypb)\n    - [multiline-strings](#multiline-strings)\n    - [templating](#templating)\n    - [nested messages](#nested-messages)\n    - [enums](#enums)\n    - [repeated fields](#repeated-fields)\n    - [maps](#maps)\n- [benchmarks](#benchmarks)\n- [project status](#status)\n\n## todos\n- [ ] currently service supports only mocking unary methods, but we plan to support streaming methods as well\n\n## installation\nYou can install gRoxy using the following command:\n\n```shell\ngo install github.com/Semior001/groxy/cmd/groxy@latest\n```\n\nOr you can pull the docker image from ghcr.io:\n\n```shell\ndocker pull ghcr.io/semior001/groxy:latest\n```\n\nOr from the docker hub:\n\n```shell\ndocker pull semior/groxy:latest\n```\n\n## usage\n\n```\nUsage:\n  groxy [OPTIONS]\n\nApplication Options:\n  -a, --addr=                Address to listen on (default: :8080) [$ADDR]\n      --stdin                Read configuration from stdin instead of file [$STDIN]\n      --signature            Enable gRoxy signature headers [$SIGNATURE]\n      --reflection           Enable gRPC reflection merger [$REFLECTION]\n      --json                 Enable JSON logging [$JSON]\n      --debug                Enable debug mode [$DEBUG]\n\nfile:\n      --file.name=           Config file name (default: groxy.yml) [$FILE_NAME]\n      --file.check-interval= Check interval for the config file (default: 3s) [$FILE_CHECK_INTERVAL]\n      --file.delay=          Delay before applying the changes (default: 500ms) [$FILE_DELAY]\n\nHelp Options:\n  -h, --help                 Show this help message\n```\n\n### example\nThe simplest configuration for a method \"Stub\" would look like this:\n\n```yaml\nversion: 1\n\nrules:\n  - match: { uri: \"com.github.Semior001.groxy.example.mock.ExampleService/Stub\" }\n    respond:\n      body: |\n        message StubResponse {\n            option              (groxypb.target) = true; // this option specifies that the message is a response \n            string message = 1 [(groxypb.value)  = \"Hello, World!\"];\n            int32 code     = 2 [(groxypb.value)  = \"200\"];\n        }\n```\n\nTo simulate slow responses, you can add a `wait` instruction:\n\n```yaml\nversion: 1\n\nrules:\n  - match: { uri: \"com.github.Semior001.groxy.example.mock.ExampleService/Stub\" }\n    respond:\n      wait: 2s  # Wait 2 seconds before responding\n      body: |\n        message StubResponse {\n            option              (groxypb.target) = true;\n            string message = 1 [(groxypb.value)  = \"Delayed response\"];\n            int32 code     = 2 [(groxypb.value)  = \"200\"];\n        }\n```\n\nThat's it. You just need to define the response message, mark it as a target response message via the option, and set values via the value option. The response message will be sent to the client when the client calls the \"Stub\" method. No need for providing protosets, no need for providing the whole set of definitions, just the message you want to send.\n\nMore importantly, if your response message contains lots of fields, which are not important for the test, you can just ignore them. gRoxy will leave them empty, and the client will not be able to distinguish between the real server and the mock server. That's ensured by the protobuf's backward compatibility.\n\nField is backward compatible if it's of the same type and the same number. Names of the fields and messages are not important.\n\n\n### configuration\ngRoxy uses a YAML configuration file to define the rules for the gRPC mocking server. \n\nThe sections of the configuration file are in the collapsed section below:\n\n\u003cdetails\u003e\n\u003csummary\u003eConfiguration file sections\u003c/summary\u003e\n\n| Section     | Description                                                                                                                                                                                          |\n|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| version     | The version of the configuration file.\u003cbr/\u003eThe current version is `1`, and any other version will raise an error.                                                                                    |\n| not-matched | The not-matched section contains the default response if the request didn't match to any rule. Not-matched section may contain a request body, or a gRPC status. \u003cbr/\u003e\u003cbr/\u003e See respond type section |\n| upstreams   | The upstreams section contains the list of the upstreams that serve gRPC reflection services.                                                                                                        |\n| rules       | The rules section contains the rules for the gRPC mocking server.                                                                                                                                    |\n\nUpstreams section is a key-value map of upstreams, where key is the name of the upstream to be referenced further in the rules section. Each upstream consists of the following fields:\n\n| Field            | Required | Description                                                                                                                                                 |\n|------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| address          | true     | The address of the upstream gRPC reflection service.                                                                                                        |\n| tls              | optional | The TLS configuration for the upstream. The TLS configuration consists of the following fields:                                                             |\n| serve-reflection | optional | The flag that indicates whether the upstream's responses should be included in the gRPC reflection responses. No-op if `--reflection` flag is not provided. |\n\nRules are defined in the rules section. Either `respond` or `forward` must be defined Each rule consists of the following fields:\n\n| Field            | Required | Description                                                                                                                   |\n|------------------|----------|-------------------------------------------------------------------------------------------------------------------------------|\n| match            | true     | The match section contains the matchers for the request.                                                                      |\n| match.uri        | true     | The URI matcher for the request. The URI matcher is a regular expression that matches the URI of the request.                 |\n| match.header     | optional | a map of headers that should be present in the request.                                                                       |\n| match.body       | optional | The body matcher for the request. This must be a protobuf snippet that defines the request message with values to be matched. |\n| respond          | optional | The respond section contains the response for the request.                                                                    |\n| forward          | optional | The forward section contains the upstream to which request should be forwarded to.                                            |\n\nThe `Respond` section contains the response for the request. The respond section may contain the following fields:\n\n| Field       | Required                   | Description                                                                                                         |\n|-------------|----------------------------|---------------------------------------------------------------------------------------------------------------------|\n| wait        | optional                   | Duration to wait before sending the response (e.g., \"2s\", \"500ms\"). Useful for simulating slow responses.          |\n| body        | optional                   | The body of the response. This must be a protobuf snippet that defines the response message with values to be sent. |\n| metadata    | optional                   | The metadata to be sent as a response.                                                                              |\n| status      | optional                   | The gRPC status to be sent as a response.                                                                           |\n| status.code | true, if status is present | The gRPC status code to be sent as a response.                                                                      |\n| status.msg  | true, if status is present | The gRPC status message to be sent as a response.                                                                   |\n\nThe `Forward` section contains the upstream to which the request should be forwarded. The forward section may contain the following fields:\n\n| Field    | Required | Description                                                                                                                                       |\n|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------|\n| upstream | true     | The name of the upstream to which the request should be forwarded. Supports templating: use `env` function to get the environment variable value. |\n| header   | optional | The headers to be sent with the request.                                                                                                          |\n\n\u003c/details\u003e\n\nThe configuration file is being watched for changes, and the server will reload the configuration file if it changes.\n\nYou can also take a look at [examples](_example) for more information.\n\n### gRPC reflection\ngRoxy supports gRPC reflection services. If you want to merge the responses from the upstream gRPC reflection services, you need to provide the `--reflection` flag and set the `serve-reflection` flag to `true` on the upstreams that should be included in the reflection responses.\n\nThe reflection responses are merged in the following way:\n1. Services are merged and unified by the package + service name.\n2. File descriptors are merged into a single array among the upstreams.\n3. `AllExtensionNumbersOfType` responds with the first non-error response from the upstreams.\n\n### groxypb\ngRoxy uses the `groxypb` annotations to define values in protobuf message snippets. It compiles protobuf in a runtime, checking the target via the `groxypb.target` option and interpreting values via the `groxypb.value` option.\n\nExample of the snippet:\n```protobuf\nmessage SomeMessage {\n    option              (groxypb.target) = true; \n    string message = 1 [(groxypb.value) = \"Hello, World!\"];\n    int32 code = 2     [(groxypb.value) = \"200\"];\n}\n```\n\n#### multiline strings\nprotobuf itself doesn't support multiline strings, so gRoxy introduces it's own syntax for them in order to allow to specify complex values in `groxypb.value` option. Multiline strings should be enclosed in backticks:\n```protobuf\nmessage Dependency {\n  string field1 = 1;\n  int32  field2 = 2;\n  bool   field3 = 3;\n}\n\nmessage SomeMessage {\n    option              (groxypb.target) = true; \n    string message = 1 [(groxypb.value) = `Hello, \n    World!`];\n    Dependency dependency = 2 [(groxypb.value) = `{\n        \"field1\": \"value1\",\n        \"field2\": 2,\n        \"field3\": true\n    }`];\n}\n```\n\n#### templating\ngRoxy supports Go templating in the `groxypb.value` field annotations, allowing dynamic response generation. Templates can access request data and use a variety of built-in functions.\n\n##### template functions\n- **Sprig functions**: All [Sprig template functions](https://masterminds.github.io/sprig/).\n- **Request data access**: Use `.fieldName` to access data from the incoming request\n\n##### examples\n\n```protobuf\nmessage EnvResponse {\n    option (groxypb.target) = true;\n    string env_value = 1 [(groxypb.value) = \"{{env \\\"API_VERSION\\\"}}\"];\n}\n\nmessage DynamicResponse {\n    option (groxypb.target) = true;\n    string result = 1 [(groxypb.value) = \"Result: {{upper (printf \\\"num-%d\\\" (mul .factor 2))}}\"];\n}\n```\n\n##### request matching with expressions\nYou can use the `groxypb.matcher` option to conditionally match requests based on field values:\n\n```protobuf\nmessage TestRequest {\n    option (groxypb.target) = true;\n    string type = 1 [(groxypb.value) = \"premium\"];           // exact match\n    int32 score = 2 [(groxypb.matcher) = \"score \u003e 100\"];     // conditional match\n}\n```\n\nThe matcher uses the [expr](https://github.com/expr-lang/expr) language for evaluations.\n\n#### nested messages\nIn case of nested messages, there are two options how to set values:\n1. Set the value in the nested message:\n```protobuf\nmessage SomeMessage {\n    option                    (groxypb.target) = true; \n    string parent_value = 1   [(groxypb.value) = \"parent\"];\n    NestedMessage nested = 2;\n}\n\nmessage NestedMessage {\n    string nested_value = 1 [(groxypb.value) = \"nested\"];\n}\n```\n\n2. Set the JSON value in the annotation to the parent's field:\n```protobuf\nmessage SomeMessage {\n    option                    (groxypb.target) = true; \n    string parent_value = 1  [(groxypb.value) = \"parent\"];\n    NestedMessage nested = 2 [(groxypb.value) = '{\"nested_value\": \"nested\"}'];\n}\n```\n\nDepending on your use case, you may find different approaches useful. For instance, if you can have multiple objects of the same type, you may specify values in the options of the nested message definition itself rather than in the parent message. On the other hand, defining an exact value in the parent message is useful if you want to get different values for the nested message in different fields.\n\n#### enums\n\nFor enums, the value should be set as a string name of the enum value:\n```protobuf\nenum SomeEnum { \n    EMPTY = 0; \n    NEEDED_VALUE = 2; \n}\n\nmessage StubResponse {\n    option (groxypb.target) = true;\n    SomeEnum some_enum = 9 [(groxypb.value) = 'NEEDED_VALUE'];\n}\n```\n\n#### repeated fields\nFor repeated fields, the value should be set as a JSON array of the values:\n```protobuf\nmessage StubResponse {\n    option (groxypb.target) = true;\n    repeated string repeated_field = 1 [(groxypb.value) = '[\"value1\", \"value2\"]'];\n}\n```\n\n#### maps\nFor maps, the value should be set as a JSON object of the key-value pairs:\n```protobuf\nmessage StubResponse {\n    option (groxypb.target) = true;\n    map\u003cstring, string\u003e map_field = 1 [(groxypb.value) = '{\"key1\": \"value1\", \"key2\": \"value2\"}'];\n}\n```\n\n## benchmarks\nAll benchmarks were performed on a MacBook Pro 2021 with M1 Pro chip with 16GB of RAM.\nAll benchmarks were measured by bringing up the [grpc-echo server](https://github.com/semior001/grpc-echo) and groxy with the [benchmark config](_example/benchmark.yaml).\nYou may observe commands that were used to perform the benchmarks below in the taskfile, under the \"bench\" label.\n\n\u003cdetails\u003e\n\u003csummary\u003ebenchmarks\u003c/summary\u003e\n\n- mocker (plain static response mockery):\n```shell\nSummary:\n  Count:\t10000\n  Total:\t1.98 s\n  Slowest:\t1.40 ms\n  Fastest:\t0.06 ms\n  Average:\t0.12 ms\n  Requests/sec:\t5059.05\n\nResponse time histogram:\n  0.056 [1]    |\n  0.190 [9483] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.325 [415]  |∎∎\n  0.459 [82]   |\n  0.594 [10]   |\n  0.729 [5]    |\n  0.863 [0]    |\n  0.998 [1]    |\n  1.133 [0]    |\n  1.267 [1]    |\n  1.402 [2]    |\n\nLatency distribution:\n  10 % in 0.09 ms \n  25 % in 0.10 ms \n  50 % in 0.11 ms \n  75 % in 0.12 ms \n  90 % in 0.15 ms \n  95 % in 0.19 ms \n  99 % in 0.33 ms \n\nStatus code distribution:\n  [OK]   10000 responses   \n```\n\n- reverse-proxy (forward the request to the grpc-echo server):\n```shell\n\nSummary:\n  Count:\t10000\n  Total:\t5.48 s\n  Slowest:\t3.67 ms\n  Fastest:\t0.33 ms\n  Average:\t0.48 ms\n  Requests/sec:\t1824.26\n\nResponse time histogram:\n  0.334 [1]    |\n  0.668 [9704] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  1.001 [245]  |∎\n  1.335 [34]   |\n  1.669 [8]    |\n  2.003 [4]    |\n  2.336 [3]    |\n  2.670 [0]    |\n  3.004 [0]    |\n  3.338 [0]    |\n  3.672 [1]    |\n\nLatency distribution:\n  10 % in 0.41 ms \n  25 % in 0.43 ms \n  50 % in 0.45 ms \n  75 % in 0.49 ms \n  90 % in 0.55 ms \n  95 % in 0.61 ms \n  99 % in 0.83 ms \n\nStatus code distribution:\n  [OK]   10000 responses   \n```\n\n- templater (template the response based on the request content):\n```shell\n\nSummary:\n  Count:\t10000\n  Total:\t2.08 s\n  Slowest:\t3.72 ms\n  Fastest:\t0.06 ms\n  Average:\t0.13 ms\n  Requests/sec:\t4806.56\n\nResponse time histogram:\n  0.063 [1]    |\n  0.429 [9928] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.795 [44]   |\n  1.161 [12]   |\n  1.527 [5]    |\n  1.893 [5]    |\n  2.259 [2]    |\n  2.625 [1]    |\n  2.991 [0]    |\n  3.357 [0]    |\n  3.723 [2]    |\n\nLatency distribution:\n  10 % in 0.09 ms \n  25 % in 0.10 ms \n  50 % in 0.11 ms \n  75 % in 0.13 ms \n  90 % in 0.17 ms \n  95 % in 0.22 ms \n  99 % in 0.39 ms \n\nStatus code distribution:\n  [OK]   10000 responses   \n```\n\n- request-matcher (match the entire content of the request, full match):\n```shell\nSummary:\n  Count:\t10000\n  Total:\t5.05 s\n  Slowest:\t10.87 ms\n  Fastest:\t0.21 ms\n  Average:\t0.42 ms\n  Requests/sec:\t1978.94\n\nResponse time histogram:\n  0.215  [1]    |\n  1.280  [9847] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  2.346  [93]   |\n  3.411  [35]   |\n  4.476  [10]   |\n  5.541  [6]    |\n  6.607  [3]    |\n  7.672  [0]    |\n  8.737  [3]    |\n  9.803  [1]    |\n  10.868 [1]    |\n\nLatency distribution:\n  10 % in 0.26 ms \n  25 % in 0.28 ms \n  50 % in 0.31 ms \n  75 % in 0.41 ms \n  90 % in 0.70 ms \n  95 % in 0.86 ms \n  99 % in 1.73 ms \n\nStatus code distribution:\n  [OK]   10000 responses   \n```\n\n- complex-request-matcher (match the content of the request with complex expressions, i.e. use of expr language):\n```shell\n\nSummary:\n  Count:\t10000\n  Total:\t2.05 s\n  Slowest:\t1.30 ms\n  Fastest:\t0.06 ms\n  Average:\t0.12 ms\n  Requests/sec:\t4886.13\n\nResponse time histogram:\n  0.063 [1]    |\n  0.186 [9362] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.310 [515]  |∎∎\n  0.433 [100]  |\n  0.556 [17]   |\n  0.680 [4]    |\n  0.803 [0]    |\n  0.926 [0]    |\n  1.050 [0]    |\n  1.173 [0]    |\n  1.297 [1]    |\n\nLatency distribution:\n  10 % in 0.09 ms \n  25 % in 0.10 ms \n  50 % in 0.11 ms \n  75 % in 0.13 ms \n  90 % in 0.16 ms \n  95 % in 0.21 ms \n  99 % in 0.33 ms \n\nStatus code distribution:\n  [OK]   10000 responses    \n```\n\n\u003c/details\u003e\n\n## status\nThe project is currently in active development, and breaking changes may occur until the release of version 1.0. However, we strive to minimize disruptions and will only introduce breaking changes when there is a compelling reason to do so.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemior001%2Fgroxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemior001%2Fgroxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemior001%2Fgroxy/lists"}