SkillAgentSearch skills...

Xun

Xun is a web framework built on Go's built-in html/template and net/http package’s router (1.22).

Install / Use

/learn @yaitoo/Xun
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Xun AI Agent Specification

Status: AUTHORITATIVE

Before writing any Xun code, read this entire document. All guidance must be derived from this file — do not rely on prior knowledge of gin/echo/chi.


Section 0 — Critical Rules (Read Before Writing Any Code)

These rules are numbered. All other sections reference them by number.

Rule 0.1 — NEVER call WithHandlerViewers() with no arguments

// WRONG — compiles but sets app.handlerViewers = nil
xun.New(xun.WithHandlerViewers())

// CORRECT — pass at least one Viewer
xun.New(xun.WithHandlerViewers(&xun.JsonViewer{}))

Consequence: app.handlerViewers == nil → all handler routes get r.Viewers == nilc.View(data) returns ErrViewNotFound → HTTP 404.

Rule 0.2 — NEVER write response body directly

// WRONG — bypasses compression and BufPool
c.Response.Write([]byte("hello"))
return nil

// CORRECT — always use c.View()
return c.View("hello")  // with StringViewer registered

Rule 0.3 — NEVER return an error from middleware when refusing

// WRONG — returns 500 + X-Log-Id
func refuseMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        if !allowed {
            return errors.New("forbidden")
        }
        return next(c)
    }
}

// CORRECT — set status and return ErrCancelled
func refuseMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        if !allowed {
            c.WriteStatus(http.StatusForbidden)
            return xun.ErrCancelled
        }
        return next(c)
    }
}

Rule 0.4 — app.Start() does NOT start the server

// WRONG — gin habit
app.Run(":8080")

// CORRECT
app := xun.New(opts...)
app.Start()                        // only prints route logs
defer app.Close()
http.ListenAndServe(":80", mux)    // server startup is caller's responsibility

Rule 0.5 — Named viewer MUST match Accept header or silently falls back

// Route: r.Viewers = [JsonViewer]
// Request: Accept: application/json

return c.View(user, "views/user/profile")  // views/user/profile is HtmlViewer
// HtmlViewer (text/html) does NOT match Accept (application/json)
// → falls back to JsonViewer (r.Viewers[0]), NOT the named viewer

