Challenge – We Need To Lint Protos Easily

Challenge - We need to lint proto files.

Proto linting with buf findings can be surprising. This write up describes options on how to reduce your buf linting failures to zero.

~ 3 minutes read
no time? jump straight to the conclusion

What is gRPC?

gRPC is a RPC framework. It is a popular framework and available for many languages including Kotlin. gRPC uses by default protocol buffer as its interface definition language.

What are proto files?

Proto files are

  • used to define the structure for the data to serialize with protocol buffers
  • text files with the .proto extension.

Protocol buffer data in proto files contains messages. Each message is a small logical block of information containing a series of name-value pairs called fields.

https://grpc.io/docs/what-is-grpc/introduction


With this data you define a contract between servers and clients: an API.
Proto files can be and shall be linted.

Why proto file linting?

Linting helps to define APIs consistently. Buf contains a CLI that can be used for proto file linting.

Linting Failures Fixes

I started with a simple hello world kotlin grpc example (branch initialState) that produced buf linting failures about

(find the full buf linting failures in the initialState appendix section)

lower snake case fields

First lint failure I worked around was lower snake case fields with adding the FIELD_LOWER_SNAKE_CASE exception (see also branch fieldLowerSnakeCase). With that edit lower snake case fields failures are gone. These failures are left:

(find the full buf linting failures in the fieldLowerSnakeCase appendix section)

service suffix

Next lint failure I fixed was service suffix with adding the SERVICE_SUFFIX exception (see also branch branch serviceSuffix). The remaining lint failures are:

(find the full buf linting failures in the serviceSuffix appendix section)

request standard name

The request standard name failure was fixed next with adding RPC_REQUEST_STANDARD_NAME exception (see also branch rpcRequestStandardName). That leaves these failures:

(find the full buf linting failures in the rpcRequestStandardName appendix section)

response standard name

After fixing the request related failure, the response related one must be fixed afterwards. Similar to the previous fix I added the RPC_RESPONSE_STANDARD_NAME exception (see also branch rpcResponseStandardName). The remaining failures are:

(find the full buf linting failures in the rpcResponseStandardName appendix section)

request response unique

There is a combined request-response failure that is not fixed so far. I could fix that by adding yet another exception: RPC_REQUEST_RESPONSE_UNIQUE (see also branch rpcRequestResponseUnique). With that there is only one kind of failure left to fix:

(find the full buf linting failures in the rpcRequestResponseUnique appendix section)

package assignment

The last package version failure required a new package structure. Not only the kotlin code package structure had to be changed, also the gradle build scripts and the proto files location.

No lint failures anymore – yay.

Conclusion

Only 6 edits are required to fix all buf proto linting failures in hello world kotlin grpc sample code:

  1. add FIELD_LOWER_SNAKE_CASE exception
  2. add SERVICE_SUFFIX exception
  3. add RPC_REQUEST_STANDARD_NAME exception
  4. add RPC_RESPONSE_STANDARD_NAME exception
  5. add RPC_REQUEST_RESPONSE_UNIQUE exception
  6. adapt packages

The main branch contains the code that fixed all linting failures.


Appendix

branch initialState

# buf lint src/main/proto --config src/main/proto/buf.yaml
src/main/proto/hello.proto:3:1:Files with package "info.ls.grpc" must be within a directory "info/ls/grpc" relative to root but were in directory ".".
src/main/proto/hello.proto:3:1:Package name "info.ls.grpc" should be suffixed with a correctly formed version, such as "info.ls.grpc.v1".
src/main/proto/hello.proto:7:9:Service name "Greeter" should be suffixed with "Service".
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:22:RPC request type "SayHelloRequest" should be named "SayHelloAgainRequest" or "GreeterSayHelloAgainRequest".
src/main/proto/hello.proto:9:48:RPC response type "SayHelloResponse" should be named "SayHelloAgainResponse" or "GreeterSayHelloAgainResponse".
src/main/proto/hello.proto:13:10:Field name "Name" should be lower_snake_case, such as "name".
src/main/proto/hello.proto:17:10:Field name "Message" should be lower_snake_case, such as "message".

branch fieldLowerSnakeCase

# buf lint src/main/proto --config src/main/proto/buf.yaml
src/main/proto/hello.proto:3:1:Files with package "info.ls.grpc" must be within a directory "info/ls/grpc" relative to root but were in directory ".".
src/main/proto/hello.proto:3:1:Package name "info.ls.grpc" should be suffixed with a correctly formed version, such as "info.ls.grpc.v1".
src/main/proto/hello.proto:7:9:Service name "Greeter" should be suffixed with "Service".
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:22:RPC request type "SayHelloRequest" should be named "SayHelloAgainRequest" or "GreeterSayHelloAgainRequest".
src/main/proto/hello.proto:9:48:RPC response type "SayHelloResponse" should be named "SayHelloAgainResponse" or "GreeterSayHelloAgainResponse".

branch serviceSuffix

# buf lint src/main/proto --config src/main/proto/buf.yaml
src/main/proto/hello.proto:3:1:Files with package "info.ls.grpc" must be within a directory "info/ls/grpc" relative to root but were in directory ".".
src/main/proto/hello.proto:3:1:Package name "info.ls.grpc" should be suffixed with a correctly formed version, such as "info.ls.grpc.v1".
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:22:RPC request type "SayHelloRequest" should be named "SayHelloAgainRequest" or "GreeterSayHelloAgainRequest".
src/main/proto/hello.proto:9:48:RPC response type "SayHelloResponse" should be named "SayHelloAgainResponse" or "GreeterSayHelloAgainResponse".

branch rpcRequestStandardName

# buf lint src/main/proto --config src/main/proto/buf.yaml
src/main/proto/hello.proto:3:1:Files with package "info.ls.grpc" must be within a directory "info/ls/grpc" relative to root but were in directory ".".
src/main/proto/hello.proto:3:1:Package name "info.ls.grpc" should be suffixed with a correctly formed version, such as "info.ls.grpc.v1".
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:48:RPC response type "SayHelloResponse" should be named "SayHelloAgainResponse" or "GreeterSayHelloAgainResponse".

branch rpcResponseStandardName

# buf lint src/main/proto --config src/main/proto/buf.yaml
src/main/proto/hello.proto:3:1:Files with package "info.ls.grpc" must be within a directory "info/ls/grpc" relative to root but were in directory ".".
src/main/proto/hello.proto:3:1:Package name "info.ls.grpc" should be suffixed with a correctly formed version, such as "info.ls.grpc.v1".
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:8:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloRequest" is used as the request or response type for multiple RPCs.
src/main/proto/hello.proto:9:3:".info.ls.grpc.SayHelloResponse" is used as the request or response type for multiple RPCs.

branch rpcRequestResponseUnique

# buf lint src/main/proto --config src/main/proto/buf.yaml
src/main/proto/hello.proto:3:1:Files with package "info.ls.grpc" must be within a directory "info/ls/grpc" relative to root but were in directory ".".
src/main/proto/hello.proto:3:1:Package name "info.ls.grpc" should be suffixed with a correctly formed version, such as "info.ls.grpc.v1".

branch fixPackageAssignment

buf lint src/main/proto --config src/main/proto/buf.yaml
[no lint failures anymore]

Source Code

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.