mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55:46 +01:00
Fix template fallback mechanism to only execute for not found errors
- Modified ExtendsNode, IncludeNode, ImportNode, and FromImportNode to only fallback when specifically dealing with ErrTemplateNotFound - When a syntax error or other functional error occurs in templates, the real error is now displayed instead of silently falling back to other templates - Added proper error type checking with errors.Is() to ensure correct fallback behavior - Improved ignoreMissing behavior in IncludeNode to only apply to not found errors 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2419e0e574
commit
529420e7f2
2 changed files with 162 additions and 15 deletions
31
node.go
31
node.go
|
|
@ -2,6 +2,7 @@ package twig
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
|
@ -684,13 +685,14 @@ func (n *ExtendsNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
// Load the parent template with resolved path
|
||||
parentTemplate, err := ctx.engine.Load(resolvedName)
|
||||
if err != nil {
|
||||
// If template not found with resolved path, try original name
|
||||
if resolvedName != templateName {
|
||||
// Only try the fallback if the template was not found AND the paths are different
|
||||
if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName {
|
||||
parentTemplate, err = ctx.engine.Load(templateName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// For any other error (including syntax errors), return immediately
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -701,7 +703,7 @@ func (n *ExtendsNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
// This ensures the parent template knows it's being extended and preserves our blocks
|
||||
parentCtx := NewRenderContext(ctx.env, ctx.context, ctx.engine)
|
||||
parentCtx.extending = true // Flag that the parent is being extended
|
||||
|
||||
|
||||
// Pass along the parent template as lastLoadedTemplate for relative path resolution
|
||||
parentCtx.lastLoadedTemplate = parentTemplate
|
||||
|
||||
|
|
@ -793,19 +795,20 @@ func (n *IncludeNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
// Load the template with resolved path
|
||||
template, err := ctx.engine.Load(resolvedName)
|
||||
if err != nil {
|
||||
// If template not found with resolved path, try original name
|
||||
if resolvedName != templateName {
|
||||
// Only try the fallback if the template was not found AND the paths are different
|
||||
if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName {
|
||||
template, err = ctx.engine.Load(templateName)
|
||||
if err != nil {
|
||||
if n.ignoreMissing {
|
||||
if n.ignoreMissing && errors.Is(err, ErrTemplateNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if n.ignoreMissing {
|
||||
if n.ignoreMissing && errors.Is(err, ErrTemplateNotFound) {
|
||||
return nil
|
||||
}
|
||||
// For any other error (including syntax errors), return immediately
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -818,7 +821,7 @@ func (n *IncludeNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
includeCtx := ctx.Clone()
|
||||
includeCtx.lastLoadedTemplate = template
|
||||
defer includeCtx.Release()
|
||||
|
||||
|
||||
return template.nodes.Render(w, includeCtx)
|
||||
}
|
||||
|
||||
|
|
@ -840,7 +843,7 @@ func (n *IncludeNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
|
||||
// Create a new context
|
||||
includeCtx = NewRenderContext(ctx.env, contextVars, ctx.engine)
|
||||
// Set the template as the lastLoadedTemplate for relative path resolutionn includeCtx.lastLoadedTemplate = template
|
||||
// Set the template as the lastLoadedTemplate for relative path resolutionn includeCtx.lastLoadedTemplate = template
|
||||
defer includeCtx.Release()
|
||||
|
||||
// If sandboxed, enable sandbox mode
|
||||
|
|
@ -1206,13 +1209,14 @@ func (n *ImportNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
// Load the template with resolved path
|
||||
template, err := ctx.engine.Load(resolvedName)
|
||||
if err != nil {
|
||||
// If template not found with resolved path, try original name
|
||||
if resolvedName != templateName {
|
||||
// Only try the fallback if the template was not found AND the paths are different
|
||||
if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName {
|
||||
template, err = ctx.engine.Load(templateName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// For any other error (including syntax errors), return immediately
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -1296,13 +1300,14 @@ func (n *FromImportNode) Render(w io.Writer, ctx *RenderContext) error {
|
|||
// Load the template with resolved path
|
||||
template, err := ctx.engine.Load(resolvedName)
|
||||
if err != nil {
|
||||
// If template not found with resolved path, try original name
|
||||
if resolvedName != templateName {
|
||||
// Only try the fallback if the template was not found AND the paths are different
|
||||
if errors.Is(err, ErrTemplateNotFound) && resolvedName != templateName {
|
||||
template, err = ctx.engine.Load(templateName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// For any other error (including syntax errors), return immediately
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,10 +80,10 @@ func TestRelativePathsWithFromImport(t *testing.T) {
|
|||
|
||||
// Create simple macro templates
|
||||
macrosTemplate := `{% macro simple() %}Macro output{% endmacro %}`
|
||||
|
||||
|
||||
// Print the template content for debugging
|
||||
t.Logf("Simple template content: %s", macrosTemplate)
|
||||
|
||||
|
||||
// Note: The template needs to be in the format: {% from "template" import macro %}
|
||||
useTemplate := `{% from "./simple.twig" import simple %}{{ simple() }}`
|
||||
t.Logf("Use template content: %s", useTemplate)
|
||||
|
|
@ -129,3 +129,145 @@ func normalizeWhitespace(s string) string {
|
|||
result := strings.Join(strings.Fields(s), " ")
|
||||
return result
|
||||
}
|
||||
|
||||
// TestRelativePathsWithExtendsInSubfolder tests a specific scenario where a template
|
||||
// in a child folder extends another template in the same folder using a relative path
|
||||
func TestRelativePathsWithExtendsInSubfolder(t *testing.T) {
|
||||
// Create temporary directories for testing
|
||||
tempDir, err := os.MkdirTemp("", "twig-test-")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a main templates directory and a child subdirectory
|
||||
templatesDir := filepath.Join(tempDir, "templates")
|
||||
err = os.Mkdir(templatesDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create templates dir: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Template dir: %s", templatesDir)
|
||||
|
||||
childDir := filepath.Join(templatesDir, "child")
|
||||
err = os.Mkdir(childDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create child dir: %v", err)
|
||||
}
|
||||
|
||||
// Create the layout template in the templates directory
|
||||
layoutTemplate := `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}Main Layout Title{% endblock %}</title>
|
||||
{% block stylesheet %}{% endblock stylesheet %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
{% block javascript %}{% endblock javascript %}
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
// Create the base template in the child directory
|
||||
baseTemplate := `{% extends '../layout.html.twig' %}
|
||||
|
||||
{% block title %}{{ title }}{% if pair is defined %} | {{ pair | split('.', 2) | first | capitalize }} | {{ pair | split('.', 2) | last | replace('something', '') }}{% endif %} | Text{% endblock %}
|
||||
|
||||
{% block stylesheet %}
|
||||
<style>
|
||||
.brand-image {
|
||||
margin-top: -.5rem;
|
||||
margin-right: .2rem;
|
||||
height: 33px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1>{{ title }}</h1>
|
||||
<p>{{ content }}</p>
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{{ '/' }}">main</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block javascript %}
|
||||
<script>
|
||||
console.log('{{ content | split(' ', 2) | first }}');
|
||||
{% set break = false %}
|
||||
{% for item in items %}
|
||||
{% if not break %}
|
||||
{% set list = item %}
|
||||
window.top.location = '/{{ list }}';
|
||||
{% set break = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</script>
|
||||
{% endblock javascript %}
|
||||
`
|
||||
|
||||
// Create the child template that extends the base using a relative path
|
||||
childTemplate := `{% extends './layout.html.twig' %}`
|
||||
|
||||
// Write templates to files
|
||||
err = os.WriteFile(filepath.Join(templatesDir, "layout.html.twig"), []byte(layoutTemplate), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write base template: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath.Join(childDir, "layout.html.twig"), []byte(baseTemplate), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write base template: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath.Join(childDir, "child.html.twig"), []byte(childTemplate), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write child template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new Twig engine with debug enabled
|
||||
engine := New()
|
||||
engine.SetDevelopmentMode(true)
|
||||
engine.SetDebug(true)
|
||||
engine.SetAutoReload(true)
|
||||
|
||||
// Register the template directory
|
||||
loader := NewFileSystemLoader([]string{templatesDir})
|
||||
engine.RegisterLoader(loader)
|
||||
|
||||
// Render the child template from the child directory
|
||||
output, err := engine.Render("child/child.html.twig", map[string]interface{}{
|
||||
"title": "Base Title",
|
||||
"content": "This is the base content.",
|
||||
"items": []string{"Item 1", "Item 2", "Item 3"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to render template: %v", err)
|
||||
}
|
||||
|
||||
//t.Logf("Output: %s", output)
|
||||
|
||||
// Verify the output contains elements from both templates
|
||||
if !strings.Contains(output, "Base Title") {
|
||||
t.Errorf("Output should contain 'Base Title' from base template title block")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "<!DOCTYPE html>") {
|
||||
t.Errorf("Output should contain DOCTYPE declaration from main layout template")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "<div class=\"container\">") {
|
||||
t.Errorf("Output should contain container div from base template template")
|
||||
}
|
||||
|
||||
// The child content should contain the base content
|
||||
if !strings.Contains(output, "This is the base content") {
|
||||
t.Errorf("Output should contain 'This is the base content' from base template")
|
||||
}
|
||||
|
||||
t.Logf("Successfully rendered template with relative extends path in subfolder")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue