diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 2b50ddbc0..e1daa4d7a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@v4 + - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days." diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37974e4b3..cf7a64a26 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} diff --git a/app.go b/app.go index 804061e43..12a4dc348 100644 --- a/app.go +++ b/app.go @@ -14,15 +14,13 @@ import ( // Without an App you can't do much! type App struct { Options - // Middleware returns the current MiddlewareStack for the App/Group. - Middleware *MiddlewareStack `json:"-"` - ErrorHandlers ErrorHandlers `json:"-"` - router *mux.Router - moot *sync.RWMutex - routes RouteList - root *App - children []*App - filepaths []string + // Middleware, ErrorHandlers, router, and filepaths are moved to Home. + Home + moot *sync.RWMutex + routes RouteList + // TODO: to be deprecated #road-to-v1 + root *App + children []*App // Routenamer for the app. This field provides the ability to override the // base route namer for something more specific to the app. @@ -44,17 +42,24 @@ func New(opts Options) *App { a := &App{ Options: opts, - ErrorHandlers: ErrorHandlers{ - http.StatusNotFound: defaultErrorHandler, - http.StatusInternalServerError: defaultErrorHandler, + Home: Home{ + name: opts.Name, + host: opts.Host, + prefix: opts.Prefix, + ErrorHandlers: ErrorHandlers{ + http.StatusNotFound: defaultErrorHandler, + http.StatusInternalServerError: defaultErrorHandler, + }, + router: mux.NewRouter(), }, - router: mux.NewRouter(), moot: &sync.RWMutex{}, routes: RouteList{}, children: []*App{}, RouteNamer: baseRouteNamer{}, } + a.Home.app = a // replace root. + a.Home.appSelf = a // temporary, reverse reference to the group app. dem := a.defaultErrorMiddleware a.Middleware = newMiddlewareStack(dem) diff --git a/errors.go b/errors.go index f98595419..228bd3865 100644 --- a/errors.go +++ b/errors.go @@ -30,7 +30,10 @@ func (h HTTPError) Unwrap() error { // Error returns the cause of the error as string. func (h HTTPError) Error() string { - return h.Cause.Error() + if h.Cause != nil { + return h.Cause.Error() + } + return "unknown cause" } // ErrorHandler interface for handling an error for a diff --git a/errors_test.go b/errors_test.go index 7042ade25..bf55f6002 100644 --- a/errors_test.go +++ b/errors_test.go @@ -143,6 +143,18 @@ func Test_defaultErrorHandler_XML_production(t *testing.T) { r.Contains(b, ``) } +func Test_defaultErrorHandler_nil_error(t *testing.T) { + r := require.New(t) + app := New(Options{}) + app.GET("/", func(c Context) error { + return c.Error(http.StatusInternalServerError, nil) + }) + + w := httptest.New(app) + res := w.JSON("/").Get() + r.Equal(http.StatusInternalServerError, res.Code) +} + func Test_PanicHandler(t *testing.T) { app := New(Options{}) app.GET("/string", func(c Context) error { diff --git a/go.mod b/go.mod index b5909ca7c..d62525b1f 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,20 @@ module github.com/gobuffalo/buffalo go 1.16 require ( - github.com/BurntSushi/toml v1.0.0 + github.com/BurntSushi/toml v1.1.0 github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.13.0 github.com/gobuffalo/envy v1.10.1 github.com/gobuffalo/events v1.4.2 - github.com/gobuffalo/flect v0.2.4 + github.com/gobuffalo/flect v0.2.5 github.com/gobuffalo/github_flavored_markdown v1.1.1 github.com/gobuffalo/helpers v0.6.4 github.com/gobuffalo/httptest v1.5.1 github.com/gobuffalo/logger v1.0.6 github.com/gobuffalo/meta v0.3.1 github.com/gobuffalo/nulls v0.4.1 - github.com/gobuffalo/plush/v4 v4.1.9 - github.com/gobuffalo/pop/v6 v6.0.1 + github.com/gobuffalo/plush/v4 v4.1.10 + github.com/gobuffalo/pop/v6 v6.0.2 github.com/gobuffalo/tags/v3 v3.1.2 github.com/google/go-cmp v0.5.6 // indirect github.com/gorilla/handlers v1.5.1 diff --git a/go.sum b/go.sum index a0f31d797..c753f91c7 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= @@ -111,9 +111,11 @@ github.com/gobuffalo/fizz v1.10.0/go.mod h1:J2XGPO0AfJ1zKw7+2BA+6FEGAkyEsdCOLvN9 github.com/gobuffalo/fizz v1.14.0 h1:hicZBYSwSWITXEDUR77tqrLU1/vScXHddd02IaFkkPI= github.com/gobuffalo/fizz v1.14.0/go.mod h1:0aF1kAZYCfKqbLM/lmZ3jXFyqqWE/kY/nIOKnNdAYXQ= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= -github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I= github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= +github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= +github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/gobuffalo/genny/v2 v2.0.8/go.mod h1:R45scCyQfff2HysNJHNanjrpvPw4Qu+rM1MOMDBB5oU= +github.com/gobuffalo/genny/v2 v2.0.9/go.mod h1:R45scCyQfff2HysNJHNanjrpvPw4Qu+rM1MOMDBB5oU= github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic= github.com/gobuffalo/github_flavored_markdown v1.1.1 h1:kUf8ginyBOTRXcKSTPsPAqlA25vQ80+xAspLIYaxmTU= github.com/gobuffalo/github_flavored_markdown v1.1.1/go.mod h1:yU32Pen+eorS58oxh/bNZx76zUOCJwmvyV5FBrvzOKQ= @@ -131,11 +133,12 @@ github.com/gobuffalo/nulls v0.4.1 h1:k7QVCJfMplv9VRQQLb4N1d8tXUdGvcdMNfp4BfMnG2M github.com/gobuffalo/nulls v0.4.1/go.mod h1:pp8e1hWTRJZFpMl4fj/CVbSMlaxjeGKkFq4RuBZi3w8= github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= github.com/gobuffalo/plush/v4 v4.0.0/go.mod h1:ErFS3UxKqEb8fpFJT7lYErfN/Nw6vHGiDMTjxpk5bQ0= -github.com/gobuffalo/plush/v4 v4.1.9 h1:u9rQBuYCeHC0ppKxsZljk5vb1oT8PQa5EMNTAN2337s= github.com/gobuffalo/plush/v4 v4.1.9/go.mod h1:9OOII9uAM5pZnhWu1OkQnboXJjaWMQ7kcTl3zNcxvTM= +github.com/gobuffalo/plush/v4 v4.1.10 h1:sBTmJJ1Hpj4ExqC4NFmCvAgH6Iq6t58JaWvAl8NEAFc= +github.com/gobuffalo/plush/v4 v4.1.10/go.mod h1:9OOII9uAM5pZnhWu1OkQnboXJjaWMQ7kcTl3zNcxvTM= github.com/gobuffalo/pop/v6 v6.0.0/go.mod h1:5rd3OnViLhjteR8+0i/mT9Q4CzkTzCoR7tm/9mmAic4= -github.com/gobuffalo/pop/v6 v6.0.1 h1:4YhzxW4hVvf0xLW9zVkhPZFuH5VmBc4ffIIP/C++SLQ= -github.com/gobuffalo/pop/v6 v6.0.1/go.mod h1:5NO7ehmyRjRctnbMDhIqKkkg6zvdueufYltxErfp9BU= +github.com/gobuffalo/pop/v6 v6.0.2 h1:NuIMTwZ54aHK92ertwv3R2SepAvhTRlQj9/MLqO9Qdo= +github.com/gobuffalo/pop/v6 v6.0.2/go.mod h1:1g+AV0EDe0rUoy1I3yA3aa9N5+/uC5RxtGbxI0KNe4c= github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= github.com/gobuffalo/tags/v3 v3.1.2 h1:68sHcwFFDstXyfbk5ovbGcQFDsupgVLs+lw1XZinHJw= github.com/gobuffalo/tags/v3 v3.1.2/go.mod h1:o3ldUfKv50jxWAC8eZHXMm8dnKW3YvyZUMr0xqUcZTI= @@ -145,8 +148,9 @@ github.com/gobuffalo/validate/v3 v3.3.1/go.mod h1:Ehu8ieNJQuUM4peDDr/0VapzdGA7Rg github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk= github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -259,8 +263,9 @@ github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfG github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ= +github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -276,25 +281,29 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= +github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= +github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= @@ -323,8 +332,9 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= +github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/luna-duclos/instrumentedsql v1.1.3 h1:t7mvC0z1jUt5A0UQ6I/0H31ryymuQRnJcWCiqV3lSAA= github.com/luna-duclos/instrumentedsql v1.1.3/go.mod h1:9J1njvFds+zN7y85EDhN9XNQLANWwZt2ULeIC8yMNYs= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= diff --git a/grifts.go b/grifts.go index 84af4a774..1cd5a28c1 100644 --- a/grifts.go +++ b/grifts.go @@ -44,22 +44,24 @@ func printMiddleware(a *App) { func printMiddlewareByRoute(a *App) { mws := map[string]string{} + // TODO: middleware is 'per App' can it be a loop for Apps? for _, r := range a.Routes() { - if mws[r.App.Name] == "" { - pname := "" + key := r.App.host + r.App.name + if mws[key] == "" { + pname := r.App.host if parent := getParentApp(r.App.root, r.App.Name); parent != nil { - pname = parent.Name + pname += parent.Name } - mws[r.App.Name] = r.App.Middleware.String() - if mws[pname] != mws[r.App.Name] { - fmt.Printf("-> %s\n", r.App.Name) - printMiddlewareStackWithIndent(mws[r.App.Name]) + mws[key] = r.App.Middleware.String() + if pname == key || mws[pname] != mws[key] { + fmt.Printf("-> %s\n", key) + printMiddlewareStackWithIndent(mws[key]) } else { - fmt.Printf("-> %s (see: %v)\n", r.App.Name, pname) + fmt.Printf("-> %s (see: %v)\n", key, pname) } } - s := "\n" + mws[r.App.Name] + s := "\n" + mws[key] for k := range r.App.Middleware.skips { mw := strings.Split(k, funcKeyDelimeter)[0] h := strings.Split(k, funcKeyDelimeter)[1] @@ -67,10 +69,10 @@ func printMiddlewareByRoute(a *App) { s = strings.Replace(s, "\n"+mw, "", 1) } } - if "\n"+mws[r.App.Name] != s { + if "\n"+mws[key] != s { ahn := strings.Split(r.HandlerName, "/") hn := ahn[len(ahn)-1] - fmt.Printf("-> %s %s (by %s)\n", r.Method, r.Path, hn) + fmt.Printf("-> %s %s (by %s)\n", r.Method, r.App.host+r.Path, hn) printMiddlewareStackWithIndent(s) } } @@ -106,10 +108,10 @@ func routesGrift(a *App) { grift.Add("routes", func(c *grift.Context) error { routes := a.Routes() w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug) - fmt.Fprintln(w, "METHOD\t PATH\t ALIASES\t NAME\t HANDLER") - fmt.Fprintln(w, "------\t ----\t -------\t ----\t -------") + fmt.Fprintln(w, "METHOD\t HOST\t PATH\t ALIASES\t NAME\t HANDLER") + fmt.Fprintln(w, "------\t ----\t ----\t -------\t ----\t -------") for _, r := range routes { - fmt.Fprintf(w, "%s\t %s\t %s\t %s\t %s\n", r.Method, r.Path, strings.Join(r.Aliases, " "), r.PathName, r.HandlerName) + fmt.Fprintf(w, "%s\t %s\t %s\t %s\t %s\t %s\n", r.Method, r.App.host, r.Path, strings.Join(r.Aliases, " "), r.PathName, r.HandlerName) } w.Flush() return nil diff --git a/home.go b/home.go new file mode 100644 index 000000000..79255e547 --- /dev/null +++ b/home.go @@ -0,0 +1,39 @@ +package buffalo + +import ( + "github.com/gorilla/mux" +) + +/* TODO: consider to split out Home (or Router, whatever) from App #road-to-v1 + Group and Domain based multi-homing are actually not an App if the concept + of the App represents the application. The App should be only one for whole + application. + + For an extreme example, App.Group().Stop() or even App.Group().Serve() are + still valid function calls while they should not be allowed and the result + could be strage. +*/ + +// Home is a container for Domains and Groups that independantly serves a +// group of pages with its own Middleware and ErrorHandlers. It is usually +// a multi-homed server domain or group of paths under a certain prefix. +// +// While the App is for managing whole application life cycle along with its +// default Home, including initializing and stopping its all components such +// as listeners and long-running jobs, Home is only for a specific group of +// services to serve its service logic efficiently. +type Home struct { + app *App // will replace App.root + appSelf *App // temporary while the App is in action. + // replace Options' Name, Host, and Prefix + name string + host string + prefix string + + // moved from App + // Middleware returns the current MiddlewareStack for the App/Group. + Middleware *MiddlewareStack `json:"-"` + ErrorHandlers ErrorHandlers `json:"-"` + router *mux.Router + filepaths []string +} diff --git a/route.go b/route.go index b5f7b3111..cab38c424 100644 --- a/route.go +++ b/route.go @@ -11,6 +11,7 @@ import ( // Routes returns a list of all of the routes defined // in this application. func (a *App) Routes() RouteList { + // CHKME: why this function is exported? can we deprecate it? if a.root != nil { return a.root.routes } @@ -64,11 +65,20 @@ type RouteHelperFunc func(opts map[string]interface{}) (template.HTML, error) // and the name of the Handler defined to process that route. type RouteList []*RouteInfo +var methodOrder = map[string]string{ + "GET": "1", + "POST": "2", + "PUT": "3", + "DELETE": "4", +} + func (a RouteList) Len() int { return len(a) } func (a RouteList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a RouteList) Less(i, j int) bool { - x := a[i].Path // + a[i].Method - y := a[j].Path // + a[j].Method + // NOTE: it was used for sorting of app.routes but we don't sort the routes anymore. + // keep it for compatibility but could be deprecated. + x := a[i].App.host + a[i].Path + methodOrder[a[i].Method] + y := a[j].App.host + a[j].Path + methodOrder[a[j].Method] return x < y } diff --git a/route_info.go b/route_info.go index b5e20d512..c6be5a636 100644 --- a/route_info.go +++ b/route_info.go @@ -49,7 +49,7 @@ func (ri *RouteInfo) Alias(aliases ...string) *RouteInfo { func (ri *RouteInfo) Name(name string) *RouteInfo { routeIndex := -1 for index, route := range ri.App.Routes() { - if route.Path == ri.Path && route.Method == ri.Method { + if route.App.host == ri.App.host && route.Path == ri.Path && route.Method == ri.Method { routeIndex = index break } diff --git a/route_mappings.go b/route_mappings.go index 4f42b0551..c0f7d4d89 100644 --- a/route_mappings.go +++ b/route_mappings.go @@ -7,7 +7,6 @@ import ( "os" "path" "reflect" - "sort" "strings" "github.com/gobuffalo/envy" @@ -20,6 +19,8 @@ const ( AssetsAgeVarName = "ASSETS_MAX_AGE" ) +// These method functions will be moved to Home structure. + // GET maps an HTTP "GET" request to the path and the specified handler. func (a *App) GET(p string, h Handler) *RouteInfo { return a.addRoute("GET", p, h) @@ -84,29 +85,6 @@ func (a *App) Mount(p string, h http.Handler) { a.ANY(path, WrapHandler(http.StripPrefix(prefix, h))) } -// Host creates a new "*App" group that matches the domain passed. -// This is useful for creating groups of end-points for different domains. -/* - a.Host("www.example.com") - a.Host("{subdomain}.example.com") - a.Host("{subdomain:[a-z]+}.example.com") -*/ -func (a *App) Host(h string) *App { - g := New(a.Options) - - g.router = a.router.Host(h).Subrouter() - g.RouteNamer = a.RouteNamer - g.Middleware = a.Middleware.clone() - g.ErrorHandlers = a.ErrorHandlers - g.root = a - - if a.root != nil { - g.root = a.root - } - - return g -} - // ServeFiles maps an path to a directory on disk to serve static files. // Useful for JavaScript, images, CSS, etc... /* @@ -156,6 +134,11 @@ type editable interface { // to the appropriate RESTful mappings. Resource returns the *App // associated with this group of mappings so you can set middleware, etc... // on that group, just as if you had used the a.Group functionality. +// +// Resource automatically creates a URL `/resources/new` if the resource +// has a function `New()`. So it could act as a restriction for the value +// of `resource_id`. URL `/resources/new` will always show the resource +// creation page instead of showing the resource called `new`. /* a.Resource("/users", &UsersResource{}) @@ -164,12 +147,12 @@ type editable interface { ur := &UsersResource{} g := a.Group("/users") g.GET("/", ur.List) // GET /users => ur.List + g.POST("/", ur.Create) // POST /users => ur.Create g.GET("/new", ur.New) // GET /users/new => ur.New g.GET("/{user_id}", ur.Show) // GET /users/{user_id} => ur.Show + g.PUT("/{user_id}", ur.Update) // PUT /users/{user_id} => ur.Update + g.DELETE("/{user_id}", ur.Destroy) // DELETE /users/{user_id} => ur.Destroy g.GET("/{user_id}/edit", ur.Edit) // GET /users/{user_id}/edit => ur.Edit - g.POST("/", ur.Create) // POST /users => ur.Create - g.PUT("/{user_id}", ur.Update) PUT /users/{user_id} => ur.Update - g.DELETE("/{user_id}", ur.Destroy) DELETE /users/{user_id} => ur.Destroy */ func (a *App) Resource(p string, r Resource) *App { g := a.Group(p) @@ -202,9 +185,14 @@ func (a *App) Resource(p string, r Resource) *App { spath := path.Join(p, "{"+paramName+"}") + // This order will become the order of route evaluation too. setFuncKey(r.List, fmt.Sprintf(handlerName, "List")) g.GET(p, r.List).ResourceName = resourceName + setFuncKey(r.Create, fmt.Sprintf(handlerName, "Create")) + g.POST(p, r.Create).ResourceName = resourceName + + // NOTE: it makes restriction that resource id cannot be 'new'. if n, ok := r.(newable); ok { setFuncKey(n.New, fmt.Sprintf(handlerName, "New")) g.GET(path.Join(p, "new"), n.New).ResourceName = resourceName @@ -213,21 +201,19 @@ func (a *App) Resource(p string, r Resource) *App { setFuncKey(r.Show, fmt.Sprintf(handlerName, "Show")) g.GET(path.Join(spath), r.Show).ResourceName = resourceName - if n, ok := r.(editable); ok { - setFuncKey(n.Edit, fmt.Sprintf(handlerName, "Edit")) - g.GET(path.Join(spath, "edit"), n.Edit).ResourceName = resourceName - } - - setFuncKey(r.Create, fmt.Sprintf(handlerName, "Create")) - g.POST(p, r.Create).ResourceName = resourceName - setFuncKey(r.Update, fmt.Sprintf(handlerName, "Update")) g.PUT(path.Join(spath), r.Update).ResourceName = resourceName setFuncKey(r.Destroy, fmt.Sprintf(handlerName, "Destroy")) g.DELETE(path.Join(spath), r.Destroy).ResourceName = resourceName + if n, ok := r.(editable); ok { + setFuncKey(n.Edit, fmt.Sprintf(handlerName, "Edit")) + g.GET(path.Join(spath, "edit"), n.Edit).ResourceName = resourceName + } + g.Prefix = path.Join(g.Prefix, spath) + g.prefix = g.Prefix return g } @@ -254,22 +240,54 @@ func (a *App) ANY(p string, h Handler) { g.GET("/users/:user_id, APIUserShowHandler) */ func (a *App) Group(groupPath string) *App { + // TODO: move this function to app.go or home.go eventually. g := New(a.Options) + // keep them for v0 compatibility g.Prefix = path.Join(a.Prefix, groupPath) g.Name = g.Prefix + // for Home structure + g.prefix = path.Join(a.prefix, groupPath) + g.host = a.host + g.name = g.prefix + g.router = a.router g.RouteNamer = a.RouteNamer g.Middleware = a.Middleware.clone() g.ErrorHandlers = a.ErrorHandlers - g.root = a - if a.root != nil { - g.root = a.root - } + + g.app = a.app // will replace g.root + g.root = g.app // will be deprecated + + // to be replaced with child Homes. currently, only used in grifts. a.children = append(a.children, g) return g } +// VirtualHost creates a new `*App` that inherits from it's parent `*App`. +// All pre-configured things on the parent App such as middlewares will be +// applied, and can be modified only for this child App. +// +// This is a multi-homing feature similar to the `VirtualHost` in Apache +// or multiple `server`s in nginx. One important different behavior is that +// there is no concept of the `default` host in buffalo (at least for now) +// and the routing decision will be made with the "first match" manner. +// (e.g. if you have already set the route for '/' for the root App before +// setting up a virualhost, the route of the root App will be picked up +// even if the client makes a request to the specified domain.) +/* + a.VirtualHost("www.example.com") + a.VirtualHost("{subdomain}.example.com") + a.VirtualHost("{subdomain:[a-z]+}.example.com") +*/ +func (a *App) VirtualHost(h string) *App { + g := a.Group("/") + g.host = h + g.router = a.router.Host(h).Subrouter() + + return g +} + // RouteHelpers returns a map of BuildPathHelper() for each route available in the app. func (a *App) RouteHelpers() map[string]RouteHelperFunc { rh := map[string]RouteHelperFunc{} @@ -280,13 +298,15 @@ func (a *App) RouteHelpers() map[string]RouteHelperFunc { return rh } -func (a *App) addRoute(method string, url string, h Handler) *RouteInfo { - a.moot.Lock() - defer a.moot.Unlock() +func (e *Home) addRoute(method string, url string, h Handler) *RouteInfo { + // NOTE: lock the root app (not this app). only the root has the affective + // routes list. + e.app.moot.Lock() + defer e.app.moot.Unlock() - url = path.Join(a.Prefix, url) - url = a.normalizePath(url) - name := a.RouteNamer.NameRoute(url) + url = path.Join(e.prefix, url) + url = e.app.normalizePath(url) + name := e.app.RouteNamer.NameRoute(url) hs := funcKey(h) r := &RouteInfo{ @@ -294,22 +314,23 @@ func (a *App) addRoute(method string, url string, h Handler) *RouteInfo { Path: url, HandlerName: hs, Handler: h, - App: a, + App: e.appSelf, // CHKME: to be replaced with Home Aliases: []string{}, } - r.MuxRoute = a.router.Handle(url, r).Methods(method) + r.MuxRoute = e.router.Handle(url, r).Methods(method) r.Name(name) - routes := a.Routes() + routes := e.app.Routes() routes = append(routes, r) - sort.Sort(routes) - - if a.root != nil { - a.root.routes = routes - } else { - a.routes = routes - } + // NOTE: sorting is fancy but we lose the evaluation order information + // of routing decision. Let's keep the routes as registered order so + // developers can easily evaluate the order with `buffalo routes` and + // can debug any routing priority issue. (just keep the original line + // as history reference) + //sort.Sort(routes) + + e.app.routes = routes return r } diff --git a/route_mappings_test.go b/route_mappings_test.go index fe80e2888..5a189e316 100644 --- a/route_mappings_test.go +++ b/route_mappings_test.go @@ -155,13 +155,13 @@ func Test_App_Routes_Resource(t *testing.T) { } } -func Test_App_Host(t *testing.T) { +func Test_App_VirtualHost(t *testing.T) { r := require.New(t) a1 := New(Options{}) r.Nil(a1.root) - h1 := a1.Host("www.example.com") + h1 := a1.VirtualHost("www.example.com") h1.GET("/foo", voidHandler) routes := h1.Routes() @@ -177,7 +177,7 @@ func Test_App_Host(t *testing.T) { a2 := New(Options{}) r.Nil(a1.root) - h2 := a2.Host("{subdomain}.example.com") + h2 := a2.VirtualHost("{subdomain}.example.com") h2.GET("/foo", voidHandler) h2.GET("/foo/{id}", voidHandler).Name("fooID") diff --git a/runtime/version.go b/runtime/version.go index 2159ff4a2..701242d91 100644 --- a/runtime/version.go +++ b/runtime/version.go @@ -1,4 +1,4 @@ package runtime // Version is the current version of the buffalo binary -var Version = "v0.18.5" +var Version = "v0.18.6"