X-Git-Url: https://git.m6w6.name/?a=blobdiff_plain;f=lib%2FAPI.php;h=3ed788a6af07b6ef00c0d52d8be25f9c88a96f52;hb=refs%2Fheads%2Fmaster;hp=a0474122cd78e67b0d24df24c72ced3032e5e7ce;hpb=e368287b3cd2dd40945ac8d1a1946bc32268007d;p=m6w6%2Fseekat diff --git a/lib/API.php b/lib/API.php index a047412..3ed788a 100644 --- a/lib/API.php +++ b/lib/API.php @@ -2,75 +2,40 @@ namespace seekat; -use AsyncInterop\Promise; use Countable; use Generator; -use http\{ - Client, Client\Request, Message\Body, QueryString, Url -}; +use http\{Client, Client\Request, Message\Body, QueryString, Url}; +use Iterator; use IteratorAggregate; -use Psr\Log\{ - LoggerInterface, NullLogger -}; -use seekat\API\{ - Call, Consumer, ContentType, Future, Iterator, Links -}; +use Psr\Log\{LoggerInterface, NullLogger}; +use seekat\API\{Call, Consumer, ContentType, Future, Links}; use seekat\Exception\InvalidArgumentException; class API implements IteratorAggregate, Countable { /** - * The current API endpoint URL - * @var Url + * API version */ - private $url; + private int $version = 3; /** * Default headers to send out to the API endpoint - * @var array */ - private $headers; + private array $headers; /** * Current endpoints links - * @var Links */ - private $links; + private ?Links $links = null; /** * Current endpoint data's Content-Type - * @var API\ContentType */ - private $type; + private API\ContentType $type; /** * Current endpoint's data - * @var array|object */ - private $data; - - /** - * Logger - * @var LoggerInterface - */ - private $logger; - - /** - * Cache - * @var Call\Cache\Service - */ - private $cache; - - /** - * Promisor - * @var Future - */ - private $future; - - /** - * The HTTP client - * @var Client - */ - private $client; + private mixed $data = null; /** * Create a new API endpoint root @@ -82,14 +47,15 @@ class API implements IteratorAggregate, Countable { * @param LoggerInterface $log A logger * @param Call\Cache\Service $cache A cache */ - function __construct(Future $future, array $headers = null, Url $url = null, Client $client = null, LoggerInterface $log = null, Call\Cache\Service $cache = null) { - $this->future = $future; - $this->cache = $cache; - $this->logger = $log ?? new NullLogger; - $this->url = $url ?? new Url("https://api.github.com"); - $this->client = $client ?? new Client; + function __construct(private readonly Future $future, + array $headers = null, + private Url $url = new Url("https://api.github.com"), + private readonly Client $client = new Client, + private readonly LoggerInterface $logger = new NullLogger, + private readonly Call\Cache\Service $cache = new Call\Cache\Service\Hollow) { + $this->type = new ContentType($this->version, "json"); $this->headers = (array) $headers + [ - "Accept" => "application/vnd.github.v3+json" + "Accept" => $this->type->getContentType() ]; } @@ -99,8 +65,8 @@ class API implements IteratorAggregate, Countable { * @param string|int $seg The "path" element to ascend into * @return API Endpoint clone referring to {$parent}/{$seg} */ - function __get($seg) : API { - if (substr($seg, -4) === "_url") { + function __get(string|int $seg) : API { + if (str_ends_with($seg, "_url")) { $url = new Url(uri_template($this->data->$seg)); $that = $this->withUrl($url); $seg = basename($that->url->path); @@ -115,6 +81,7 @@ class API implements IteratorAggregate, Countable { (string) $this->url, (string) $that->url ], + "data" => $that->data ]); return $that; @@ -125,31 +92,23 @@ class API implements IteratorAggregate, Countable { * * @param string $method The API's "path" element to ascend into * @param array $args Array of arguments forwarded to \seekat\API::get() - * @return Promise + * @return mixed promise */ - function __call(string $method, array $args) : Promise { + function __call(string $method, array $args) { /* We cannot implement an explicit then() method, * because the Promise implementation might think * we're actually implementing Thenable, * which might cause an infinite loop. - * FIXXME: then/when */ - if ($method === "when") { - $promise = $this->get(); - $promise->when(...$args); - return $promise; - } - + if ($method === "then" /* * very short-hand version: - * ->users->m6w6->gists->get()->when(...) + * ->users->m6w6->gists->get()->then(...) * vs: * ->users->m6w6->gists(...) */ - if (is_callable(current($args))) { - $promise = $this->get(); - $promise->when(current($args)); - return $promise; + || is_callable(current($args))) { + return $this->future->handlePromise($this->get(), ...$args); } return (new Call($this, $method))($args); @@ -159,14 +118,14 @@ class API implements IteratorAggregate, Countable { * Run the send loop through a generator * * @param callable|Generator $cbg A \Generator or a factory of a \Generator yielding promises - * @return Promise The promise of the generator's return value + * @return mixed The promise of the generator's return value * @throws InvalidArgumentException */ - function __invoke($cbg) : Promise { - $this->logger->debug(__FUNCTION__); + function __invoke(callable|Generator $cbg) { + $this->logger->debug(__METHOD__, [$cbg]); $consumer = new Consumer($this->getFuture(), function() { - $this->client->send(); + $this->client->send(); }); invoke: @@ -197,12 +156,7 @@ class API implements IteratorAggregate, Countable { * @return string */ function __toString() : string { - if (is_scalar($this->data)) { - return (string) $this->data; - } - - /* FIXME */ - return json_encode($this->data); + return (string) $this->type->encode($this->data); } /** @@ -211,7 +165,15 @@ class API implements IteratorAggregate, Countable { * @return Iterator */ function getIterator() : Iterator { - return new Iterator($this); + foreach ($this->data as $key => $cur) { + if ($this->__get($key)->exists("url", $url)) { + $url = new Url($url); + $val = $this->withUrl($url)->withData($cur); + } else { + $val = $this->__get($key)->withData($cur); + } + yield $key => $val; + } } /** @@ -220,41 +182,43 @@ class API implements IteratorAggregate, Countable { * @return int */ function count() : int { - return count($this->data); + if (is_array($this->data)) { + $count = count($this->data); + } else if ($this->data instanceof Countable) { + $count = count($this->data); + } else if (is_object($this->data)) { + $count = count((array) $this->data); + } else { + $count = 0; + } + $this->logger->debug("count()", [ + "of type" => typeof($this->data), + "count" => $count + ]); + return $count; } - /** - * @return Url - */ function getUrl() : Url { return $this->url; } - /** - * @return LoggerInterface - */ function getLogger() : LoggerInterface { return $this->logger; } - /** - * @return Future - */ - function getFuture() { + function getFuture() : Future { return $this->future; } - /** - * @return Client - */ public function getClient(): Client { return $this->client; } - /** - * @return array|object - */ - function getData() { + public function getCache() : Call\Cache\Service { + return $this->cache; + } + + function getData() : mixed { return $this->data; } @@ -263,10 +227,17 @@ class API implements IteratorAggregate, Countable { * * @return null|Links */ - function getLinks() { + function getLinks() : ?Links { return $this->links; } + /** + * @return int + */ + function getVersion() : int { + return $this->version; + } + /** * Export the endpoint's underlying data * @@ -275,7 +246,7 @@ class API implements IteratorAggregate, Countable { function export() : array { $data = $this->data; $url = clone $this->url; - $type = $this->type ? clone $this->type : null; + $type = clone $this->type; $links = $this->links ? clone $this->links : null; $headers = $this->headers; return compact("url", "data", "type", "links", "headers"); @@ -303,7 +274,7 @@ class API implements IteratorAggregate, Countable { * @param mixed $data * @return API clone */ - function withData($data) : API { + function withData(mixed $data) : API { $that = clone $this; $that->data = $data; return $that; @@ -330,7 +301,7 @@ class API implements IteratorAggregate, Countable { * @param mixed $value * @return API clone */ - function withHeader(string $name, $value) : API { + function withHeader(string $name, mixed $value) : API { $that = clone $this; if (isset($value)) { $that->headers[$name] = $value; @@ -350,7 +321,11 @@ class API implements IteratorAggregate, Countable { * @return API clone */ function as(string $type, bool $keepdata = true) : API { - $that = ContentType::apply($this, $type); + $ct = new ContentType($this->version, $type); + + $that = $ct->apply($this); + $that->type = $ct; + if (!$keepdata) { $that->data = null; } @@ -362,10 +337,10 @@ class API implements IteratorAggregate, Countable { * * @param mixed $args The HTTP query string parameters * @param array $headers The request's additional HTTP headers - * @return Promise + * @return mixed promise */ - function head($args = null, array $headers = null, $cache = null) : Promise { - return $this->request("HEAD", $args, null, $headers, $cache); + function head($args = null, array $headers = null) { + return $this->request("HEAD", $args, null, $headers); } /** @@ -373,10 +348,10 @@ class API implements IteratorAggregate, Countable { * * @param mixed $args The HTTP query string parameters * @param array $headers The request's additional HTTP headers - * @return Promise + * @return mixed promise */ - function get($args = null, array $headers = null, $cache = null) : Promise { - return $this->request("GET", $args, null, $headers, $cache); + function get($args = null, array $headers = null) { + return $this->request("GET", $args, null, $headers); } /** @@ -384,9 +359,9 @@ class API implements IteratorAggregate, Countable { * * @param mixed $args The HTTP query string parameters * @param array $headers The request's additional HTTP headers - * @return Promise + * @return mixed promise */ - function delete($args = null, array $headers = null) : Promise { + function delete($args = null, array $headers = null) { return $this->request("DELETE", $args, null, $headers); } @@ -396,9 +371,9 @@ class API implements IteratorAggregate, Countable { * @param mixed $body The HTTP message's body * @param mixed $args The HTTP query string parameters * @param array $headers The request's additional HTTP headers - * @return Promise + * @return mixed promise */ - function post($body = null, $args = null, array $headers = null) : Promise { + function post($body = null, $args = null, array $headers = null) { return $this->request("POST", $args, $body, $headers); } @@ -408,9 +383,9 @@ class API implements IteratorAggregate, Countable { * @param mixed $body The HTTP message's body * @param mixed $args The HTTP query string parameters * @param array $headers The request's additional HTTP headers - * @return Promise + * @return mixed promise */ - function put($body = null, $args = null, array $headers = null) : Promise { + function put($body = null, $args = null, array $headers = null) { return $this->request("PUT", $args, $body, $headers); } @@ -420,9 +395,9 @@ class API implements IteratorAggregate, Countable { * @param mixed $body The HTTP message's body * @param mixed $args The HTTP query string parameters * @param array $headers The request's additional HTTP headers - * @return Promise + * @return mixed promise */ - function patch($body = null, $args = null, array $headers = null) : Promise { + function patch($body = null, $args = null, array $headers = null) { return $this->request("PATCH", $args, $body, $headers); } @@ -463,7 +438,7 @@ class API implements IteratorAggregate, Countable { $seg, typeof($this->data, false), $exists ? "true" : "false" ), [ "url" => (string) $this->url, - "val" => $val, + "val" => typeof($val, false), ]); return $exists; @@ -474,12 +449,11 @@ class API implements IteratorAggregate, Countable { * * @param string $method The HTTP request method * @param mixed $args The HTTP query string parameters - * @param mixed $body Thee HTTP message's body - * @param array $headers The request's additional HTTP headers - * @param Call\Cache\Service $cache - * @return Promise + * @param mixed $body The HTTP message's body + * @param ?array $headers The request's additional HTTP headers + * @return mixed promise */ - private function request(string $method, $args = null, $body = null, array $headers = null, Call\Cache\Service $cache = null) : Promise { + private function request(string $method, $args = null, $body = null, array $headers = null) { if (isset($this->data)) { $this->logger->debug("request -> resolve", [ "method" => $method, @@ -489,15 +463,12 @@ class API implements IteratorAggregate, Countable { "headers" => $headers, ]); - return Future\resolve($this->future, $this); + return $this->future->resolve($this); } $url = $this->url->mod(["query" => new QueryString($args)]); $request = new Request($method, $url, ((array) $headers) + $this->headers, - $body = is_array($body) ? json_encode($body) : ( - is_resource($body) ? new Body($body) : ( - is_scalar($body) ? (new Body)->append($body) : - $body))); + $body = $this->type->encode(is_resource($body) ? new Body($body) : $body)); $this->logger->info("request -> deferred", [ "method" => $method, @@ -507,6 +478,6 @@ class API implements IteratorAggregate, Countable { "headers" => $headers, ]); - return (new Call\Deferred($this, $request, $cache ?: $this->cache))(); + return (new Call\Deferred($this, $request, $this->cache))(); } }