Skip to content

Commit 56474ae

Browse files
committed
docs: add some more
1 parent c0f1db1 commit 56474ae

15 files changed

+598
-346
lines changed

README.md

Lines changed: 2 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
# Muxt [![Go Reference](https://pkg.go.dev/badge/github.com/crhntr/muxt.svg)](https://pkg.go.dev/github.com/crhntr/muxt) [![Go](https://github.com/crhntr/muxt/actions/workflows/go.yml/badge.svg)](https://github.com/crhntr/muxt/actions/workflows/go.yml)
22

3-
Since Go 1.22, the standard library route **mu**ltiple**x**er [`*http.ServeMux`](https://pkg.go.dev/net/http#ServeMux) uses http methods, hosts, and path parameters.
4-
Muxt extends this syntax to add method signatures and type static analysis based template type safety to make it faster to write and test server side rendered hypermedia web applications.
5-
6-
Muxt generates Go code. It does not require you to add any dependencies outside the Go standard library.
3+
**Muxt** is a Go code generator that helps you build server-side rendered web apps with minimal overhead, leveraging Go 1.22’s improved `http.ServeMux` and standard `html/template` features.
4+
No extra runtime dependencies are required—just plain Go code.
75

86
- It allows you to register HTTP routes from [HTML templates](https://pkg.go.dev/html/template)
97
- It generates handler functions and registers them on an [`*http.ServeMux`](https://pkg.go.dev/net/http#ServeMux)
@@ -90,145 +88,6 @@ var (
9088

9189
```
9290

93-
### Making Template Source Files Discoverable
94-
95-
Muxt needs your template source files to be embedded in the package in the current directory for it to discover and parse them (see the "Go Generate Comment Example" above).
96-
97-
You need to add a globally scoped variable with type `embed.FS` (like `templatesSource` in the example).
98-
It should be passed into a call either the function `"html/template".ParseFS` or method `"html/template".Template.ParseFS`.
99-
Before it does so, it can call any of the following functions or methods in the right hand side of the `templates` variable declaration.
100-
101-
Muxt will call any of the functions:
102-
- [`Must`](https://pkg.go.dev/html/template#Must)
103-
- [`Parse`](https://pkg.go.dev/html/template#Parse)
104-
- [`New`](https://pkg.go.dev/html/template#New)
105-
- [`ParseFS`](https://pkg.go.dev/html/template#ParseFS)
106-
107-
or methods:
108-
- [`Template.Parse`](https://pkg.go.dev/html/template#Template.Parse)
109-
- [`Template.New`](https://pkg.go.dev/html/template#Template.New)
110-
- [`Template.ParseFS`](https://pkg.go.dev/html/template#Template.ParseFS)
111-
- [`Template.Delims`](https://pkg.go.dev/html/template#Template.Delims)
112-
- [`Template.Option`](https://pkg.go.dev/html/template#Template.Option)
113-
- [`Template.Funcs`](https://pkg.go.dev/html/template#Template.Option)
114-
115-
Muxt iterates over the resulting parsed templates to discover templates matching the template name pattern documented in the "Naming Templates" section below.
116-
117-
### Naming Templates
118-
119-
`muxt generate` will read your embedded HTML templates and generate/register an [`http.HandlerFunc`](https://pkg.go.dev/net/http#HandlerFunc) for each template with a name that matches an expected patten.
120-
121-
If the template name does not match the pattern, it is ignored by muxt.
122-
123-
Since Go 1.22, the standard library route **mu**ltiple**x**er can parse path parameters.
124-
125-
It has expects strings like this
126-
127-
`[METHOD ][HOST]/[PATH]`
128-
129-
Muxt extends this a little bit.
130-
131-
`[METHOD ][HOST]/[PATH ][HTTP_STATUS ][CALL]`
132-
133-
A template name that muxt understands looks like this:
134-
135-
```gotemplate
136-
{{define "GET /greet/{language} 200 Greeting(ctx, language)" }}
137-
<h1>{{.Hello}}</h1>
138-
{{end}}
139-
```
140-
141-
In this template name
142-
- Passed through to `http.ServeMux`
143-
- we define the HTTP Method `GET`,
144-
- the path prefix `/greet/`
145-
- the path parameter called `language` (available in the call scope as `language`)
146-
- Used by muxt to generate a `http.HandlerFunc`
147-
- the status code to use when muxt calls WriteHeader is `200` aka `http.StatusOK`
148-
- the method name on the configured receiver to call is `Greeting`
149-
- the parameters to pass to `Greeting` are `ctx` and `language`
150-
151-
#### [`*http.ServeMux`](https://pkg.go.dev/net/http#ServeMux) Patterns
152-
153-
Here is an excerpt from [the standard libary documentation.](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux)
154-
155-
> Patterns can match the method, host and path of a request. Some examples:
156-
> - "/index.html" matches the path "/index.html" for any host and method.
157-
> - "GET /static/" matches a GET request whose path begins with "/static/".
158-
> - "example.com/" matches any request to the host "example.com".
159-
> - "example.com/{$}" matches requests with host "example.com" and path "/".
160-
> - "/b/{bucket}/o/{objectname...}" matches paths whose first segment is "b" and whose third segment is "o". The name "bucket" denotes the second segment and "objectname" denotes the remainder of the path.
161-
162-
#### The Method Call Scope
163-
164-
There are three parameters you can pass to a method that always generate the same code
165-
166-
- `ctx` -> `http.Request.Context`
167-
- `request` -> `*http.Request`
168-
- `response` -> `http.ResponseWriter` (if you use this, muxt won't generate code to call WriteHeader, you have to do this)
169-
170-
Using these three, the generated code will look something like this.
171-
172-
Given `{{define "GET / F(ctx, response, request)"}}Hello{{end}}`,
173-
174-
You will get a handler generated like this:
175-
176-
```go
177-
package main
178-
179-
import (
180-
"context"
181-
"net/http"
182-
)
183-
184-
type RoutesReceiver interface {
185-
F(ctx context.Context, response http.ResponseWriter, request *http.Request) any
186-
}
187-
188-
func routes(mux *http.ServeMux, receiver RoutesReceiver) {
189-
mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) {
190-
ctx := request.Context()
191-
data := receiver.F(ctx, response, request)
192-
execute(response, request, false, "GET / F(ctx, response, request)", http.StatusOK, data)
193-
})
194-
}
195-
196-
func execute(http.ResponseWriter, *http.Request, bool, string, int, any) {}
197-
```
198-
199-
You can also map path values from the path pattern to identifiers and pass them to your handler.
200-
201-
202-
Given `{{define "GET /articles/{id} ReadArticle(ctx, id)"}}{{end}}`,
203-
204-
You will get a handler generated like this:
205-
206-
```go
207-
package main
208-
209-
import (
210-
"context"
211-
"net/http"
212-
)
213-
214-
type RoutesReceiver interface {
215-
ReadArticle(ctx context.Context, id string) any
216-
}
217-
218-
func routes(mux *http.ServeMux, receiver RoutesReceiver) {
219-
mux.HandleFunc("GET /articles/{id}", func(response http.ResponseWriter, request *http.Request) {
220-
ctx := request.Context()
221-
id := request.PathValue("id")
222-
data := receiver.ReadArticle(ctx, id)
223-
execute(response, request, true, "GET /articles/{id} ReadArticle(ctx, id)", http.StatusOK, data)
224-
})
225-
}
226-
227-
func execute(http.ResponseWriter, *http.Request, bool, string, int, any) {}
228-
```
229-
230-
_TODO add more documentation on form and typed arguments_
231-
23291
## Examples
23392

23493
The [example directory](example) has a worked example.

docs/action_type_checking.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Action Type Checking
2+
3+
`muxt check`
4+
5+
Does best effort static analysis of template actions given the results from endpoint methods.
6+
It works in many (and some not so) simple cases.
7+
Template execution does a bunch of runtime evaluation that makes complete type checking impossible.
8+
Avoid using the empty interface and you'll probably be fine.
9+
10+
If you wanna check out the code, it is in ./internal/templatetype.
11+
At some point I'd like to open source that as a subcomponent.
12+
I also want to support explicitly setting a template type via `gotype: ` comments that GoLand (by JetBrains) uses for tab completion.
13+
14+
I also would like to extend this code to create better template documentation and maybe a storybook kind thing... someday.

docs/call_parameters.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Method Parameter Field Sets
2+
3+
This is the (wip) "argument" documentation.
4+
5+
## The Method Call Scope
6+
7+
There are three parameters you can pass to a method that always generate the same code
8+
9+
- `ctx` -> `http.Request.Context`
10+
- `request` -> `*http.Request`
11+
- `response` -> `http.ResponseWriter` (if you use this, muxt won't generate code to call WriteHeader, you have to do this)
12+
13+
Using these three, the generated code will look something like this.
14+
15+
Given `{{define "GET / F(ctx, response, request)"}}Hello{{end}}`,
16+
17+
You will get a handler generated like this:
18+
19+
```go
20+
package main
21+
22+
import (
23+
"context"
24+
"net/http"
25+
)
26+
27+
type RoutesReceiver interface {
28+
F(ctx context.Context, response http.ResponseWriter, request *http.Request) any
29+
}
30+
31+
func routes(mux *http.ServeMux, receiver RoutesReceiver) {
32+
mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) {
33+
ctx := request.Context()
34+
data := receiver.F(ctx, response, request)
35+
execute(response, request, false, "GET / F(ctx, response, request)", http.StatusOK, data)
36+
})
37+
}
38+
39+
func execute(http.ResponseWriter, *http.Request, bool, string, int, any) {}
40+
```
41+
42+
You can also map path values from the path pattern to identifiers and pass them to your handler.
43+
44+
45+
Given `{{define "GET /articles/{id} ReadArticle(ctx, id)"}}{{end}}`,
46+
47+
You will get a handler generated like this:
48+
49+
```go
50+
package main
51+
52+
import (
53+
"context"
54+
"net/http"
55+
)
56+
57+
type RoutesReceiver interface {
58+
ReadArticle(ctx context.Context, id string) any
59+
}
60+
61+
func routes(mux *http.ServeMux, receiver RoutesReceiver) {
62+
mux.HandleFunc("GET /articles/{id}", func(response http.ResponseWriter, request *http.Request) {
63+
ctx := request.Context()
64+
id := request.PathValue("id")
65+
data := receiver.ReadArticle(ctx, id)
66+
execute(response, request, true, "GET /articles/{id} ReadArticle(ctx, id)", http.StatusOK, data)
67+
})
68+
}
69+
70+
func execute(http.ResponseWriter, *http.Request, bool, string, int, any) {}
71+
```
72+
73+
## Parsing
74+
75+
Many basic Go types are supported.
76+
77+
Integer variants are most common.
78+
79+
If a type implements [`encoding.TextUmarshaler`](https://pkg.go.dev/encoding#TextUnmarshaler) we will use that.

docs/call_results.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Method Result Field Sets
2+
3+
This is the (wip) "returns" documentation.
4+
5+
It's kinda similar to template functions.
6+
7+
You *should* just return one struct value (including maybe one or more error fields).
8+
9+
However, you can also return a value and an error or a boolean.
10+
11+
The following would be acceptable result sets.
12+
13+
```go
14+
package domain
15+
16+
type T struct{
17+
MissingDataReason error
18+
}
19+
20+
type Server struct{}
21+
22+
func (Server) F1() T { return T{}}
23+
func (Server) F2() (T, bool) { return T{}, false}
24+
func (Server) F3() (T, error) { return T{}, nil}
25+
```

docs/custom_execute_func.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
You can write your own version of the execute function.
2+
3+
Just copy the signature and replace the body as you like.
4+
5+
Remember to honor the "writeHeader" parameter.
6+
If a method receives a response writer, you should expect that method call WriteHeader and not do so in your execute implementation.

0 commit comments

Comments
 (0)