From f2e90bef45f6253ddd7b49b3ce2706e1d0e36b65 Mon Sep 17 00:00:00 2001 From: bejaratommy Date: Mon, 20 Apr 2026 15:25:12 +0000 Subject: [PATCH] feat: add joinHostPort and splitHostPort network functions Fixes #472. Adds two new template functions that wrap Go's standard library net.JoinHostPort and net.SplitHostPort: - joinHostPort(host, port): combines host and port into host:port, correctly bracketing IPv6 addresses as [host]:port. - splitHostPort(hostport): splits host:port into a map{host, port}, handling IPv6 bracket notation transparently. --- CHANGELOG.md | 6 ++++++ docs/network.md | 21 +++++++++++++++++++++ functions.go | 4 ++++ network.go | 9 +++++++++ network_test.go | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ef766a..4c035c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Release 3.4.0 (unreleased) + +### Added + +- #472: Added `joinHostPort` and `splitHostPort` network functions + ## Release 3.3.0 (2024-08-29) ### Added diff --git a/docs/network.md b/docs/network.md index ea6cfd7f..65cbfd48 100644 --- a/docs/network.md +++ b/docs/network.md @@ -9,3 +9,24 @@ The `getHostByName` receives a domain name and returns the ip address. ``` getHostByName "www.google.com" would return the corresponding ip address of www.google.com ``` + +## joinHostPort + +`joinHostPort` combines a host and port into a network address of the form `host:port`. For IPv6 addresses the host is enclosed in square brackets: `[host]:port`. This is the inverse of `splitHostPort`. + +``` +joinHostPort "192.0.2.1" "80" -> "192.0.2.1:80" +joinHostPort "2001:db8::1" "80" -> "[2001:db8::1]:80" +joinHostPort "example.com" "443" -> "example.com:443" +``` + +## splitHostPort + +`splitHostPort` splits a network address of the form `host:port` or `[host]:port` into its host and port components, returning a map with keys `host` and `port`. This is the inverse of `joinHostPort`. + +``` +{{- with splitHostPort "192.0.2.1:80" }} +host: {{ .host }} +port: {{ .port }} +{{- end }} +``` diff --git a/functions.go b/functions.go index cda47d26..b99f832a 100644 --- a/functions.go +++ b/functions.go @@ -91,6 +91,8 @@ var nonhermeticFunctions = []string{ // Network "getHostByName", + "joinHostPort", + "splitHostPort", } var genericMap = map[string]interface{}{ @@ -272,6 +274,8 @@ var genericMap = map[string]interface{}{ // Network: "getHostByName": getHostByName, + "joinHostPort": joinHostPort, + "splitHostPort": splitHostPort, // Paths: "base": path.Base, diff --git a/network.go b/network.go index 108d78a9..5fe1eb31 100644 --- a/network.go +++ b/network.go @@ -10,3 +10,12 @@ func getHostByName(name string) string { //TODO: add error handing when release v3 comes out return addrs[rand.Intn(len(addrs))] } + +func joinHostPort(host, port string) string { + return net.JoinHostPort(host, port) +} + +func splitHostPort(hostport string) map[string]string { + host, port, _ := net.SplitHostPort(hostport) + return map[string]string{"host": host, "port": port} +} diff --git a/network_test.go b/network_test.go index 9c153f0a..91e5213c 100644 --- a/network_test.go +++ b/network_test.go @@ -16,3 +16,36 @@ func TestGetHostByName(t *testing.T) { assert.NotNil(t, ip) assert.NotEmpty(t, ip) } + +func TestJoinHostPort(t *testing.T) { + tests := []struct { + tpl string + expected string + }{ + {`{{joinHostPort "192.0.2.1" "80"}}`, "192.0.2.1:80"}, + {`{{joinHostPort "2001:db8::1" "80"}}`, "[2001:db8::1]:80"}, + {`{{joinHostPort "example.com" "443"}}`, "example.com:443"}, + } + for _, tt := range tests { + result, err := runRaw(tt.tpl, nil) + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } +} + +func TestSplitHostPort(t *testing.T) { + tests := []struct { + tpl string + expectedHost string + expectedPort string + }{ + {`{{with splitHostPort "192.0.2.1:80"}}{{.host}}/{{.port}}{{end}}`, "192.0.2.1", "80"}, + {`{{with splitHostPort "[2001:db8::1]:80"}}{{.host}}/{{.port}}{{end}}`, "2001:db8::1", "80"}, + {`{{with splitHostPort "example.com:443"}}{{.host}}/{{.port}}{{end}}`, "example.com", "443"}, + } + for _, tt := range tests { + result, err := runRaw(tt.tpl, nil) + assert.NoError(t, err) + assert.Equal(t, tt.expectedHost+"/"+tt.expectedPort, result) + } +}