namespace seekat\API;
-use seekat\{
- API, Exception\InvalidArgumentException, Exception\UnexpectedValueException
-};
-use http\{
- Header, Message\Body
-};
-
-final class ContentType
-{
+use http\Header;
+use http\Message\Body;
+use http\Params;
+use seekat\API;
+use seekat\API\ContentType\Handler;
+use seekat\Exception\InvalidArgumentException;
+use seekat\Exception\UnexpectedValueException;
+
+final class ContentType {
/**
- * API version
* @var int
*/
- static private $version = 3;
-
- /**
- * Content type handler map
- * @var array
- */
- static private $types = [
- "json" => "self::fromJson",
- "base64" => "self::fromBase64",
- "sha" => "self::fromData",
- "raw" => "self::fromData",
- "html" => "self::fromData",
- "diff" => "self::fromData",
- "patch" => "self::fromData",
- "text/plain"=> "self::fromData",
- "application/octet-stream" => "self::fromStream",
- ];
+ private $version;
/**
* Content type abbreviation
*/
private $type;
+ /**
+ * Content type handler map
+ * @var array
+ */
+ static private $types = [];
+
/**
* Register a content type handler
- * @param string $type The content type (abbreviation)
- * @param callable $handler The handler as function(Body $body):mixed;
*/
- static function register(string $type, callable $handler) {
- self::$types[$type] = $handler;
+ static function register(Handler $handler) {
+ foreach ($handler->types() as $type) {
+ self::$types[$type] = $handler;
+ }
}
/**
}
/**
- * Get/set the API version to use
- * @param int $v if not null, update the API version
- * @return int the previously set version
+ * API Version
+ *
+ * @param int $version
+ * @param string $type
*/
- static function version(int $v = null) : int {
- $api = self::$version;
- if (isset($v)) {
- self::$version = $v;
+ function __construct(int $version = 3, string $type = null) {
+ $this->version = $version;
+ if (isset($type)) {
+ $this->setContentType($this->extractTypeFromParams(new Params($type)));
}
- return $api;
}
/**
- * @param API $api
- * @param string $type
- * @return API
+ * @param Header $contentType
*/
- static function apply(API $api, string $type) : API {
- $part = "[^()<>@,;:\\\"\/\[\]?.=[:space:][:cntrl:]]+";
- if (preg_match("/^$part\/$part\$/", $type)) {
- $that = $api->withHeader("Accept", $type);
- } else {
- switch (substr($type, 0, 1)) {
- case "+":
- case ".":
- case "":
- break;
- default:
- $type = ".$type";
- break;
- }
- $vapi = static::version();
- $that = $api->withHeader("Accept", "application/vnd.github.v$vapi$type");
- }
- return $that;
+ function setContentTypeHeader(Header $contentType) {
+ $this->type = $this->extractTypeFromHeader($contentType);
}
/**
- * @param Body $json
- * @return mixed
- * @throws UnexpectedValueException
+ * @param string $contentType
*/
- private static function fromJson(Body $json) {
- $decoded = json_decode($json);
- if (!isset($decoded) && json_last_error()) {
- throw new UnexpectedValueException("Could not decode JSON: ".
- json_last_error_msg());
- }
- return $decoded;
+ function setContentType(string $contentType) {
+ $this->type = $this->extractType($contentType);
}
/**
- * @param Body $base64
- * @return string
- * @throws UnexpectedValueException
+ * @return int
*/
- private static function fromBase64(Body $base64) : string {
- if (false === ($decoded = base64_decode($base64, true))) {
- throw new UnexpectedValueException("Could not decode BASE64");
- }
- return $decoded;
+ function getVersion() : int {
+ return $this->version;
}
-
/**
- * @param Body $stream
- * @return resource stream
+ * Get the (abbreviated) content type name
+ * @return string
*/
- private static function fromStream(Body $stream) {
- return $stream->getResource();
+ function getType() : string {
+ return $this->type;
}
/**
- * @param Body $data
+ * Get the (full) content type
* @return string
*/
- private static function fromData(Body $data) : string {
- return (string) $data;
+ function getContentType() : string {
+ return $this->composeType($this->type);
}
/**
- * @param Header $contentType
- * @throws InvalidArgumentException
+ * @param API $api
+ * @return API clone
*/
- function __construct(Header $contentType) {
- if (strcasecmp($contentType->name, "Content-Type")) {
- throw new InvalidArgumentException(
- "Expected Content-Type header, got ". $contentType->name);
- }
- $vapi = static::version();
- $this->type = preg_replace("/
- (?:application\/(?:vnd\.github(?:\.v$vapi)?)?)
- (?|
- \. ([^.+]+)
- | (?:\.[^.+]+)?\+? (json)
- )/x", "\\1", current(array_keys($contentType->getParams()->params)));
+ function apply(API $api) : API {
+ return $api->withHeader("Accept", $this->getContentType());
}
/**
- * Get the (abbreviated) content type name
- * @return string
+ * Decode a response message's body according to its content type
+ * @param Body $data
+ * @return mixed
+ * @throws UnexpectedValueException
*/
- function getType() : string {
- return $this->type;
+ function decode(Body $data) {
+ $type = $this->getType();
+ if (static::registered($type)) {
+ return self::$types[$type]->decode($data);
+ }
+ throw new UnexpectedValueException("Unhandled content type '$type'");
}
/**
- * Parse a response message's body according to its content type
- * @param Body $data
- * @return mixed
+ * Encode a request message's body according to its content type
+ * @param mixed $data
+ * @return Body
* @throws UnexpectedValueException
*/
- function parseBody(Body $data) {
+ function encode($data) : Body {
$type = $this->getType();
if (static::registered($type)) {
- return call_user_func(self::$types[$type], $data, $type);
+ return self::$types[$type]->encode($data);
}
throw new UnexpectedValueException("Unhandled content type '$type'");
}
+
+ private function composeType(string $type) : string {
+ $part = "[^()<>@,;:\\\"\/\[\]?.=[:space:][:cntrl:]]+";
+ if (preg_match("/^$part\/$part\$/", $type)) {
+ return $type;
+ }
+
+ switch (substr($type, 0, 1)) {
+ case "+":
+ case ".":
+ case "":
+ break;
+ default:
+ $type = ".$type";
+ break;
+ }
+ return "application/vnd.github.v{$this->version}$type";
+ }
+
+ private function extractType(string $type) : string {
+ return preg_replace("/
+ (?:application\/(?:vnd\.github(?:\.v{$this->version})?)?)
+ (?|
+ \. ([^.+]+)
+ | (?:\.[^.+]+)?\+? (json)
+ )/x", "\\1", $type);
+ }
+
+ private function extractTypeFromParams(Params $params) : string {
+ return $this->extractType(current(array_keys($params->params)));
+ }
+
+ private function extractTypeFromHeader(Header $header) : string {
+ if (strcasecmp($header->name, "Content-Type")) {
+ throw new InvalidArgumentException(
+ "Expected Content-Type header, got ". $header->name);
+ }
+ return $this->extractTypeFromParams($header->getParams());
+ }
}
+
+ContentType::register(new Handler\Text);
+ContentType::register(new Handler\Json);
+ContentType::register(new Handler\Base64);
+ContentType::register(new Handler\Stream);