0

I'm new to Rust and trying to create a simple gRPC application. Here is the directory structure:

grpc-protobuf
├── Cargo.toml
├── build.rs
├── proto
│   ├── hello
│   │   └── hello.proto
│   └── messages
│       └── hello.proto
└── src
    └── lib.rs

I have the protobuf messages defined under proto/messages/hello.proto and below is the file content:

syntax = "proto3";
package proto.messages.v1;

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

The service that uses the messages is defined as proto/hello/hello.proto:

syntax = "proto3";
package hello;

import "messages/hello.proto";

service Greeter {
  rpc SayHello (proto.messages.v1.HelloRequest) returns (proto.messages.v1.HelloResponse) {}
}

And here is how I'm creating a package:

pub mod hello {
    tonic::include_proto!("hello");
}

pub mod messages {
    tonic::include_proto!("proto.messages.v1");
}

As I try to build the package I get the following errors:

➜ cargo build -p grpc-protobuf
   Compiling grpc-protobuf v0.1.0 (/Users/gaurav.gahlot/workspace/rusty/rust-grpc/grpc-protobuf)
error[E0433]: failed to resolve: could not find `proto` in the crate root
  --> /Users/gaurav.gahlot/workspace/rusty/rust-grpc/target/debug/build/grpc-protobuf-6fc85551b5cb3a84/out/hello.rs:91:31
   |
91 |                 super::super::proto::messages::v1::HelloRequest,
   |                               ^^^^^ could not find `proto` in the crate root

error[E0433]: failed to resolve: could not find `proto` in the crate root
  --> /Users/gaurav.gahlot/workspace/rusty/rust-grpc/target/debug/build/grpc-protobuf-6fc85551b5cb3a84/out/hello.rs:94:43
   |
94 |             tonic::Response<super::super::proto::messages::v1::HelloResponse>,
   |                                           ^^^^^ could not find `proto` in the crate root

error[E0433]: failed to resolve: could not find `proto` in the crate root
   --> /Users/gaurav.gahlot/workspace/rusty/rust-grpc/target/debug/build/grpc-protobuf-6fc85551b5cb3a84/out/hello.rs:124:51
    |
Gaurav Gahlot
  • 1,583
  • 3
  • 14
  • 19
  • Drive-by comment: proto `package` hierarchy is challenging but consistent. There are 2 components: the file system (root) path (I'd recommend you consider using `${PWD}/proto`) and the `package` path (which is relative to the root path). In this case, you have one package `package messages` (not `proto` because that's the root; and not `v1` because there's no folder `v1`) and `package hello` (as you have it). This would be a correct input structure for protoc. – DazWilkin Jul 09 '23 at 15:32
  • It should then be `rpc SayHello(messages.HelloRequest) return (messages.HelloResponse)` – DazWilkin Jul 09 '23 at 15:35
  • I'm going to repro your code as I'll find that the easiest way to debug it. – DazWilkin Jul 09 '23 at 15:35
  • I'd encourage you to rename `messages/hello.proto` to `messages/messages.proto` too. – DazWilkin Jul 09 '23 at 15:40
  • @DazWilkin Thank you for looking into it. I already tried using `package messages` instead of `package proto.messages.v1` and it worked with the lib setup as well. Considering your idea I have put the complete code at [gauravgahlot/rust-grpc](https://github.com/gauravgahlot/rust-grpc) GitHub repo. The `main` branch as the working code. The PR on the other hand has the versioning model I'm trying to achieve. – Gaurav Gahlot Jul 09 '23 at 17:48
  • Your PR is fine except you want `tonic::include_proto!("messages.v1");`. This is where it's a good idea to verify what `protoc` generated by looking at the `OUT_DIR` content. – DazWilkin Jul 09 '23 at 18:15
  • `protoc` generated `messages.hello.v1.rs` and that's what I have in the PR as well. However, the build fails with the error: `tonic::Response, ^^^^^ could not find "hello" in "messages"` You may check the build error [here](https://github.com/gauravgahlot/rust-grpc/actions/runs/5501362643/jobs/10024892525?pr=1). – Gaurav Gahlot Jul 09 '23 at 18:39
  • You're not correctly referencing the `Hello*` Messages in the new package hierarchy. It is now `messages.hello.v1` (not just `messages`). So you need e.g. `messages::hello::v1::{HelloResponse, HelloRequest}` and `pub mod messages { pub mod hello { pub mod v1 { tonic::include_proto!("messages.hello.v1"); }}}` – DazWilkin Jul 09 '23 at 19:33
  • Aha! That's what I was missing. Thank you so much for your help. Please update your answer accordingly, and I would happy be to accept it – Gaurav Gahlot Jul 10 '23 at 02:14

1 Answers1

0

I've replicated your file structure (I'm using bin rather than lib):

.
├── build.rs
├── Cargo.toml
├── proto
│   ├── hello
│   └── messages
├── src
│   └── main.rs
└── target

proto/hello/hello.proto:

syntax = "proto3";

package hello;

import "messages/messages.proto";

service Greeter {
  rpc SayHello (messages.HelloRequest) returns (messages.HelloResponse) {}
}

proto/messages/messages.proto:

syntax = "proto3";

package messages;

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

build.rs:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::configure()
        .build_server(true)
        .compile(
            &[
                "proto/hello/hello.proto",
                "proto/messages/messages.proto",
            ],
            &["proto"],
        )?;
    Ok(())
}

