Change address syntax to be a URL #3
							
								
								
									
										59
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								README.md
									
									
									
									
									
								
							@@ -17,56 +17,57 @@ Just replace `http.ListenAndServe` with `anyhttp.ListenAndServe`.
 | 
			
		||||
 | 
			
		||||
Syntax
 | 
			
		||||
 | 
			
		||||
    unix/<path to socket>
 | 
			
		||||
    unix?path=<socket_path>&mode=<socket file mode>&remove_existing=<yes|no>
 | 
			
		||||
 | 
			
		||||
Examples
 | 
			
		||||
 | 
			
		||||
    unix/relative/path.sock
 | 
			
		||||
    unix//var/run/app/absolutepath.sock
 | 
			
		||||
    unix?path=relative/path.sock
 | 
			
		||||
    unix?path=/var/run/app/absolutepath.sock
 | 
			
		||||
    unix?path=/run/app.sock&mode=600&remove_existing=no
 | 
			
		||||
 | 
			
		||||
| option          | description                                    | default  |
 | 
			
		||||
|-----------------|------------------------------------------------|----------|
 | 
			
		||||
| path            | path to unix socket                            | Required |
 | 
			
		||||
| mode            | socket file mode                               | 666      |
 | 
			
		||||
| remove_existing | Whether to remove existing socket file or fail | true     |
 | 
			
		||||
 | 
			
		||||
### Systemd Socket activated fd:
 | 
			
		||||
 | 
			
		||||
Syntax
 | 
			
		||||
 | 
			
		||||
    sysd/fdidx/<fd index starting at 0>
 | 
			
		||||
    sysd/fdname/<fd name set using FileDescriptorName socket setting >
 | 
			
		||||
    sysd?idx=<fd index>&name=<fd name>&check_pid=<yes|no>&unset_env=<yes|no>&idle_timeout=<duration>
 | 
			
		||||
 | 
			
		||||
Only one of `idx` or `name` has to be set
 | 
			
		||||
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
    # First (or only) socket fd passed to app
 | 
			
		||||
    sysd/fdidx/0
 | 
			
		||||
    sysd?idx=0
 | 
			
		||||
 | 
			
		||||
    # Socket with FileDescriptorName
 | 
			
		||||
    sysd/fdname/myapp
 | 
			
		||||
    sysd?name=myapp
 | 
			
		||||
 | 
			
		||||
    # Using default name
 | 
			
		||||
    sysd/fdname/myapp.socket
 | 
			
		||||
    # Using default name and auto shutdown if no requests received in last 30 minutes
 | 
			
		||||
    sysd?name=myapp.socket&idle_timeout=30m
 | 
			
		||||
 | 
			
		||||
### TCP port
 | 
			
		||||
| option       | description                                                                                | default          |
 | 
			
		||||
|--------------|--------------------------------------------------------------------------------------------|------------------|
 | 
			
		||||
| name         | Name configured via FileDescriptorName or socket file name                                 | Required         |
 | 
			
		||||
| idx          | FD Index. Actual fd num will be 3 + idx                                                    | Required         |
 | 
			
		||||
| idle_timeout | time to wait before shutdown. [syntax][0]                                                  | no auto shutdown |
 | 
			
		||||
| check_pid    | Check process PID matches LISTEN_PID                                                       | true             |
 | 
			
		||||
| unset_env    | Unsets the LISTEN\* environment variables, so they don't get passed to any child processes | true             |
 | 
			
		||||
 | 
			
		||||
If the address is a number less than 65536, it is assumed as a port and passed
 | 
			
		||||
as `http.ListenAndServe(":<port>",...)` Anything else is directly passed to
 | 
			
		||||
`http.ListenAndServe` as well. Below examples should work
 | 
			
		||||
### TCP
 | 
			
		||||
 | 
			
		||||
If the address is not one of above, it is assumed to be tcp and passed to `http.ListenAndServe`.
 | 
			
		||||
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
    :http
 | 
			
		||||
    :8888
 | 
			
		||||
    127.0.0.1:8080
 | 
			
		||||
 | 
			
		||||
