Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.18.0"
".": "0.19.0"
}
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 52
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-c6da5deb317c83b7a10434593eb22ec7cb27009aba0b92efaefbbe21884054ad.yml
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/hypeman-75aa32bfceac1a349267baf08a13df9d8dc37fd07525f45675d064654eac0e1f.yml
openapi_spec_hash: ff73a0e1f7a8bd5a5d1ae38d994bb9cd
config_hash: ed668fae8826ff533f38df16c9664f44
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.19.0 (2026-05-08)

Full Changelog: [v0.18.0...v0.19.0](https://github.com/kernel/hypeman-go/compare/v0.18.0...v0.19.0)

### Features

* **go:** add default http client with timeout ([4562f58](https://github.com/kernel/hypeman-go/commit/4562f586a6f26caebf5908b9e899cd578a3af4c1))
* support setting headers via env ([3b6d6cd](https://github.com/kernel/hypeman-go/commit/3b6d6cd3ff96bef7d0f01e58116ca74eef5c8e1c))


### Bug Fixes

* **go:** avoid panic when http.DefaultTransport is wrapped ([90abdb2](https://github.com/kernel/hypeman-go/commit/90abdb2fe804de224236e5a0556b5675afb49c09))


### Chores

* avoid embedding reflect.Type for dead code elimination ([768a2a1](https://github.com/kernel/hypeman-go/commit/768a2a12c13728f05946bd4c0f24e5f3a90f8620))
* **internal:** more robust bootstrap script ([53ef042](https://github.com/kernel/hypeman-go/commit/53ef042e66ce7e050877be0e08613c41e66026de))
* redact api-key headers in debug logs ([0f70339](https://github.com/kernel/hypeman-go/commit/0f703395ab4135be91ac30c47ed94bf2e61989b5))

## 0.18.0 (2026-04-17)

Full Changelog: [v0.17.0...v0.18.0](https://github.com/kernel/hypeman-go/compare/v0.17.0...v0.18.0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/kernel/hypeman-go@v0.18.0'
go get -u 'github.com/kernel/hypeman-go@v0.19.0'
```

<!-- x-release-please-end -->
Expand Down
11 changes: 10 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"slices"
"strings"

"github.com/kernel/hypeman-go/internal/requestconfig"
"github.com/kernel/hypeman-go/option"
Expand All @@ -31,13 +32,21 @@ type Client struct {
// DefaultClientOptions read from the environment (HYPEMAN_API_KEY,
// HYPEMAN_BASE_URL). This should be used to initialize new clients.
func DefaultClientOptions() []option.RequestOption {
defaults := []option.RequestOption{option.WithEnvironmentProduction()}
defaults := []option.RequestOption{option.WithHTTPClient(defaultHTTPClient()), option.WithEnvironmentProduction()}
if o, ok := os.LookupEnv("HYPEMAN_BASE_URL"); ok {
defaults = append(defaults, option.WithBaseURL(o))
}
if o, ok := os.LookupEnv("HYPEMAN_API_KEY"); ok {
defaults = append(defaults, option.WithAPIKey(o))
}
if o, ok := os.LookupEnv("HYPEMAN_CUSTOM_HEADERS"); ok {
for _, line := range strings.Split(o, "\n") {
colon := strings.Index(line, ":")
if colon >= 0 {
defaults = append(defaults, option.WithHeader(strings.TrimSpace(line[:colon]), strings.TrimSpace(line[colon+1:])))
}
}
}
return defaults
}

Expand Down
30 changes: 30 additions & 0 deletions default_http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

package hypeman

import (
"net/http"
"time"
)

// defaultResponseHeaderTimeout bounds the time between a fully written request
// and the server's response headers. It does not apply to the response body,
// so long-running streams are unaffected. Without this, a server that accepts
// the connection but never responds would hang the request indefinitely.
const defaultResponseHeaderTimeout = 10 * time.Minute

// defaultHTTPClient returns an [*http.Client] used when the caller does not
// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the
// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout]
// is set so stuck connections fail fast instead of compounding across retries.
// If [http.DefaultTransport] has been wrapped (for example by otelhttp for
// distributed tracing), the wrapping is preserved and the header timeout is
// skipped.
func defaultHTTPClient() *http.Client {
if t, ok := http.DefaultTransport.(*http.Transport); ok {
t = t.Clone()
t.ResponseHeaderTimeout = defaultResponseHeaderTimeout
return &http.Client{Transport: t}
}
return &http.Client{Transport: http.DefaultTransport}
}
4 changes: 2 additions & 2 deletions internal/apiform/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type encoderField struct {
}

type encoderEntry struct {
reflect.Type
typ reflect.Type
dateFormat string
arrayFmt string
root bool
Expand All @@ -76,7 +76,7 @@ func (e *encoder) marshal(value any, writer *multipart.Writer) error {

func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
typ: t,
dateFormat: e.dateFormat,
arrayFmt: e.arrayFmt,
root: e.root,
Expand Down
4 changes: 2 additions & 2 deletions internal/apijson/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type decoderField struct {
}

type decoderEntry struct {
reflect.Type
typ reflect.Type
dateFormat string
root bool
}
Expand Down Expand Up @@ -108,7 +108,7 @@ func (d *decoderBuilder) unmarshalWithExactness(raw []byte, to any) (exactness,

func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc {
entry := decoderEntry{
Type: t,
typ: t,
dateFormat: d.dateFormat,
root: d.root,
}
Expand Down
4 changes: 2 additions & 2 deletions internal/apijson/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type encoderField struct {
}

type encoderEntry struct {
reflect.Type
typ reflect.Type
dateFormat string
root bool
}
Expand All @@ -63,7 +63,7 @@ func (e *encoder) marshal(value any) ([]byte, error) {

func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
typ: t,
dateFormat: e.dateFormat,
root: e.root,
}
Expand Down
4 changes: 2 additions & 2 deletions internal/apiquery/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type encoderField struct {
}

type encoderEntry struct {
reflect.Type
typ reflect.Type
dateFormat string
root bool
settings QuerySettings
Expand All @@ -42,7 +42,7 @@ type Pair struct {

func (e *encoder) typeEncoder(t reflect.Type) encoderFunc {
entry := encoderEntry{
Type: t,
typ: t,
dateFormat: e.dateFormat,
root: e.root,
settings: e.settings,
Expand Down
2 changes: 1 addition & 1 deletion internal/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package internal

const PackageVersion = "0.18.0" // x-release-please-version
const PackageVersion = "0.19.0" // x-release-please-version
46 changes: 44 additions & 2 deletions option/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"net/http/httputil"
)

// sensitiveLogHeaders are redacted before request and response content is
// written to the debug logger.
var sensitiveLogHeaders = []string{"authorization", "api-key", "x-api-key", "cookie", "set-cookie"}

// WithDebugLog logs the HTTP request and response content.
// If the logger parameter is nil, it uses the default logger.
//
Expand All @@ -20,7 +24,7 @@ func WithDebugLog(logger *log.Logger) RequestOption {
logger = log.Default()
}

if reqBytes, err := httputil.DumpRequest(req, true); err == nil {
if reqBytes, err := dumpRedactedRequest(req); err == nil {
logger.Printf("Request Content:\n%s\n", reqBytes)
}

Expand All @@ -29,10 +33,48 @@ func WithDebugLog(logger *log.Logger) RequestOption {
return resp, err
}

if respBytes, err := httputil.DumpResponse(resp, true); err == nil {
if respBytes, err := dumpRedactedResponse(resp); err == nil {
logger.Printf("Response Content:\n%s\n", respBytes)
}

return resp, err
})
}

// dumpRedactedRequest dumps req with sensitive headers replaced. The
// original headers are restored via defer so a panic in DumpRequest cannot
// leak the placeholder map into the live request sent downstream.
func dumpRedactedRequest(req *http.Request) ([]byte, error) {
origHeaders := req.Header
req.Header = redactDebugHeaders(origHeaders)
defer func() { req.Header = origHeaders }()
return httputil.DumpRequest(req, true)
}

func dumpRedactedResponse(resp *http.Response) ([]byte, error) {
origHeaders := resp.Header
resp.Header = redactDebugHeaders(origHeaders)
defer func() { resp.Header = origHeaders }()
return httputil.DumpResponse(resp, true)
}

func redactDebugHeaders(headers http.Header) http.Header {
var redacted http.Header
for _, name := range sensitiveLogHeaders {
values := headers.Values(name)
if len(values) == 0 {
continue
}
if redacted == nil {
redacted = headers.Clone()
}
redacted.Del(name)
for range values {
redacted.Add(name, "***")
}
}
if redacted == nil {
return headers
}
return redacted
}
2 changes: 1 addition & 1 deletion scripts/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e

cd "$(dirname "$0")/.."

if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
echo -n "==> Install Homebrew dependencies? (y/N): "
read -r response
Expand Down
Loading