Skip to content

Commit

Permalink
rec -> obj
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Jul 30, 2024
1 parent 8c697ce commit a4db3c0
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 39 deletions.
4 changes: 2 additions & 2 deletions apps/website/docs/contracts/cookbook/custom_matchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ function age(min, max): Contract<number, number> {
Now you can use this matcher in your schema:

```ts
import { rec, str, and, num } from '@withease/contracts';
import { obj, str, and, num } from '@withease/contracts';

const User = rec({
const User = obj({
name: str,
age: and(num, age(18, 100)),
});
Expand Down
10 changes: 5 additions & 5 deletions apps/website/docs/contracts/cookbook/optional_fields.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Optional Fields

By default, all fields mentioned in the schema of `rec` are required. However, you can make a field optional explicitly.
By default, all fields mentioned in the schema of `obj` are required. However, you can make a field optional explicitly.

In case you expect a field to have `null` as a value, you can add it to the field definition as follows:

```ts
import { rec, str, num, or, val } from '@withease/contracts';
import { obj, str, num, or, val } from '@withease/contracts';

const UserWithOptionalAge = rec({
const UserWithOptionalAge = obj({
name: str,
age: or(num, val(null)),
});
Expand All @@ -16,9 +16,9 @@ const UserWithOptionalAge = rec({
If you expect a field to be missing, you can pass `undefined` as a value:

```ts
import { rec, str, num, or, val } from '@withease/contracts';
import { obj, str, num, or, val } from '@withease/contracts';

const UserWithPossibleNoAge = rec({
const UserWithPossibleNoAge = obj({
name: str,
age: or(num, val(undefined)),
});
Expand Down
16 changes: 8 additions & 8 deletions apps/website/docs/contracts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ npm install @withease/contracts
`@withease/contracts` provides a special type `UnContract` that can be used to extract a type from a _Contract_.

```ts
import { type UnContract, rec, str, num } from '@withease/contracts';
import { type UnContract, obj, str, num } from '@withease/contracts';
const UserContract = rec({
const UserContract = obj({
id: num,
name: str,
email: str,
Expand All @@ -70,7 +70,7 @@ type User = UnContract<typeof UserContract>;

```ts
import { createJsonQuery } from '@farfetched/core';
import { rec, str, arr, val, or } from '@withease/contracts';
import { obj, str, arr, val, or } from '@withease/contracts';
const characterQuery = createJsonQuery({
params: declareParams<{ id: number }>(),
Expand All @@ -82,15 +82,15 @@ const characterQuery = createJsonQuery({
// after receiving data from the server
// check if it is conforms the Contract to ensure
// API does not return something unexpected
contract: rec({
contract: obj({
id: str,
name: str,
status: Status,
species: str,
type: str,
gender: Gender,
origin: rec({ name: str, url: str }),
location: rec({ name: str, url: str }),
origin: obj({ name: str, url: str }),
location: obj({ name: str, url: str }),
image: or(val('Female'), val('Male'), val('Genderless')),
episode: arr(str),
}),
Expand Down Expand Up @@ -128,15 +128,15 @@ For instance, you can define a part of a [_Contract_](/protocols/contract) with
```ts
import { z } from 'zod';
import { arr, rec } from '@withease/contracts';
import { arr, obj } from '@withease/contracts';
import { zodContract } from '@farfetched/zod';

const User = z.object({
name: z.string(),
});

const MyContract = arr(
rec({
obj({
// 👇 easily integrate Zod via compatibility layer
users: zodContract(User),
})
Expand Down
22 changes: 11 additions & 11 deletions packages/contracts/src/contracts.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, test, expect } from 'vitest';

import { bool, num, str, rec, or, val, arr, and, Contract } from './index';
import { bool, num, str, obj, or, val, arr, and, Contract } from './index';

describe('bool', () => {
it('valid', () => {
Expand Down Expand Up @@ -313,9 +313,9 @@ describe('str', () => {
});
});

describe('rec, overload with fields', () => {
describe('obj, overload with fields', () => {
it('empty object', () => {
const cntrct = rec({});
const cntrct = obj({});

expect(cntrct.isData({})).toBeTruthy();
expect(cntrct.getErrorMessages({})).toEqual([]);
Expand All @@ -326,7 +326,7 @@ describe('str', () => {
});

it('object with bool', () => {
const cntrct = rec({ enabled: bool });
const cntrct = obj({ enabled: bool });

expect(cntrct.isData({ enabled: true })).toBeTruthy();
expect(cntrct.getErrorMessages({ enabled: true })).toEqual([]);
Expand Down Expand Up @@ -358,20 +358,20 @@ describe('str', () => {
});

it('optional field edge case', () => {
expect(rec({ name: or(str, val(undefined)) }).isData({})).toBeTruthy();
expect(obj({ name: or(str, val(undefined)) }).isData({})).toBeTruthy();
});
});

describe('rec, overload with types', () => {
describe('obj, overload with types', () => {
it('empty object', () => {
const cntrct = rec(str, num);
const cntrct = obj(str, num);

expect(cntrct.isData({})).toBeTruthy();
expect(cntrct.getErrorMessages({})).toEqual([]);
});

it('invalid field type', () => {
const cntrct = rec(str, str);
const cntrct = obj(str, str);

expect(cntrct.isData({ a: 'a' })).toBeTruthy();
expect(cntrct.getErrorMessages({ a: 'a' })).toEqual([]);
Expand Down Expand Up @@ -451,8 +451,8 @@ describe('str', () => {

describe('complex nested', () => {
test('format errors for nested objects', () => {
const cntrct = rec({
user: rec({ name: str }),
const cntrct = obj({
user: obj({ name: str }),
});

expect(cntrct.isData({ user: { name: 'a' } })).toBeTruthy();
Expand All @@ -468,7 +468,7 @@ describe('complex nested', () => {
});

test('supports objects in arrays', () => {
const cntrct = arr(rec({ name: str }));
const cntrct = arr(obj({ name: str }));

expect(cntrct.isData([])).toBeTruthy();
expect(cntrct.getErrorMessages([])).toEqual([]);
Expand Down
20 changes: 9 additions & 11 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { func } from 'superstruct';

/**
* A type that allows to extract the result type of a _Contract_.
*
* @example
* const User = rec({
* const User = obj({
* name: str,
* age: num,
* });
Expand Down Expand Up @@ -137,7 +135,7 @@ export function or<T extends Array<Contract<unknown, any>>>(
* };
* }
*
* const User = rec({
* const User = obj({
* name: str,
* age: and(num, age(18, 100)),
* });
Expand All @@ -164,38 +162,38 @@ export function and<T>(
/**
* Function that creates a _Contract_ that checks if a value is object and every property is conform to the given _Contract_.
*
* @overload "rec(str, contract)"
* @overload "obj(str, contract)"
*
* @example
* const Ids = rec(str, num);
* const Ids = obj(str, num);
*
* Ids.isData({ id1: 1, id2: 2 }) === true;
* Ids.isData({ id1: 1, id2: '2' }) === false;
*/
export function rec<V>(
export function obj<V>(
keys: typeof str,
values: Contract<unknown, V>
): Contract<unknown, Record<string, V>>;

/**
* Function that creates a _Contract_ that checks if a value is conform to an object with the given _Contracts_ as properties.
*
* @overload "rec(shape)"
* @overload "obj(shape)"
*
* @example
* const User = rec({
* const User = obj({
* name: str,
* age: num,
* });
*
* User.isData({ name: 'Alice', age: 42 }) === true;
* User.isData({ name: 'Alice' }) === false;
*/
export function rec<C extends Record<string, Contract<unknown, any>>>(
export function obj<C extends Record<string, Contract<unknown, any>>>(
c: C
): Contract<unknown, { [key in keyof C]: UnContract<C[key]> }>;

export function rec(shape: any, fieldContract?: any): any {
export function obj(shape: any, fieldContract?: any): any {
const check = (x: unknown) => {
if (typeof x !== 'object' || x === null) return false;

Expand Down
4 changes: 2 additions & 2 deletions packages/contracts/src/interop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { object, string } from 'superstruct';
import { runtypeContract } from '@farfetched/runtypes';
import { superstructContract } from '@farfetched/superstruct';

import { rec, arr } from './index';
import { obj, arr } from './index';

describe('runtypes', () => {
it('supports Runtype inside', () => {
const cntrct = rec({ name: runtypeContract(Array(String)) });
const cntrct = obj({ name: runtypeContract(Array(String)) });

expect(cntrct.isData({ name: ['foo'] })).toBe(true);
expect(cntrct.getErrorMessages({ name: ['foo'] })).toEqual([]);
Expand Down

0 comments on commit a4db3c0

Please sign in to comment.