Skip to content

feat: enable custom layouts for error pages (Fixes #755)#771

Open
AmanKashyap0807 wants to merge 1 commit into
GenieFramework:mainfrom
AmanKashyap0807:main
Open

feat: enable custom layouts for error pages (Fixes #755)#771
AmanKashyap0807 wants to merge 1 commit into
GenieFramework:mainfrom
AmanKashyap0807:main

Conversation

@AmanKashyap0807

Copy link
Copy Markdown

Fixes #755

Description

This PR introduces a standardized way to render HTML error pages (404, 500, etc.) using the application's main layout. This resolves the issue where error pages were "naked" (missing headers/footers), making it difficult to include mandatory legal links (Imprint/Privacy) on error screens.

Changes

  1. Configuration: Added Genie.config.error_layout (defaults to nothing for backward compatibility).
  2. Html Renderer: Updated serve_error_page to check this configuration.
    • If error_layout is set, it renders the specific error view (e.g., :errors, :error_404) wrapped in the defined layout.
    • If nothing, it falls back to the existing behavior (serving static error files).
  3. Documentation: Updated README.md to mention the new configuration option.

How to Test (Integration)

  1. In a Genie app, create a layout app/layouts/legal.jl.html containing a footer.
  2. Create an error view resources/errors/views/error_404.jl.html.
  3. Set Genie.config.error_layout = :legal in config/initializers.jl.
  4. Trigger a 404 (e.g., visit a non-existent route).
  5. Result: The error message renders inside the legal layout.

Verification of Implementation (Unit Simulation)

I verified this locally using a standalone script that mocks the view/layout registration and asserts that the Router produces HTML containing both the layout footer and the error message.

Click to see the verification script used
# BYPASS PKG ACTIVATION TO AVOID VERSION STRING ERROR
push!(LOAD_PATH, @__DIR__)

using Genie
using Genie.Router
using Genie.Renderer.Html

println("Starting verification...")

# --- 1. SETUP: Mock Layout ---
module Layouts
using Genie.Renderer.Html
function legal_layout(; context=@__MODULE__, kwargs...)
    """
    <footer>
        <a href="/imprint">IMPRINT LINK</a>
    </footer>
    <div class="content">$(Genie.Renderer.Html.view!())</div>
    """
end
end
Core.eval(Genie.Renderer.Html, :(legal_layout = $(Layouts.legal_layout)))

# --- 2. SETUP: Mock View Detection for (Resource, ViewName) ---

module MockRenderer
using Genie.Renderer.Html
using Genie.Renderer

# This function mimics the target behavior: rendering the view content
function mock_render(resource::Symbol, view::Symbol; layout=nothing, kwargs...)
    if resource == :errors && view == :error_404
        content = "<h1>Custom 404 Page</h1>"
        # Apply layout if present
        if layout == :legal_layout
            Genie.Renderer.Html.view!(content)
            return Genie.Renderer.Html.legal_layout()
        end
        return content
    end
    return "MOCK_MISS_MATCH"
end
end

# CRITICAL: Mocking the method to capture the specific call signature
Core.eval(Genie.Renderer.Html, quote
    function html(resource::Symbol, view::Symbol; layout=nothing, kwargs...)
        $(MockRenderer.mock_render)(resource, view; layout=layout, kwargs...)
    end
end)


# --- 3. CONFIGURE ---
try
    Genie.config.error_layout = :legal_layout
    println("✅ Configuration accepted error_layout.")
catch e
    println("❌ FAILED: Genie.config.error_layout does not exist.")
    exit(1)
end


# --- 4. EXECUTE ---
println("Attempting to render 404...")

try
    global response = Genie.Router.error("Msg", MIME"text/html", Val(404))

    body_str = String(response.body)
    println("\n--- RESPONSE BODY ---")
    println(body_str)
    println("---------------------\n")

    if occursin("IMPRINT LINK", body_str) && occursin("Custom 404 Page", body_str)
        println("✅ Success: The code correctly calls the layout-aware renderer.")
    else
        println("❌ Failure: Content mismatch.")
    end

catch e
    println("❌ FAILED: Runtime error.")
    showerror(stdout, e)
end

@andreeco

Copy link
Copy Markdown

I tried out something similar to this and it worked. However, I don't know if we should fix the error view files to app/resources/errors/views, especially in regard to Stipple. We should leave it up to the maintainers to decide the best way to handle this. Thanks for your efforts to solve this issue!

julia> julia> Genie.Generator.newapp_mvc("TestApp");

julia> mkpath("app/resources/errors/views")
"app/resources/errors/views"

julia> open("app/resources/errors/views/error_404.jl.html", "w") do f
           write(f, "<h1>Custom 404</h1><p>\$(vars(:message))</p>")
       end
43

julia> open("app/resources/errors/views/error_500.jl.html", "w") do f
           write(f, "<h1>Custom 500</h1><p>\$(vars(:message))</p><span>\$(vars(:info))</span>")
       end
70

julia> Genie.config.error_layout = :app
:app

julia> res404 = Genie.Router.error("Page Not Found", MIME"text/html", Val(404))
HTTP.Messages.Response:
"""
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8

<!DOCTYPE html><html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Genie :: The Highly Productive Julia Web Framework</title>
  </head>
  <body>
    <h1>Custom 404</h1>
    <p>Page Not Found</p>
  </body></html>"""

julia> res505 = Genie.Router.error("Crash!", MIME"text/html", Val(500), error_info="Stacktrace info")
HTTP.Messages.Response:
"""
HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8

<!DOCTYPE html><html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Genie :: The Highly Productive Julia Web Framework</title>
  </head>
  <body>
    <h1>Custom 500</h1>
    <p>Crash!</p>
    <span>Stacktrace info</span>
  </body></html>"""

@AmanKashyap0807

Copy link
Copy Markdown
Author

Thanks for the feedback and for testing it out! @andreeco

You raise a valid point about the directory structure (app/resources/errors/views).

I decided to treat "Errors" like a standard Genie Resource (similar to how we have resources/users or resources/posts) to keep the folder structure consistent with the rest of the framework's MVC pattern.

However, since this is a new feature, I am totally open to moving these files to a different location (like app/views/errors) if the maintainers prefer that standard!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Error Pages with Imprint and Privacy Links

2 participants