## Idle server auto shutdown
 | 
			
		||||
 | 
			
		||||
When using systemd socket activation, idle servers can be shut down to save on
 | 
			
		||||
resources.  They will be restarted with socket activation when new request
 | 
			
		||||
arrives. Quick example for the case. (Error checking skipped for brevity)
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
addrType, httpServer, done, _ := anyhttp.Serve(addr, idle.WrapHandler(nil))
 | 
			
		||||
if addrType == anyhttp.SystemdFD {
 | 
			
		||||
    idle.Wait(30 * time.Minute)
 | 
			
		||||
    httpServer.Shutdown(context.TODO())
 | 
			
		||||
}
 | 
			
		||||
<-done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
https://pkg.go.dev/go.balki.me/anyhttp
 | 
			
		||||
@@ -75,3 +76,5 @@ https://pkg.go.dev/go.balki.me/anyhttp
 | 
			
		||||
 | 
			
		||||
  * https://gist.github.com/teknoraver/5ffacb8757330715bcbcc90e6d46ac74#file-unixhttpd-go
 | 
			
		||||
  * https://github.com/coreos/go-systemd/tree/main/activation
 | 
			
		||||
 | 
			
		||||
[0]: https://pkg.go.dev/time#ParseDuration
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										194
									
								
								anyhttp.go
									
									
									
									
									
								
							
							
						
						
									
										194
									
								
								anyhttp.go
									
									
									
									
									
								
							@@ -2,16 +2,21 @@
 | 
			
		||||
package anyhttp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"go.balki.me/anyhttp/idle"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AddressType of the address passed
 | 
			
		||||
