Contracts
You will learn
We believe that frontend applications should not trust remote data, it is important to validate data before using it. Farfetched provides a way to validate data using Contracts.
What is a Contract
In general Contract in Farfetched is a simple object with only a few properties that allow application to check any data and decide how it should be treated — as a success response or as a failed one.
Let's take a look at these properties:
isData
is a function that takes any data and returnstrue
if it is a valid data andfalse
otherwise.getErrorMessages
is a function that takes any data and returns an array of strings with description of reasons why data is invalid, it would be called only ifisData
returnedfalse
.
That's it, any object with these three properties is a Contract.
TIP
If you are using TypeScript, isData
function has to be type predicate.
How to create a Contract
Of course, you can create your own Contract by hand, let's create a simple one together.
import { type Contract } from '@farfetched/core';
const numberContract: Contract<
unknown, // it take some unknown data
number // and returns number if it is valid
> = {
// it is valid if data is a number
isData: (data): data is number => typeof data === 'number',
// if data is not a number,
// we return an array with description of reasons why data is invalid
getErrorMessages: (data) => {
return [`Expected number, got ${typeof data}`];
},
};
So, we created a Contract that takes unknown data and checks if it is a number. We can apply this Contract to any factory:
const someQuery = createQuery({
// ...
contract: numberContract,
});
Third-party solutions
Even though Farfetched provides a way to create your own Contract, it's way more convenient to use third-party solutions for contracts.
Runtypes
We recommend using Runtypes, it has first class TS-support, it is well-documented and has a lot of useful features. Farfetched provides an integration to use Runtype as a Contract.
import { Number } from 'runtypes';
import { runtypeContract } from '@farfetched/runtypes';
const numberContract = runtypeContract(Number);
That is a complete equivalent of the previous example. For such a simple contract, it's not a big deal, but for more complex contracts, it's much more convenient to use Runtypes than to create a contract by hand.
import { Record, String, Number, Union, Literal } from 'runtypes';
const characterContract = runtypeContract(
Record({
id: Number,
name: String,
status: Union(Literal('Alive'), Literal('Dead'), Literal('unknown')),
species: String,
type: String,
origin: Record({ name: String, url: Url }),
location: Record({ name: String, url: Url }),
})
);
Other integrations
Farfetched provides integrations for the following third-party solutions:
If you are using any other solution for contracts, you can easily create a wrapper for it to case your internal contracts to Farfetched Contract. Check source code of Runtypes integration for inspiration.