Using flow-runtime to enforce strict type constraints at runtime

Name one relatively unknown project that everyone should be using? flow-runtime

Image for post
Image for post
Take Flow to the runtime journey.

I have been using Flow in every project I have created in the last two years. Strict type checking, whether it is Flow or TypeScript, is awesome. I aim for 80%+ type coverage across the project. I will go as far as to say that using strict types makes majority of the usual test cases redundant, esp. those that check contract between different parts of the application. That is, unless your application is consuming third-party data.

Compilation-time type checking shortcomings

type WeatherResponseType = {|
+temperature: number
|};
const weatherResponseHead = await fetch('https://weather.api');const weatherResponse: WeatherResponseType = await weatherResponseHead.json();

There are absolutely no guarantees that weatherResponse is going to return this response in the future. Type annotations still provide a value here — your application knows the shape of the response and can validate the contract at the build time. However, compilation time checking is not going to help if the API produces an unexpected response in the future. This is where the flow-runtime comes in.

Runtime type checking

{
"plugins": [
[
"flow-runtime",
{
"annotate": true,
"assert": true
}
]
]
}

In the case of the earlier example, the transpiled code becomes:

import t from 'flow-runtime';const WeatherResponseType = t.type('WeatherResponseType', t.exactObject(t.property('temperature', t.number())));const weatherResponseHead = await fetch('https://weather.api');
const weatherResponse = WeatherResponseType.assert((await weatherResponseHead.json()));

As you can see, WeatherResponseType type annotation has been converted to a collection of functions used to assert the shape of the input at the runtime.

Continuing with out example, suppose that the API response changed to:

{
"temperatureCelsius": 30,
"temperatureFahrenheit": 86
}

In this case, Flow will throw an error in the following format:

WeatherResponseType should not contain the key: "temperatureCelsius"Expected: {|
temperature: number;
|}
Actual: {
temperatureCelsius: number;
temperatureFahrenheit: number;
}
-------------------------------------------------WeatherResponseType should not contain the key: "temperatureFahrenheit"Expected: {|
temperature: number;
|}
Actual: {
temperatureCelsius: number;
temperatureFahrenheit: number;
}

Importance of catching the error early

Your program will fail only if you are lucky. With an exception of limited scenarios where JavaScript is going to throw an error (e.g. attempt to access a property of undefined), JavaScript is going to quietly perform value coercion This has bitten me quite a few times when I was expecting an external API to return an identifier, e.g.

{
"id": 1,
"name": "Foo"
}

My application would expect the above output and use it to construct URL of a resource, e.g.

type VenueType = {|
+id: number,
+name: string
|};
const venue: VenueType = await getVenueById(1);
const url = 'https://go2cinema.com/venues/' + venue.id + '-' + slugify(venue.name);

Then suddenly, the API changes its response format to:

{
"name": "Foo",
"venueId": 1
}

Without flow-runtime, the program is going to continue to quietly construct URLs as https://go2cinema.com/venues/undefined-vue-shepherds-bush (notice the undefined).

The cost of runtime assertions

Weighting the benefits of runtime assertions

Overall, I’d estimate that replacing JSON schemas with flow-runtime reduced the codebase size of the feed aggregators by +30%. Meanwhile, flow-runtime is continually proving itself to be the most-valuable-player at discovering bugs.

Author

Thank you Charles!

Written by

Software architect, startup adviser. Editor of https://medium.com/applaudience. Founder of https://go2cinema.com.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store