Home Reference Source Repository

src/collection/RestEndpointProperty.js

import URI from "urijs";

import { getOptions, extendOptions } from "./fetchOptions";
import RestQueryBuilder from "./RestQueryBuilder";

import ObjectProperty from "../ObjectProperty";
import PrimitiveProperty from "../PrimitiveProperty";
import shadow from "../decorators/shadow";
import StateType from "../StateType";
import Store from "../Store";

import appDebug, { CollectionRestEndpointPropertyKey as DebugKey } from "../debug";
const debug = appDebug(DebugKey);

/*

*/
export default class RestEndpointProperty extends ObjectProperty {
	constructor(stateType=RestEndpointProperty.type) {
		super(stateType);

		this._keyed.addPropertyType("url", PrimitiveProperty.type);
	}

	static createFor(url) {
		const type = RestEndpointProperty.type
			.initialState({ url: url.endsWith("/") ?url :`${url}/` })

		return new RestEndpointProperty(type);
	}

	static getGlobalOptions() {
		return getOptions();
	}

	static extendGlobalOptions(options) {
		extendOptions(options);
	}


	//------------------------------------------------------------------------------------------------------
	// The Endpoint implementation API
	//------------------------------------------------------------------------------------------------------

	@shadow
	get id() {
 		return this._().url;
 	}

	@shadow
	isConnected() {
		return !!this.id;
	}

	@shadow
	queryBuilder() {
		return new RestQueryBuilder();
	}

	@shadow
	doCreate(shadowModel, model) {
		const url = this.id;
		const options = getOptions("POST", {
				body: JSON.stringify(model),
				headers: {
					'Content-Type': 'application/json'
				},
			});

		return fetch(url, options)
			.then( response => {
					if (!response.ok) {
						return rejectOnError(response, url.toString());
					}

					return response.json();
				})
	}

	@shadow
	doDelete(id, options={}) {
		const uri = URI(`${id}`).absoluteTo(this.id);

		return fetch(uri.toString(), getOptions("DELETE", options))
			.then( response => {
					if (!response.ok) {
						return rejectOnError(response, uri.toString());
					}

					return id;
				});
	}

	@shadow
	doFetch(filter) {
		const startTime = Date.now();
		const url = filter ?filter.applyFilter(this.id) :this.id;

		debug( d => d(`doFetch() - path=${ this.$().dotPath() }, url=${url}`) );

		return fetch(url, getOptions("GET"))
			.then( response => {
					debug( d => d(`doFetch() response - path=${ this.$().dotPath() }, url=${url}, ok=${response.ok}, ` +
						`elapsed=${Date.now() - startTime}`, response) );

					if (!response.ok) {
						return rejectOnError(response, url.toString());
					}

					return response.json();
				})
			.then( json => {
					debug( d => d(`doFetch() success - path=${ this.$().dotPath() }, url=${url}, ` +
						`elapsed=${ Date.now() - startTime }`, json) );

					return json;
				})
			.catch( error => {
				debug( d => d(`doFetch() Fetch Error - path=${ this.$().dotPath() }, url=${url}`, error) );

				return Promise.reject(error);
			})
	}

	@shadow
	doFind(id) {
		const uri = URI(`${id}`).absoluteTo(this.id);

		return fetch(uri.toString(), getOptions("GET"))
			.then( response => {
					if (!response.ok) {
						return rejectOnError(response, uri.toString());
					}

					return response.json();
				});
	}

	@shadow
	doUpdate(id, shadowModel, changedProps) {
		const uri = URI(`${id}`).absoluteTo(this.id);
		const options = {
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify(changedProps),
			};

		return fetch(uri.toString(), getOptions("PUT", options))
			.then( response => {
					if (!response.ok) {
						return rejectOnError(response, uri.toString());
					}

					return response.json();
				});
	}
}

// TODO: extract text and generate a standard error description value (like login) that will make
//       sense across end point types
function rejectOnError(response, url) {
	const error = new Error(`${response.statusText} - status=${response.status}`);

	error.url = url;
	error.status = response.status;
	error.response = response;

	debug( d => response.text( text =>  d(`Response Error - url=${url}, text=${ text }`, response) ) );

	return Store.reject(error);
}


StateType.defineType(RestEndpointProperty, spec => {
		spec.autoshadowOff
			.initialState({})
			.readonly;
	});