Implemented multiple response standards and types

This commit is contained in:
Zi Xing 2022-01-02 23:27:51 -05:00
parent 78ce281b0c
commit 26d359c189
18 changed files with 542 additions and 117 deletions

View File

@ -2,11 +2,14 @@
namespace Methods\v1;
use KimchiAPI\Objects\Response;
class PingMethod extends \KimchiAPI\Abstracts\Method
{
public function execute()
public function execute(): Response
{
// TODO: Implement execute() method.
$response = new Response();
$response->ResultData = ['Foo' => 'Bar'];
return $response;
}
}

View File

@ -3,6 +3,7 @@
"configuration": {
"logging_enabled": true,
"root_path": "/test",
"debugging_mode": true,
"framework_signature": true,
"api_signature": true,
"khm_enabled": true,
@ -14,7 +15,7 @@
"version": "v1",
"enabled": true,
"methods": [
{"methods": ["GET", "POST"], "path": "ping", "class": "PingMethod"}
{"methods": ["GET", "POST"], "path": "ping", "class": "\\Methods\\v1\\PingMethod"}
]
}
]

View File

@ -0,0 +1,26 @@
<?php
namespace KimchiAPI\Abstracts;
abstract class ResponseStandard
{
/**
* Returns the classic Intellivoid API standard structure
*/
const IntellivoidAPI = 'INTELLIVOID_API';
/**
* Returns the re-defined Kimchi API standard
*/
const KimchiAPI = 'KIMCHI_API';
/**
* Returns the jsonapi.org standard
*/
const JsonApiOrg = 'JSONAPI.ORG';
/**
* Returns the Google API response standard
*/
const GoogleAPI = 'GOOGLE_API';
}

View File

@ -0,0 +1,18 @@
<?php
namespace KimchiAPI\Abstracts;
abstract class ResponseType
{
const Automatic = 'automatic';
const Json = 'application/json';
const Msgpack = 'application/x-msgpack';
const ZiProto = 'application/ziproto';
const XML = 'text/xml';
const Yaml = 'text/x-yaml';
}

View File

