diff --git a/README.md b/README.md
index 84a866d..feaee5a 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ func main() {
// Initialize sessions
sushi.InitSessions("sessions.json")
- app.Get("/", func(ctx sushi.Ctx, params []any) {
+ app.Get("/", func(ctx sushi.Ctx) {
ctx.SendHTML("
Hello Sushi!
")
})
@@ -41,35 +41,86 @@ api.Get("/users", listUsersHandler)
api.Post("/users", createUserHandler)
```
-## Parameters
+## Parameters and Queries
-URL parameters are automatically converted to the appropriate type:
+Sushi provides a fluent API for accessing URL parameters, query strings, and form data with automatic type conversion:
+
+### URL Parameters (Named Routes)
```go
-// Numeric parameters become integers
-app.Get("/users/:id", func(ctx sushi.Ctx, params []any) {
- userID := params[0].(int) // /users/123 -> 123
- // ...
+app.Get("/users/:id", func(ctx sushi.Ctx) {
+ // Get URL parameter with fluent API
+ userID := ctx.Param("id").Int() // /users/123 -> 123
+ userIDStr := ctx.Param("id").String() // /users/123 -> "123"
+
+ // With defaults
+ limit := ctx.Param("limit").IntDefault(10)
})
-// String parameters stay strings
-app.Get("/users/:name", func(ctx sushi.Ctx, params []any) {
- name := params[0].(string) // /users/john -> "john"
- // ...
-})
+app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx) {
+ userID := ctx.Param("id").Int()
+ slug := ctx.Param("slug").String()
-// Mixed types
-app.Get("/users/:id/posts/:slug", func(ctx sushi.Ctx, params []any) {
- userID := params[0].(int) // 123
- slug := params[1].(string) // "my-post"
- // ...
+ // Check if parameter exists
+ if ctx.Param("optional").Exists() {
+ // Handle optional parameter
+ }
+})
+```
+
+### Query Parameters
+
+```go
+app.Get("/search", func(ctx sushi.Ctx) {
+ // Get query parameters with fluent API
+ query := ctx.Query("q").String() // ?q=hello -> "hello"
+ page := ctx.Query("page").IntDefault(1) // ?page=2 -> 2
+ limit := ctx.Query("limit").IntDefault(20) // Default to 20 if not present
+ sortBy := ctx.Query("sort").StringDefault("name") // Default to "name"
+
+ // Boolean query params
+ includeDeleted := ctx.Query("deleted").Bool() // ?deleted=true -> true
+
+ // Float values
+ minPrice := ctx.Query("min_price").Float() // ?min_price=19.99 -> 19.99
+
+ // Check existence
+ if ctx.Query("filter").Exists() {
+ filter := ctx.Query("filter").String()
+ // Apply filter
+ }
+})
+```
+
+## Form Data
+
+Access form data with the fluent API:
+
+```go
+app.Post("/users", func(ctx sushi.Ctx) {
+ // Get form fields with fluent API
+ name := ctx.Input("name").String()
+ email := ctx.Input("email").String()
+ age := ctx.Input("age").IntDefault(0)
+
+ // Check if checkbox is checked
+ agreeToTerms := ctx.Input("terms").Bool() // Checks for "true", "on", "1", "yes"
+
+ // Validate required fields
+ if ctx.Input("email").IsEmpty() {
+ ctx.SendError(400, "Email is required")
+ return
+ }
+
+ // Get array of values (for multiple select, checkboxes)
+ tags := ctx.GetFormArray("tags[]")
})
```
## Response Helpers
```go
-func myHandler(ctx sushi.Ctx, params []any) {
+func myHandler(ctx sushi.Ctx) {
// JSON responses
ctx.SendJSON(map[string]string{"message": "success"})
@@ -87,15 +138,20 @@ func myHandler(ctx sushi.Ctx, params []any) {
// Status only
ctx.SendStatus(204)
+
+ // File responses
+ ctx.SendFile("/path/to/file.pdf")
+
+ // Raw bytes
+ ctx.SendBytes(imageData, "image/png")
}
-```
## Middleware
```go
// Custom middleware
func loggingMiddleware() sushi.Middleware {
- return func(ctx sushi.Ctx, params []any, next func()) {
+ return func(ctx sushi.Ctx, next func()) {
println("Request:", string(ctx.Method()), string(ctx.Path()))
next()
println("Status:", ctx.Response.StatusCode())
@@ -111,7 +167,7 @@ admin.Use(auth.RequireAuth("/login"))
## Authentication Workflow
-### 1. Setup Password Hashing
+### 1. Password Hashing
```go
import "git.sharkk.net/Sharkk/Sushi/password"
@@ -176,9 +232,16 @@ func main() {
### 4. Login Handler
```go
-func loginHandler(ctx sushi.Ctx, params []any) {
- email := string(ctx.PostArgs().Peek("email"))
- password := string(ctx.PostArgs().Peek("password"))
+func loginHandler(ctx sushi.Ctx) {
+ // Use fluent API for form data
+ email := ctx.Input("email").String()
+ password := ctx.Input("password").String()
+
+ // Validate inputs
+ if ctx.Input("email").IsEmpty() || ctx.Input("password").IsEmpty() {
+ ctx.SendError(400, "Email and password are required")
+ return
+ }
// Find user by email/username
user := findUserByEmail(email)
@@ -204,7 +267,7 @@ func loginHandler(ctx sushi.Ctx, params []any) {
### 5. Logout Handler
```go
-func logoutHandler(ctx sushi.Ctx, params []any) {
+func logoutHandler(ctx sushi.Ctx) {
ctx.Logout()
ctx.Redirect("/")
}
@@ -213,7 +276,7 @@ func logoutHandler(ctx sushi.Ctx, params []any) {
### 6. Getting Current User
```go
-func dashboardHandler(ctx sushi.Ctx, params []any) {
+func dashboardHandler(ctx sushi.Ctx) {
user := ctx.GetCurrentUser().(*User)
html := fmt.Sprintf("Welcome, %s!
", user.Username)
@@ -230,7 +293,7 @@ import "git.sharkk.net/Sharkk/Sushi/csrf"
app.Use(csrf.Middleware())
// In your form template
-func loginPageHandler(ctx sushi.Ctx, params []any) {
+func loginPageHandler(ctx sushi.Ctx) {
csrfField := csrf.CSRFHiddenField(ctx)
html := fmt.Sprintf(`
@@ -266,7 +329,7 @@ app.Get("/assets/*path", sushi.StaticEmbed(files))
## Sessions
```go
-func someHandler(ctx sushi.Ctx, params []any) {
+func someHandler(ctx sushi.Ctx) {
sess := ctx.GetCurrentSession()
// Set session data
@@ -357,7 +420,7 @@ func main() {
app.Listen(":8080")
}
-func homeHandler(ctx sushi.Ctx, params []any) {
+func homeHandler(ctx sushi.Ctx) {
if ctx.IsAuthenticated() {
ctx.Redirect("/dashboard")
return
@@ -365,7 +428,7 @@ func homeHandler(ctx sushi.Ctx, params []any) {
ctx.SendHTML(`Login`)
}
-func loginPageHandler(ctx sushi.Ctx, params []any) {
+func loginPageHandler(ctx sushi.Ctx) {
html := fmt.Sprintf(`