Search code examples
protocol-buffersprotobuf-javaprotobuf.js

Import shared protobuf package into different protobuf packages


I'm designing some protobuf schemas for a project, what I want is to have independent packages, and a shared package. please see the example below.

// file: shared.proto
syntax = "proto3";
package package.shared;

message Address {
    string line = 1;
    string city = 2;
    string state = 3;
    string country = 4;
    uint32 zip = 5;
}

// file: invoice.proto
syntax = "proto3";
package package.invoice;

import "./shared.proto";

message Invoice {
    // some fields
    shared.Address address = 1;
}

// file: customer.proto
syntax = "proto3";
package package.customer;

import "./shared.proto";

message Customer {
    // some fields
    shared.Address address = 1;
}

I tried the above approach to design what I needed however, it's not working, getting the error Import Error: ./shared.proto is not found or has some error

Right now, I'm duplicating the shared fields with Invoice and Customer as a work around, I will be generating these protobuf's for TypeScript and Java once complete.

If anyone knows a proper solution, please answer.

Note: This question is not a duplicate question, I tried to find import and shared package-related documents or answers, but still no luck.

Thanks


Solution

  • It is a little gnarly.

    Think of protobuf packages like namespaces in your preferred programming language.

    Generally, you want to reflect the package structure via the folder structure.

    I tend to root my protos in a folder called protos. I'm using foo rather than package because package is a reserved name and could be problematic:

    .
    └── protos
        └── foo
            ├── invoice
            │   └── invoice.proto
            └── shared
                └── address.proto
    

    The folder names should match the package (!) path (foo.invoice, foo.shared) while the file names generally reflect the message name.

    invoice.proto:

    syntax = "proto3";
    
    package foo.invoice;
    
    import "foo/shared/address.proto";
    
    message Invoice {
        foo.shared.Address address = 1;
    }
    

    Invoice in package foo.invoice must reference Address by its fully-qualified package name foo.shared.Address.

    address.proto:

    syntax = "proto3";
    
    package foo.shared;
    
    message Address {
        string line = 1;
        string city = 2;
        string state = 3;
        string country = 4;
        uint32 zip = 5;
    }
    

    Then, using protoc:

    protoc \
    --proto_path=${PWD}/protos \
    --java_out=${PWD} \
    ${PWD}/protos/foo/shared/address.proto \
    ${PWD}/protos/foo/invoice/invoice.proto
    

    Because we've a single root (${PWD}/protos) for the protos we're using, we can using a single --proto_path=${PWD}/protos to anchor the set.

    It's necessary to fully specify the path (including the root) to each of the protos that we want to compile.

    Produces:

    .
    ├── foo
    │   ├── invoice
    │   │   └── InvoiceOuterClass.java
    │   └── shared
    │       └── AddressOuterClass.java
    └── protos
        └── foo
            ├── invoice
            │   └── invoice.proto
            └── shared
                └── address.proto