Interface definition reference
Introduction
The LotusRPC definition is written in YAML and therefore benefits from all the features and tooling that are available for YAML. Think about editor support, easy parsing in various programming languages. The fact that there is a schema available, makes it possible to have code completion and documentation in supporting editors.
Top level
The LotusRPC definition file has the following properties:
| Required | Optional |
|---|---|
| name | structs |
| services | enums |
| constants | |
| settings | |
| user_settings |
It is not allowed to use additional properties at top level. This can be done under user_settings.
Name
This is the name of the RPC engine. It is used in generated files and directories, as well as generated code. Therefore, the name must be a valid C++ identifier
Services
A single RPC engine can contain up to 255 different user services. A service can be considered a group of related functions. In the generated code a service with functions corresponds to a class with methods. A RPC engine must have at least one service.
A service has the following properties
| Required | Optional |
|---|---|
| name | id |
| functions | |
| streams | |
| functions_before_streams |
name is the name of the service. It must be a valid C++ identifier.
id is the service ID. When not explicitly specified, it is generated by LotusRPC.
Although functions and streams are both optional, at least one of them must be present.
Service ID
Every LotusRPC service has an identifier that is needed for proper transfer of information between two endpoints. If the service identifier is not specified, LotusRPC will generate one. When starting out with a fresh RPC, it’s usually not necessary to specify service IDs. Later on it may be useful to explicitly specify a service ID for backwards compatibility.
Note: The most efficient code is generated when service IDs are contiguous and start at 0. This is the default when no IDs are specified.
The service ID is encoded in an 8-bit field and ID 255 is reserved for the LotusRPC meta service. This means that a user service can have any ID in the range of 0 to 254.
Functions
A single LotusRPC service must contain at least one function or stream and up to 256 (combined). A function can have any number of arguments and return values.
A function has the following properties:
| Required | Optional |
|---|---|
| name | id |
| params | |
| returns | |
| returns_alias |
name is the name of the function. It must be a valid C++ identifier. id is the function identifier, similar to the service ID. params is a list of parameters and returns is a list of return values. Every item in params and returns is a LrpcType.
It is possible to define an alias for the combined function returns with returns_alias. For functions with multiple, complex return values this can significantly improve readability of generated C++ code. The alias must be a valid C++ identifier and not collide with any of the function parameter or return names.
Example:
functions:
- name: get_log
returns_alias: LogChunk
returns:
- name: entries
type: string_64
count: 5
- name: count
type: uint32_t
See C++ API — Multiple return values for the generated code.
Streams
Streams are similar to functions, but they don’t have any return values. In fact, there is no response to stream data at all. Stream data can consist of any number of items. Data can be streamed from client to server or vice versa (determined by origin), but a data stream is always initiated by the client. While params in a function are always from client to server, in a stream the direction of params depends on the direction of the stream.
A stream has the following properties:
| Required | Optional |
|---|---|
| name | id |
| origin | params |
| finite |
name is the name of the stream. It must be a valid C++ identifier. origin determines the direction of the stream. It can be either client or server. id is the stream identifier, similar to the service ID. params is a list of parameters. Every item in params is a LrpcType.
Sometimes a stream can produce an infinite amount of messages, for example a sensor data stream from server to client. In this case the client starts the stream and stops the stream when needed. In other cases a stream is limited by design, for example retrieving all log messages stored on a device. In this case it’s useful for the receiving side to know when the last message has been received. LotusRPC can help in this situation if the finite property is set to true, but it does come at a small cost. Every message gets an implicit boolean parameter (one byte) that is only true for the final message. The LotusRPC client CLI uses this information to gracefully terminate a streaming session.
Functions and streams ordering
When a service contains both functions and streams, automatic ID assignment depends on which is specified first.
Example 1.
A definition first specifies two functions in a service and then two streams. No explicit IDs are given. The functions will receive IDs 0 and 1 and the streams will receive IDs 2 and 3.
Example 2.
A definition first specifies two streams in a service and then two functions. The second stream has an explicit ID of 55. LotusRPC generates the other IDs and the result are: first stream has ID 0, second stream has ID 55. First function has ID 56, second function has ID 57.
Example 3.
A definition specifies a service with three functions. The first function has an explicit ID of 20, the second function has an explicit ID of 19, the third function does not have an explicit ID. LotusRPC generates an error because the generated ID for the third function is 20, resulting in duplicate function IDs.
The order-based default can be overridden with the functions_before_streams boolean property on the service. When set to true, functions always receive their IDs before streams regardless of the order they appear in the definition. When set to false, streams always receive their IDs first.
Structs
LotusRPC supports defining custom aggregate data types in the structs property. structs contains a list of custom struct definitions, where every item has the following properties:
| Required | Optional |
|---|---|
| name | external |
| fields | external_namespace |
name is the name of the struct. It must be a valid C++ identifier. fields is a list of data members, every member being a LrpcType. Custom structs can be referenced inside the LotusRPC definition file by prepending the name with the @ sign.
Enums
LotusRPC supports defining custom enum types in the enums property. enums contains a list of custom enum definitions, where every item has the following properties:
| Required | Optional |
|---|---|
| name | external |
| fields | external_namespace |
name is the name of the enum. It must be a valid C++ identifier. fields is a list of enum fields, every field having a required name property and an optional id property. name is the enum label and id is the value of the label. fields can also simply be a list of strings, every string being the name of a field. The field IDs are determined automatically in that case. This allows for shorter notation in case the specific value of the IDs is not important.
It is possible to use an enum in LotusRPC that already exists in your codebase. In this case, the external property must be used to specify the file that contains the definition of the enum. If the external enum lives inside a namespace, that namespace must be specified with the external_namespace property. LotusRPC does not generate any functional code for external enums, but it does generate some checks to verify that the external enum corresponds to the LotusRPC definition file. LotusRPC also creates an alias for the external type (unless code is generated in the global namespace and the external enum lives in the global namespace). The alias is used internally in code generated by LotusRPC.
Example:
...
enums:
# short notation
- { name: MyEnum, fields: [V0, V1, V2, V3]}
# short notation, external enum in namespace
- { name: MyEnum2, fields: [V0, V1, V2, V3], external: ext_files/MyEnum2.hpp, external_namespace: "a::b::c"}
# full notation, external enum in global namespace
- name: MyEnum3
external: ext_files/MyEnum3.hpp
fields:
- {name: V0} # id omitted, defaults to 0
- {name: V1} # id omitted, defaults to 1
- {name: V55, id: 55}
- {name: V200, id: 200 }
- {name: V201} # id omitted, defaults to 201
...
Constants
LotusRPC supports defining constants in the constants property. A constant can have the following types:
- (u)intx_t, with x being 8, 16, 32 or 64
- float, double
- bool
- string
- byte array
constants contains a list of constant definitions, where every item has the following properties:
| Required | Optional |
|---|---|
| name | |
| value | cppType |
name is the name of the constant. value is the value of the constant. The type of the constant is deduced from its value, but it’s possible to explicitly specify the type that the constant should have in the generated C++ code. E.g. the value 111 will by default be given the type int32_t, but when the cppType is uint8_t, it will get that type. As another example, the value 3.14 will by default be given the type float, but it can also be a string constant when the cppType is string. The latter could alternatively be achieved by prefixing the value with !!str. This forces the YAML parser to treat the value as a string and is unrelated to LotusRPC.
From Python, the value of a constant in the definition can be obtained with LrpcDef.constant.
There is currently no other use case for constants than to provide a single source of truth for constants that are needed on both the client side and the server side. Notably, it is not (yet) possible to reference a constant in other parts of the definition, e.g. as the size of an array. To achieve this kind of behavior, the anchor and alias features of yaml may be used.
Example:
...
# an additional property called my_prop
# is used as an anchor with name array_size
# The additional property is allowed under
# user_settings
user_settings:
my_prop: &array_size 55
constants:
# implicit int32_t
- name: c0
value: 111
# explicit uint16_t
- name: c1
value: 111
cppType: uint16_t
# explicit string
- name: c2
value: 111
cppType: string
# using array_size to define a constant and as array size
- name: c3
value: *array_size
services:
- name: srv0
functions:
- name: f0
params:
- name: my_array
type: uint16_t
count: *array_size
...
Settings and user settings
Settings are documented on the Settings reference page.
Definition overlays
Definition overlays are documented on the Overlays reference page.
LrpcType
The LotusRPC definition file uses LrpcType to describe function arguments, function return values and struct fields.
A LrpcType has the following properties:
| Required | Optional |
|---|---|
| name | count |
| type |
name is the name of the LrpcType.
LrpcType.type
See the supported data types table on the home page, and the C++ type mapping for the generated code equivalents.
LrpcType.count
Specifying count as a number (at least 1) turns the LotusRPC type into an array of that size. Specifying count as ? turns the LotusRPC type into an optional. If count is omitted, the type specified by type is used without any modifications.