Protocol Buffers/Protobuf & OAS
Protocol Buffers
Use ApiHug swagger extensions to map protobuf services to HTTP APIs and OpenAPI documentation.
The OpenAPI Specification provides a standard, language-agnostic way to describe HTTP APIs. Protocol Buffers already act as an IDL for typed contracts, so ApiHug extends protobuf with swagger-specific metadata and uses that single definition to generate:
ApiHug does this through the apihug/protobuf/swagger/annotations.proto extension points:
| Level | Proto element | Extension |
|---|---|---|
| Service | service | (hope.swagger.svc) |
| Method | rpc | (hope.swagger.operation) |
| Message | message | (hope.swagger.schema) |
| Field | field | (hope.swagger.field) |
| Enum | enum | (hope.swagger.enm) |
import "apihug/protobuf/swagger/annotations.proto";
If you use mock rules in swagger field or response customization, also import:
import "apihug/protobuf/mock/mock.proto";
hope.swagger.svc defines the service base path and service description.
service UserAdminService {
option (hope.swagger.svc) = {
path: "/user/admin";
description: "User admin server";
};
}
Use this to keep the service-level route prefix and human-readable description in one place.
hope.swagger.operation is the core extension. It maps one RPC to one HTTP-facing operation.
rpc RegisterCustomer (RegisterRequest) returns (CustomerRegisteredResponse) {
option (hope.swagger.operation) = {
post: "/register";
description: "Admin registers a new customer";
tags: "user";
priority: CRITICAL;
group: TENANT;
authorization: {
rbac: {
authorities: ["USER_ADD"];
}
};
};
}
The common pieces are:
get, post, put, patch, deletedescription, summarytags, groupauthorizationdeprecated, internal, hidequestionsApiHug distinguishes between pageable results and plain repeated collections.
pageable: true means the operation should behave like a paginated query:
page, size, sortrpc SearchPets (PetQueryRequest) returns (Pet) {
option (hope.swagger.operation) = {
post: "/pets/search";
pageable: true;
description: "Paginated pet query";
};
}
Use input_repeated and output_repeated when the API is list-style but not pageable.
rpc BatchCreateUsers (CreateUserRequest) returns (UserResponse) {
option (hope.swagger.operation) = {
post: "/users/batch";
input_repeated: true;
output_repeated: true;
description: "Batch create users";
};
}
Deprecated compatibility fields still exist in the source proto:
input_pluralout_pluralNew definitions should use:
input_repeatedoutput_repeatedBy default, ApiHug may wrap responses according to the framework conventions. raw: true tells the toolchain to preserve the response shape directly instead of applying the normal wrapper.
rpc Healthz (google.protobuf.Empty) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
get: "/healthz";
raw: true;
description: "Raw health response";
};
}
Some operation flags are for controller/runtime integration rather than schema shape.
session: true tells the Java runtime that the controller method needs access to HttpSession.
rpc UpdateWizardState (WizardStateRequest) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
post: "/wizard/state";
session: true;
description: "Update state backed by HttpSession";
};
}
Use this only when the generated controller really needs the servlet session object. It is not a replacement for ordinary query, header, or cookie parameters.
Use parameters to describe path/query/header/cookie/session style inputs explicitly. This is especially important when the transport-level parameters are not fully expressed by the request message shape alone.
rpc GetPetById (google.protobuf.Empty) returns (Pet) {
option (hope.swagger.operation) = {
get: "/pets/{id}";
parameters: {
parameter: {
name: "id";
in: PATH;
schema: {
format: LONG;
minimum: 1;
};
};
parameter: {
name: "include-deleted";
in: QUERY;
schema: {
format: BOOLEAN;
};
};
};
};
}
For list-style query/path/header inputs, the parameter-level repeated flag is is_repeated.
When one field may be rendered into overlapping path templates, use field_configuration.path_param_name to force the exact path variable name.
string owner_id = 1 [(hope.swagger.field) = {
description: "Owner id";
field_configuration: {
path_param_name: "owner-id";
};
}];
This is especially useful for generated OpenAPI output where the default derived field name would create an awkward or conflicting path placeholder.
response_media_type customizes the declared output media type when JSON is not the right contract.
rpc ExportPdf (google.protobuf.Empty) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
get: "/export/pdf";
response_media_type: APPLICATION_PDF;
raw: true;
};
}
For upload-style APIs, use multipart-related configuration.
rpc UploadMeta (OpenApiMetaUploadRequest) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
post: "/upload-meta";
consumes: "multipart/form-data";
multipart: true;
description: "Upload API metadata";
};
}
The old multiple flag still exists in the proto as a deprecated compatibility alias. Prefer multipart.
ApiHug can enrich request and response definitions beyond the raw message field types.
request_schema adds JSON-schema-like request payload metadata and validation hints.
option (hope.swagger.operation) = {
post: "/register";
request_schema: {
title: "RegisterCustomerPayload";
description: "Validated customer registration body";
};
};
response_schema customizes the shape and metadata of a primitive or custom response payload.
option (hope.swagger.operation) = {
get: "/ping";
response_schema: {
title: "PingResponse";
description: "Simple primitive response";
};
};
body_empty allows an intentionally empty response body when that is part of the contract.
option (hope.swagger.operation) = {
delete: "/pets/{id}";
body_empty: true;
};
For primitive response customization, mock can be attached at the response customization level, and field-level mock can still be used through hope.swagger.field.
group marks the operation audience:
CUSTOMERTENANTPLATFORMThis is useful when one proto surface feeds multiple consumer-facing contexts.
internal: true marks the operation as internal-onlyhide: true suppresses it from external-facing documentation viewsoption (hope.swagger.operation) = {
post: "/internal/rebuild-cache";
internal: true;
hide: true;
};
questions is a repeated natural-language hint field for AI/LLM usage. It helps make the API easier for agents to discover semantically, not just structurally.
option (hope.swagger.operation) = {
post: "/orders/search";
questions: "How do I search recent orders?";
questions: "Query paged order data for a customer";
};
This is especially useful when the generated contract is consumed by MCP/tooling or other agent-facing layers.
service PetService {
option (hope.swagger.svc) = {
path: "/pet";
description: "Pet manager service";
};
rpc ListPets (PetQueryRequest) returns (Pet) {
option (hope.swagger.operation) = {
post: "/list";
pageable: true;
description: "Paginated pet list";
tags: "pet";
group: TENANT;
questions: "How do I list pets with paging?";
authorization: {
rbac: {
authorities: ["PET_VIEW"];
}
};
};
};
rpc UploadPhoto (UploadRequest) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
post: "/upload";
consumes: "multipart/form-data";
multipart: true;
body_empty: true;
description: "Upload pet photo";
};
};
}
Use this page as the practical overview. For the field-level reference of every swagger extension message and operation option, see: