Reputation: 12263
I have this code that is building a url endpoint taking in different parameters:
const query = (obj) => {
let starter = 'query='
let queryToString = JSON.stringify(obj)
return `${starter}${queryToString}`
}
const params = (str) => `${str}`
const endpoint = (protocol, base, params, query) => {
if (!params && !query) return `${base}`
if (!params) return `${base}${query}`
return `${protocol}${base}?${params}&${query}`
}
const baseUrl = 'api.content.io'
const protocol = (secure = true) => secure ? 'https://' : 'http://'
let result = endpoint(protocol(), baseUrl, params('limit=5&order=desc'),
query({
field: 'title',
include: 'brands'
}));
console.log(result)
This builds a string like:
https: //api.content.io?limit=5&order=desc&query={"field":"title","include":"brands"}
Is it possible to refactor this code such that the conditionals inside the endpoint
function can be removed, the right concatenation strings be applied and the whole thing chained into a functional call like
Endpoint.chain(protocol(p)).chain(base(b)).chain(params(p)).chain(query(q)).build()
How do I get started with doing this?
UPDATE: The solutions below are pretty good but I would like to understand how functional programmers use ADTs (Algebraic Data Types) with Monads to solve this problem. The idea is to run a chain
of functions and then a fold
to get back the value I want
Upvotes: 3
Views: 299
Reputation: 4787
Although the above answers will work, but if you want the syntax to start with Endpoint().
then use a class EndpointBuilder and a factory function like below.
class EndpointBuilder {
constructor() {
this.params = [];
this.protocol = 'http://';
this.baseUrl = 'api.endpoint.io';
}
base(url) {
if (url && url.trim().length > 0)
this.baseUrl = url;
return this;
}
secure() {
this.protocol = 'https://';
return this;
}
setParam(param, val) {
if (param && val)
this.params.push({ param, val });
return this
}
toString() {
const url = `${this.protocol}${this.baseUrl}`;
if (this.params.length <= 0)
return url;
let qString = '';
this.params.forEach(p => {
qString = `${qString}${qString.length > 0 ? '&' : ''}${p.param}=${JSON.stringify(p.val)}`;
});
return `${url}?${qString}`;
}
};
// Endpoint Factory
const Endpoint = function () {
return new EndpointBuilder();
};
Usage Example:
const url1 = Endpoint().setParam('limit', 5).setParam('query', { field: 'title', include: 'brands' }).toString();
const url2 = Endpoint().base('another.endpoint.io').setParam('limit', 5).setParam('order', 'desc').setParam('query', { field: 'title', include: 'brands' }).toString();
const url3 = Endpoint().base('another.endpoint.io').secure().setParam('limit', 5).setParam('order', 'desc').setParam('query', { field: 'title', include: 'brands' }).toString();
console.log(url1);
// http://api.endpoint.io?limit=5&query={"field":"title","include":"brands"}
console.log(url2);
// http://another.endpoint.io?limit=5&order="desc"&query={"field":"title","include":"brands"}
console.log(url3);
// https://another.endpoint.io?limit=5&order="desc"&query={"field":"title","include":"brands"}
Upvotes: 1
Reputation: 350034
You could use a class for that, which has methods that allow chaining (they return this
):
class EndPoint {
constructor() {
// defaults
this.prot = 'https://';
this.bas = 'api.content.io';
this.qry = '';
this.par = '';
}
secure(on = true) {
this.prot = on ? 'https://' : 'http://';
return this;
}
base(str) {
this.bas = str;
return this;
}
query(obj) {
this.qry = `query=${JSON.stringify(obj)}`
return this;
}
params(str) {
this.par = str
return this;
}
build() {
let sep1 = this.qry.length || this.par.length ? '?' : '';
let sep2 = this.qry.length && this.par.length ? '&' : '';
return `${this.prot}${this.bas}${sep1}${this.par}${sep2}${this.qry}`;
}
};
let result = new EndPoint().secure(true).base('www.example.com')
.params('limit=5&order=desc').query({ field: 'title', include: 'brands' }).build();
console.log(result);
Upvotes: 2
Reputation: 11718
Are you looking for something like this?
Chaining basically happens when you return the complete object in every function call. Here each function sets the value of its given chunk and returns the this object for whatever context it's attached to. Each function would be able to process the value it receives and the value function would build the url based on the set parameters.
var Endpoint = {
protocol: protocol,
base: base,
params: params,
query: query,
value: value
}
function protocol(b) {
protocol.value = b && 'https://' || 'http://';
return this;
}
function base(s) {
base.value = s || '';
return this;
}
function params(s) {
params.value = s || '';
return this;
}
function query(s) {
query.value = s || '';
return this;
}
function value(s) {
return '$protocol$base$params$params$query'
.replace('$protocol',protocol.value)
.replace('$base',base.value)
.replace('$params',params.value)
.replace('$query',query.value)
}
Endpoint
.protocol(true)
.base('www.foo.com')
.params('one/two/three')
.query('?foo=bar')
.value()
// "https://www.foo.comone/two/three$params?foo=bar"
Upvotes: 2