X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fseekat;a=blobdiff_plain;f=lib%2FAPI%2FContentType.php;h=e69f775ee454ef8e9dd8fcd33b74cbad03d346a5;hp=3ce123d1024e391dc30eea192c6956e463631eb6;hb=2121556150be871684b5046af7cf250b8219128d;hpb=626d8937c75f6d8fca463fa2b374f645068b2d6d diff --git a/lib/API/ContentType.php b/lib/API/ContentType.php index 3ce123d..e69f775 100644 --- a/lib/API/ContentType.php +++ b/lib/API/ContentType.php @@ -2,36 +2,19 @@ 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 @@ -39,13 +22,19 @@ final class ContentType */ 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; + } } /** @@ -66,123 +55,132 @@ final class ContentType } /** - * 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))) { - 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);