Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnoweb): "No render" page/component #3611

Merged
merged 18 commits into from
Jan 29, 2025
1 change: 1 addition & 0 deletions examples/gno.land/r/docs/docs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Explore various examples to learn more about Gno functionality and usage.
- [Buttons](/r/docs/buttons) - Add buttons to your realm's render.
- [AVL Pager](/r/docs/avl_pager) - Paginate through AVL tree items.
- [Img Embed](/r/docs/img_embed) - Demonstrates how to embed an image.
- [Optional Render](/r/docs/optional_render) - Render() is optional in realms.
- ...
<!-- meta issue with suggestions: https://github.com/gnolang/gno/issues/3292 -->

Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/r/docs/optional_render/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/docs/optional_render
7 changes: 7 additions & 0 deletions examples/gno.land/r/docs/optional_render/optional_render.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package optional_render

func Info() string {
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
return `Having a Render() function in your realm is optional!
If you do decide to have a Render() function, it must have the following signature:
func Render(path string) string { ... }`
}
1 change: 1 addition & 0 deletions gno.land/pkg/gnoweb/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func TestRoutes(t *testing.T) {
{"/game-of-realms", found, "/contribute"},
{"/gor", found, "/contribute"},
{"/blog", found, "/r/gnoland/blog"},
{"/r/docs/optional_render", ok, "No Render"},
{"/r/not/found/", notFound, ""},
{"/404/not/found", notFound, ""},
{"/아스키문자가아닌경로", notFound, ""},
Expand Down
34 changes: 31 additions & 3 deletions gno.land/pkg/gnoweb/components/view_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,38 @@ package components

const StatusViewType ViewType = "status-view"

// StatusData holds the dynamic fields for the "status" template
type StatusData struct {
Message string
Title string
Body string
ButtonURL string
ButtonText string
}

func StatusComponent(message string) *View {
return NewTemplateView(StatusViewType, "status", StatusData{message})
// StatusErrorComponent returns a view for error scenarios
func StatusErrorComponent(message string) *View {
return NewTemplateView(
StatusViewType,
"status",
StatusData{
Title: "Error: " + message,
Body: "Something went wrong.",
ButtonURL: "/",
ButtonText: "Go Back Home",
},
)
}

// StatusNoRenderComponent returns a view for non-error notifications
func StatusNoRenderComponent(pkgPath string) *View {
return NewTemplateView(
StatusViewType,
"status",
StatusData{
Title: "No Render",
Body: "This realm does not implement a Render() function.",
ButtonURL: pkgPath + "$source",
ButtonText: "View Realm Source",
},
)
}
10 changes: 7 additions & 3 deletions gno.land/pkg/gnoweb/components/views/status.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{{ define "status" }}
<div class="col-span-10 flex flex-col h-full w-full mt-10 pb-24 justify-center items-center">
<img src="/public/imgs/gnoland.svg" alt="gno land" width="70px" height="70px" />
<h1 class="text-600 font-bold text-gray-600 pb-4 capitalize"><span>Error:</span> <span>{{ .Message }}</span></h1>
<p class="pb-3">Something went wrong. Let’s find our way back!</p>
<a href="/" class="rounded border py-1 px-2 hover:bg-gray-100">Go Back Home</a>
<h1 class="text-600 font-bold text-gray-600 pb-4 capitalize">
{{ .Title }}
</h1>
<p class="pb-3">{{ .Body }}</p>
<a href="{{ .ButtonURL }}" class="rounded border py-1 px-2 hover:bg-gray-100">
{{ .ButtonText }}
</a>
</div>
{{ end }}
18 changes: 11 additions & 7 deletions gno.land/pkg/gnoweb/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
gnourl, err := ParseGnoURL(r.URL)
if err != nil {
h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err)
return http.StatusNotFound, components.StatusComponent("invalid path")
return http.StatusNotFound, components.StatusErrorComponent("invalid path")
}

breadcrumb := generateBreadcrumbPaths(gnourl)
Expand All @@ -130,7 +130,7 @@
return h.GetPackageView(gnourl)
default:
h.Logger.Debug("invalid path: path is neither a pure package or a realm")
return http.StatusBadRequest, components.StatusComponent("invalid path")
return http.StatusBadRequest, components.StatusErrorComponent("invalid path")
}
}

Expand Down Expand Up @@ -160,6 +160,10 @@

meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs())
if err != nil {
if errors.Is(err, ErrRenderNotDeclared) {
return http.StatusOK, components.StatusNoRenderComponent(gnourl.Path)
}

h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL())
return GetClientErrorStatusPage(gnourl, err)
}
Expand Down Expand Up @@ -223,7 +227,7 @@

if len(files) == 0 {
h.Logger.Debug("no files available", "path", gnourl.Path)
return http.StatusOK, components.StatusComponent("no files available")
return http.StatusOK, components.StatusErrorComponent("no files available")

Check warning on line 230 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L230

Added line #L230 was not covered by tests
}

var fileName string
Expand Down Expand Up @@ -266,7 +270,7 @@

if len(files) == 0 {
h.Logger.Debug("no files available", "path", gnourl.Path)
return http.StatusOK, components.StatusComponent("no files available")
return http.StatusOK, components.StatusErrorComponent("no files available")

Check warning on line 273 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L273

Added line #L273 was not covered by tests
}

return http.StatusOK, components.DirectoryView(components.DirData{
Expand All @@ -283,13 +287,13 @@

switch {
case errors.Is(err, ErrClientPathNotFound):
return http.StatusNotFound, components.StatusComponent(err.Error())
return http.StatusNotFound, components.StatusErrorComponent(err.Error())
case errors.Is(err, ErrClientBadRequest):
return http.StatusInternalServerError, components.StatusComponent("bad request")
return http.StatusInternalServerError, components.StatusErrorComponent("bad request")

Check warning on line 292 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L292

Added line #L292 was not covered by tests
case errors.Is(err, ErrClientResponse):
fallthrough // XXX: for now fallback as internal error
default:
return http.StatusInternalServerError, components.StatusComponent("internal error")
return http.StatusInternalServerError, components.StatusErrorComponent("internal error")

Check warning on line 296 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L296

Added line #L296 was not covered by tests
}
}

Expand Down
1 change: 1 addition & 0 deletions gno.land/pkg/gnoweb/webclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

var (
ErrClientPathNotFound = errors.New("package not found")
ErrRenderNotDeclared = errors.New("render function not declared")
ErrClientBadRequest = errors.New("bad request")
ErrClientResponse = errors.New("node response error")
)
Expand Down
5 changes: 5 additions & 0 deletions gno.land/pkg/gnoweb/webclient_html.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (*

pkgPath = strings.Trim(pkgPath, "/")
data := fmt.Sprintf("%s/%s:%s", s.domain, pkgPath, args)

rawres, err := s.query(qpath, []byte(data))
if err != nil {
return nil, err
Expand Down Expand Up @@ -213,6 +214,10 @@ func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) {
return nil, ErrClientPathNotFound
}

if strings.Contains(err.Error(), "Render not declared") { /// XXX shoud this be a vm error?
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
return nil, ErrRenderNotDeclared
}

s.logger.Error("response error", "path", qpath, "log", qres.Response.Log)
return nil, fmt.Errorf("%w: %s", ErrClientResponse, err.Error())
}
Expand Down
Loading