resource
The resource
package provides types and functions for dealing with HATEOAS REST resources.
Usage
Create a module dedicated to resources and export as necessary.
import {
Link as LinkR,
payload as payloadR,
Resource as ResourceR,
resource as resourceR,
ResourceWithErrors as ResourceWithErrorsR,
ResourceWithMetadata as ResourceWithMetadataR,
} from '@execonline-inc/resource';
import { Result } from 'resulty';
const rels = ['prev', 'next', 'self'] as const;
export type Rel = typeof rels[number];
export const toRel = (value: string): Result<string, Rel> =>
toResult(
`Expected to find an HTTP rel string. Instead I found ${value}`,
find(rel => rel === value, rels)
);
export type Link = LinkR<Rel>;
export type Resource<T> = ResourceR<T, Rel>;
export type ResourceWithErrors<T> = ResourceWithErrorsR<T, Rel>;
export type ResourceWithMetadata<T, M> = ResourceWithMetadataR<T, M, Rel>;
export const resource: <T>(links: ReadonlyArray<Link>, payload: T) => Resource<T> = resourceR;
export const payload: <T, R extends Resource<T>>(r: R) => T = payloadR;
Types
ResourceCollection
import { Empty, None, Results } from '@execonline-inc/resource';
type ResourceCollection<T, Rel extends string> = None | Empty | Results<T, Rel>;
None
interface None {
kind: 'none';
}
Empty
interface Empty {
kind: 'empty';
}
Results
import { Resource } from '@execonline-inc/resource';
interface Results<T, Rel extends string> {
kind: 'results';
results: Resource<T[], Rel>;
}
Link
import { Method } from 'ajaxian';
interface Link<Rel extends string> {
rel: Rel;
href: string;
method: Method;
type: string;
}
ServerError
interface ServerError {
type: string;
param: string;
message: string;
code: string;
source: string;
}
Linkable
import { Link } from '@execonline-inc/resource';
interface Linkable<Rel extends string> {
links: ReadonlyArray<Link<Rel>>;
}
PossiblyLinkable
import { Linkable } from '@execonline-inc/resource';
interface PossiblyLinkable<Rel extends string> {
whenLinks: Maybe<Linkable<Rel>>;
}
Payloaded
interface Payloaded<T> {
payload: T;
}
Resource
import { Linkable, Payloaded } from '@execonline-inc/resource';
interface Resource<T, Rel extends string> extends Payloaded<T>, Linkable<Rel> {}
ResourceWithErrors
import { Resource, ServerError } from '@execonline-inc/resource';
interface ResourceWithErrors<T, Rel extends string> extends Resource<T, Rel> {
errors: ServerError[];
}
IdentifiablePayload
interface IdentifiablePayload {
id: number;
}
ResourceWithMetadata
import { Resource } from '@execonline-inc/resource';
interface ResourceWithMetadata<T, M, Rel extends string> extends Resource<T, Rel> {
metadata: M;
}
ValidationError
interface ValidationError {
kind: 'validation-error';
on: string;
param: string;
error: string;
detail: string;
}
ValidationErrors
import { ValidationError } from '@execonline-inc/resource';
type ValidationErrors = ValidationError[];
PaginationMetadata
interface PaginationMetadata {
resultsCount: number;
pageCount: number;
perPage: number;
currentPage: number;
}
Functions
none
Creates a None
object.
import { none, ResourceCollection } from '@execonline-inc/resource';
const result: ResourceCollection<unknown, 'self'> = none();
empty
Creates an Empty
object.
import { empty, ResourceCollection } from '@execonline-inc/resource';
const result: ResourceCollection<unknown, 'self'> = empty();
results
Creates a Results
object.
import { Link, results, Resource, ResourceCollection } from '@execonline-inc/resource';
interface ExamplePayload {
kind: 'example-payload';
}
const payload = { kind: 'example-payload' };
const links: ReadonlyArray<Link<'self'>> = [
{
rel: 'self',
href: 'https://example.com/',
method: 'get',
type: 'application/json',
},
];
const resource: Resource<ExamplePayload[], 'self'> = { payload: [payload], links };
const result: ResourceCollection<ExamplePayload, 'self'> = results(resource);
resources
This function returns the appropriate ResourceCollection
type depending on the presence of the given resource and its payload.
import { Link, resources, Resource, ResourceCollection } from '@execonline-inc/resource';
import { just } from 'maybeasy';
interface ExamplePayload {
kind: 'example-payload';
}
const payload = { kind: 'example-payload' };
const links: ReadonlyArray<Link<'self'>> = [
{
rel: 'self',
href: 'https://example.com/',
method: 'get',
type: 'application/json',
},
];
const resource: Resource<ExamplePayload[], 'self'> = { payload: [payload], links };
const result: ResourceCollection<ExamplePayload, 'self'> = resources(just(resource));
linksDecoder
This function returns a decoder for decoding HATEOAS links with valid rel
values. It takes a function that checks for valid rel
values.
import { find } from '@execonline-inc/collections';
import { toResult } from '@execonline-inc/maybe-adapter';
import { Link, linksDecoder } from '@execonline-inc/resource';
import { Result } from 'resulty';
const validRels = ['self'] as const;
type Rel = typeof validRels[number];
const toRel = (v: string): Result<string, Rel> =>
toResult(
`Invalid rel found: ${v}`,
find(rel => rel === v, validRels)
);
const linksDecoder: Decoder<ReadonlyArray<Link<Rel>>> = linksDecoder<Rel>(toRel);
errorDecoder
This decoder is for a particular error object structure.
import { errorDecoder, ServerError } from '@execonline-inc/resource';
const obj = {
type: 'some type',
param: 'some param',
code: '123',
source: 'some source',
message: 'some message',
};
const decoder: Decoder<ServerError> = errorDecoder;
const result: Result<string, ServerError> = decoder.decodeAny(obj);
resourceDecoder
This function with a curried and non-curried form. The returned decoder is for a HATEOAS resource structure.
import { find } from '@execonline-inc/collections';
import { resourceDecoder, Resource } from '@execonline-inc/resource';
import { toResult } from '@execonline-inc/maybe-adapter';
import { Result } from 'resulty';
type Rel = 'self';
interface ExamplePayload {}
const obj = { payload: {}, links: [] };
const toRel = (v: string): Result<string, Rel> =>
toResult(
`Invalid rel found: ${v}`,
find(rel => rel === v, validRels)
);
const payloadDecoder: Decoder<ExamplePayload> = succeed({});
const decoder: Decoder<Resource<ExamplePayload, Rel>> = resourceDecoder<ExamplePayload, Rel>(toRel)(
payloadDecoder
);
const result: Result<string, Resource<ExamplePayload, Rel>> = decoder.decodeAny(obj);
resourceWithMetadataDecoder
This curried function returns a decoder for a resource with an additional metadata
key.
import { find } from '@execonline-inc/collections';
import { resourceWithMetadataDecoder, ResourceWithMetadata } from '@execonline-inc/resource';
import { toResult } from '@execonline-inc/maybe-adapter';
import Decoder, { succeed } from 'jsonous';
import { Result } from 'resulty';
type Rel = 'self';
interface ExamplePayload {}
interface ExampleMetadata {}
const obj = {
payload: {},
links: [],
metadata: {},
};
const toRel = (v: string): Result<string, Rel> =>
toResult(
`Invalid rel found: ${v}`,
find(rel => rel === v, validRels)
);
const payloadDecoder: Decoder<ExamplePayload> = succeed({});
const metadataDecoder: Decoder<ExampleMetadata> = succeed({});
const decoder: Decoder<ResourceWithMetadata<
ExamplePayload,
ExampleMetadata,
Rel
>> = resourceWithMetadataDecoder<ExamplePayload, ExampleMetadata, Rel>(toRel)(
payloadDecoder,
metadataDecoder
);
const result: Result<
string,
ResourceWithMetadata<ExamplePayload, ExampleMetadata, Rel>
> = decoder.decodeAny(obj);
resourceWithErrorsDecoder
This curried function returns a decoder for a resource with an additional errors
key.
import { find } from '@execonline-inc/collections';
import { resourceWithErrorsDecoder, ResourceWithErrors } from '@execonline-inc/resource';
import { toResult } from '@execonline-inc/maybe-adapter';
import Decoder, { succeed } from 'jsonous';
import { Result } from 'resulty';
type Rel = 'self';
interface ExamplePayload {}
const obj = {
payload: {},
links: [],
errors: [],
};
const toRel = (v: string): Result<string, Rel> =>
toResult(
`Invalid rel found: ${v}`,
find(rel => rel === v, validRels)
);
const payloadDecoder: Decoder<ExamplePayload> = succeed({});
const decoder: Decoder<ResourceWithErrors<ExamplePayload, Rel>> = resourceWithErrorsDecoder<
ExamplePayload,
Rel
>(toRel)(payloadDecoder, metadataDecoder);
const result: Result<string, ResourceWithErrors<ExamplePayload, Rel>> = decoder.decodeAny(obj);
paginationMetadataDecoder
This decoder decodes a specific pagination object structure.
import { paginationMetadataDecoder, PaginationMetadata } from '@execonline-inc/resource';
import { Result } from 'resulty';
const obj = { results_count: 1, page_count: 1, per_page: 1, current_page: 1 };
const result: Result<string, PaginationMetadata> = paginationMetadataDecoder.decodeAny(obj);
validationErrorDecoder
This decoder decodes a specific validation error object structure.
import { validationErrorDecoder, ValidationError } from '@execonline-inc/resource';
import { Result } from 'resulty';
const obj = {
kind: 'validation-error',
on: 'that',
param: 'that',
error: 'error',
detail: 'detail',
};
const result: Result<string, ValidationError> = validationErrorDecoder.decodeAny(obj);
validationErrorsDecoder
This decoder is for decoding an array with validationErrorDecoder
.
selfUrl
This function finds the link in a resource with a rel
of self
.
import { Resource, selfUrl } from '@execonline-inc/resource';
import { Maybe } from 'maybeasy';
type Rel = 'self';
interface ExamplePayload {}
const resource: Resource<ExamplePaylod, Rel> = {
payload: {},
links: [
{
rel: 'self',
href: 'https://example.com/',
method: 'get',
type: 'application/json',
},
],
};
const result: Maybe<Link<Rel>> = selfUrl<ExamplePayload, Rel>(resource);
iconUrl
This function finds the link in a resource with a rel
of icon
.
import { iconUrl, Resource } from '@execonline-inc/resource';
import { Maybe } from 'maybeasy';
type Rel = 'icon';
interface ExamplePayload {}
const resource: Resource<ExamplePaylod, Rel> = {
payload: {},
links: [
{
rel: 'icon',
href: 'https://example.com/',
method: 'get',
type: 'application/json',
},
],
};
const result: Maybe<Link<Rel>> = iconUrl<ExamplePayload, Rel>(resource);
isNotSelf
This curried function determines if the given URL is not the same as the link in the resource with a rel of self
.
import { isNotSelf, Resource } from '@execonline-inc/resource';
type Rel = 'self';
interface ExamplePayload {}
const resource: Resource<ExamplePaylod, Rel> = {
payload: {},
links: [
{
rel: 'self',
href: 'https://example.com/',
method: 'get',
type: 'application/json',
},
],
};
const result: boolean = isNotSelf('https://not.example.com/')(resource);
resource
This function constructs a resource object given separated links and a payload.
import { Link, resource, Resource } from '@execonline-inc/resource';
type Rel = 'self';
interface ExamplePayload {}
const links: ReadonlyArray<Link<Rel>> = [];
const payload: ExamplePayload = {};
const result: Resource<ExamplePayload, Rel> = resource<Rel, ExamplePayload>(links, payload);
payload
This function retrieves the payload from a given resource.
import { payload, Resource } from '@execonline-inc/resource';
type Rel = 'self';
interface ExamplePayload {}
const resource: Resource<ExamplePayload, Rel> = { payload: {}, links: [] };
const result: ExamplePayload = payload(resource);
links
This function retrieves the links from a given resource.
import { links, Link, Resource } from '@execonline-inc/resource';
type Rel = 'self';
interface ExamplePayload {}
const resource: Resource<ExamplePayload, Rel> = { payload: {}, links: [] };
const result: ReadonlyArray<Link<Rel>> = links(resource);