929dc0c2c80907eea6ab59aba6dc489b98e1647b
[m6w6/seekat] / lib / API / ContentType.php
1 <?php
2
3 namespace seekat\API;
4
5 use http\{
6 Header,
7 Message\Body
8 };
9
10 use InvalidArgumentException;
11 use UnexpectedValueException;
12
13 class ContentType
14 {
15 /**
16 * API version
17 * @var int
18 */
19 static private $version = 3;
20
21 /**
22 * Content type handler map
23 * @var array
24 */
25 static private $types = [
26 "json" => "self::fromJson",
27 "base64" => "self::fromBase64",
28 "sha" => "self::fromData",
29 "raw" => "self::fromData",
30 "html" => "self::fromData",
31 "diff" => "self::fromData",
32 "patch" => "self::fromData",
33 "text/plain"=> "self::fromData",
34 ];
35
36 /**
37 * Content type abbreviation
38 * @var string
39 */
40 private $type;
41
42 /**
43 * Register a content type handler
44 * @param string $type The content type (abbreviation)
45 * @param callable $handler The handler as function(Body $body):mixed;
46 */
47 static function register(string $type, callable $handler) {
48 self::$types[$type] = $handler;
49 }
50
51 /**
52 * Check whether a handler is registered for a particular content type
53 * @param string $type The (abbreviated) content type
54 * @return bool
55 */
56 static function registered(string $type) : bool {
57 return isset(self::$types[$type]);
58 }
59
60 /**
61 * Unregister a content type handler
62 * @param string $type
63 */
64 static function unregister(string $type) {
65 unset(self::$types[$type]);
66 }
67
68 /**
69 * Get/set the API version to use
70 * @param int $v if not null, update the API version
71 * @return int the previously set version
72 */
73 static function version(int $v = null) : int {
74 $api = self::$version;
75 if (isset($v)) {
76 self::$version = $v;
77 }
78 return $api;
79 }
80
81 /**
82 * @param Body $json
83 * @return mixed
84 * @throws UnexpectedValueException
85 */
86 private static function fromJson(Body $json) {
87 $decoded = json_decode($json);
88 if (!isset($decoded) && json_last_error()) {
89 throw new UnexpectedValueException("Could not decode JSON: ".
90 json_last_error_msg());
91 }
92 return $decoded;
93 }
94
95 /**
96 * @param Body $base64
97 * @return string
98 * @throws UnexpectedValueException
99 */
100 private static function fromBase64(Body $base64) : string {
101 if (false === ($decoded = base64_decode($base64))) {
102 throw new UnexpectedValueException("Could not decode BASE64");
103 }
104 return $decoded;
105 }
106
107 /**
108 * @param Body $data
109 * @return string
110 */
111 private static function fromData(Body $data) : string {
112 return (string) $data;
113 }
114
115 /**
116 * @param Header $contentType
117 * @throws InvalidArgumentException
118 */
119 function __construct(Header $contentType) {
120 if (strcasecmp($contentType->name, "Content-Type")) {
121 throw new InvalidArgumentException(
122 "Expected Content-Type header, got ". $contentType->name);
123 }
124 $vapi = static::version();
125 $this->type = preg_replace("/
126 (?:application\/(?:vnd\.github(?:\.v$vapi)?)?)
127 (?|
128 \. ([^.+]+)
129 | (?:\.[^.+]+)?\+? (json)
130 )/x", "\\1", current(array_keys($contentType->getParams()->params)));
131 }
132
133 /**
134 * Get the (abbreviated) content type name
135 * @return string
136 */
137 function getType() : string {
138 return $this->type;
139 }
140
141 /**
142 * Parse a response message's body according to its content type
143 * @param Body $data
144 * @return mixed
145 * @throws UnexpectedValueException
146 */
147 function parseBody(Body $data) {
148 $type = $this->getType();
149 if (static::registered($type)) {
150 return call_user_func(self::$types[$type], $data, $type);
151 }
152 throw new UnexpectedValueException("Unhandled content type '$type'");
153 }
154 }