NOTE tonic-build permits crate relative addresses. The proto_path is defined to be proto and then individual protobuf files must include one of the proto_path's (we only have proto) and the package path.

main.rs:

pub mod hello {
    tonic::include_proto!("hello");
}
pub mod messages {
    tonic::include_proto!("messages");
}

use hello::{
    greeter_server::{Greeter, GreeterServer},
};
use messages::{
    HelloRequest, HelloResponse,
};

Coming from using gRPC mostly with Go and some Python, I like how tonic works but found the protoc generation initially confusing. tonic-build uses OUT_DIR as the location for the protoc generated sources. You can:

ls target/debug/build/grpc-protobuf*/out

Yielding:

target/debug/build/grpc-protobuf-be913437690683ab/out:
hello.rs  messages.rs
Update

When you have a nested package hierarchy, e.g. package messages.v1, there are several changes in particular to the rust mod hierarchy to reflect the package hierarchy:

.
├── build.rs
├── Cargo.lock
├── Cargo.toml
├── proto
│   ├── hello
│   │   └── hello.proto
│   └── messages
│       └── v1
│           └── messages.proto
├── README.md
├── src
│   └── main.rs
└── target

NOTE folder structure proto/messages/v1 to reflect revised package name.

proto/hello/hello.proto:

syntax = "proto3";

package hello;

import "messages/v1/messages.proto";

service Greeter {
  rpc SayHello (messages.v1.HelloRequest)
    returns (messages.v1.HelloResponse) {}
}

NOTE messages.v1.Hello*

proto/messages/v1/messages.proto:

syntax = "proto3";

package messages.v1;

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

NOTE package path updated.

build.rs:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::configure()
        .build_server(true)
        .compile(
            &[
                "proto/hello/hello.proto",
                "proto/messages/v1/messages.proto",
            ],
            &["proto"],
        )?;

    Ok(())
}

NOTE proto imports updated.

main.rs:

pub mod hello {
    tonic::include_proto!("hello");
}
pub mod messages {
    pub mod v1 {
        tonic::include_proto!("messages.v1");
    }
}

use hello::{
    greeter_server::{Greeter,GreeterServer},
};
use messages::v1::{
    HelloRequest, HelloResponse,
};

NOTE pub mod messages { pub mod v1 { ... }} and use updated.

DazWilkin
  • 32,823
  • 5
  • 47
  • 88