Customer
Sample REST server demonstrating various styles of REST endpoints:
- Static, where resource names are predefined at endpoint registration time {/customers}.
- Dynamic, where the resource name (or part of it) is supplied at call-time (e.g. /db/{tbl}/{col}.
- API-like: same as static but uses verbs instead of names (e.g. /getCustomers)
//
// @desc App initialization.
//
init:{
.rest.init[]; / Initialize REST framework
.rest.brkOnErr: $[first[string .z.o]in"ml";$[()~@[system;"tty -s";0b];{[]1b};{[]0b}];{[]'`nyi}][]; // Break on error when attached to console
initStatic[]; / Sample static endpoints
initDynamic[]; / Sample dynamic endpoints
initApi[]; / Sample API-like endpoints
.rest.register[`get;"/help"; / Returns information about registered endpoints
"Retrieves information about registered REST endpoints";
{.rest.t};
()!()];
.rest.register[`get;"/hc"; / health-check
"health-check endpoint";
{"ok"};
()!()];
.rest.register[`get;"/_ping";
"for testing";
{"pong"};
()!()];
//
// Hook request processing.
//
req:{b,::enlist y; .h.hn . .rest.process[x;y]};
.z.ph:req[`GET];
.z.pp:req[`POST];
}
//
// Sample parameter subset to support paging.
//
pagingParams:.rest.input.param[`i;-6h;0b;0;"Offset of first row"],
.rest.input.param[`cnt;-6h;0b;10;"Number of rows to return"];
//
// Wraps `#` to limit to data size.
//
take:{[n;d]min[(n;count d)]#d}
//
// Initialization examples.
//
//
// Static endpoints, and various styles of versioning.
//
initStatic:{
.rest.register[`get;"/customers";
"Returns all customers";
{take[x[`arg;`cnt]]select from customers where i>=x[`arg;`i]};
pagingParams
];
.rest.register[`get;"/customers.2";
"Returns all customers (version 2)";
{take[x[`arg;`cnt]]update newCol:1 from select from customers where i>=x[`arg;`i]};
pagingParams
];
.rest.register[`get;"/v3/customers";
"Returns all customers (version 3)";
{take[x[`arg;`cnt]]update newCol:1, newCol2:10 from select from customers where i>=x[`arg;`i]};
pagingParams
];
.rest.register[`get;"/customers/{id}";
"Returns one or more customers by their IDs";
{select from customers where id in x[`arg;`id]};
.rest.input.param[`id;6h;1b;0Ni;"One or more customer IDs"]
];
//! the following is work in progress
/ .rest.template[`newCustomer;
/ rest.input.param[`name;-11h;`one;`;"Customer name"],
/ rest.input.param[`loc;10h;`required`nullable;`;"Customer location"]
/ ];
/ .rest.template[`newCustomer;
/ rest.input.param[`name;-11h;"Customer name";`flags`defVal!(`one;)],
/ rest.input.param[`loc;10h;`one;`;"Customer location"]
/ ];
//! concept of flags to capture both requirement, and vector-ness:
//! zeroOrOne
//! zeroOrMany
//! one
//! oneOrMany
//! nullable?
/ .rest.register[`post;"/customers";
/ "Creates one or more customers";
/ {break;};
/ .rest.input.param[`id;6h;1b;0Ni;"One or more customer IDs"],
/ .rest.input.param[`details;`newCustomer;`zeroOrMany;::;"One or more customer objects"],
/ .rest.input.header[`$"custom-header";6h;1b;0Ni;"One or more customer IDs"],
/ .rest.input.body[`newCustomer;`oneOrMany;::;"One or more customer object"]
/ ];
/ .rest.register[`put;"/customers/{id}";
/ "Update one or more customers by their IDs";
/ {brk2};
/ .rest.input.param[`id;6h;1b;0Ni;"One or more customer IDs"]
/ ];
}
initDynamic:{
.rest.register[`get;"/db";
"Retrieves list of table names";
{tables[]};
()!()
];
.rest.register[`get;"/db/{table}";
"Retrieves a table";
.tbl.getData;
.rest.input.param[`table;-11h;1b;`;"Table name"],
pagingParams
];
.rest.register[`get;"/db/{table}/meta";
"Retrieves metadata of a table";
{0!meta x[`arg;`table]};
.rest.input.param[`table;-11h;1b;`;"Table name"]
];
.rest.register[`get;"/db/{table}/{col}";
"Retrieves a column subset of a table";
.tbl.getData;
.rest.input.param[`table;-11h;1b;`;"Table name"],
.rest.input.param[`col;11h;1b;`;"Result columns"],
pagingParams
];
/ .rest.register[`post;"/db/query"; / Perform a dynamic query
/ "Queries a column subset of a table where the POST body is the where-clause";
/ .tbl.getData;
/ ()!()
/ ];
}
initApi:{
.rest.register[`get;"/getCustomers";
"Returns all customers";
{([] id:til 10; nm:10?`5)};
::
];
}
//
// Automatic data retrieval.
//
//
// @desc Generic data retrieval handler. Look for {tbl}, {id}, and {rel} arguments
//
.tbl.getData:{
tn:x[`arg;`table];
i_:$[`i in key x[`arg];x[`arg;`i];0];
cnt:$[`cnt in key x[`arg];x[`arg;`cnt];0W];
if[not tn in tables[]; 'table]; / Check whether table exists
w:$[""~0N!flt:x[`data];();enlist parse flt]; / If request is a POST, then data is expected to be the where clause
c:$[`col in key x`arg;{x!x}x[`arg;`col];()]; / Columns to retrieve
take[cnt]select from ?[tn;w;0b;c] where i>=i_
}
{
init[];
}[]
//
// Test data
//
n:100
customers:([] id:1+til n; name:n?`7)
sn:`aapl`goog`msft`nke`ftse
symbols:1!([] sym:`aapl`goog`msft`nke`ftse; src:count[sn]?`8);
trades:([] ts:$[12h;.z.d]+$[`minute;1]*til n;
sym:n?(0!symbols)[`sym];
price:n?10.0;
size:n?1000)