Skip to content

Commit

Permalink
v0.7c
Browse files Browse the repository at this point in the history
  • Loading branch information
Pedro A. Hortas committed Nov 4, 2018
1 parent 459f5dd commit f371da4
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 57 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7b (2018-10-28)
0.7c (2018-11-03)
19 changes: 16 additions & 3 deletions documentation/composites.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
- Responses from already processed composite calls (child calls) can be reused inside the same composite under the
payload of other child calls with the usage of variables.

- Variables can aggregate results through iterables, or simply represent a plain value.
- Variables can aggregate results through iterables, or simply represent a plain value. They can be used under,
but not limited to, payload and headers properties.


2. Scope
Expand Down Expand Up @@ -73,12 +74,16 @@
+ An integer value that indicates a position in an array.
+ They can be used in variable path segments that represent a linear array.

- "options":
+ A segment inside a composite variable, denoted by "(field1|field2|...)", indicating that "field1" should be
selected if exists, otherwise "field2", and so on.


5. Syntax

{
/* Required: Composite name. Should be a meaningful name that represents the procedure */
"name" : "<string>",
"composite" : "<string>",

/* Required: The composite content. Each entry of the array represents a RESTful call */
"content" : [
Expand All @@ -95,7 +100,9 @@
/* Optional: Set to true to hide the results from this child request in the response. Defaults to false. */
"hidden" : <boolean>,

/* Optional: A map of request headers to be included in this child request. */
/* Optional: A map of request headers to be included in this child request.
* When strict header types option is disabled, all header values will be cast to string.
*/
"headers": {
"<string>" : "<string>",
...
Expand Down Expand Up @@ -135,4 +142,10 @@
- Example with array index : "$$last.data.result[0].id$$"
Resulting type will be the same of the segment "id"

- Example with options : "$$last.data.(zip|postal_code)$$"
Resulting value and type will be of the field "zip" if exists, otherwise "postal_code"

- Example w/ numeric index : "$$4.data.id$$"

- Example w/ named index : "$$xpto.data.id$$"

8 changes: 7 additions & 1 deletion documentation/ndsl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"ordering" : "<string>",
"show" : [ "<array>", "<of>", "<strings>" ],
"aggregations" : { <object> },
"query": : { <object> }
"query" : { <object> },
"404" : <boolean>
}


Expand Down Expand Up @@ -64,6 +65,11 @@
* The query object, where search criteria is set (see section 3).


1.11. Property - 404

* Forces a 404 status code when no results are available for the selected criteria, limit and offset.



2. Aggregations Object

Expand Down
1 change: 1 addition & 0 deletions system/config/composite.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
$composite['child_accept_encoding'] = 'gzip';
$composite['child_content_encoding'] = 'gzip';
$composite['child_basic_auth'] = NULL;
$composite['strict_header_types'] = false;
1 change: 1 addition & 0 deletions system/config/restful.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

$restful['debug']['enabled'] = false;
$restful['debug']['level'] = 1;
$restful['debug']['directory'] = '/var/log/uweb';

$restful['request']['encoding']['process'] = true;
$restful['request']['header']['id'] = 'uweb-request-id';
Expand Down
41 changes: 27 additions & 14 deletions system/models/restful.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
*/

/* Define subcodes */
define('UWEB_RESTFUL_SUBCODE_RELATED_COULDNT_CONNECT', 100);
define('UWEB_RESTFUL_SUBCODE_RELATED_OPERATION_TIMEOUT', 101);
define('UWEB_SUBCODE_RESTFUL_RELATED_COULDNT_CONNECT', 100);
define('UWEB_SUBCODE_RESTFUL_RELATED_OPERATION_TIMEOUT', 101);


/* RESTful class */
Expand Down Expand Up @@ -483,6 +483,8 @@ public function __construct() {
/* Setup debug and logging parameters */
$this->_debug = current_config()['restful']['debug']['enabled'];
$this->_debug_level = current_config()['restful']['debug']['level'];
$this->_debug_directory = current_config()['restful']['debug']['directory'];
$this->_debug_content = array();
$this->_logging = current_config()['restful']['log']['enabled'];
$this->_event = current_config()['restful']['event']['enabled'];

Expand Down Expand Up @@ -747,15 +749,18 @@ public function input() {
}
}

/* Check if debug is enabled. */
if ($this->_debug) {
/* If so, dump input contents to error log */
error_log($raw_data);
}

/* Decode json data */
$json_data = json_decode($raw_data, true);

/* Check if debug is enabled. */
if ($this->_debug === true) {
/* Check if the debugging level includes input logging */
if (($this->_debug_level >= 2) && ($json_data !== NULL)) {
/* If so, include input content */
$this->_debug_content['input'] = $json_data;
}
}

/* If we're unable to decode the JSON data, this is a bad request */
if ($json_data === NULL) {
/* Cannot decode JSON data */
Expand Down Expand Up @@ -897,9 +902,17 @@ public function output($code, $data = NULL, $force_close = false) {
$this->header('content-length', strlen($output));

/* Check if debug is enabled */
if ($this->_debug) {
/* If so, dump output contents to error log */
error_log($output);
if ($this->_debug === true) {
/* Check if the debugging level includes output logging */
if ($this->_debug_level >= 1) {
/* If so, include output content */
$this->_debug_content['output'] = $body;
}

/* Dump debug content to a file under the debug output directory */
$fp = fopen($this->_debug_directory . '/' . $this->id() . '.json', 'w+');
fwrite($fp, json_encode($this->_debug_content, JSON_PRETTY_PRINT));
fclose($fp);
}

/* Check if we should inform the client to close the connection */
Expand Down Expand Up @@ -1782,15 +1795,15 @@ function ($c, $raw_header) use (&$_response_headers) {
/* Update RESTful subcodes for the current request, based on cURL errno for this related request */
switch ($curl_errno) {
case CURLE_COULDNT_CONNECT: {
$this->subcode(UWEB_RESTFUL_SUBCODE_RELATED_COULDNT_CONNECT);
$this->subcode(UWEB_SUBCODE_RESTFUL_RELATED_COULDNT_CONNECT);
} break;
case CURLE_OPERATION_TIMEOUTED: {
/* Check if the connection was established in the first place. If not, this timeout also means a connection failure */
if (!curl_getinfo($ch, CURLINFO_CONNECT_TIME)) {
$this->subcode(UWEB_RESTFUL_SUBCODE_RELATED_COULDNT_CONNECT);
$this->subcode(UWEB_SUBCODE_RESTFUL_RELATED_COULDNT_CONNECT);
}

$this->subcode(UWEB_RESTFUL_SUBCODE_RELATED_OPERATION_TIMEOUT);
$this->subcode(UWEB_SUBCODE_RESTFUL_RELATED_OPERATION_TIMEOUT);
} break;
}

Expand Down
88 changes: 52 additions & 36 deletions system/modules/composite.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private function _combine_headers($composite_headers = array(), $composite_name

/* Normalize case for header names and replace existing ones from parent request */
foreach ($composite_headers as $k => $v) {
$headers[strtolower($k)] = $v;
$headers[strtolower($k)] = (string) $v;
}

/* Initialize headers list */
Expand Down Expand Up @@ -450,7 +450,7 @@ private function _replace_variables(&$v, $k, $output) {
if (is_castable_integer($variables[0])) {
$index = intval($variables[0]);
} else if ($variables[0] == 'last') {
$index = count($output) - 1;
$index = $this->_current_index - 1;
} else {
/* Check for empty indexes - this should never occur */
if (!$variables[0]) {
Expand Down Expand Up @@ -547,6 +547,11 @@ public function process() {
$this->restful->output('400');
}

if (isset($input['single']) && (gettype($input['single']) != 'boolean')) {
$this->restful->error('Composite contains a property "single" with an invalid type. Expecting boolean.');
$this->restful->output('400');
}

if (!isset($input['content'])) {
$this->restful->error('No content found in composite data. A "content" key with a linear array as its type is required.');
$this->restful->output('400');
Expand All @@ -568,6 +573,21 @@ public function process() {
for ($i = 0; $i < count($input['content']); $i ++) {
$e = $input['content'][$i];

/* Check if unsafe is set and contains a valid type */
if (isset($e['unsafe'])) {
/* Validate type */
if (gettype($e['unsafe']) != 'boolean') {
$this->restful->error('Composite element index ' . $i . ' contains a "unsafe" property with an invalid type. Expecting boolean.');
$this->restful->output('400');
}

/* Check if explicit unsafe is true and if it is allowed to exist */
if ($e['unsafe'] && !current_config()['composite']['allow_explicit_unsafe']) {
$this->restful->error('Composite element index ' . $i . ' contains explicit unsafe requests.');
$this->restful->output('403');
}
}

/* Check required keys */
foreach (array('endpoint', 'method') as $k) {
if (!isset($e[$k]) || !$e[$k]) {
Expand Down Expand Up @@ -615,28 +635,20 @@ public function process() {
}

foreach ($e['headers'] as $k => $v) {
if (gettype($v) != 'string') {
$this->restful->error('Composite element index ' . $i . ' containing a "headers" key of the right type (non-linear array), but at least one entry value of this array is not of string type for the following key: ' . $k);
$this->restful->output('400');
}
if (current_config()['composite']['strict_header_types'] === true) {
if (gettype($v) != 'string') {
$this->restful->error('Composite element index ' . $i . ' containing a "headers" key of the right type (non-linear array), but at least one entry value of this array is not of strict string type for the following key: ' . $k);
$this->restful->output('400');
}
} else {
if (!in_array(gettype($v), array('string', 'integer', 'double', 'boolean', 'NULL'))) {
$this->restful->error('Composite element index ' . $i . ' containing a "headers" key of the right type (non-linear array), but at least one entry value of this array is not of an acceptable types for the following key: ' . $k);
$this->restful->output('400');
}
}
}
}

/* Check if unsafe is set and contains a valid type */
if (isset($e['unsafe'])) {
/* Validate type */
if (gettype($e['unsafe']) != 'boolean') {
$this->restful->error('Composite element index ' . $i . ' contains a "unsafe" property with an invalid type. Expecting boolean.');
$this->restful->output('400');
}

/* Check if explicit unsafe is true and if it is allowed to exist */
if ($e['unsafe'] && !current_config()['composite']['allow_explicit_unsafe']) {
$this->restful->error('Composite element index ' . $i . ' contains explicit unsafe requests.');
$this->restful->output('403');
}
}

/* Check if required is set and contains a valid type */
if (isset($e['required']) && (gettype($e['required']) != 'boolean')) {
$this->restful->error('Composite element index ' . $i . ' contains a "required" property with an invalid type. Expecting boolean.');
Expand Down Expand Up @@ -668,8 +680,8 @@ public function process() {

/** Execute composite **/

$output_list = array();
$status_list = array();
$output_list = array_pad(array(), count($input['content']), NULL);
$status_list = array_pad(array(), count($input['content']), NULL);

for ($i = 0; $i < count($input['content']); $i ++) {
/* Update current index */
Expand All @@ -678,9 +690,6 @@ public function process() {
/* Shorten variable name for the current content entry */
$c = $input['content'][$i];

/* Combine headers */
$c_headers = $this->_combine_headers(isset($c['headers']) ? $c['headers'] : array(), $input['composite']);

/* If this entry is marked as unsafe, replace any variables found. */
if (isset($c['unsafe']) && ($c['unsafe'] === true)) {
/* Reset any previous indication of found variables from previous child requests */
Expand All @@ -689,15 +698,18 @@ public function process() {
/* Security is now in the hands of the requester, as an unsafe call was forced.
* The requester must grant that a the content of the payload is always fully controlled programatically, and never filled or partially controlled by a third party.
*/
$this->_lookup_variables($c['payload'], $output_list);
$this->_lookup_variables($c, $output_list);

/* Check if explicit unsafe requests can appear without variables set, and if not, check if any variables were set */
if (!current_config()['composite']['allow_unsafe_without_vars'] && !$this->_unsafe_variables_found) {
$this->restful->error('Composite element index ' . $i . ' contains an enabled unsafe property, but no variables were found in the payload.');
$this->restful->error('Composite element index ' . $i . ' contains an enabled unsafe property, but no variables were found in its content.');
$this->restful->output('403');
}
}

/* Combine headers */
$c_headers = $this->_combine_headers(isset($c['headers']) ? $c['headers'] : array(), $input['composite']);

/* Perform request */
$r = $this->restful->request(
$c['method'],
Expand All @@ -714,6 +726,12 @@ public function process() {
current_config()['composite']['child_content_encoding']
);

/* Update status list */
array_push($status_list, $status_code);

/* Update output list */
$output_list[$i] = $r;

/* Check if the returned status code falls under the lowest code to trigger an error */
if ($status_code >= current_config()['composite']['lowest_error_code']) {
/* Check if the request is marked as required, and if so, abort execution... */
Expand All @@ -724,26 +742,24 @@ public function process() {
/* Set result partial error subcode when a request fails and is not marked as required */
$this->restful->subcode(UWEB_SUBCODE_COMPOSITE_ERROR_RESULT_PARTIAL);
}
} else if (isset($input['single']) && ($input['single'] === true)) {
/* If "single" property is enabled, the first successful request shall end the composite execution. */
break;
}

/* Update status list */
array_push($status_list, $status_code);

/* Update output list */
array_push($output_list, $r);
}


/** Post-process results **/

/* Check for composite child requests which results should be removed from the effective response */
for ($i = 0; $i < count($input['content']); $i ++) {
if (isset($input['content'][$i]['hidden']) && ($input['content'][$i]['hidden'] === true)) {
if (($status_list[$i] !== NULL) && isset($input['content'][$i]['hidden']) && ($input['content'][$i]['hidden'] === true)) {
/* Exclude result from output list */
$output_list[$i] = array('hidden' => true);
$output_list[$i] = array('hidden' => true, 'code' => $status_list[$i]);
}
}


/* All good */
return array(
'code' => '201',
Expand Down
14 changes: 14 additions & 0 deletions system/modules/es.php
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,12 @@ private function _filter_constant_score($config, $index, $input, $max_records =
$data[$index]['total'] = $output['hits']['total'];
$data[$index]['count'] = count($output['hits']['hits']);

/* Check if 404 response code was requested when no results are present */
if (!$data[$index]['count'] && isset($input['404']) && ($input['404'] === true)) {
$this->restful->error('No results.');
$this->restful->output('404'); /* Not Found */
}

/* inorder results require a pre-existing array, filled with empty (false) values */
if (isset($search['inorder']) && ($search['inorder'] === true)) {
/* Get 'in' criteria values */
Expand Down Expand Up @@ -902,10 +908,18 @@ private function _fulltext_boosted_score($config, $index, $input, $max_records =
$this->restful->output('502'); /* Bad Gateway */
}

/* Initialize index output */
$data[$index]['total'] = $output['hits']['total'];
$data[$index]['count'] = count($output['hits']['hits']);
$data[$index]['result'] = array();

/* Check if 404 response code was requested when no results are present */
if (!$data[$index]['count'] && isset($input['404']) && ($input['404'] === true)) {
$this->restful->error('No results.');
$this->restful->output('404'); /* Not Found */
}

/* Process index output */
foreach ($output['hits']['hits'] as $hit) {
$filtered_hit = array();

Expand Down
Loading

0 comments on commit f371da4

Please sign in to comment.