Rule 0.6 — pages/* auto-registers GET only

// File: pages/admin/dashboard.html
// Route: GET /admin/dashboard     ← GET only, no POST/PUT/DELETE auto-registered
// To handle POST, register explicitly:
app.Post("/admin/dashboard", handler)

Rule 0.7 — {$} means trailing slash required

app.Get("/posts/{$}")   // matches GET /posts/  ONLY
app.Get("/posts/")       // matches GET /posts/abc, GET /posts/123
app.Get("/posts")        // matches GET /posts  ONLY (no slash)

Section 1 — Types

HandleFunc       = func(c *Context) error
Middleware       = func(next HandleFunc) HandleFunc
Option           = func(*App)
RoutingOption    = func(*RoutingOptions)
chain            = interface{ Next(hf HandleFunc) HandleFunc }

HandleFunc returns error, not nil. See Section 10 for error meanings.


Section 2 — App

2.1 Creation

app := xun.New(opts ...Option) *App

2.2 Fields

| Field | Type | Default | Overridden-By | Nil-Result | |-------|------|---------|---------------|------------| | app.mux | *http.ServeMux | http.DefaultServeMux | WithMux(mux) | — | | app.handlerViewers | []Viewer | []Viewer{&JsonViewer{}} | WithHandlerViewers(v...) | All handler routes return 404 (Rule 0.1) | | app.fsys | fs.FS | nil | WithFsys(fsys) | Page routing disabled | | app.watch | bool | false | WithWatch() | Hot reload disabled | | app.interceptor | Interceptor | nil | WithInterceptor(i) | Redirect/RequestReferer use defaults | | app.compressors | []Compressor | nil | WithCompressor(c...) | No compression | | app.viewers | map[string]Viewer | empty map | HtmlViewEngine.Load() registers views/* | Named viewers unavailable | | app.funcMap | template.FuncMap | xun.builtins | WithTemplateFunc, WithTemplateFuncMap | Builtin asset func unavailable | | app.routes | map[string]*Routing | empty map | app.Get/Post/etc, app.HandlePage | — |

2.3 App.Start()

app.Start()

Writes info-level logs for each registered route (pattern + viewer MIME types). Does NOT start the HTTP server. Server startup is the caller's responsibility.

2.4 App.Close()

Currently a no-op. Reserved for future use.

2.5 Option Functions

WithMux(mux *http.ServeMux) Option
WithFsys(fsys fs.FS) Option
WithWatch() Option                    // dev only — not thread-safe
WithHandlerViewers(v ...Viewer) Option
WithViewEngines(ve ...ViewEngine) Option
WithInterceptor(i Interceptor) Option
WithCompressor(c ...Compressor) Option
WithTemplateFunc(name string, fn any) Option
WithTemplateFuncMap(fm template.FuncMap) Option
WithBuildAssetURL(match func(string) bool) Option
WithLogger(logger *slog.Logger) Option

2.6 Route Registration

app.Get(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Post(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Put(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Delete(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Group(prefix string) *group

Pattern format: "METHOD pattern" (e.g., "GET /users/{id}"). Go 1.22 ServeMux syntax.


Section 3 — Group

group implements chain.

func (g *group) Use(middleware ...Middleware)
func (g *group) Get(pattern string, hf HandleFunc, opts ...RoutingOption)
func (g *group) HandleFunc(pattern string, hf HandleFunc, opts ...RoutingOption)
func (g *group) Next(hf HandleFunc) HandleFunc

Middleware chain construction (inside-out):

// given [A, B, C] and handler H:
// build: C(B(A(H)))
next := H
for i := len(g.middlewares); i > 0; i-- {
    next = g.middlewares[i-1](next)
}

Section 4 — Middleware

Middleware signature: func(next HandleFunc) HandleFunc

func AuthMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        // pre logic
        token := c.Request.Header.Get("X-Token")
        if token == "" {
            c.WriteStatus(http.StatusUnauthorized)
            return xun.ErrCancelled
        }
        err := next(c)
        // post logic (runs after handler)
        return err
    }
}

Pre-logic: runs before next(c). Post-logic: runs after next(c) returns. On refusal: ALWAYS set status + return xun.ErrCancelled (Rule 0.3).


Section 5 — Context

Context wraps *http.Request, ResponseWriter, and application state.

5.1 Fields

c.Request  *http.Request    // standard library
c.Response ResponseWriter   // xun interface (extends http.ResponseWriter)
c.Routing  Routing          // route metadata
c.App      *App            // application instance
c.TempData TempData        // map[string]any, request-scoped storage

5.2 Standard Library Equivalents

Use standard library directly for these:

c.Request.PathValue("id")              // path parameter (Go 1.22+)
c.Request.URL.Query().Get("name")     // query string
c.Request.Header.Get("X-Token")       // headers
c.Request.Cookie("session_id")        // read cookie
c.Request.Body                         // request body
c.Request.ParseMultipartForm()         // multipart form
c.Request.Context()                    // context.Context
c.Response.Header().Set(k, v)         // response headers
http.SetCookie(c.Response, &cookie)   // write cookie
c.Request.RemoteAddr                   // client address (no proxy support; use ext/proxyproto)

5.3 xun-Specific Methods

c.View(data any, options ...string) error
c.Redirect(url string, statusCode ...int)
c.AcceptLanguage() []string
c.Accept() []MimeType
c.RequestReferer() string
c.WriteStatus(code int)
c.WriteHeader(key string, value string)
c.Get(key string) any
c.Set(key string, value any)

5.4 c.View(data any, options ...string) Behavior

IF options[0] is provided (named viewer name):
  → getViewer(name) checks: named viewer.MimeType() matches any Accept header
  → IF match: use named viewer
  → IF no match: proceed to step 2

ELSE skip to step 2.

STEP 2: Iterate Accept headers, match against r.Viewers:
  → First matching viewer is used

STEP 3: No match found:
  → Use r.Viewers[0] as fallback

STEP 4: r.Viewers is empty at this point:
  → Return ErrViewNotFound → HTTP 404

c.View() sets status 200 automatically. Call c.WriteStatus() before c.View() to override.

5.5 c.Redirect(url string, statusCode ...int)

Sets Location header. Default status: http.StatusFound (302). Interceptor can override if configured.


Section 6 — Routing

6.1 Routing Fields

type Routing struct {
    Pattern string
    Handle  HandleFunc
    chain   chain           // *App or *group
    Options *RoutingOptions
    Viewers []Viewer       // viewers for this route
}

6.2 Routing.Next(ctx)

func (r *Routing) Next(ctx *Context) error {
    return r.chain.Next(r.Handle)(ctx)
}

6.3 RoutingOptions Fields

type RoutingOptions struct {
    metadata map[string]any
    viewers  []Viewer
}

6.4 RoutingOption Functions

WithViewer(v ...Viewer) RoutingOption
WithMetadata(key string, value any) RoutingOption
WithNavigation(name, icon, access string) RoutingOption

Section 7 — Viewer

7.1 Interface

type Viewer interface {
    MimeType() *MimeType
    Render(ctx *Context, data any) error
}

7.2 Built-in Viewers

| Viewer | MimeType | Default For | |--------|----------|------------| | HtmlViewer | text/html | Page routes | | JsonViewer | application/json | Handler routes (only if app.handlerViewers not overridden) | | TextViewer | text/* (from filename) | Text templates | | XmlViewer | text/xml | — | | StringViewer | text/plain | — | | `FileV

View on GitHub
GitHub Stars91
CategoryDevelopment
Updated7d ago
Forks4

Languages

Go

Security Score

100/100

Audited on Mar 31, 2026

No findings