Skip to content

Commit

Permalink
feat: improve documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Nov 21, 2024
1 parent 724783e commit defd22b
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 86 deletions.
51 changes: 26 additions & 25 deletions clientip/clientip.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ type TrustedIPRange interface {
}

// The IPRangeResolverFunc type is an adapter to allow the use of
// ordinary functions as TrustedIPRange. If f is a function
// ordinary functions as [TrustedIPRange]. If f is a function
// with the appropriate signature, IPRangeResolverFunc() is a
// TrustedIPRange that calls f.
// [TrustedIPRange] that calls f.
type IPRangeResolverFunc func() ([]net.IPNet, error)

// TrustedIPRange calls f().
Expand Down Expand Up @@ -66,7 +66,7 @@ type Chain struct {
strategies []fox.ClientIPStrategy
}

// NewChain creates a Chain that attempts to use the given strategies to
// NewChain creates a [Chain] that attempts to use the given strategies to
// derive the client IP, stopping when the first one succeeds.
func NewChain(strategies ...fox.ClientIPStrategy) Chain {
return Chain{strategies: strategies}
Expand Down Expand Up @@ -100,9 +100,9 @@ func NewRemoteAddr() RemoteAddr {
return RemoteAddr{}
}

// ClientIP derives the client IP using the RemoteAddr strategy. The returned net.IPAddr may contain a zone identifier.
// ClientIP derives the client IP using the [RemoteAddr] strategy. The returned [net.IPAddr] may contain a zone identifier.
// This should only happen if remoteAddr has been modified to something illegal, or if the server is accepting connections
// on a Unix domain socket (in which case RemoteAddr is "@"). If no valid IP can be derived, an error is returned.
// on a Unix domain socket (in which case [RemoteAddr] is "@"). If no valid IP can be derived, an error is returned.
func (s RemoteAddr) ClientIP(c fox.Context) (*net.IPAddr, error) {
ipAddr, err := ParseIPAddr(c.Request().RemoteAddr)
if err != nil {
Expand All @@ -121,7 +121,7 @@ type SingleIPHeader struct {
headerName string
}

// NewSingleIPHeader creates a SingleIPHeader strategy that uses the headerName request header to get the client IP.
// NewSingleIPHeader creates a [SingleIPHeader] strategy that uses the headerName request header to get the client IP.
func NewSingleIPHeader(headerName string) SingleIPHeader {
if headerName == "" {
panic(errors.New("header must not be empty"))
Expand All @@ -138,7 +138,7 @@ func NewSingleIPHeader(headerName string) SingleIPHeader {
return SingleIPHeader{headerName: headerName}
}

// ClientIP derives the client IP using the SingleIPHeader. The returned net.IPAddr may contain a zone identifier.
// ClientIP derives the client IP using the [SingleIPHeader]. The returned [net.IPAddr] may contain a zone identifier.
// If no valid IP can be derived, an error is returned.
func (s SingleIPHeader) ClientIP(c fox.Context) (*net.IPAddr, error) {
// RFC 2616 does not allow multiple instances of single-IP headers (or any non-list header).
Expand All @@ -164,7 +164,7 @@ type LeftmostNonPrivate struct {
blacklistedRanges []net.IPNet
}

// NewLeftmostNonPrivate creates a LeftmostNonPrivate strategy. By default, loopback, link local and private net ip range
// NewLeftmostNonPrivate creates a [LeftmostNonPrivate] strategy. By default, loopback, link local and private net ip range
// are blacklisted.
func NewLeftmostNonPrivate(key HeaderKey, opts ...BlacklistRangeOption) LeftmostNonPrivate {
if key > 1 {
Expand All @@ -179,8 +179,8 @@ func NewLeftmostNonPrivate(key HeaderKey, opts ...BlacklistRangeOption) Leftmost
return LeftmostNonPrivate{headerName: key.String(), blacklistedRanges: orSlice(cfg.ipRanges, privateAndLocalRanges)}
}

// ClientIP derives the client IP using the LeftmostNonPrivate.
// The returned net.IPAddr may contain a zone identifier. If no valid IP can be derived, an error returned.
// ClientIP derives the client IP using the [LeftmostNonPrivate].
// The returned [net.IPAddr] may contain a zone identifier. If no valid IP can be derived, an error returned.
func (s LeftmostNonPrivate) ClientIP(c fox.Context) (*net.IPAddr, error) {
ipAddrs := getIPAddrList(c.Request().Header, s.headerName)
for _, ip := range ipAddrs {
Expand All @@ -202,7 +202,7 @@ type RightmostNonPrivate struct {
trustedRanges []net.IPNet
}

// NewRightmostNonPrivate creates a RightmostNonPrivate strategy. By default, loopback, link local and private net ip range
// NewRightmostNonPrivate creates a [RightmostNonPrivate] strategy. By default, loopback, link local and private net ip range
// are trusted.
func NewRightmostNonPrivate(key HeaderKey, opts ...TrustedRangeOption) RightmostNonPrivate {
if key > 1 {
Expand All @@ -220,8 +220,8 @@ func NewRightmostNonPrivate(key HeaderKey, opts ...TrustedRangeOption) Rightmost
}
}

// ClientIP derives the client IP using the RightmostNonPrivate.
// The returned net.IPAddr may contain a zone identifier. If no valid IP can be derived, an error returned.
// ClientIP derives the client IP using the [RightmostNonPrivate].
// The returned [net.IPAddr] may contain a zone identifier. If no valid IP can be derived, an error returned.
func (s RightmostNonPrivate) ClientIP(c fox.Context) (*net.IPAddr, error) {
ipAddrs := getIPAddrList(c.Request().Header, s.headerName)
// Look backwards through the list of IP addresses
Expand All @@ -244,7 +244,7 @@ type RightmostTrustedCount struct {
trustedCount int
}

// NewRightmostTrustedCount creates a RightmostTrustedCount strategy. trustedCount is the number of trusted reverse proxies.
// NewRightmostTrustedCount creates a [RightmostTrustedCount] strategy. trustedCount is the number of trusted reverse proxies.
// The IP returned will be the (trustedCount-1)th from the right. For example, if there's only one trusted proxy, this
// strategy will return the last (rightmost) IP address.
func NewRightmostTrustedCount(key HeaderKey, trustedCount int) RightmostTrustedCount {
Expand All @@ -259,8 +259,8 @@ func NewRightmostTrustedCount(key HeaderKey, trustedCount int) RightmostTrustedC
return RightmostTrustedCount{headerName: key.String(), trustedCount: trustedCount}
}

// ClientIP derives the client IP using the RightmostTrustedCount.
// The returned net.IPAddr may contain a zone identifier. If no valid IP can be derived, an error returned.
// ClientIP derives the client IP using the [RightmostTrustedCount].
// The returned [net.IPAddr] may contain a zone identifier. If no valid IP can be derived, an error returned.
func (s RightmostTrustedCount) ClientIP(c fox.Context) (*net.IPAddr, error) {
ipAddrs := getIPAddrList(c.Request().Header, s.headerName)

Expand Down Expand Up @@ -297,7 +297,7 @@ type RightmostTrustedRange struct {
headerName string
}

// NewRightmostTrustedRange creates a RightmostTrustedRange strategy. headerName must be "X-Forwarded-For"
// NewRightmostTrustedRange creates a [RightmostTrustedRange] strategy. headerName must be "X-Forwarded-For"
// or "Forwarded". trustedRanges must contain all trusted reverse proxies on the path to this server and can
// be private/internal or external (for example, if a third-party reverse proxy is used).
func NewRightmostTrustedRange(key HeaderKey, resolver TrustedIPRange) RightmostTrustedRange {
Expand All @@ -312,8 +312,8 @@ func NewRightmostTrustedRange(key HeaderKey, resolver TrustedIPRange) RightmostT
return RightmostTrustedRange{headerName: key.String(), resolver: resolver}
}

// ClientIP derives the client IP using the RightmostTrustedRange.
// The returned net.IPAddr may contain a zone identifier. If no valid IP can be derived, an error is returned.
// ClientIP derives the client IP using the [RightmostTrustedRange].
// The returned [net.IPAddr] may contain a zone identifier. If no valid IP can be derived, an error is returned.
func (s RightmostTrustedRange) ClientIP(c fox.Context) (*net.IPAddr, error) {
trustedRange, err := s.resolver.TrustedIPRange()
if err != nil {
Expand All @@ -340,7 +340,7 @@ func (s RightmostTrustedRange) ClientIP(c fox.Context) (*net.IPAddr, error) {
return nil, fmt.Errorf("%w: unable to find a valid IP address", ErrRightmostTrustedRange)
}

// MustParseIPAddr panics if ParseIPAddr fails.
// MustParseIPAddr panics if [ParseIPAddr] fails.
func MustParseIPAddr(ipStr string) *net.IPAddr {
ipAddr, err := ParseIPAddr(ipStr)
if err != nil {
Expand All @@ -349,12 +349,13 @@ func MustParseIPAddr(ipStr string) *net.IPAddr {
return ipAddr
}

// ParseIPAddr safely parses the given string into a net.IPAddr. It also returns an error for unspecified (like "::") and zero-value
// addresses (like "0.0.0.0"). These are nominally valid IPs (net.ParseIP will accept them), but they are never valid "real" client IPs.
// ParseIPAddr safely parses the given string into a [net.IPAddr]. It also returns an error for unspecified (like "::")
// and zero-value addresses (like "0.0.0.0"). These are nominally valid IPs ([net.ParseIP] will accept them), but they
// are never valid "real" client IPs.
//
// The function returns the following errors:
// - ErrInvalidIpAddress: if the IP address cannot be parsed.
// - ErrUnspecifiedIpAddress: if the IP address is unspecified (e.g., "::" or "0.0.0.0").
// - [ErrInvalidIpAddress]: if the IP address cannot be parsed.
// - [ErrUnspecifiedIpAddress]: if the IP address is unspecified (e.g., "::" or "0.0.0.0").
func ParseIPAddr(ip string) (*net.IPAddr, error) {
host, _, err := net.SplitHostPort(ip)
if err == nil {
Expand Down Expand Up @@ -387,7 +388,7 @@ func ParseIPAddr(ip string) (*net.IPAddr, error) {
}

// AddressesAndRangesToIPNets converts a slice of strings with IPv4 and IPv6 addresses and CIDR ranges (prefixes) to
// net.IPNet instances. If net.ParseCIDR or net.ParseIP fail, an error will be returned. Zones in addresses or ranges
// [net.IPNet] instances. If [net.ParseCIDR] or [net.ParseIP] fail, an error will be returned. Zones in addresses or ranges
// are not allowed and will result in an error.
func AddressesAndRangesToIPNets(ranges ...string) ([]net.IPNet, error) {
var result []net.IPNet
Expand Down
6 changes: 3 additions & 3 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ func copyWithResize[S ~[]T, T any](dst, src *S) {
copy(*dst, *src)
}

// Scope returns the HandlerScope associated with the current Context.
// This indicates the scope in which the handler is being executed, such as RouteHandler, NoRouteHandler, etc.
// Scope returns the [HandlerScope] associated with the current [Context].
// This indicates the scope in which the handler is being executed, such as [RouteHandler], [NoRouteHandler], etc.
func (c *cTx) Scope() HandlerScope {
return c.scope
}
Expand All @@ -398,7 +398,7 @@ func (c *cTx) getQueries() url.Values {
return c.cachedQuery
}

// WrapF is an adapter for wrapping http.HandlerFunc and returns a HandlerFunc function.
// WrapF is an adapter for wrapping [http.HandlerFunc] and returns a [HandlerFunc] function.
// The route parameters are being accessed by the wrapped handler through the context.
func WrapF(f http.HandlerFunc) HandlerFunc {
return func(c Context) {
Expand Down
4 changes: 2 additions & 2 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func newConflictErr(method, path string, matched []string) *RouteConflictError {
}
}

// Error returns a formatted error message for the RouteConflictError.
// Error returns a formatted error message for the [RouteConflictError].
func (e *RouteConflictError) Error() string {
if !e.isUpdate {
return e.insertError()
Expand All @@ -59,7 +59,7 @@ func (e *RouteConflictError) updateError() string {

}

// Unwrap returns the sentinel value ErrRouteConflict.
// Unwrap returns the sentinel value [ErrRouteConflict].
func (e *RouteConflictError) Unwrap() error {
return e.err
}
14 changes: 8 additions & 6 deletions fox.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package fox

import (
"cmp"
"fmt"
"math"
"net"
Expand Down Expand Up @@ -56,7 +57,7 @@ type MiddlewareFunc func(next HandlerFunc) HandlerFunc
// chosen and tuned for your network configuration. This should result in the strategy never returning an error
// i.e., never failing to find a candidate for the "real" IP. Consequently, getting an error result should be treated as
// an application error, perhaps even worthy of panicking. Builtin best practices strategies can be found in the
// github.com/tigerwill90/fox/strategy package. See https://adam-p.ca/blog/2022/03/x-forwarded-for/ for more details on
// github.com/tigerwill90/fox/clientip package. See https://adam-p.ca/blog/2022/03/x-forwarded-for/ for more details on
// how to choose the right strategy for your use-case and network.
type ClientIPStrategy interface {
// ClientIP returns the "real" client IP according to the implemented strategy. It returns an error if no valid IP
Expand Down Expand Up @@ -164,7 +165,7 @@ func (fox *Router) IgnoreTrailingSlashEnabled() bool {
return fox.ignoreTrailingSlash
}

// ClientIPStrategyEnabled returns whether the router is configured with a ClientIPStrategy.
// ClientIPStrategyEnabled returns whether the router is configured with a [ClientIPStrategy].
func (fox *Router) ClientIPStrategyEnabled() bool {
_, ok := fox.ipStrategy.(noClientIPStrategy)
return !ok
Expand All @@ -191,7 +192,7 @@ func (fox *Router) Handle(method, pattern string, handler HandlerFunc, opts ...R

// MustHandle registers a new handler for the given method and route pattern. On success, it returns the newly registered [Route]
// This function is a convenience wrapper for the [Router.Handle] function and panics on error. It's perfectly safe to
// add a new handler while the router serving requests. This function is safe for concurrent use by multiple goroutines.
// add a new handler while the router is serving requests. This function is safe for concurrent use by multiple goroutines.
// To override an existing route, use [Router.Update].
func (fox *Router) MustHandle(method, pattern string, handler HandlerFunc, opts ...RouteOption) *Route {
rte, err := fox.Handle(method, pattern, handler, opts...)
Expand Down Expand Up @@ -260,13 +261,14 @@ func (fox *Router) Route(method, pattern string) *Route {

// Reverse perform a reverse lookup for the given method, host and path and return the matching registered [Route]
// (if any) along with a boolean indicating if the route was matched by adding or removing a trailing slash
// (trailing slash action recommended). This function is safe for concurrent use by multiple goroutine and while
// mutation on routes are ongoing. See also [Router.Lookup] as an alternative.
// (trailing slash action recommended). If the path is empty, a default slash is automatically added. This function
// is safe for concurrent use by multiple goroutine and while mutation on routes are ongoing. See also [Router.Lookup]
// as an alternative.
func (fox *Router) Reverse(method, host, path string) (route *Route, tsr bool) {
tree := fox.getRoot()
c := tree.ctx.Get().(*cTx)
c.resetNil()
n, tsr := tree.lookup(method, host, path, c, true)
n, tsr := tree.lookup(method, host, cmp.Or(path, "/"), c, true)
tree.ctx.Put(c)
if n != nil {
return n.route, tsr
Expand Down
4 changes: 2 additions & 2 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import (
"testing"
)

// NewTestContext returns a new Router and its associated Context, designed only for testing purpose.
// NewTestContext returns a new [Router] and its associated [Context], designed only for testing purpose.
func NewTestContext(w http.ResponseWriter, r *http.Request, opts ...GlobalOption) (*Router, Context) {
fox := New(opts...)
c := newTextContextOnly(fox, w, r)
return fox, c
}

// NewTestContextOnly returns a new Context associated with the provided Router, designed only for testing purpose.
// NewTestContextOnly returns a new [Context] designed only for testing purpose.
func NewTestContextOnly(w http.ResponseWriter, r *http.Request, opts ...GlobalOption) Context {
fox := New(opts...)
return newTextContextOnly(fox, w, r)
Expand Down
2 changes: 1 addition & 1 deletion internal/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func SplitHostZone(s string) (host, zone string) {

// SplitHostPort separates host and port. If the port is not valid, it returns
// the entire input as host, and it doesn't check the validity of the host.
// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
// Unlike [net.SplitHostPort], but per RFC 3986, it requires ports to be numeric.
func SplitHostPort(hostPort string) (host, port string) {
host = hostPort

Expand Down
9 changes: 5 additions & 4 deletions iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package fox

import (
"cmp"
"iter"
)

Expand Down Expand Up @@ -99,12 +100,12 @@ func (it Iter) Routes(methods iter.Seq[string], pattern string) iter.Seq2[string
}

// Reverse returns a range iterator over all routes registered in the routing tree that match the given host and path
// for the provided HTTP methods. Unlike Routes, which matches an exact route, Reverse is used to match an url
// for the provided HTTP methods. Unlike [Iter.Routes], which matches an exact route, Reverse is used to match an url
// (e.g., a path from an incoming request) to a registered routes in the tree. The iterator reflect a snapshot of the
// routing tree at the time [Iter] is created.
//
// If WithIgnoreTrailingSlash or WithRedirectTrailingSlash option is enabled on a route, Reverse will match it regardless
// of whether a trailing slash is present.
// If [WithIgnoreTrailingSlash] or [WithRedirectTrailingSlash] option is enabled on a route, Reverse will match it regardless
// of whether a trailing slash is present. If the path is empty, a default slash is automatically added.
//
// This function is safe for concurrent use by multiple goroutine and while mutation on routes are ongoing.
func (it Iter) Reverse(methods iter.Seq[string], host, path string) iter.Seq2[string, *Route] {
Expand All @@ -113,7 +114,7 @@ func (it Iter) Reverse(methods iter.Seq[string], host, path string) iter.Seq2[st
defer c.Close()
for method := range methods {
c.resetNil()
n, tsr := it.root.lookup(it.tree, method, host, path, c, true)
n, tsr := it.root.lookup(it.tree, method, host, cmp.Or(path, "/"), c, true)
if n != nil && (!tsr || n.route.redirectTrailingSlash || n.route.ignoreTrailingSlash) {
if !yield(method, n.route) {
return
Expand Down
4 changes: 2 additions & 2 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"time"
)

// LoggerWithHandler returns a middleware that logs request information using the provided slog.Handler.
// LoggerWithHandler returns a middleware that logs request information using the provided [slog.Handler].
// It logs details such as the remote or client IP, HTTP method, request path, status code and latency.
func LoggerWithHandler(handler slog.Handler) MiddlewareFunc {
log := slog.New(handler)
Expand Down Expand Up @@ -66,7 +66,7 @@ func LoggerWithHandler(handler slog.Handler) MiddlewareFunc {
}
}

// Logger returns a middleware that logs request information to os.Stdout or os.Stderr (for ERROR level).
// Logger returns a middleware that logs request information to [os.Stdout] or [os.Stderr] (for ERROR level).
// It logs details such as the remote or client IP, HTTP method, request path, status code and latency.
func Logger() MiddlewareFunc {
return LoggerWithHandler(slogpretty.DefaultHandler)
Expand Down
Loading

0 comments on commit defd22b

Please sign in to comment.