Skip to content
This repository has been archived by the owner on Jun 1, 2023. It is now read-only.

Commit

Permalink
Try to make Hack docks site re-usable
Browse files Browse the repository at this point in the history
First step. Just putting it up so I can pull it into a few projects easily to
see how well it works.

refs hhvm/user-documentation#206
  • Loading branch information
fredemmott committed Feb 9, 2017
1 parent b7b33c8 commit 0a1df5a
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 125 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
*.swp
vendor/
1 change: 1 addition & 0 deletions .hhconfig
@@ -0,0 +1 @@
assume_php=false
12 changes: 12 additions & 0 deletions composer.json
@@ -0,0 +1,12 @@
{
"name": "fredemmott/hack-router",
"description": "URI routing for Hack",
"keywords": ["hack", "router", "routing", "hhvm"],
"homepage": "https://github.com/fredemmott/hack-router",
"require": {
"nikic/fast-route": "^0.7.0"
},
"autoload": {
"classmap": ["src/"],
}
}
62 changes: 62 additions & 0 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

120 changes: 47 additions & 73 deletions src/BaseRouter.php
Expand Up @@ -9,98 +9,72 @@
*
*/

namespace Facebook\HackRouter;

use HHVM\UserDocumentation\ArgAssert;
use HHVM\UserDocumentation\BuildPaths;
use HHVM\UserDocumentation\LocalConfig;
abstract class BaseRouter<
TBaseController,
TGETController as TBaseController,
TPOSTController as TBaseController
> {
abstract protected function getGETRoutes(): ImmMap<string, classname<TGETController>>;
abstract protected function getPOSTRoutes(): ImmMap<string, classname<TGETController>>;

class Router {
private function getReadOnlyRoutes(
): KeyedIterable<string, classname<WebController>> {
return ImmMap {
'/' => HomePageController::class,
'/search' => SearchController::class,
'/{product:(?:hack|hhvm)}/'
=> GuidesListController::class,
'/{product:(?:hack)}/reference/'
=> APIListController::class,
'/{product:(?:hack)}/reference/{type:(?:class|function|interface|trait)}/'
=> APIListController::class,
'/{product:(?:hack)}/reference/{type:(?:class|function|interface|trait)}/{name}/'
=> APIGenericPageController::class,
'/{product:(?:hack)}/reference/{type:(?:class|interface|trait)}/{class}/{method}/'
=> APIMethodPageController::class,
'/{product:(?:hack|hhvm)}/{guide}/'
=> RedirectToGuideFirstPageController::class,
'/{product:(?:hack|hhvm)}/{guide}/{page}'
=> GuidePageController::class,
'/manual/en/{legacy_id}.php'
=> LegacyRedirectController::class,
'/robots.txt'
=> RobotsTxtController::class,
'/__content'
=> WebPageContentController::class,
'/s/{checksum}{file:/.+}'
=> StaticResourcesController::class,
'/j/{keyword}'
=> JumpController::class,
};
}

private function getWriteRoutes(
): KeyedIterable<string, classname<WebController>> {
return ImmMap {
'/__survey/go'
=> SurveyRedirectController::class,
'/__survey/nothanks'
=> SurveyNoThanksController::class,
};
}

private function getDispatcher(): \FastRoute\Dispatcher {
return \FastRoute\cachedDispatcher(
function(\FastRoute\RouteCollector $r): void {
foreach ($this->getReadOnlyRoutes() as $route => $classname) {
$r->addRoute('GET', $route, $classname);
}

foreach ($this->getWriteRoutes() as $route => $classname) {
$r->addRoute('POST', $route, $classname);
}
},
/* HH_FIXME[4110] nikic/fastroute#83*/
shape(
'cacheFile' => BuildPaths::FASTROUTE_CACHE,
'cacheDisabled' => !LocalConfig::CACHE_ROUTES,
),
);
protected function getCacheFilePath(): ?string {
return null;
}

public function routeRequest(
\Psr\Http\Message\ServerRequestInterface $request
): (classname<WebController>, ImmMap<string, string>) {
$path = $request->getUri()->getPath();
string $method,
string $path,
): (classname<TBaseController>, ImmMap<string, string>) {
$route = $this->getDispatcher()->dispatch(
$request->getMethod(),
$method,
$path,
);
switch ($route[0]) {
case \FastRoute\Dispatcher::NOT_FOUND:
throw new HTTPNotFoundException($path);
throw new NotFoundException($method, $path);
case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
throw new HTTPMethodNotAllowedException($path);
throw new MethodNotAllowedException($method, $path);
case \FastRoute\Dispatcher::FOUND:
return tuple(
ArgAssert::isClassname($route[1], WebController::class),
$route[1],
(new Map($route[2]))
->map($encoded ==> urldecode($encoded))
->toImmMap(),
);
}

invariant_violation(
"Unknown fastroute result: %s",
var_export($route[0], true),
throw new UnknownException($route, $method, $path);
}

private function getDispatcher(): \FastRoute\Dispatcher {
$cache_file = $this->getCacheFilePath();
if ($cache_file !== null) {
$options = shape(
'cacheFile' => $cache_file,
'cacheDisabled' => false,
);
} else {
$options = shape(
'cacheDisabled' => true,
);
}

return \FastRoute\cachedDispatcher(
$rc ==> $this->addRoutesToCollector($rc),
$options,
);
}

private function addRoutesToCollector(
\FastRoute\RouteCollector $r,
): void {
foreach ($this->getGETRoutes() as $route => $classname) {
$r->addRoute('GET', $route, $classname);
}
foreach ($this->getPOSTRoutes() as $route => $classname) {
$r->addRoute('POST', $route, $classname);
}
}
}
22 changes: 22 additions & 0 deletions src/MethodNotAllowedException.php
@@ -0,0 +1,22 @@
<?hh // strict
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

namespace Facebook\HackRouter;

final class MethodNotAllowedException extends RoutingException {
public function __construct(string $method, string $path) {
parent::__construct(
"Method Not Allowed: ".$method.' ('.$path.')',
$method,
$path,
);
}
}
22 changes: 22 additions & 0 deletions src/NotFoundException.php
@@ -0,0 +1,22 @@
<?hh // strict
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

namespace Facebook\HackRouter;

final class NotFoundException extends RoutingException {
public function __construct(string $method, string $path) {
parent::__construct(
"Not Found: ".$path,
$method,
$path,
);
}
}
30 changes: 30 additions & 0 deletions src/RoutingException.php
@@ -0,0 +1,30 @@
<?hh // strict
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

namespace Facebook\HackRouter;

abstract class RoutingException extends \Exception {
protected function __construct(
string $message,
private string $requestMethod,
private string $requestedPath,
) {
parent::__construct($message);
}

public function getRequestMethod(): string {
return $this->requestMethod;
}

public function getRequestedPath(): string {
return $this->requestedPath;
}
}
30 changes: 30 additions & 0 deletions src/UnknownException.php
@@ -0,0 +1,30 @@
<?hh // strict
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

namespace Facebook\HackRouter;

final class UnknownException extends RoutingException {
public function __construct(
private array<mixed> $fastRouteResult,
string $method,
string $path,
) {
parent::__construct(
"Unknown FastRoute result: ".var_export($fastRouteResult, true),
$method,
$path,
);
}

public function getFastRouteResult(): array<mixed> {
return $this->fastRouteResult;
}
}
52 changes: 0 additions & 52 deletions src/http_exceptions.php

This file was deleted.

0 comments on commit 0a1df5a

Please sign in to comment.