@ -6,10 +6,13 @@
use khm\Exceptions\DatabaseException;
use khm\khm;
use KimchiAPI\Abstracts\Method;
use KimchiAPI\Exceptions\ApiException;
use KimchiAPI\Exceptions\ConnectionBlockedException;
use KimchiAPI\Exceptions\InternalServerException;
use KimchiAPI\Exceptions\IOException;
use KimchiAPI\Exceptions\RouterException;
use KimchiAPI\KimchiAPI;
use KimchiAPI\Objects\Configuration;
use KimchiAPI\Utilities\Client;
@ -64,6 +67,7 @@
* @throws ApiException
* @throws ConnectionBlockedException
* @throws DatabaseException
* @throws RouterException
*/
public function initialize()
{
@ -73,6 +77,7 @@
define('KIMCHI_API_RESOURCES_PATH', $this->ResourcesPath);
define('KIMCHI_API_CONFIGURATION_PATH', $this->ConfigurationFilePath);
define('KIMCHI_API_NAME', $this->Configuration->Name);
define('KIMCHI_API_DEBUGGING_MODE', $this->Configuration->ServerConfiguration->DebuggingMode);
define('KIMCHI_API_ROOT_PATH', $this->Configuration->ServerConfiguration->RootPath);
define('KIMCHI_API_SIGNATURES', $this->Configuration->ServerConfiguration->ApiSignature);
define('KIMCHI_API_FRAMEWORK_SIGNATURE', $this->Configuration->ServerConfiguration->FrameworkSignature);
@ -119,7 +124,7 @@
/**
* @return void
* @throws \KimchiAPI\Exceptions\RouterException
* @throws RouterException
*/
private function defineRoutes()
{
@ -130,8 +135,12 @@
$full_path = '/' . $version->Version . '/' . $method->Path;
$this->Router->map(implode('|', $method->Methods), $full_path, function() use ($version, $method, $full_path)
{
print($full_path);
exit();
if(class_exists($method->Class) == false)
throw new ApiException('API Method not found');
/** @var Method $method_class */
$method_class = new $method->Class;
KimchiAPI::handleResponse($method_class->execute());
}, $version->Version . '/' . $method->Class);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace KimchiAPI\Exceptions;
use Exception;
use Throwable;
class UnsupportedResponseStandardException extends Exception
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace KimchiAPI\Exceptions;
use Exception;
use Throwable;
class UnsupportedResponseTypeExceptions extends Exception
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -2,6 +2,9 @@
namespace KimchiAPI\Interfaces;
use KimchiAPI\Objects\Request;
use KimchiAPI\Objects\Response;
interface MethodInterface
{
/**
@ -36,7 +39,6 @@
* Executes the method by passing on the parameter
*
* @param Request $request
* @throws RequestException
* @return Response
*/
public function execute(Request $request): Response;

View File

@ -0,0 +1,16 @@
<?php
namespace KimchiAPI\Interfaces;
use KimchiAPI\Objects\Response;
interface ResponseStandardInterface
{
/**
* Converts the response to a response standard
*
* @param Response $response
* @return array
*/
public static function convertToResponseStandard(Response $response): array;
}

View File

@ -7,9 +7,16 @@
use Exception;
use khm\Exceptions\DatabaseException;
use KimchiAPI\Abstracts\Method;
use KimchiAPI\Abstracts\ResponseStandard;
use KimchiAPI\Abstracts\ResponseType;
use KimchiAPI\Classes\API;
use KimchiAPI\Exceptions\IOException;
use KimchiAPI\Exceptions\MissingComponentsException;
use KimchiAPI\Exceptions\UnsupportedResponseStandardException;
use KimchiAPI\Objects\Response;
use KimchiAPI\Objects\ResponseStandards\GoogleAPI;
use KimchiAPI\Objects\ResponseStandards\IntellivoidAPI;
use KimchiAPI\Objects\ResponseStandards\JsonApiOrg;
use KimchiAPI\Utilities\Converter;
use ppm\Exceptions\AutoloaderException;
use ppm\Exceptions\InvalidComponentException;
@ -241,15 +248,59 @@
}
catch(Exception $e)
{
var_dump($e);
exit();
}
}
else
{
var_dump($API->getRouter()->getRoutes());
print("404");
exit();
}
}
/**
* Handles the response handler and returns the response data to the client
*
* @param Response $response
* @throws Exceptions\UnsupportedResponseTypeExceptions
* @throws UnsupportedResponseStandardException
*/
public static function handleResponse(Response $response)
{
http_response_code($response->ResponseCode);
if($response->ResponseType == ResponseType::Automatic)
$response->ResponseType = ResponseType::Json;
switch($response->ResponseStandard)
{
case ResponseStandard::GoogleAPI:
$response_data = GoogleAPI::convertToResponseStandard($response);
break;
case ResponseStandard::IntellivoidAPI:
$response_data = IntellivoidAPI::convertToResponseStandard($response);
break;
case ResponseStandard::JsonApiOrg:
$response_data = JsonApiOrg::convertToResponseStandard($response);
break;
case ResponseStandard::KimchiAPI:
$response_data = Objects\ResponseStandards\KimchiAPI::convertToResponseStandard($response);
break;
default:
throw new UnsupportedResponseStandardException('The response standard \'' . $response->ResponseStandard . '\' is not supported');
}
$return_results = Converter::serializeResponse($response_data, $response->ResponseType);
http_response_code($response->ResponseCode);
foreach($response->Headers as $header => $value)
header("$header: $value");
header('Content-Type: ' . $response->ResponseType);
header('Content-Length: ' . strlen($return_results));
print($return_results);
exit();
}
}

View File

