Candid Style Guide
Candid
Candid is a serialization format for the Internet Computer. It is used to define the interface of canisters, and to encode and decode messages that are sent to and from canisters.
Motivation
Canisters and their client interactions are written in various languages. However, Candid, which is language-agnostic, describes these interfaces. Therefore, Candid interfaces should maintain a consistent pattern, unbiased by the language used in the canister interfaces they describe.
Naming
Types
Use PascalCase
(upper camel case) for type definitions.
This has no additional benefit aside from consistency.
Don't use inconsisent or alternative casings for type definitions.
type USER = record {};
type post = record {};
Do use PascalCase
casing for type definitions consistently.
type User = record {};
type Post = record {};
Record field names
Use lower_snake_case
for field definitions.
This has no additional benefit aside from consistency.
Don't use inconsisent or alternative casings for field definitions.
type User = record {
Id : nat64;
displayName : text;
};
type Post = record {
ID : nat64;
USER_ID : nat64;
Content : text;
};
Do use lower_snake_case
casing for field definitions consistently.
type User = record {
user_id : nat64;
name : text;
};
type Post = record {
id : nat64;
user_id : nat64;
content : text;
};
Variants
Use lower_snake_case
for variant definitions.
This has no additional benefit aside from consistency.
Don't use inconsisent or alternative casings for variant definitions.
type UserRole = variant {
Admin;
MODERATOR;
user;
superUser;
};
Do use lower_snake_case
casing for variant definitions consistently.
type UserRole = variant {
admin;
moderator;
user;
super_user;
};
Service methods
Use lower_snake_case
for service method names.
This has no additional benefit aside from consistency.
Don't use inconsisent or alternative casings for service method definitions.
service : {
CREATE_USER : (UserRequest) -> (UserResponse);
UpdateUser : (UserRequest) -> (UserResponse);
};
Do use lower_snake_case
casing for service method definitions consistently.
service : {
create_user : (UserRequest) -> (UserResponse);
update_user : (UserRequest) -> (UserResponse);
};
Tuple field types
Use type
aliases for tuple field types.
This can help greatly with readability, by allowing the alias to act as a label for the field.
Don't use built-in Candid types for tuples.
type UserPost = record {
nat64;
nat64;
};
Do use type aliases in tuples.
type UserId = nat64;
type PostId = nat64;
type UserPost = record {
UserId;
PostId;
};
Type aliases
Use type
aliases to define common types that are used in multiple places.
This can help with readability and make it easier to change the underlying type in the future.
Don't define common types using built-in Candid types.
type User = record {
id : nat64;
name : text;
};
type Post = record {
id : nat64;
user_id : nat64;
content : text;
};
Do define common types with type aliases.
type UserId = nat64;
type PostId = nat64;
type User = record {
id : UserId;
name : text;
};
type Post = record {
id : PostId;
user_id : UserId;
content : text;
};
Request and response types
Use separate request and response types for each service method. Name them {MethodName}Request
and {MethodName}Reponse
respectively.
This allows for changes to a single service method without affecting other methods. It may be tempting to reuse common request or response types for multiple methods, but over time this can lead to a lot of messy coupling between methods. Unique request and response types also allow readers to quickly understand the purpose of each type, without needing to explicitly check the service method that it is used for.
Don't share common request and response types between multiple service methods.
type UserRequest = record {
name : text;
};
type UserResponse = record {
id : nat64;
name : text;
};
service : {
create_user : (UserRequest) -> (UserResponse);
update_user : (UserRequest) -> (UserResponse);
};
Do define unique request and response types for each service method.
type CreateUserRequest = record {
name : text;
};
type CreateUserResponse = record {
id : nat64;
name : text;
};
type UpdateUserRequest = record {
name : text;
};
type UpdateUserResponse = record {
id : nat64;
name : text;
};
service : {
create_user : (CreateUserRequest) -> (CreateUserResponse);
update_user : (UpdateUserRequest) -> (UpdateUserResponse);
};
References and inspiration
- Candid Spec
- Candid Reference
- Protobuf Style Guide
- Google gRPC API design guide
- Kreya gRPC - Best Practices
Changelog
- 2023-12-30: Added naming conventions for Candid primitives