Table of contents
One of the reasons why I like serverless technologies is that code is a liability. I can push a lot of the logic to handle and process requests as possible to the cloud provider, and focus on the core business logic.
However, I am still liable for the code that I need to write. The Rust programming language is quite interesting for this, as it’s designed for highly safe systems. You might already know some of its features on the memory safety side, but it also offer other useful patterns for building safe applications.
For example, let’s say we are building an application that returns a JSON response to the end user. Its representation will be different based on whether we return a success or not.
If the operation is a success, we’ll return a data attribute containing the object’s properties:
However, if there is any issue, we’ll return an error error:
To represent the response before serializing it into a JSON object, we could create a struct with all possible properties, and mark both
error as optional.
If you’re new to Rust, you might be wondering what are those
Option<> types we use for the fields. An Option either contain some value or none. As those values depends on if the response marks a success or not, they might not be present in the response.
This is why we use
skip_serializing_if for these fields. With serde, we can prevent serializing fields based on the result of a function. If the Option is None (doesn’t contain a value), we will skip this field in the JSON object.
Doing like so works pretty well to represent both possibles states. We can manually transform a
Result<> from another operation into our struct.
However, the problem with representing our responses this way is that a developer could accidentally create a response that would be valid in Rust, but invalid from the end users.
Here, we have a response that’s marked as unsuccessful, contains some data and and error message. From the end-user’s perspective, this is an undefined behavior compared to the original specification.
We could argue that it’s up to the developer to ensure they’re writing code that will behave as expected, but can we do better? If we go back to what we want to send as responses, there are two possible states:
- The operation is a success and we return a data object; or
- The operation was unsuccessful and we return an error message.
In Rust, we can just represent this as an Enum. Contrary to many programming languages, we can use Enum to represent variants that hold data.
We have two variants here: a success that contains a Data value, and an error that contains a String (our error message). From a developer point of view, that means they can only represent valid responses.
For reference, here’s how we would map the
Result<> of the operation into a response:
However, we’re still missing one thing. If you run the code above, you will get an error as our
ConditionalFieldsResponse enum doesn’t implement Serialize!
serde crate supports some enum representation patterns out of the box. In this case, we need to have a custom representation as the
success key is a boolean value.
Thankfully, we can create our own implementation of the
If you want to see a sample implementation that you can just download and run, I’ve created a GitHub repository.