protoc-gen-gofilter

High-performance, Zero-Reflection Field-Level Permission Control for Go Protobuf.
protoc-gen-gofilter is a Protoc plugin designed to solve "Field-Level" granular permission control issues in gRPC/Protobuf services. It generates efficient filtering code via Code Generation, avoiding the performance overhead and type unsafety of traditional Go Reflection.
β¨ Features
- π Extreme Performance: Implemented based on BitSet (bitmask), filtering operations require only O(1) bitwise operations, which is tens of times faster than reflection.
- π‘οΈ Zero Reflection: All filtering logic is generated at Compile-time, with no runtime reflection overhead.
- π Declarative Definition: Define permission rules directly in
.proto files, serving as a Single Source of Truth (SSOT).
- π§© Decoupled Architecture: Separates "Permission Decision" from "Permission Enforcement", perfectly supporting mixed RBAC and ABAC models.
- π§ Easy Integration: Generated code provides standard interfaces, easily integrated into gRPC Interceptors or Middleware.
π¦ Installation
go install github.com/hsuanshao/protoc-gen-gofilter/cmd/protoc-gen-gofilter@latest
2. Download Runtime Library
Import the dependency in your project:
go get github.com/hsuanshao/protoc-gen-gofilter
```ds
## π Quick Start
### Step 1: Define Proto
In your `.proto` file, import `filter.proto` and use the `(filter.apply)` option to tag fields that require permission control.
> π‘ **Tip**: For convenience, it is recommended to copy `pb/filter.proto` from this project to your `third_party` directory, or use vendoring.
**myapp.proto**
```proto
syntax = "proto3";
package myapp;
option go_package = "example.com/my-project/pb/myapp";
// 1. Import definition
import "github.com/hsuanshao/protoc-gen-gofilter/protos/filter/filter.proto";
message UserProfile {
int64 id = 1;
string name = 2;
// 2. Tag permission: Only users with "user.email.read" permission can see this field
string email = 3 [(filter.apply) = "user.email.read"];
// Supports repeated and optional fields
repeated string secrets = 4 [(filter.apply) = "admin.secrets.read"];
}
Step 2: Generate Go Code
When running protoc, add the --gofilter_out parameter.
protoc --go_out=. --go_opt=paths=source_relative \
--gofilter_out=. --gofilter_opt=paths=source_relative \
--proto_path=. \
myapp.proto
After execution, you will see myapp_filter.pb.go generated, containing the FilterFields method.
Step 3: Use in Go
In your business logic or gRPC Interceptor, calculate user permissions and execute filtering.
package main
import (
"fmt"
"github.com/hsuanshao/protoc-gen-gofilter/entity/filter"
"example.com/my-project/pb/myapp"
)
func main() {
// 1. Simulate data
data := &myapp.UserProfile{
Id: 1,
Name: "Alice",
Email: "alice@example.com",
Secrets: []string{"top-secret"},
}
// 2. [Decision Layer] Calculate permission BitMask based on user role
// In real scenarios, this is usually combined with a Policy Engine (e.g., OPA, Casbin, or DB rules)
userMask := filter.NewBitSet()
// Assume the user has permission to read Email but not Secrets
// Registry.GetID returns the unique integer ID for the permission string
if emailPermID, ok := filter.Registry.GetID("user.email.read"); ok {
userMask.Set(emailPermID)
}
// 3. [Enforcement Layer] Execute filtering
// FilterFields is an auto-generated method, extremely fast
data.FilterFields(userMask)
// 4. Verify results
fmt.Printf("Email: %s\n", data.Email) // Output: alice@example.com
fmt.Printf("Secrets: %v\n", data.Secrets) // Output: [] (filtered to nil)
}
ποΈ Architecture Design
This tool uses a Permission Flattening strategy:
- Compile Time: The Plugin scans
.proto files, extracts all permission strings (e.g., "user.email.read"), and generates an init() function to automatically register them with the global Registry at startup, obtaining unique int IDs.
- Runtime:
- BitSet: Uses a dynamically sized
[]uint64 to represent sets of permissions.
- Check: Generated code directly uses
mask.Has(int_id) for bitwise checks. If the check fails, the field is directly set to its Zero Value (nil, 0, "").
This design moves the overhead of string comparison to startup time, leaving only extremely efficient integer operations during request processing.
π€ Contributing
Contributions via Issues or Pull Requests are welcome!
- Fork this project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature)
- Commit your changes (
git commit -m 'Add some AmazingFeature')
- Push to the Branch (
git push origin feature/AmazingFeature)
- Open a Pull Request
π License
Distributed under the MIT License. See LICENSE for more information.