X-Git-Url: https://git.m6w6.name/?a=blobdiff_plain;f=lib%2FAPI%2FContentType.php;h=e69f775ee454ef8e9dd8fcd33b74cbad03d346a5;hb=654d736df2c46ec2520f73e9089d06a44f2b9c50;hp=b2eb5fbc0690f74af90b67214d37a6ca93b452a1;hpb=98264bf4807a1b82ee7d1584ebb73b4bbba58e6e;p=m6w6%2Fseekat diff --git a/lib/API/ContentType.php b/lib/API/ContentType.php index b2eb5fb..e69f775 100644 --- a/lib/API/ContentType.php +++ b/lib/API/ContentType.php @@ -4,86 +4,183 @@ namespace seekat\API; 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; -class ContentType -{ - static private $version = 3; - - 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", - ]; +final class ContentType { + /** + * @var int + */ + private $version; + /** + * Content type abbreviation + * @var string + */ private $type; - static function register(string $type, callable $handler) { - self::$types[$type] = $handler; + /** + * Content type handler map + * @var array + */ + static private $types = []; + + /** + * Register a content type handler + */ + static function register(Handler $handler) { + foreach ($handler->types() as $type) { + self::$types[$type] = $handler; + } } + /** + * Check whether a handler is registered for a particular content type + * @param string $type The (abbreviated) content type + * @return bool + */ static function registered(string $type) : bool { return isset(self::$types[$type]); } + /** + * Unregister a content type handler + * @param string $type + */ static function unregister(string $type) { unset(self::$types[$type]); } - static function version(int $v = null) : int { - $api = self::$version; - if (isset($v)) { - self::$version = $v; + /** + * API Version + * + * @param int $version + * @param string $type + */ + function __construct(int $version = 3, string $type = null) { + $this->version = $version; + if (isset($type)) { + $this->setContentType($this->extractTypeFromParams(new Params($type))); } - return $api; - } - - 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; } - private static function fromBase64(Body $base64) : string { - if (false === ($decoded = base64_decode($base64))) { - throw new \UnexpectedValueExcpeption("Could not decode BASE64"); + /** + * @param Header $contentType + */ + function setContentTypeHeader(Header $contentType) { + $this->type = $this->extractTypeFromHeader($contentType); + } + + /** + * @param string $contentType + */ + function setContentType(string $contentType) { + $this->type = $this->extractType($contentType); + } + + /** + * @return int + */ + function getVersion() : int { + return $this->version; + } + + /** + * Get the (abbreviated) content type name + * @return string + */ + function getType() : string { + return $this->type; + } + + /** + * Get the (full) content type + * @return string + */ + function getContentType() : string { + return $this->composeType($this->type); + } + + /** + * @param API $api + * @return API clone + */ + function apply(API $api) : API { + return $api->withHeader("Accept", $this->getContentType()); + } + + /** + * Decode a response message's body according to its content type + * @param Body $data + * @return mixed + * @throws UnexpectedValueException + */ + function decode(Body $data) { + $type = $this->getType(); + if (static::registered($type)) { + return self::$types[$type]->decode($data); } + throw new UnexpectedValueException("Unhandled content type '$type'"); } - private static function fromData(Body $data) : string { - return (string) $data; + /** + * Encode a request message's body according to its content type + * @param mixed $data + * @return Body + * @throws UnexpectedValueException + */ + function encode($data) : Body { + $type = $this->getType(); + if (static::registered($type)) { + return self::$types[$type]->encode($data); + } + throw new UnexpectedValueException("Unhandled content type '$type'"); } - function __construct(Header $contentType) { - if (strcasecmp($contentType->name, "Content-Type")) { - throw new \InvalidArgumentException( - "Expected Content-Type header, got ". $contentType->name); + 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; } - $vapi = static::version(); - $this->type = preg_replace("/ - (?:application\/(?:vnd\.github(?:\.v$vapi)?)?) + 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", current(array_keys($contentType->getParams()->params))); + )/x", "\\1", $type); } - function getType() : string { - return $this->type; + private function extractTypeFromParams(Params $params) : string { + return $this->extractType(current(array_keys($params->params))); } - function parseBody(Body $data) { - $type = $this->getType(); - if (static::registered($type)) { - return call_user_func(self::$types[$type], $data, $type); + private function extractTypeFromHeader(Header $header) : string { + if (strcasecmp($header->name, "Content-Type")) { + throw new InvalidArgumentException( + "Expected Content-Type header, got ". $header->name); } - throw new \UnexpectedValueException("Unhandled content type '$type'"); + 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);