
Spec
API Interface Extension User Manual
Map gRPC Service to HTTP RESTful API and generate OpenAPI/Swagger documentation.
Extension Package: apihug/protobuf/swagger/swagger.proto + annotations.proto
Scope: Service/Method(rpc)/Message/Field
Scenarios: HTTP API, OpenAPI documentation, frontend-backend coordination
NEVER add any file-level option declarations in proto files:
// ❌ Forbidden to use the following options (rely on default code generation configuration)
option java_package = "...";
option java_multiple_files = true;
option go_package = "...";
All code generation configurations are automatically managed by the build system. Strictly forbidden to manually add any file-level options.
API layer (api/) must not reference Domain entities:
// ❌ Forbidden to reference domain layer entities
import "com/example/domain/entities/user.proto"; // Wrong!
import "com/example/domain/entities/order.proto"; // Wrong!
message CreateUserRequest {
// ❌ Forbidden to use Domain Entity types
UserEntity user = 1; // Wrong!
repeated OrderEntity orders = 2; // Wrong!
}
✅ Correct approach:
// ✅ Allow referencing constant layer (infra/)
import "com/example/infra/settings/user_constant.proto";
// ✅ Allow referencing other API messages
import "com/example/api/common/response.proto";
message CreateUserRequest {
string user_name = 1; // ✅ Use basic types
UserStatusEnum status = 2; // ✅ Enums can be shared
CommonResponse response = 3; // ✅ API internal reference
}
Architecture Principles: API layer is responsible for interface contracts, Domain layer is responsible for data persistence, both are transformed through Service layer, strictly forbidden to directly depend.
import "apihug/protobuf/swagger/annotations.proto";
Optional import (only when Mock data is needed):
import "apihug/protobuf/mock/mock.proto";
| Level | proto Element | Extension Point | Function |
|---|---|---|---|
| Service | service | (hope.swagger.svc) | Define service base path, description |
| Method | rpc | (hope.swagger.operation) | Define HTTP method, path, permissions, etc. |
| Message | message | (hope.swagger.schema) | Define request/response object description |
| Field | field | (hope.swagger.field) | Define field constraints, examples, validation rules |
service ServiceName {
option (hope.swagger.svc) = {
path: "/base_path";
description: "Service description";
};
// rpc methods...
}
syntax = "proto3";
package com.example.pet.service;
import "apihug/protobuf/swagger/annotations.proto";
service PetService {
option (hope.swagger.svc) = {
path: "/pet";
description: "Pet Manager service";
};
rpc GetPet (...) returns (...) {
// Method definition...
}
}
path (string)/user, /order, /pet)path: "/pet"description (string)description: "Pet management service"rpc MethodName (RequestType) returns (ResponseType) {
option (hope.swagger.operation) = {
post: "/path"; // or get/put/patch/delete
description: "Method description";
// Other configurations...
};
}
| Method | Use Case | Example Path |
|---|---|---|
get | Query resources | get: "/users" |
post | Create resources, complex queries | post: "/users" |
put | Full update resources | put: "/users/{id}" |
patch | Partial update resources | patch: "/users/{id}" |
delete | Delete resources | delete: "/users/{id}" |
Use {variable_name} to define path parameters:
option (hope.swagger.operation) = {
get: "/pet/{id}";
// ...
};
rpc UploadMeta (OpenApiMetaUploadRequest) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
post: "/upload-meta";
description: "Open platform upload API metadata";
tags: "open";
group: TENANT;
authorization: {
low_limit_risky_mode: ANONYMOUS
};
priority: HIGH;
consumes: "multipart/form-data"; // File upload
};
}
rpc ReturnPageable (Customer) returns (Pet) {
option (hope.swagger.operation) = {
post: "/batch-user-pet";
pageable: true; // Enable pagination
description: "Return pageable data based on request";
tags: "user";
tags: "pet";
request_name: "pet";
};
}
rpc UpdateByPath (google.protobuf.Empty) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
post: "/update-by-path/{user-id}/try-more/{user-name}";
description: "Test two path parameters";
tags: "other";
parameters: {
parameter: {
name: "user-id";
in: PATH;
schema: {
format: INTEGER;
maximum: 12345
};
};
parameter: {
name: "user-name";
in: PATH;
schema: {
format: STRING;
empty: false;
pattern: "^[A-Za-z0-9+_.-]+@(.+)$";
max_length: 64
};
}
}
};
}
rpc PlaceOrder (google.protobuf.Empty) returns (PlaceOrderRequest) {
option (hope.swagger.operation) = {
post: "/place-order";
description: "Place order operation";
summary: "Place order detailed operation";
tags: "user";
tags: "pet";
security: {
security_requirement: {
key: "jwt";
value: {
scope: "pet:read";
scope: "pet:write";
};
}
}
};
}
description (string)description: "Upload file metadata"summary (string)<120 characters)summary: "Create order"tags (repeated string)tags: "user";
tags: "order";
operation_id (string)operation_id: "getUserById"request_name (string)request_name: "petRequest"pageable (optional bool)Type: optional bool (Optional boolean!)
Function: Enable paginated query
Effect:
page, size, sort parametersPage<T> structureExample:
pageable: true
input_repeated / output_repeated (optional bool)Type: optional bool (Optional boolean!)
Function: Mark request/response as list
Example:
input_repeated: true; // Request is List<T>
output_repeated: true; // Response is List<T>
Deprecated field warning:
input_plural (deprecated) → Use input_repeatedout_plural (deprecated) → Use output_repeatedraw (optional bool)Function: Do not wrap response (return raw type directly)
Example:
raw: true // Don't wrap as Result<T>
body_empty (bool)Function: Allow response body to be empty
Example:
body_empty: true // POST returns empty body
consumes (repeated string)"application/json", "multipart/form-data"consumes: "multipart/form-data";
produces (repeated string)produces: "application/json";
response_media_type (MediaType enum)Type: Operation.MediaType enum
Function: Specify response media type
Common values:
| Enum Value | MIME Type | Use Case |
|---|---|---|
APPLICATION_JSON | application/json | JSON response (default) |
TEXT_PLAIN | text/plain | Plain text |
TEXT_HTML | text/html | HTML page |
APPLICATION_PDF | application/pdf | PDF file |
APPLICATION_XLSX | … | Excel file |
IMAGE_PNG | image/png | PNG image |
MULTIPART_FORM_DATA | multipart/form-data | File upload |
Example:
response_media_type: APPLICATION_PDF;
parameters (Parameters message)Function: Define path/query/header/cookie/session parameters
⚠️ Mandatory rules:
/{id}) must be declared in parametersparametersParameter Fields:
| Field | Type | Description |
|---|---|---|
name | string | Parameter name |
in | IN enum | Location (QUERY/PATH/HEADER/COOKIE/SESSION) |
schema | JSONSchema | Parameter constraints |
plural | bool | Is array |
IN Enum Values:
| Enum Value | Meaning |
|---|---|
QUERY | Query parameter (?key=value) |
PATH | Path parameter (/{id}) |
HEADER | HTTP header (Authorization: xxx) |
COOKIE | Cookie |
SESSION | Session (framework-specific) |
Example:
parameters: {
parameter: {
name: "user-id";
in: QUERY;
plural: true; // Array parameter
schema: {
format: INTEGER;
description: "user id";
}
};
parameter: {
name: "start-date";
in: QUERY;
schema: {
format: DATE;
description: "begin date";
empty: false;
date_format: BASIC_ISO_DATE;
}
}
}
authorization (Authorization message)Method 1: Low-level risk mode
authorization: {
low_limit_risky_mode: ANONYMOUS // or LOGIN / ACTIVE
}
LowLimitRiskyMode enum:
| Enum Value | Meaning |
|---|---|
ANONYMOUS | No login required |
LOGIN | Login required |
ACTIVE | Login required and account activated |
Method 2: RBAC (Role + Permission)
authorization: {
rbac: {
roles: {
roles: ["ROLE_ADMIN", "ROLE_USER"]
};
authorities: ["USER_CREATE", "USER_DELETE"];
combinator: AND; // or OR
}
}
RBAC Fields:
roles.roles: Role listauthorities: Permission list (must be defined in Authority enum)combinator: Combination method (AND=both must be satisfied, OR=satisfy one)Method 3: SpEL Expression
authorization: {
expression: "hasRole('ADMIN') and #userId == authentication.principal.id"
}
group (Group enum)Function: Mark the frontend group to which the interface belongs
Optional values:
| Enum Value | Meaning |
|---|---|
CUSTOMER | User-side (C-side) |
TENANT | Tenant management side (B-side) |
PLATFORM | Platform operation side |
Example:
group: TENANT;
priority (Priority enum)Function: Mark interface importance
Optional values:
| Enum Value | Value | Meaning |
|---|---|---|
NA | 0 | Not set |
LOW | 1 | Low priority |
MIDDLE | 4 | Medium (requires team lead approval) |
HIGH | 8 | High (requires project manager approval) |
CRITICAL | 16 | Critical (requires VP approval) |
FATAL | 32 | May affect business (requires CTO approval) |
Example:
priority: HIGH;
internal (bool)internal: truehide (bool)hide: truedeprecated (bool)deprecated: truequestions (repeated string)Function: Natural language questions (for LLM to understand interface purpose)
Example:
questions: [
"How to get user order list?",
"Query the most recent page of order data",
"How to get user orders?"
];
message MessageName {
option (hope.swagger.schema) = {
json_schema: {
title: "Object title";
description: "Object description";
};
};
// Field definitions...
}
syntax = "proto3";
package com.example.bean;
import "swagger/annotations.proto";
message PlaceOrderRequest {
option (hope.swagger.schema) = {
json_schema: {
title: "PlaceOrder";
description: "Place order request";
};
external_docs: {
url: "https://example.com/order-design";
description: "Order design detailed information"
}
};
uint64 id = 1 [(hope.swagger.field) = {
description: "Request ID";
empty: false;
maximum: 12345;
example: "1111";
}];
// More fields...
}
description (string)description: "User ID"example (string)example: "13800138000"title (string)title: "User Name"default (string)default: "ACTIVE"Important: The following three fields are oneof EmptyConstraint, only one can be selected!
empty (bool)empty: false // Cannot be empty
blank (bool)blank: false // Cannot be blank
nullable (bool)nullable: false // Cannot be null
Selection recommendation:
String, forbid empty → empty: false
String, forbid blank → blank: false
Number/Date, forbid null → nullable: false
Collection, forbid empty → empty: false
Type: uint64
max_length: 100;
min_length: 1;
Type: uint64
max_items: 50;
min_items: 1;
Type: uint64
max_properties: 20;
min_properties: 1;
Type: double
maximum: 12345;
minimum: 1;
Type: bool enum
exclusive_maximum: true; // Open interval (value < max)
exclusive_minimum: true; // Open interval (min < value)
Type: double
multiple_of: 10; // Must be a multiple of 10
pattern (string)pattern: "^[A-Za-z0-9+_.-]+@(.+)$"; // Email format
pattern: "^1[3-9]\\d{9}$"; // Phone number
enum (repeated string)enum: ["PENDING", "APPROVED", "REJECTED"];
format (JSONSchemaFormat enum)Function: Explicitly specify field type (used for path/query parameters)
Common values:
| Enum Value | Corresponding Type | Use Case |
|---|---|---|
STRING | string | String |
INTEGER | int32 | Integer |
LONG | int64 | Long integer |
DOUBLE | double | Float |
BOOLEAN | bool | Boolean |
DATE | LocalDate | Date (yyyy-MM-dd) |
DATE_TIME | LocalDateTime | Date time |
TIME | LocalTime | Time |
UUID | UUID | UUID string |
EMAIL | string | |
PASSWORD | string | Password (hide display) |
BINARY | bytes | Binary data |
Example:
uint64 id = 1 [(hope.swagger.field) = {
format: LONG; // Explicitly specify as Long type
}];
time_constraint_type (TimeConstraintType enum)Optional values:
| Enum Value | Meaning | Equivalent Jakarta Annotation |
|---|---|---|
NA | No restriction | - |
FUTURE | Must be future time | @Future |
FUTURE_OR_PRESENT | Future or present | @FutureOrPresent |
PAST | Must be past time | @Past |
PAST_OR_PRESENT | Past or present | @PastOrPresent |
Example:
string expire_date = 1 [(hope.swagger.field) = {
time_constraint_type: FUTURE; // Must be future date
}];
date_format (DateFormat enum) or customized_date_format (string)Predefined formats:
| Enum Value | Format | Example |
|---|---|---|
BASIC_ISO_DATE | yyyyMMdd | 20231225 |
ISO_LOCAL_DATE | yyyy-MM-dd | 2023-12-25 |
ISO_TIME | HH:mm:ss.SSSSSSS | 10:15:30.123 |
ISO_LOCAL_TIME | HH:mm:ss | 10:15:30 |
ISO_LOCAL_DATE_TIME | yyyy-MM-didn’t’HH:mm:ss | 2023-12-25T10:15:30 |
YYYY_MM_DD_HH_MM_SS | yyyy-MM-dd HH:mm:ss | 2023-12-25 10:15:30 |
YYYY_MM_DD_HH_MM_SS_SSS | yyyy-MM-dd HH:mm:ss:SSS | 2023-12-25 10:15:30:123 |
Custom format:
customized_date_format: "yyyy/MM/dd";
Example:
string created_at = 1 [(hope.swagger.field) = {
date_format: YYYY_MM_DD_HH_MM_SS;
}];
email: true; // bool type
assert: true; // Must be true
assert: false; // Must be false
digits_integer: 3; // Integer part up to 3 digits
digits_fraction: 2; // Decimal part up to 2 digits
decimal_max: "999.99";
decimal_min: "0.01";
string phone = 1 [(hope.swagger.field) = {
description: "Phone number";
mock: {
// mock rules...
};
}];
option (hope.swagger.operation) = {
post: "/ping";
mock: {
// mock rules...
};
};
Function: Automatically generate data based on semantics
Common values:
| Enum Value | Generated Content | Example |
|---|---|---|
EMAIL | user@example.com | |
URL | URL | https://example.com |
IP4 | IPv4 address | 192.168.1.1 |
IP6 | IPv6 address | … |
GUID / UUID | UUID | 550e8400-… |
PHONE | International phone | +1-555-1234 |
CN_PHONE | China mobile phone | 13800138000 |
CN_ADDRESS | China address | Shanghai Pudong… |
CN_GENDER | Chinese gender | Male/Female |
NAME | English name | John Doe |
GENDER | English gender | Male/Female |
COUNTRY | Country name | China |
ANIMAL | Animal name | Cat |
COLOR | Color | Red |
Example:
mock: {nature: EMAIL}
mock: {nature: CN_PHONE}
Fields:
length (uint32): Exact lengthmin / max (uint32): Length rangepool (Pool enum): Character pool
ORIGINAL: Keep as isLOWER: LowercaseUPPER: UppercaseNUMBER: Pure numbersSYMBOL: SymbolsCAPITALIZE: First letter uppercasecustomized_pool (string): Custom character poolcandidates (repeated string): Candidate value listExample:
mock: {
string_rule: {
min: 3;
max: 20;
pool: LOWER;
}
}
mock: {
string_rule: {
candidates: ["PENDING", "APPROVED", "REJECTED"]
}
}
Fields:
min / max (int64): Integer part rangemin_integer / max_integer (uint32): Integer part digitsmin_fraction / max_fraction (uint32): Decimal part digitsExample:
mock: {
number_rule: {
min: 1;
max: 100000;
min_fraction: 0;
max_fraction: 2; // 0-2 decimal places
}
}
Method 1: Birthday
mock: {
date_rule: {
birth_day: {
min_age: 18;
max_age: 60;
}
}
}
Method 2: Relative Time
mock: {
date_rule: {
time_gap: 30;
time_unit: DAYS; // NANOSECONDS/MICROSECONDS/MILLISECONDS/SECONDS/MINUTES/HOURS/DAYS
dir: PAST; // PAST/FUTURE
}
}
Types:
PARAGRAPH: ParagraphSENTENCE: SentenceWORD: WordTITLE: TitleExample:
mock: {
chinese_rule: {
type: TITLE;
min: 5;
max: 20;
}
}
Types:
FIRST: Given nameLAST: SurnameNAME: Full nameExample:
mock: {
chinese_name_rule: {
type: NAME
}
}
Types:
REGION: Region (North China, East China…)PROVINCE: ProvinceCITY: CityCOUNTY: District/CountyADDRESS: Complete addressExample:
mock: {
china_address_rule: {
type: ADDRESS;
with_prefix: true;
}
}
❌ Wrong:
empty: "false" // String, wrong!
nullable: "true" // String, wrong!
plural: "false" // String, wrong!
✅ Correct:
empty: false // bool type
nullable: true
plural: false
❌ Wrong (old syntax):
length: {value: 32} // Old syntax, no longer needed!
max_length: {value: 100}
maximum: {value: 12345}
✅ Correct:
length: 32 // uint32
max_length: 100 // uint64
maximum: 12345 // double
proto3 new syntax: optional bool for three-state boolean
optional bool pageable = 51; // Not set/true/false
optional bool raw = 52;
Difference:
bool: Default false (two-state)optional bool: Can be not set (three-state)syntax = "proto3";
package com.example.pet.service;
import "com/example/pet/bean/request.proto";
import "swagger/annotations.proto";
import "google/protobuf/empty.proto";
service PetService {
option (hope.swagger.svc) = {
path: "/pet";
description: "Pet management service";
};
// File upload
rpc UploadFile (UploadRequest) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
post: "/upload";
description: "Upload pet photo";
tags: "pet";
consumes: "multipart/form-data";
multipart: true;
authorization: {
low_limit_risky_mode: LOGIN
};
};
};
// Paginated query
rpc ListPets (PetQueryRequest) returns (Pet) {
option (hope.swagger.operation) = {
post: "/list";
description: "Paginated query pet list";
tags: "pet";
pageable: true;
authorization: {
rbac: {
authorities: ["PET_VIEW"];
}
};
};
};
// Path parameter
rpc GetPetById (google.protobuf.Empty) returns (Pet) {
option (hope.swagger.operation) = {
get: "/pets/{id}";
description: "Get pet details by ID";
tags: "pet";
parameters: {
parameter: {
name: "id";
in: PATH;
schema: {
format: LONG;
minimum: 1;
};
}
};
authorization: {
low_limit_risky_mode: ANONYMOUS
};
};
};
// Complex permission control
rpc DeletePet (google.protobuf.Empty) returns (google.protobuf.Empty) {
option (hope.swagger.operation) = {
delete: "/pets/{id}";
description: "Delete pet";
tags: "pet";
priority: HIGH;
parameters: {
parameter: {
name: "id";
in: PATH;
schema: {
format: LONG;
};
}
};
authorization: {
rbac: {
roles: {roles: ["ROLE_ADMIN"]};
authorities: ["PET_DELETE"];
combinator: AND;
}
};
};
};
}
syntax = "proto3";
package com.example.pet.bean;
import "swagger/annotations.proto";
import "com/example/pet/enumeration/constants.proto";
message PlaceOrderRequest {
option (hope.swagger.schema) = {
json_schema: {
title: "PlaceOrderRequest";
description: "Place order request object";
};
};
uint64 id = 1 [(hope.swagger.field) = {
description: "Request ID";
empty: false;
maximum: 12345;
example: "1111";
}];
uint64 pet_id = 2 [(hope.swagger.field) = {
description: "Pet ID";
empty: false;
minimum: 1;
maximum: 999999;
example: "1985";
}];
uint32 quantity = 3 [(hope.swagger.field) = {
description: "Purchase quantity";
empty: false;
minimum: 1;
maximum: 100;
example: "5";
}];
com.example.pet.enumeration.OrderStatus order_status = 4 [(hope.swagger.field) = {
description: "Order status";
}];
string ship_date = 5 [(hope.swagger.field) = {
description: "Shipping date";
example: "2023-12-25";
date_format: ISO_LOCAL_DATE;
time_constraint_type: FUTURE;
empty: false;
}];
bool complete = 6 [(hope.swagger.field) = {
description: "Is completed";
example: "false";
}];
string phone = 7 [(hope.swagger.field) = {
description: "Contact phone";
example: "13800138000";
pattern: "^1[3-9]\\d{9}$";
mock: {nature: CN_PHONE};
}];
string email = 8 [(hope.swagger.field) = {
description: "Contact email";
email: true;
empty: false;
mock: {nature: EMAIL};
}];
string remark = 9 [(hope.swagger.field) = {
description: "Remark";
max_length: 500;
mock: {
chinese_rule: {
type: SENTENCE;
max: 50;
}
};
}];
}
❌ Wrong:
empty: "false" // String
max_length: {value: 100} // Old syntax
pageable: "false" // String
✅ Correct:
empty: false // bool type
max_length: 100 // uint64 type
pageable: true // optional bool
❌ Wrong:
empty: false;
blank: false; // Both set!
✅ Correct:
empty: false; // Only set one
❌ Wrong:
input_plural: true; // Deprecated
out_plural: true; // Deprecated
✅ Correct:
input_repeated: true; // Use new field
output_repeated: true;
Query single resource → GET /resources/{id}
Query list → GET /resources or POST /resources/search
Create resource → POST /resources
Full update → PUT /resources/{id}
Partial update → PATCH /resources/{id}
Delete resource → DELETE /resources/{id}
Resource collection → /pets
Specific resource → /pets/{id}
Sub-resource → /pets/{id}/photos
Action → /pets/{id}/activate (verb form)
Complex query → POST /pets/search
Public interface → low_limit_risky_mode: ANONYMOUS
Login required → low_limit_risky_mode: LOGIN
Role required → rbac.roles
Permission required → rbac.authorities
Complex rules → rbac.combinator: AND
Email → nature: EMAIL
Phone → nature: CN_PHONE
UUID → nature: GUID
Name → chinese_name_rule
Address → china_address_rule
Date → date_rule
Enum → string_rule.candidates
service ServiceName {
option (hope.swagger.svc) = {
path: "/path";
description: "Description";
};
rpc MethodName (...) returns (...) {
option (hope.swagger.operation) = {
post: "/path";
description: "Description";
};
}
}
option (hope.swagger.operation) = {
post: "/path";
description: "Description";
tags: "tag";
authorization: {
low_limit_risky_mode: LOGIN
};
};
string field_name = sequence [(hope.swagger.field) = {
description: "Description";
example: "Example";
empty: false;
}];