Skip to content

Commit

Permalink
format: XML
Browse files Browse the repository at this point in the history
  • Loading branch information
LukeCarrier committed Jun 16, 2018
1 parent 7ea99fc commit 2ef71e6
Show file tree
Hide file tree
Showing 2 changed files with 330 additions and 0 deletions.
185 changes: 185 additions & 0 deletions classes/format/xml_format.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Loaded REST.
*
* @package webservice_loadedrest
* @author Luke Carrier <[email protected]>
* @copyright 2018 Luke Carrier
*/

namespace webservice_loadedrest\format;

use coding_exception;
use Exception;
use external_description;
use external_multiple_structure;
use external_single_structure;
use external_value;
use invalid_parameter_exception;
use XMLWriter;

defined('MOODLE_INTERNAL') || die;

/**
* XML format.
*/
class xml_format extends abstract_format implements format {
/**
* XML version.
*
* @var string
*/
const VERSION = '1.0';

/**
* XML document encoding.
*
* @var string
*/
const ENCODING = 'utf-8';

/**
* @inheritdoc
*/
public function get_name() {
return 'xml';
}

/**
* @inheritdoc
*/
public function serialise($params) {
throw new coding_exception('unimplemented');
}

/**
* @inheritdoc
*/
public function deserialise($body) {
$oldvalue = libxml_use_internal_errors(true);
$data = simplexml_load_string($body);
$errors = libxml_get_errors();
libxml_use_internal_errors($oldvalue);

if ($data === false) {
throw new invalid_parameter_exception(
'mangled and hideous though it was, request body could not'
. ' be parsed as valid xml');
}

return json_decode(json_encode($data), true);
}

/**
* @inheritdoc
*/
public function send_headers() {
parent::send_headers();
header('Content-Type: application/xml; charset=utf-8');
header('Content-Disposition: inline; filename="response.xml"');
}

/**
* @inheritdoc
*/
public function send_error(Exception $exception) {
$doc = new XMLWriter();
$doc->openMemory();
$doc->startDocument(static::VERSION, static::ENCODING);
$doc->startElement('response');
$doc->startElement('success');
$doc->text('false');
$doc->endElement();
$doc->startElement('exception');
$doc->startAttribute('class');
$doc->text(get_class($exception));
$doc->endAttribute();
$doc->startAttribute('code');
$doc->text($exception->getCode());
$doc->endAttribute();
$doc->startElement('message');
$doc->text($exception->getMessage());
$doc->endElement();

if (debugging() && property_exists($exception, 'debuginfo')) {
$doc->startElement('debug');
/** @noinspection PhpUndefinedFieldInspection */
$doc->text($exception->debuginfo);
$doc->endElement();
}

$doc->endElement();
$doc->endDocument();
echo $doc->outputMemory();
}

/**
* @inheritdoc
*/
public function send_response($result, external_description $description) {
$doc = new XMLWriter();
$doc->openMemory();
$doc->startDocument(static::VERSION, static::ENCODING);
$doc->startElement('response');
$this->to_xml($doc, $result, $description);
$doc->endElement();
$doc->endDocument();
echo $doc->outputMemory();
}

/**
* Dump the result to XML.
*
* @param XMLWriter $doc
* @param $result
* @param external_description $description
* @param string|null $key
*/
protected function to_xml(XMLWriter $doc, $result, external_description $description, $key=null) {
$singlekey = $key ?? 'value';

if ($description instanceof external_value) {
switch ($description->type) {
case PARAM_BOOL:
$doc->startElement($singlekey);
$doc->text($result ? 'true' : 'false');
$doc->endElement();
break;
default:
$doc->startElement($singlekey);
$doc->text($result);
$doc->endElement();
}
} elseif ($description instanceof external_single_structure) {
$doc->startElement($singlekey);
foreach ($description->keys as $singlekey => $keydescription) {
$this->to_xml($doc, $result[$singlekey], $keydescription, $singlekey);
}
$doc->endElement();
} elseif ($description instanceof external_multiple_structure) {
$doc->startElement($singlekey);
foreach ($result as $resultitem) {
$this->to_xml($doc, $resultitem, $description->content, $singlekey);
}
$doc->endElement();
} else {
throw new coding_exception(sprintf(
'unknown external_description type %s', get_class($description)));
}
}
}
145 changes: 145 additions & 0 deletions tests/xml_format_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Loaded REST.
*
* @package webservice_loadedrest
* @author Luke Carrier <[email protected]>
* @copyright 2018 Luke Carrier
*/

use webservice_loadedrest\format\xml_format;

defined('MOODLE_INTERNAL') || die;

// Data providers seem to execute before the PHPUnit bootstrap, so Moodle's
// classloader hasn't yet been enabled before we start instantiating external_*
// objects.
global $CFG;
require_once $CFG->libdir . '/externallib.php';

/**
* Loaded REST server test suite.
*
* @group webservice_loadedrest
*/
class webservice_loadedrest_xml_format_testcase extends advanced_testcase {
public function data_deserialise() {
return [
[
'body' => '<request><some>text</some></request>',
'expect' => [
'some' => 'text',
],
],
[
'body' => '<request><some>1</some></request>',
'expect' => [
'some' => 1,
],
],
];
}

/**
* @dataProvider data_deserialise
*/
public function test_deserialise($body, $expect) {
$format = new xml_format();
$this->assertEquals($expect, $format->deserialise($body));
}


/**
* @expectedException invalid_parameter_exception
* @expectedExceptionMessage mangled and hideous though it was, request body could not be parsed as valid xml
*/
public function test_deserialise_throws() {
$format = new xml_format();
$format->deserialise('<trololol');
}

public function test_send_error() {
$format = new xml_format();
$exception = new Exception('message', 1);

ob_start();
$format->send_error($exception);
$output = ob_get_contents();
ob_end_clean();

$this->assertRegExp('%\<response\>.*\</response\>%', $output);
$this->assertRegExp('%\<success\>false\</success\>%', $output);
$this->assertRegExp('%\<exception%', $output);
$this->assertRegExp('%class="Exception"%', $output);
$this->assertRegExp('%code="1"%', $output);
$this->assertRegExp('%\<message\>message\</message\>%', $output);
}

public function data_send_response() {
return [
[
'result' => true,
'description' => new external_value(PARAM_BOOL),
'expect' => [
'%<value>true</value>%',
],
],
[
'result' => [
'key' => 3.14,
],
'description' => new external_single_structure([
'key' => new external_value(PARAM_FLOAT),
]),
'expect' => [
'%<key>3.14</key>%',
],
],
[
'result' => [
[
'key' => 3.14,
],
],
'description' => new external_multiple_structure(new external_single_structure([
'key' => new external_value(PARAM_FLOAT),
])),
'expect' => [
'%<key>3.14</key>%',
],
],
];
}

/**
* @dataProvider data_send_response
*/
public function test_send_response($result, external_description $description, $expect) {
$format = new xml_format();

ob_start();
$format->send_response($result, $description);
$output = ob_get_contents();
ob_end_clean();

$this->assertRegExp('%\<response\>.*\</response\>%', $output);
foreach ($expect as $regexp) {
$this->assertRegExp($regexp, $output);
}
}
}

0 comments on commit 2ef71e6

Please sign in to comment.