@@ -97,6 +102,8 @@ type SysdConfig struct {
 | 
			
		||||
	CheckPID bool
 | 
			
		||||
	// Unsets the LISTEN* environment variables, so they don't get passed to any child processes
 | 
			
		||||
	UnsetEnv bool
 | 
			
		||||
	// Shutdown http server if no requests received for below timeout
 | 
			
		||||
	IdleTimeout *time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultSysdConfig has the default values for SysdConfig
 | 
			
		||||
@@ -196,65 +203,57 @@ func (s *SysdConfig) GetListener() (net.Listener, error) {
 | 
			
		||||
	return nil, errors.New("neither FDIndex nor FDName set")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetListener gets a unix or systemd socket listener
 | 
			
		||||
func GetListener(addr string) (AddressType, net.Listener, error) {
 | 
			
		||||
	if strings.HasPrefix(addr, "unix/") {
 | 
			
		||||
		usc := NewUnixSocketConfig(strings.TrimPrefix(addr, "unix/"))
 | 
			
		||||
		l, err := usc.GetListener()
 | 
			
		||||
		return UnixSocket, l, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.HasPrefix(addr, "sysd/fdidx/") {
 | 
			
		||||
		idx, err := strconv.Atoi(strings.TrimPrefix(addr, "sysd/fdidx/"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return Unknown, nil, fmt.Errorf("invalid fdidx, addr:%q err: %w", addr, err)
 | 
			
		||||
		}
 | 
			
		||||
		sysdc := NewSysDConfigWithFDIdx(idx)
 | 
			
		||||
		l, err := sysdc.GetListener()
 | 
			
		||||
		return SystemdFD, l, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.HasPrefix(addr, "sysd/fdname/") {
 | 
			
		||||
		sysdc := NewSysDConfigWithFDName(strings.TrimPrefix(addr, "sysd/fdname/"))
 | 
			
		||||
		l, err := sysdc.GetListener()
 | 
			
		||||
		return SystemdFD, l, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if port, err := strconv.Atoi(addr); err == nil {
 | 
			
		||||
		if port > 0 && port < 65536 {
 | 
			
		||||
			addr = fmt.Sprintf(":%v", port)
 | 
			
		||||
		} else {
 | 
			
		||||
			return Unknown, nil, fmt.Errorf("invalid port: %v", port)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if addr == "" {
 | 
			
		||||
		addr = ":http"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l, err := net.Listen("tcp", addr)
 | 
			
		||||
	return TCP, l, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Serve creates and serve a http server.
 | 
			
		||||
func Serve(addr string, h http.Handler) (AddressType, *http.Server, <-chan error, error) {
 | 
			
		||||
	addrType, listener, err := GetListener(addr)
 | 
			
		||||
func Serve(addr string, h http.Handler) (addrType AddressType, srv *http.Server, idler idle.Idler, done <-chan error, err error) {
 | 
			
		||||
	addrType, usc, sysc, err := parseAddress(addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return addrType, nil, nil, err
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	srv := &http.Server{Handler: h}
 | 
			
		||||
	done := make(chan error)
 | 
			
		||||
	go func() {
 | 
			
		||||
		done <- srv.Serve(listener)
 | 
			
		||||
		close(done)
 | 
			
		||||
 | 
			
		||||
	listener, err := func() (net.Listener, error) {
 | 
			
		||||
		if usc != nil {
 | 
			
		||||
			return usc.GetListener()
 | 
			
		||||
		} else if sysc != nil {
 | 
			
		||||
			return sysc.GetListener()
 | 
			
		||||
		}
 | 
			
		||||
		if addr == "" {
 | 
			
		||||
			addr = ":http"
 | 
			
		||||
		}
 | 
			
		||||
		return net.Listen("tcp", addr)
 | 
			
		||||
	}()
 | 
			
		||||
	return addrType, srv, done, nil
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	errChan := make(chan error)
 | 
			
		||||
	done = errChan
 | 
			
		||||
	if addrType == SystemdFD && sysc.IdleTimeout != nil {
 | 
			
		||||
		idler = idle.CreateIdler(*sysc.IdleTimeout)
 | 
			
		||||
		srv = &http.Server{Handler: idle.WrapIdlerHandler(idler, h)}
 | 
			
		||||
		waitErrChan := make(chan error)
 | 
			
		||||
		go func() {
 | 
			
		||||
			waitErrChan <- srv.Serve(listener)
 | 
			
		||||
		}()
 | 
			
		||||
		go func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case err := <-waitErrChan:
 | 
			
		||||
				errChan <- err
 | 
			
		||||
			case <-idler.Chan():
 | 
			
		||||
				errChan <- srv.Shutdown(context.TODO())
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	} else {
 | 
			
		||||
		srv = &http.Server{Handler: h}
 | 
			
		||||
		go func() {
 | 
			
		||||
			errChan <- srv.Serve(listener)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListenAndServe is the drop-in replacement for `http.ListenAndServe`.
 | 
			
		||||
// Supports unix and systemd sockets in addition
 | 
			
		||||
func ListenAndServe(addr string, h http.Handler) error {
 | 
			
		||||
	_, _, done, err := Serve(addr, h)
 | 
			
		||||
	_, _, _, done, err := Serve(addr, h)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -267,3 +266,98 @@ func UnsetSystemdListenVars() {
 | 
			
		||||
	_ = os.Unsetenv("LISTEN_FDS")
 | 
			
		||||
	_ = os.Unsetenv("LISTEN_FDNAMES")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseAddress(addr string) (addrType AddressType, usc *UnixSocketConfig, sysc *SysdConfig, err error) {
 | 
			
		||||
	usc = nil
 | 
			
		||||
	sysc = nil
 | 
			
		||||
	err = nil
 | 
			
		||||
	u, err := url.Parse(addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return TCP, nil, nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	if u.Path == "unix" {
 | 
			
		||||
		duc := DefaultUnixSocketConfig
 | 
			
		||||
		usc = &duc
 | 
			
		||||
		addrType = UnixSocket
 | 
			
		||||
		for key, val := range u.Query() {
 | 
			
		||||
			if len(val) != 1 {
 | 
			
		||||
				err = fmt.Errorf("unix socket address error. Multiple %v found: %v", key, val)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if key == "path" {
 | 
			
		||||
				usc.SocketPath = val[0]
 | 
			
		||||
			} else if key == "mode" {
 | 
			
		||||
				if _, serr := fmt.Sscanf(val[0], "%o", &usc.SocketMode); serr != nil {
 | 
			
		||||
					err = fmt.Errorf("unix socket address error. Bad mode: %v, err: %w", val, serr)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else if key == "remove_existing" {
 | 
			
		||||
				if removeExisting, berr := strconv.ParseBool(val[0]); berr == nil {
 | 
			
		||||
					usc.RemoveExisting = removeExisting
 | 
			
		||||
				} else {
 | 
			
		||||
					err = fmt.Errorf("unix socket address error. Bad remove_existing: %v, err: %w", val, berr)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				err = fmt.Errorf("unix socket address error. Bad option; key: %v, val: %v", key, val)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if usc.SocketPath == "" {
 | 
			
		||||
			err = fmt.Errorf("unix socket address error. Missing path; addr: %v", addr)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	} else if u.Path == "sysd" {
 | 
			
		||||
		dsc := DefaultSysdConfig
 | 
			
		||||
		sysc = &dsc
 | 
			
		||||
		addrType = SystemdFD
 | 
			
		||||
		for key, val := range u.Query() {
 | 
			
		||||
			if len(val) != 1 {
 | 
			
		||||
				err = fmt.Errorf("systemd socket fd address error. Multiple %v found: %v", key, val)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if key == "name" {
 | 
			
		||||
				sysc.FDName = &val[0]
 | 
			
		||||
			} else if key == "idx" {
 | 
			
		||||
				if idx, ierr := strconv.Atoi(val[0]); ierr == nil {
 | 
			
		||||
					sysc.FDIndex = &idx
 | 
			
		||||
				} else {
 | 
			
		||||
					err = fmt.Errorf("systemd socket fd address error. Bad idx: %v, err: %w", val, ierr)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else if key == "check_pid" {
 | 
			
		||||
				if checkPID, berr := strconv.ParseBool(val[0]); berr == nil {
 | 
			
		||||
					sysc.CheckPID = checkPID
 | 
			
		||||
				} else {
 | 
			
		||||
					err = fmt.Errorf("systemd socket fd address error. Bad check_pid: %v, err: %w", val, berr)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else if key == "unset_env" {
 | 
			
		||||
				if unsetEnv, berr := strconv.ParseBool(val[0]); berr == nil {
 | 
			
		||||
					sysc.UnsetEnv = unsetEnv
 | 
			
		||||
				} else {
 | 
			
		||||
					err = fmt.Errorf("systemd socket fd address error. Bad unset_env: %v, err: %w", val, berr)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else if key == "idle_timeout" {
 | 
			
		||||
				if timeout, terr := time.ParseDuration(val[0]); terr == nil {
 | 
			
		||||
					sysc.IdleTimeout = &timeout
 | 
			
		||||
				} else {
 | 
			
		||||
					err = fmt.Errorf("systemd socket fd address error. Bad idle_timeout: %v, err: %w", val, terr)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				err = fmt.Errorf("systemd socket fd address error. Bad option; key: %v, val: %v", key, val)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (sysc.FDIndex == nil) == (sysc.FDName == nil) {
 | 
			
		||||
			err = fmt.Errorf("systemd socket fd address error. Exactly only one of name and idx has to be set. name: %v, idx: %v", sysc.FDName, sysc.FDIndex)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Just assume as TCP address
 | 
			
		||||
		return TCP, nil, nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										132
									
								
								anyhttp_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								anyhttp_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
package anyhttp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_parseAddress(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string // description of this test case
 | 
			
		||||
		// Named input parameters for target function.
 | 
			
		||||
		addr         string
 | 
			
		||||
		wantAddrType AddressType
 | 
			
		||||
		wantUsc      *UnixSocketConfig
 | 
			
		||||
		wantSysc     *SysdConfig
 | 
			
		||||
		wantErr      bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:         "tcp port",
 | 
			
		||||
			addr:         ":8080",
 | 
			
		||||
			wantAddrType: TCP,
 | 
			
		||||
			wantUsc:      nil,
 | 
			
		||||
			wantSysc:     nil,
 | 
			
		||||
			wantErr:      false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "unix address",
 | 
			
		||||
			addr:         "unix?path=/run/foo.sock&mode=660",
 | 
			
		||||
			wantAddrType: UnixSocket,
 | 
			
		||||
			wantUsc: &UnixSocketConfig{
 | 
			
		||||
				SocketPath:     "/run/foo.sock",
 | 
			
		||||
				SocketMode:     0660,
 | 
			
		||||
				RemoveExisting: true,
 | 
			
		||||
			},
 | 
			
		||||
			wantSysc: nil,
 | 
			
		||||
			wantErr:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "systemd address",
 | 
			
		||||
			addr:         "sysd?name=foo.socket",
 | 
			
		||||
			wantAddrType: SystemdFD,
 | 
			
		||||
			wantUsc:      nil,
 | 
			
		||||
			wantSysc: &SysdConfig{
 | 
			
		||||
				FDIndex:     nil,
 | 
			
		||||
				FDName:      ptr("foo.socket"),
 | 
			
		||||
				CheckPID:    true,
 | 
			
		||||
				UnsetEnv:    true,
 | 
			
		||||
				IdleTimeout: nil,
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "systemd address with index",
 | 
			
		||||
			addr:         "sysd?idx=0&idle_timeout=30m",
 | 
			
		||||
			wantAddrType: SystemdFD,
 | 
			
		||||
			wantUsc:      nil,
 | 
			
		||||
			wantSysc: &SysdConfig{
 | 
			
		||||
				FDIndex:     ptr(0),
 | 
			
		||||
				FDName:      nil,
 | 
			
		||||
				CheckPID:    true,
 | 
			
		||||
				UnsetEnv:    true,
 | 
			
		||||
				IdleTimeout: ptr(30 * time.Minute),
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "systemd address. Bad example",
 | 
			
		||||
			addr:         "sysd?idx=0&idle_timeout=30m&name=foo",
 | 
			
		||||
			wantAddrType: SystemdFD,
 | 
			
		||||
			wantUsc:      nil,
 | 
			
		||||
			wantSysc:     nil,
 | 
			
		||||
			wantErr:      true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			gotAddrType, gotUsc, gotSysc, gotErr := parseAddress(tt.addr)
 | 
			
		||||
			if gotErr != nil {
 | 
			
		||||
				if !tt.wantErr {
 | 
			
		||||
					t.Errorf("parseAddress() failed: %v", gotErr)
 | 
			
		||||
				}
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if tt.wantErr {
 | 
			
		||||
				t.Fatal("parseAddress() succeeded unexpectedly")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if gotAddrType != tt.wantAddrType {
 | 
			
		||||
				t.Errorf("parseAddress() addrType = %v, want %v", gotAddrType, tt.wantAddrType)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !check(gotUsc, tt.wantUsc) {
 | 
			
		||||
				t.Errorf("parseAddress() Usc = %v, want %v", gotUsc, tt.wantUsc)
 | 
			
		||||
			}
 | 
			
		||||
			if !check(gotSysc, tt.wantSysc) {
 | 
			
		||||
				if (gotSysc == nil || tt.wantSysc == nil) ||
 | 
			
		||||
					!(check(gotSysc.FDIndex, tt.wantSysc.FDIndex) &&
 | 
			
		||||
						check(gotSysc.FDName, tt.wantSysc.FDName) &&
 | 
			
		||||
						check(gotSysc.IdleTimeout, tt.wantSysc.IdleTimeout)) {
 | 
			
		||||
					t.Errorf("parseAddress() Sysc = %v, want %v", asJSON(gotSysc), asJSON(tt.wantSysc))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helpers
 | 
			
		||||
 | 
			
		||||
// print value instead of pointer
 | 
			
		||||
func asJSON[T any](val T) string {
 | 
			
		||||
	op, err := json.Marshal(val)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err.Error()
 | 
			
		||||
	}
 | 
			
		||||
	return string(op)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ptr[T any](val T) *T {
 | 
			
		||||
	return &val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// nil safe equal check
 | 
			
		||||
func check[T comparable](got, want *T) bool {
 | 
			
		||||
	if (got == nil) != (want == nil) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if got == nil {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return *got == *want
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user