☕ #web - Web Directive (Java)
#web - Web Directive (Java)
The #web
directive provides enterprise-grade web application capabilities for Java applications, enabling dynamic web page generation, routing, and server-side rendering with Spring Boot integration.
Basic Syntax
Basic web route
#web /home {
@render("home.html", {
title: "Welcome",
user: @auth.user
})
}Web route with parameters
#web /user/{id} {
user: @get_user(@params.id)
@render("user.html", {user: user})
}Web route with query parameters
#web /search {
query: @request.query.q
results: @search_database(query)
@render("search.html", {query: query, results: results})
}
Java Implementation
import org.tusklang.java.TuskLang;
import org.tusklang.java.directives.WebDirective;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;@Controller
public class WebController {
private final TuskLang tuskLang;
private final WebDirective webDirective;
private final UserService userService;
private final SearchService searchService;
public WebController(TuskLang tuskLang,
UserService userService,
SearchService searchService) {
this.tuskLang = tuskLang;
this.webDirective = new WebDirective();
this.userService = userService;
this.searchService = searchService;
}
// Basic web route
@GetMapping("/home")
public String home(Model model, @AuthenticationPrincipal User user) {
model.addAttribute("title", "Welcome");
model.addAttribute("user", user);
return "home";
}
// Web route with path parameters
@GetMapping("/user/{id}")
public String userProfile(@PathVariable Long id, Model model) {
User user = userService.getUserById(id);
model.addAttribute("user", user);
return "user";
}
// Web route with query parameters
@GetMapping("/search")
public String search(@RequestParam String q, Model model) {
List<SearchResult> results = searchService.searchDatabase(q);
model.addAttribute("query", q);
model.addAttribute("results", results);
return "search";
}
}
Web Configuration
Detailed web configuration
#web {
route: "/dashboard"
method: "GET"
template: "dashboard.html"
layout: "main.html"
cache: true
cache_ttl: 300
} {
data: @get_dashboard_data(@auth.user.id)
@render(template, {data: data, user: @auth.user})
}Web route with middleware
#web {
route: "/admin"
method: "GET"
middleware: ["auth", "admin_check"]
template: "admin.html"
} {
@render(template, {admin_data: @get_admin_data()})
}Web route with conditions
#web {
route: "/premium"
method: "GET"
condition: @auth.user.isPremium()
template: "premium.html"
} {
@render(template, {premium_content: @get_premium_content()})
}
Java Web Configuration
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import java.util.Map;
import java.util.List;@Component
@ConfigurationProperties(prefix = "tusk.web")
public class WebConfig {
private String defaultTemplate = "default";
private String defaultLayout = "main";
private boolean defaultCache = false;
private int defaultCacheTtl = 300;
private Map<String, WebRouteDefinition> routes;
private List<String> globalMiddleware;
private Map<String, String> templates;
// Getters and setters
public String getDefaultTemplate() { return defaultTemplate; }
public void setDefaultTemplate(String defaultTemplate) { this.defaultTemplate = defaultTemplate; }
public String getDefaultLayout() { return defaultLayout; }
public void setDefaultLayout(String defaultLayout) { this.defaultLayout = defaultLayout; }
public boolean isDefaultCache() { return defaultCache; }
public void setDefaultCache(boolean defaultCache) { this.defaultCache = defaultCache; }
public int getDefaultCacheTtl() { return defaultCacheTtl; }
public void setDefaultCacheTtl(int defaultCacheTtl) { this.defaultCacheTtl = defaultCacheTtl; }
public Map<String, WebRouteDefinition> getRoutes() { return routes; }
public void setRoutes(Map<String, WebRouteDefinition> routes) { this.routes = routes; }
public List<String> getGlobalMiddleware() { return globalMiddleware; }
public void setGlobalMiddleware(List<String> globalMiddleware) { this.globalMiddleware = globalMiddleware; }
public Map<String, String> getTemplates() { return templates; }
public void setTemplates(Map<String, String> templates) { this.templates = templates; }
public static class WebRouteDefinition {
private String route;
private String method = "GET";
private String template;
private String layout;
private boolean cache;
private int cacheTtl;
private List<String> middleware;
private String condition;
private Map<String, Object> data;
// Getters and setters
public String getRoute() { return route; }
public void setRoute(String route) { this.route = route; }
public String getMethod() { return method; }
public void setMethod(String method) { this.method = method; }
public String getTemplate() { return template; }
public void setTemplate(String template) { this.template = template; }
public String getLayout() { return layout; }
public void setLayout(String layout) { this.layout = layout; }
public boolean isCache() { return cache; }
public void setCache(boolean cache) { this.cache = cache; }
public int getCacheTtl() { return cacheTtl; }
public void setCacheTtl(int cacheTtl) { this.cacheTtl = cacheTtl; }
public List<String> middleware() { return middleware; }
public void setMiddleware(List<String> middleware) { this.middleware = middleware; }
public String getCondition() { return condition; }
public void setCondition(String condition) { this.condition = condition; }
public Map<String, Object> getData() { return data; }
public void setData(Map<String, Object> data) { this.data = data; }
}
}
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
private final WebConfig config;
private final List<HandlerInterceptor> interceptors;
public WebConfiguration(WebConfig config, List<HandlerInterceptor> interceptors) {
this.config = config;
this.interceptors = interceptors;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add global middleware
if (config.getGlobalMiddleware() != null) {
for (String middlewareName : config.getGlobalMiddleware()) {
HandlerInterceptor interceptor = createInterceptor(middlewareName);
registry.addInterceptor(interceptor);
}
}
// Add route-specific middleware
if (config.getRoutes() != null) {
config.getRoutes().forEach((routeName, routeDef) -> {
if (routeDef.getMiddleware() != null) {
for (String middlewareName : routeDef.getMiddleware()) {
HandlerInterceptor interceptor = createInterceptor(middlewareName);
registry.addInterceptor(interceptor).addPathPatterns(routeDef.getRoute());
}
}
});
}
}
private HandlerInterceptor createInterceptor(String middlewareName) {
switch (middlewareName) {
case "auth":
return new AuthInterceptor();
case "admin_check":
return new AdminCheckInterceptor();
case "cache":
return new CacheInterceptor();
default:
return new CustomInterceptor(middlewareName);
}
}
}
Template Rendering
Template rendering with data
#web /dashboard {
user_data: @get_user_data(@auth.user.id)
recent_activity: @get_recent_activity(@auth.user.id)
@render("dashboard.html", {
user: @auth.user,
user_data: user_data,
recent_activity: recent_activity,
title: "Dashboard"
})
}Template with layout
#web /profile {
profile: @get_user_profile(@auth.user.id)
@render_with_layout("profile.html", "main.html", {
profile: profile,
title: "User Profile"
})
}Conditional template rendering
#web /content {
content: @get_content(@request.params.id)
if (@content.isPremium()) {
@render("premium-content.html", {content: content})
} else {
@render("free-content.html", {content: content})
}
}
Java Template Rendering
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.springframework.ui.Model;@Service
public class TemplateRenderingService {
private final TemplateEngine templateEngine;
private final WebConfig config;
public TemplateRenderingService(TemplateEngine templateEngine, WebConfig config) {
this.templateEngine = templateEngine;
this.config = config;
}
public String renderTemplate(String templateName, Map<String, Object> data) {
Context context = new Context();
// Add data to context
if (data != null) {
data.forEach(context::setVariable);
}
// Add default data
context.setVariable("config", config);
context.setVariable("timestamp", LocalDateTime.now());
return templateEngine.process(templateName, context);
}
public String renderWithLayout(String templateName, String layoutName, Map<String, Object> data) {
// Create layout context
Context layoutContext = new Context();
layoutContext.setVariable("config", config);
layoutContext.setVariable("timestamp", LocalDateTime.now());
// Render main content
String mainContent = renderTemplate(templateName, data);
layoutContext.setVariable("content", mainContent);
// Add data to layout context
if (data != null) {
data.forEach(layoutContext::setVariable);
}
return templateEngine.process(layoutName, layoutContext);
}
public String renderConditional(String condition, String trueTemplate, String falseTemplate,
Map<String, Object> data) {
if (evaluateCondition(condition, data)) {
return renderTemplate(trueTemplate, data);
} else {
return renderTemplate(falseTemplate, data);
}
}
private boolean evaluateCondition(String condition, Map<String, Object> data) {
// Simple condition evaluation
if (condition.contains("isPremium")) {
Object content = data.get("content");
return content instanceof Content && ((Content) content).isPremium();
}
return false;
}
}
@Controller
public class TemplateController {
private final TemplateRenderingService templateService;
private final UserService userService;
private final ContentService contentService;
public TemplateController(TemplateRenderingService templateService,
UserService userService,
ContentService contentService) {
this.templateService = templateService;
this.userService = userService;
this.contentService = contentService;
}
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal User user, Model model) {
UserData userData = userService.getUserData(user.getId());
List<Activity> recentActivity = userService.getRecentActivity(user.getId());
Map<String, Object> data = new HashMap<>();
data.put("user", user);
data.put("user_data", userData);
data.put("recent_activity", recentActivity);
data.put("title", "Dashboard");
String rendered = templateService.renderTemplate("dashboard", data);
model.addAttribute("renderedContent", rendered);
return "layout";
}
@GetMapping("/profile")
public String profile(@AuthenticationPrincipal User user, Model model) {
UserProfile profile = userService.getUserProfile(user.getId());
Map<String, Object> data = new HashMap<>();
data.put("profile", profile);
data.put("title", "User Profile");
String rendered = templateService.renderWithLayout("profile", "main", data);
model.addAttribute("renderedContent", rendered);
return "layout";
}
@GetMapping("/content/{id}")
public String content(@PathVariable Long id, Model model) {
Content content = contentService.getContent(id);
Map<String, Object> data = new HashMap<>();
data.put("content", content);
String rendered = templateService.renderConditional(
"content.isPremium()",
"premium-content",
"free-content",
data
);
model.addAttribute("renderedContent", rendered);
return "layout";
}
}
Dynamic Routing
Dynamic route with parameters
#web /product/{category}/{id} {
category: @params.category
product_id: @params.id
product: @get_product(category, product_id)
@render("product.html", {product: product, category: category})
}Dynamic route with optional parameters
#web /blog/{year}/{month}/{day} {
year: @params.year || @date.year()
month: @params.month || @date.month()
day: @params.day || @date.day()
posts: @get_blog_posts(year, month, day)
@render("blog.html", {posts: posts, year: year, month: month, day: day})
}Dynamic route with query parameters
#web /api/search/{type} {
search_type: @params.type
query: @request.query.q
filters: @request.query.filters
results: @search_by_type(search_type, query, filters)
@render_json({results: results, type: search_type})
}
Java Dynamic Routing
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.util.Map;
import java.util.List;@Controller
public class DynamicRoutingController {
private final ProductService productService;
private final BlogService blogService;
private final SearchService searchService;
private final TemplateRenderingService templateService;
public DynamicRoutingController(ProductService productService,
BlogService blogService,
SearchService searchService,
TemplateRenderingService templateService) {
this.productService = productService;
this.blogService = blogService;
this.searchService = searchService;
this.templateService = templateService;
}
@GetMapping("/product/{category}/{id}")
public String product(@PathVariable String category,
@PathVariable Long id,
Model model) {
Product product = productService.getProduct(category, id);
Map<String, Object> data = new HashMap<>();
data.put("product", product);
data.put("category", category);
String rendered = templateService.renderTemplate("product", data);
model.addAttribute("renderedContent", rendered);
return "layout";
}
@GetMapping("/blog/{year}/{month}/{day}")
public String blog(@PathVariable(required = false) Integer year,
@PathVariable(required = false) Integer month,
@PathVariable(required = false) Integer day,
Model model) {
// Use current date if parameters are not provided
LocalDate currentDate = LocalDate.now();
int blogYear = year != null ? year : currentDate.getYear();
int blogMonth = month != null ? month : currentDate.getMonthValue();
int blogDay = day != null ? day : currentDate.getDayOfMonth();
List<BlogPost> posts = blogService.getBlogPosts(blogYear, blogMonth, blogDay);
Map<String, Object> data = new HashMap<>();
data.put("posts", posts);
data.put("year", blogYear);
data.put("month", blogMonth);
data.put("day", blogDay);
String rendered = templateService.renderTemplate("blog", data);
model.addAttribute("renderedContent", rendered);
return "layout";
}
@GetMapping("/api/search/{type}")
public ResponseEntity<Map<String, Object>> search(@PathVariable String type,
@RequestParam String q,
@RequestParam(required = false) String filters) {
List<SearchResult> results = searchService.searchByType(type, q, filters);
Map<String, Object> response = new HashMap<>();
response.put("results", results);
response.put("type", type);
return ResponseEntity.ok(response);
}
}
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product getProduct(String category, Long id) {
return productRepository.findByCategoryAndId(category, id)
.orElseThrow(() -> new ProductNotFoundException("Product not found"));
}
}
@Service
public class BlogService {
private final BlogPostRepository blogPostRepository;
public BlogService(BlogPostRepository blogPostRepository) {
this.blogPostRepository = blogPostRepository;
}
public List<BlogPost> getBlogPosts(int year, int month, int day) {
LocalDate date = LocalDate.of(year, month, day);
return blogPostRepository.findByDate(date);
}
}
@Service
public class SearchService {
private final Map<String, SearchStrategy> searchStrategies;
public SearchService(List<SearchStrategy> strategies) {
this.searchStrategies = strategies.stream()
.collect(Collectors.toMap(SearchStrategy::getType, Function.identity()));
}
public List<SearchResult> searchByType(String type, String query, String filters) {
SearchStrategy strategy = searchStrategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("Unknown search type: " + type);
}
return strategy.search(query, filters);
}
}
Form Handling
Form display
#web /contact {
@render("contact.html", {title: "Contact Us"})
}Form processing
#web /contact method: "POST" {
name: @request.body.name
email: @request.body.email
message: @request.body.message
@validate_form({name: name, email: email, message: message})
@send_contact_email(name, email, message)
@redirect("/contact?success=true")
}Form with file upload
#web /upload method: "POST" {
file: @request.files.upload
description: @request.body.description
@validate_file(file)
@upload_file(file, description)
@redirect("/upload?success=true")
}
Java Form Handling
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.validation.BindingResult;
import javax.validation.Valid;@Controller
public class FormController {
private final ContactService contactService;
private final FileUploadService fileUploadService;
private final ValidationService validationService;
public FormController(ContactService contactService,
FileUploadService fileUploadService,
ValidationService validationService) {
this.contactService = contactService;
this.fileUploadService = fileUploadService;
this.validationService = validationService;
}
@GetMapping("/contact")
public String contactForm(Model model) {
model.addAttribute("title", "Contact Us");
model.addAttribute("contactForm", new ContactForm());
return "contact";
}
@PostMapping("/contact")
public String processContact(@Valid @ModelAttribute ContactForm contactForm,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
return "contact";
}
try {
// Validate form
validationService.validateContactForm(contactForm);
// Send contact email
contactService.sendContactEmail(contactForm.getName(),
contactForm.getEmail(),
contactForm.getMessage());
redirectAttributes.addFlashAttribute("success", "Message sent successfully!");
return "redirect:/contact?success=true";
} catch (Exception e) {
redirectAttributes.addFlashAttribute("error", "Failed to send message: " + e.getMessage());
return "redirect:/contact?error=true";
}
}
@GetMapping("/upload")
public String uploadForm(Model model) {
model.addAttribute("uploadForm", new UploadForm());
return "upload";
}
@PostMapping("/upload")
public String processUpload(@ModelAttribute UploadForm uploadForm,
@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
try {
// Validate file
validationService.validateFile(file);
// Upload file
String fileUrl = fileUploadService.uploadFile(file, uploadForm.getDescription());
redirectAttributes.addFlashAttribute("success", "File uploaded successfully!");
redirectAttributes.addFlashAttribute("fileUrl", fileUrl);
return "redirect:/upload?success=true";
} catch (Exception e) {
redirectAttributes.addFlashAttribute("error", "Failed to upload file: " + e.getMessage());
return "redirect:/upload?error=true";
}
}
}
@Service
public class ContactService {
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
public ContactService(JavaMailSender mailSender, TemplateEngine templateEngine) {
this.mailSender = mailSender;
this.templateEngine = templateEngine;
}
public void sendContactEmail(String name, String email, String message) throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo("contact@example.com");
helper.setSubject("New Contact Form Submission");
helper.setFrom("noreply@example.com");
// Generate email content
Context context = new Context();
context.setVariable("name", name);
context.setVariable("email", email);
context.setVariable("message", message);
context.setVariable("timestamp", LocalDateTime.now());
String emailContent = templateEngine.process("contact-email", context);
helper.setText(emailContent, true);
mailSender.send(mimeMessage);
}
}
@Service
public class FileUploadService {
private final String uploadDirectory = "/uploads";
public String uploadFile(MultipartFile file, String description) throws IOException {
// Create upload directory if it doesn't exist
Path uploadPath = Paths.get(uploadDirectory);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// Generate unique filename
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String filename = UUID.randomUUID().toString() + fileExtension;
// Save file
Path filePath = uploadPath.resolve(filename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// Save file metadata
FileMetadata metadata = new FileMetadata();
metadata.setFilename(filename);
metadata.setOriginalFilename(originalFilename);
metadata.setDescription(description);
metadata.setUploadDate(LocalDateTime.now());
metadata.setFileSize(file.getSize());
fileMetadataRepository.save(metadata);
return "/uploads/" + filename;
}
}
@Service
public class ValidationService {
public void validateContactForm(ContactForm form) {
List<String> errors = new ArrayList<>();
if (form.getName() == null || form.getName().trim().isEmpty()) {
errors.add("Name is required");
}
if (form.getEmail() == null || !isValidEmail(form.getEmail())) {
errors.add("Valid email is required");
}
if (form.getMessage() == null || form.getMessage().trim().isEmpty()) {
errors.add("Message is required");
}
if (!errors.isEmpty()) {
throw new ValidationException("Form validation failed", errors);
}
}
public void validateFile(MultipartFile file) {
List<String> errors = new ArrayList<>();
if (file.isEmpty()) {
errors.add("File is required");
}
if (file.getSize() > 10 1024 1024) { // 10MB limit
errors.add("File size must be less than 10MB");
}
String contentType = file.getContentType();
if (contentType == null || !isAllowedContentType(contentType)) {
errors.add("File type not allowed");
}
if (!errors.isEmpty()) {
throw new ValidationException("File validation failed", errors);
}
}
private boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
private boolean isAllowedContentType(String contentType) {
List<String> allowedTypes = Arrays.asList(
"image/jpeg", "image/png", "image/gif",
"application/pdf", "text/plain"
);
return allowedTypes.contains(contentType);
}
}
Web Caching
Web route with caching
#web {
route: "/cached-page"
cache: true
cache_ttl: 300
} {
data: @get_expensive_data()
@render("cached-page.html", {data: data})
}Conditional caching
#web {
route: "/conditional-cached"
cache: @is_production()
cache_ttl: 600
} {
data: @get_data()
@render("conditional-cached.html", {data: data})
}Cache invalidation
#web {
route: "/cache-invalidate"
method: "POST"
} {
@invalidate_cache("cached-page")
@redirect("/admin/cache-status")
}
Java Web Caching
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;@Service
public class WebCachingService {
private final CacheManager cacheManager;
private final EnvironmentService environmentService;
public WebCachingService(CacheManager cacheManager, EnvironmentService environmentService) {
this.cacheManager = cacheManager;
this.environmentService = environmentService;
}
@Cacheable(value = "web-pages", key = "#pageName")
public String getCachedPage(String pageName, Map<String, Object> data) {
// Expensive operation to generate page content
return generatePageContent(pageName, data);
}
public String getConditionalCachedPage(String pageName, Map<String, Object> data) {
if (environmentService.isProduction()) {
return getCachedPage(pageName, data);
} else {
// No caching in development
return generatePageContent(pageName, data);
}
}
@CacheEvict(value = "web-pages", allEntries = true)
public void invalidateAllPages() {
// This method will clear all cached pages
}
@CacheEvict(value = "web-pages", key = "#pageName")
public void invalidatePage(String pageName) {
// This method will clear specific cached page
}
private String generatePageContent(String pageName, Map<String, Object> data) {
// Simulate expensive operation
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Generated content for " + pageName;
}
}
@Controller
public class CachedWebController {
private final WebCachingService cachingService;
private final DataService dataService;
public CachedWebController(WebCachingService cachingService, DataService dataService) {
this.cachingService = cachingService;
this.dataService = dataService;
}
@GetMapping("/cached-page")
public String cachedPage(Model model) {
Map<String, Object> data = dataService.getExpensiveData();
String content = cachingService.getCachedPage("cached-page", data);
model.addAttribute("content", content);
return "cached-page";
}
@GetMapping("/conditional-cached")
public String conditionalCachedPage(Model model) {
Map<String, Object> data = dataService.getData();
String content = cachingService.getConditionalCachedPage("conditional-cached", data);
model.addAttribute("content", content);
return "conditional-cached";
}
@PostMapping("/cache-invalidate")
public String invalidateCache(RedirectAttributes redirectAttributes) {
cachingService.invalidateAllPages();
redirectAttributes.addFlashAttribute("success", "Cache invalidated successfully");
return "redirect:/admin/cache-status";
}
}
Web Testing
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.context.TestPropertySource;@WebMvcTest(WebController.class)
@TestPropertySource(properties = {
"tusk.web.default-template=default",
"tusk.web.default-layout=main"
})
public class WebControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@MockBean
private SearchService searchService;
@Test
public void testHomePage() throws Exception {
User user = new User();
user.setId(1L);
user.setName("Test User");
when(userService.getUserById(1L)).thenReturn(user);
mockMvc.perform(get("/home"))
.andExpect(status().isOk())
.andExpect(view().name("home"))
.andExpect(model().attribute("title", "Welcome"))
.andExpect(model().attribute("user", user));
}
@Test
public void testUserProfile() throws Exception {
User user = new User();
user.setId(1L);
user.setName("Test User");
when(userService.getUserById(1L)).thenReturn(user);
mockMvc.perform(get("/user/1"))
.andExpect(status().isOk())
.andExpect(view().name("user"))
.andExpect(model().attribute("user", user));
}
@Test
public void testSearch() throws Exception {
List<SearchResult> results = Arrays.asList(
new SearchResult("Result 1"),
new SearchResult("Result 2")
);
when(searchService.searchDatabase("test")).thenReturn(results);
mockMvc.perform(get("/search").param("q", "test"))
.andExpect(status().isOk())
.andExpect(view().name("search"))
.andExpect(model().attribute("query", "test"))
.andExpect(model().attribute("results", results));
}
@Test
public void testContactForm() throws Exception {
mockMvc.perform(get("/contact"))
.andExpect(status().isOk())
.andExpect(view().name("contact"))
.andExpect(model().attribute("title", "Contact Us"));
}
@Test
public void testContactFormSubmission() throws Exception {
mockMvc.perform(post("/contact")
.param("name", "Test User")
.param("email", "test@example.com")
.param("message", "Test message"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/contact?success=true"));
}
}
Configuration Properties
application.yml
tusk:
web:
default-template: "default"
default-layout: "main"
default-cache: false
default-cache-ttl: 300
routes:
dashboard:
route: "/dashboard"
method: "GET"
template: "dashboard.html"
layout: "main.html"
cache: true
cache-ttl: 300
middleware: ["auth"]
admin:
route: "/admin"
method: "GET"
template: "admin.html"
middleware: ["auth", "admin_check"]
condition: "user.hasRole('ADMIN')"
premium:
route: "/premium"
method: "GET"
template: "premium.html"
condition: "user.isPremium()"
global-middleware: ["logging", "auth"]
templates:
home: "home.html"
dashboard: "dashboard.html"
profile: "profile.html"
contact: "contact.html"spring:
thymeleaf:
cache: false
prefix: "classpath:/templates/"
suffix: ".html"
encoding: "UTF-8"
mode: "HTML"
web:
resources:
static-locations: "classpath:/static/"
cache:
period: 3600
Summary
The #web
directive in TuskLang provides comprehensive web application capabilities for Java applications. With Spring Boot integration, flexible routing, template rendering, and caching support, you can implement sophisticated web applications that enhance your application's functionality.
Key features include: - Multiple web patterns: Template rendering, dynamic routing, form handling - Spring Boot integration: Seamless integration with Spring Boot web framework - Flexible configuration: Configurable routes with middleware and conditions - Template rendering: Support for multiple template engines and layouts - Form handling: Comprehensive form processing and validation - Caching support: Built-in caching capabilities with conditional caching - Testing support: Comprehensive testing utilities
The Java implementation provides enterprise-grade web capabilities that integrate seamlessly with Spring Boot applications while maintaining the simplicity and power of TuskLang's declarative syntax.