# Protoreg

`protoreg` provides a shared protobuf registry, enabling other plugins to access proto definitions at runtime, making it ideal for building custom gRPC interceptors, middleware, and dynamic message handling.

## Features

* Parses and registers `.proto` files at startup
* Provides a shared `protoregistry` accessible by other plugins
* Supports multiple import paths for proto dependencies
* Automatically resolves and registers proto file dependencies
* Exposes service and method descriptors for reflection-based operations
* Thread-safe registry access

## Use Cases

* **Custom gRPC Interceptors**: Build interceptors that need to inspect or modify messages based on their proto definitions
* **Dynamic Message Handling**: Deserialize and serialize protobuf messages without compile-time generated code
* **Request/Response Validation**: Validate gRPC requests against proto schemas
* **Logging & Monitoring**: Create detailed logs with field-level message inspection

## Configuration

{% hint style="warning" %}
The `protoreg` plugin must be enabled alongside the `grpc` plugin in your configuration. Without the `grpc` plugin enabled, the `protoreg` plugin will not be started.
{% endhint %}

The plugin is configured under the `protoreg` section in your `.rr.yaml` configuration file:

```yaml
version: '3'

protoreg:
  proto_path:
    - proto/commonapis
    - proto/serviceapis
  files:
    - service/v1/service.proto
    - user/v1/user.proto
```

### Configuration Options

#### `proto_path` (required)

A list of directories where the plugin will search for proto files and their dependencies. These paths work [similarly to the `--proto-path`/`-I` flag in `protoc`](https://www.mankier.com/1/protoc#--proto_path).

**Why it's needed**: Proto files often import other proto files (e.g., `import "google/protobuf/timestamp.proto"`). The import paths tell the parser where to find these dependencies. You typically need:

* A path containing your service definitions
* A path containing shared/common message definitions
* Paths to any third-party proto files (like Google's well-known types)

```yaml
proto_path:
  - proto/commonapis     # Shared message definitions
  - proto/serviceapis    # Service-specific proto definitions
  - proto/third_party    # External dependencies (e.g., google/api)
```

#### `files` (required)

A list of proto files to parse and register. Paths are relative to the `proto_path` directories. This works similarly to passing [variadic proto files argument in `protoc`](https://www.mankier.com/1/protoc#Options-@%3Cfilename%3E).

**Why it's needed**: This explicitly declares which proto files define the services and messages you want to access at runtime. The plugin will:

1. Parse each listed proto file
2. Automatically resolve and register all imported dependencies
3. Build a registry of all services and their methods

```yaml
files:
  - service/v1/service.proto    # Your gRPC service definitions
  - user/v1/user.proto          # Additional service definitions
```

## Example: Project Structure

A typical project structure when using this plugin:

```
my-project/
├── .rr.yaml
├── proto/
│   ├── commonapis/
│   │   └── common/
│   │       └── v1/
│   │           └── message.proto
│   └── serviceapis/
│       └── service/
│           └── v1/
│               └── service.proto
└── ...
```

**proto/commonapis/common/v1/message.proto**:

```protobuf
syntax = "proto3";

package common.v1;

message Request {
  string id = 1;
  string payload = 2;
}

message Response {
  string id = 1;
  string result = 2;
}
```

**proto/serviceapis/service/v1/service.proto**:

```protobuf
syntax = "proto3";

package service.v1;

import "common/v1/message.proto";

service MyService {
  rpc Process (common.v1.Request) returns (common.v1.Response) {}
  rpc Ping (common.v1.Request) returns (common.v1.Response) {}
}
```

**Configuration (.rr.yaml)**:

```yaml
version: '3'

protoreg:
  proto_path:
    - proto/commonapis
    - proto/serviceapis
  files:
    - service/v1/service.proto
```

## Using the Registry in Your Plugin

Other plugins can depend on `protoreg` to access the registry. Here's how to use it in a custom plugin:

```go
package myplugin

import (
	"github.com/jhump/protoreflect/desc"
	"github.com/jhump/protoreflect/v2/protoresolve"
)

type Registry interface {
	Registry() *protoresolve.Registry
	Services() map[string]*desc.ServiceDescriptor
	FindMethodByFullPath(method string) (*desc.MethodDescriptor, error)
}

type Plugin struct {
    registry Registry
}

func (p *Plugin) Init(registry Registry) error {

    p.registry = registry

    // Access the underlying registry
    reg := p.registry.Registry()

    // Get all registered services
    services := p.registry.Services()

    // Find a specific method by its full path
    method, err := p.registry.FindMethodByFullPath("/service.v1.MyService/Process")
    if err != nil {
        return err
    }

    // Use the method descriptor for reflection
    inputType := method.GetInputType()
    outputType := method.GetOutputType()

    return nil
}
```

### Registry Interface

The plugin exposes the following interface:

```go
type Registry interface {
    // Registry returns the underlying protoresolve.Registry that
	// contains all the parsed descriptors
    Registry() *protoresolve.Registry

    // Services returns a map of all registered service descriptors
    // keyed by their fully qualified name
    Services() map[string]*desc.ServiceDescriptor

    // FindMethodByFullPath finds a method descriptor by its full gRPC path
    // Format: "/package.Service/Method" or "package.Service/Method"
    FindMethodByFullPath(method string) (*desc.MethodDescriptor, error)
}
```

## Example: gRPC Interceptor

Here's an example of using the registry in a custom gRPC interceptor to log request details:

```go
package interceptor

import (
    "context"
    "log"

    "github.com/roadrunner-server/protoreg/v5"
    "google.golang.org/grpc"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/types/dynamicpb"
)

type Plugin struct {
    registry protoreg.Registry
}

func (i *Plugin) UnaryInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    // Find the method descriptor
    method, err := i.registry.FindMethodByFullPath(info.FullMethod)
    if err != nil {
        log.Printf("Method not found in registry: %s", info.FullMethod)
        return handler(ctx, req)
    }

    // Log method information
    log.Printf("Method: %s", method.GetFullyQualifiedName())
    log.Printf("Input type: %s", method.GetInputType().GetFullyQualifiedName())
    log.Printf("Output type: %s", method.GetOutputType().GetFullyQualifiedName())

    // You can also dynamically inspect the message fields
    inputDesc := method.GetInputType()
    for _, field := range inputDesc.GetFields() {
        log.Printf("  Field: %s (type: %s)", field.GetName(), field.GetType().String())
    }

    return handler(ctx, req)
}
```

## Troubleshooting

### "proto path does not exist"

Ensure all paths in `proto_path` exist and are accessible. Paths can be absolute or relative to the RoadRunner working directory.

### "proto file does not exist"

The proto file must exist in one of the configured `proto_path`. Check that:

1. The file path in `files` is relative to an import path, not an absolute path
2. The directory structure matches the import path

### "file already registered"

This occurs when the same proto file is included multiple times (e.g., listed in `files` and also imported by another file). The plugin handles dependencies automatically, so you typically only need to list your top-level service definitions in the `files` section.

## License

MIT License - see LICENSE file for details.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.roadrunner.dev/docs/grpc/protoreg.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
