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.