@ -20,6 +20,13 @@
*/
public $RootPath;
/**
* Indicates if debugging mode is enabled or not
*
* @var bool
*/
public $DebuggingMode;
/**
* Indicates if the framework signature headers are to be returned to the HTTP response
*
@ -66,11 +73,12 @@
return [
'logging_enabled' => (bool)$this->LoggingEnabled,
'root_path' => $this->RootPath,
'debugging_mode' => (bool)$this->DebuggingMode,
'framework_signature' => (bool)$this->FrameworkSignature,
'api_signature' => (bool)$this->ApiSignature,
'headers' => $this->Headers,
'khm_enabled' => (bool)$this->KhmEnabled,
'firewall_deny' => $this->FirewallDeny
'firewall_deny' => $this->FirewallDeny,
'headers' => $this->Headers
];
}
@ -90,6 +98,9 @@
if(isset($data['root_path']))
$ServerConfigurationObject->RootPath = $data['root_path'];
if(isset($data['debugging_mode']))
$ServerConfigurationObject->DebuggingMode = $data['debugging_mode'];
if(isset($data['framework_signature']))
$ServerConfigurationObject->FrameworkSignature = $data['framework_signature'];

View File

@ -5,6 +5,9 @@
namespace KimchiAPI\Objects;
use Exception;
use KimchiAPI\Abstracts\ResponseStandard;
use KimchiAPI\Abstracts\ResponseType;
use KimchiAPI\Utilities\Converter;
class Response
{
@ -29,13 +32,6 @@
*/
public $ErrorMessage;
/**
* Optional information about the error in a data form
*
* @var array|string|int|bool|null
*/
public $ErrorData;
/**
* The result data of the request
*
@ -43,6 +39,51 @@
*/
public $ResultData;
/**
* @var string|ResponseStandard
*/
public $ResponseStandard;
/**
* The HTTP response code to return
*
* @var int
*/
public $ResponseCode;
/**
* The response type to return to the client
*
* @var string|ResponseType
*/
public $ResponseType;
/**
* Custom HTTP headers to return
*
* @var array
*/
public $Headers;
/**
* Optional exception for debugging purposes
*
* @var Exception|null
*/
public $Exception;
public function __construct()
{
$this->Success = true;
$this->ErrorCode = null;
$this->ResponseStandard = ResponseStandard::KimchiAPI;
$this->ResultData = null;
$this->ResponseCode = 200;
$this->ResponseType = ResponseType::Automatic;
$this->Headers = [];
$this->Exception = null;
}
/**
* Returns an array representation of the object
*
@ -51,78 +92,41 @@
*/
public function toArray(bool $compact=false): array
{
if($compact)
{
return [
0x001 => $this->Success,
0x002 => $this->ErrorCode,
0x003 => $this->ErrorMessage,
0x004 => $this->ErrorData,
0x005 => $this->ResultData
];
}
else
{
return [
"success" => $this->Success,
"error_code" => $this->ErrorCode,
"error_message" => $this->ErrorMessage,
"error_data" => $this->ErrorData,
"result_data" => $this->ResultData
];
}
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return Response
*/
public static function fromArray(array $data): Response
{
$response_object = new Response();
if(isset($data[0x001]))
$response_object->Success = $data[0x001];
if(isset($data["success"]))
$response_object->Success = $data["success"];
if(isset($data[0x002]))
$response_object->ErrorCode = $data[0x002];
if(isset($data["error_code"]))
$response_object->ErrorCode = $data["error_code"];
if(isset($data[0x003]))
$response_object->ErrorMessage = $data[0x003];
if(isset($data["error_message"]))
$response_object->ErrorMessage = $data["error_message"];
if(isset($data[0x004]))
$response_object->ErrorData = $data[0x004];
if(isset($data["error_data"]))
$response_object->ErrorData = $data["error_data"];
if(isset($data[0x005]))
$response_object->ResultData = $data[0x005];
if(isset($data["result_data"]))
$response_object->ResultData = $data["result_data"];
return $response_object;
return [
'success' => $this->Success,
'error_code' => $this->ErrorCode,
'error_message' => $this->ErrorMessage,
'result_data' => $this->ResultData,
'response_standard' => $this->ResponseStandard,
'response_code' => $this->ResponseCode,
'response_type' => $this->ResponseType,
'headers' => $this->Headers,
'exception' => ($this->Exception == null ? null : Converter::exceptionToArray($this->Exception))
];
}
/**
* Constructs a response from an exception
* @param Exception $exception
* @param bool $internal_error
* @return Response
*/
public static function fromException(Exception $exception): Response
public static function fromException(Exception $exception, bool $internal_error=false): Response
{
$response_object = new Response();
$response_object->Success = false;
$response_object->ErrorCode = $exception->getCode();
$response_object->ErrorMessage = $exception->getMessage();
$response_object->Exception = $exception;
if($internal_error)
{
$response_object->ErrorCode = 0;
$response_object->ErrorMessage = 'There was an internal server error';
}
else
{
$response_object->ErrorCode = $exception->getCode();
$response_object->ErrorMessage = $exception->getMessage();
}
return $response_object;
}

View File

@ -0,0 +1,41 @@
<?php
namespace KimchiAPI\Objects\ResponseStandards;
use KimchiAPI\Objects\Response;
use KimchiAPI\Utilities\Converter;
class GoogleAPI implements \KimchiAPI\Interfaces\ResponseStandardInterface
{
/**
* @inheritDoc
*/
public static function convertToResponseStandard(Response $response): array
{
if($response->Success)
{
return [
'data' => $response->ResultData
];
}
if(defined('KIMCHI_API_DEBUGGING_MODE') && KIMCHI_API_DEBUGGING_MODE)
{
return [
'error' => [
'code' => $response->ErrorCode,
'message' => $response->ErrorMessage,
'exception' => ($response->Exception == null ? null : Converter::exceptionToArray($response->Exception))
]
];
}
return [
'error' => [
'code' => $response->ErrorCode,
'message' => $response->ErrorMessage
]
];
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace KimchiAPI\Objects\ResponseStandards;
use KimchiAPI\Interfaces\ResponseStandardInterface;
use KimchiAPI\Objects\Response;
use KimchiAPI\Utilities\Converter;
class IntellivoidAPI implements ResponseStandardInterface
{
/**
* @inheritDoc
*/
public static function convertToResponseStandard(Response $response): array
{
if($response->Success)
{
return [
'success' => true,
'response_code' => (int)$response->ResponseCode,
'results' => $response->ResultData
];
}
if(defined('KIMCHI_API_DEBUGGING_MODE') && KIMCHI_API_DEBUGGING_MODE)
{
return [
'success' => false,
'response' => (int)$response->ResponseCode,
'error' => [
'error_code' => $response->ErrorCode,
'type' => 'NOT_APPLICABLE (BACKWARDS COMPATIBILITY)',
'message' => $response->ErrorMessage
],
'exception' => ($response->Exception == null ? null : Converter::exceptionToArray($response->Exception))
];
}
return [
'success' => false,
'response_code' => (int)$response->ResponseCode,
'error' => [
'error_code' => $response->ErrorCode,
'type' => 'NOT_APPLICABLE (BACKWARDS COMPATIBILITY)',
'message' => $response->ErrorMessage
]
];
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace KimchiAPI\Objects\ResponseStandards;
use KimchiAPI\Interfaces\ResponseStandardInterface;
use KimchiAPI\Objects\Response;
use KimchiAPI\Utilities\Converter;
class JsonApiOrg implements ResponseStandardInterface
{
/**
* @inheritDoc
*/
public static function convertToResponseStandard(Response $response): array
{
if($response->Success)
{
return [
'data' => [$response->ResultData]
];
}
if(defined('KIMCHI_API_DEBUGGING_MODE') && KIMCHI_API_DEBUGGING_MODE)
{
return [
'errors' => [
[
'status' => $response->ErrorCode,
'detail' => $response->ErrorMessage,
'source' => ($response->Exception == null ? null : Converter::exceptionToArray($response->Exception))
]
]
];
}
return [
'errors' => [
[
'status' => $response->ErrorCode,
'detail' => $response->ErrorMessage,
]
]
];
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace KimchiAPI\Objects\ResponseStandards;
use KimchiAPI\Objects\Response;
use KimchiAPI\Utilities\Converter;
class KimchiAPI implements \KimchiAPI\Interfaces\ResponseStandardInterface
{
/**
* @inheritDoc
*/
public static function convertToResponseStandard(Response $response): array
{
if($response->Success)
{
return [
'status' => true,
'result' => $response->ResultData
];
}
else
{
if(defined('KIMCHI_API_DEBUGGING_MODE') && KIMCHI_API_DEBUGGING_MODE)
{
return [
'status' => false,
'error_code' => $response->ErrorCode,
'description' => $response->ErrorMessage,
'exception' => ($response->Exception == null ? null : Converter::exceptionToArray($response->Exception))
];
}
return [
'status' => false,
'error_code' => $response->ErrorCode,
'description' => $response->ErrorMessage
];
}
}
}

View File

@ -2,51 +2,37 @@
namespace KimchiAPI\Utilities;
use Exception;
use KimchiAPI\Abstracts\ResponseType;
use KimchiAPI\Exceptions\UnsupportedResponseTypeExceptions;
use Symfony\Component\Yaml\Yaml;
use ZiProto\ZiProto;
class Converter
{
/**
* Converts the string to a function safe name
* Converts an exception to an array representation
*
* @param string $input
* @return string
* @param Exception $e
* @return array
*/
public static function functionNameSafe(string $input): string
public static function exceptionToArray(Exception $e): array
{
$out = preg_replace("/(?>[^\w\.])+/", "_", $input);
// Replace any underscores at start or end of the string.
if ($out[0] == "_")
$return_results = [
'file_path' => $e->getFile(),
'line' => $e->getLine(),
'code' => $e->getCode(),
'message' => $e->getMessage(),
'trace' => $e->getTrace()
];
if($e->getPrevious() !== null)
{
$out = substr($out, 1);
}
if ($out[-1] == "_")
{
$out = substr($out, 0, -1);
/** @noinspection PhpParamsInspection */
$return_results['previous'] = self::exceptionToArray($e->getPrevious());
}
return $out;
}
/**
* Truncates a long string
*
* @param string $input
* @param int $length
* @return string
*/
public static function truncateString(string $input, int $length): string
{
return (strlen($input) > $length) ? substr($input,0, $length).'...' : $input;
}
/**
* Sanitize Method
*
* @param string $command
* @return string
*/
public static function sanitizeMethod(string $command): string
{
return str_replace(' ', '', self::ucWordsUnicode(str_replace('_', ' ', $command)));
return $return_results;
}
/**
@ -92,4 +78,34 @@
return null;
}
/**
* Serializes the response
*
* @param array $data
* @param string $response_type
* @return string
* @throws UnsupportedResponseTypeExceptions
*/
public static function serializeResponse(array $data, string $response_type): string
{
if($response_type == ResponseType::Automatic)
$response_type = ResponseType::Json;
switch($response_type)
{
case ResponseType::Json:
return json_encode($data, JSON_UNESCAPED_SLASHES);
case ResponseType::ZiProto:
case ResponseType::Msgpack:
return ZiProto::encode($data);
case ResponseType::Yaml:
return Yaml::dump($data);
default:
throw new UnsupportedResponseTypeExceptions('The response type \'' . $response_type . '\' is not supported by KimchiAPI');
}
}
}

View File

@ -13,6 +13,18 @@
"version": "latest",
"source": "default@github/intellivoid/khm",
"required": true
},
{
"package": "net.intellivoid.ziproto",
"version": "latest",
"source": "default@github/intellivoid/ziproto",
"required": true
},
{
"package": "com.symfony.yaml",
"version": "latest",
"source": "symfony@composer/yaml",
"required": true
}
],
"configuration": {
@ -31,6 +43,14 @@
"required": true,
"file": "Abstracts/Method.php"
},
{
"required": true,
"file": "Abstracts/ResponseType.php"
},
{
"required": true,
"file": "Abstracts/ResponseStandard.php"
},
{
"required": true,
"file": "Classes/Router.php"
@ -47,6 +67,10 @@
"required": true,
"file": "KimchiAPI.php"
},
{
"required": true,
"file": "Exceptions/UnsupportedResponseStandardException.php"
},
{
"required": true,
"file": "Exceptions/BadEnvironmentException.php"
@ -75,6 +99,10 @@
"required": true,
"file": "Exceptions/RouterException.php"
},
{
"required": true,
"file": "Exceptions/UnsupportedResponseTypeExceptions.php"
},
{
"required": true,
"file": "Exceptions/ApiException.php"
@ -95,6 +123,10 @@
"required": true,
"file": "Interfaces/MethodInterface.php"
},
{
"required": true,
"file": "Interfaces/ResponseStandardInterface.php"
},
{
"required": true,
"file": "Objects/Configuration/MethodConfiguration.php"
@ -118,6 +150,22 @@
{
"required": true,
"file": "Objects/Configuration.php"
},
{
"required": true,
"file": "Objects/ResponseStandards/KimchiAPI.php"
},
{
"required": true,
"file": "Objects/ResponseStandards/JsonApiOrg.php"
},
{
"required": true,
"file": "Objects/ResponseStandards/IntellivoidAPI.php"
},
{
"required": true,
"file": "Objects/ResponseStandards/GoogleAPI.php"
}
],
"files": [