Skip to content
Merged
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
101 changes: 101 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.0] - 2026-06-16

First release of the dexpace Go SDK: a transport-agnostic HTTP-client toolkit
built on `net/http`, with zero third-party runtime dependencies. Requests and
responses are standard `*http.Request` / `*http.Response` values, and the
transport seam is satisfied by `*http.Client`.

### Added

#### Pipeline and client

- A composable policy pipeline (`pipeline`) that runs an ordered chain of
policies over an `*http.Request` and terminates in a transport. Policies can
inspect or mutate a request, continue the chain, or short-circuit, and can
replay the request body across attempts.
- A default `net/http`-backed transport (`transport`) that terminates a
pipeline, cloned from `http.DefaultTransport` with larger idle-connection
limits.
- An umbrella `Client` configured through functional options, wiring the default
policy stack with sensible ordering.

#### Resilience

- Retry policy (`retry`) with exponential backoff and full jitter, support for
the `Retry-After` header, and automatic request-body rewind so retried
requests resend their payload.
- Idempotency-key stamping (`idempotency`) that adds an `Idempotency-Key` header
to POST requests by default, and can be disabled per client.

#### Authentication

All credential policies require an HTTPS transport and refuse to attach
credentials over plaintext.

- Bearer-token authentication (`auth`) driven by a pluggable `TokenCredential`,
with a token cache that can be shared across clients.
- HTTP Basic authentication.
- API-key authentication via a configurable header.
- HTTP Digest authentication (RFC 7616, MD5/SHA-256, `qop=auth`).

#### Errors, logging, and observability

- An opt-in typed error model (`httperr`): a `ResponseError` for non-success
responses (buffering and rewinding the response body) and a `TransportError`
for transport failures.
- Structured request/response logging (`logging`) via `log/slog`.
- Vendor-neutral tracing and metrics SPIs (`instrumentation`) with no-op
defaults, plus tracing and metrics policies. The tracing policy emits a span
per request and injects a W3C `traceparent` header; the metrics policy records
request duration and in-flight requests.
- Default-deny URL redaction (`redact`) shared by logs, traces, and errors:
userinfo is stripped and query values are redacted unless explicitly
allowlisted.

#### Value types and helpers

- Immutable media-type value (`mediatype`) with parsing and common constants.
- Canonical HTTP header-name constants (`header`).
- Conditional- and range-request value types (`conditions`): ETag, Range, and
Conditions.
- A serialization seam (`serde`) with a JSON default and a `Tristate` type for
distinguishing absent, null, and present fields in PATCH payloads.
- A layered settings resolver (`config`) that sources values from explicit
overrides, then `DEXPACE_*` environment variables, then defaults.

#### Streaming and bodies

- Server-Sent Events (`sse`): a WHATWG-compliant `text/event-stream` parser, a
reconnecting stream that replays `Last-Event-ID` after an interruption, and
`Client.EventStream` to run a stream through the pipeline.
- JSON Lines / NDJSON streaming decoder (`jsonl`) exposed as a generic
`iter.Seq2`.
- Multipart `multipart/form-data` request-body builder (`formdata`) with
replayable bodies and file uploads.
- Generic pagination (`pagination`) as `iter.Seq2` range-over-func iterators,
with cursor/token, page-number, and RFC 8288 Link-header strategies and a
page cap.

#### Webhooks

- Inbound webhook signature verification (`webhook`): constant-time HMAC-SHA256
comparison with a configurable timestamp-tolerance window for replay
protection.

### Requirements

- Go 1.26 or newer.
- Zero third-party runtime dependencies; only the standard library is imported
by non-test code.

[Unreleased]: https://github.com/dexpace/go-sdk/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/dexpace/go-sdk/releases/tag/v0.1.0
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ The SDK ships **zero third-party runtime dependencies**; only the standard
library is imported by non-test code. See [`CONTRIBUTING.md`](./CONTRIBUTING.md)
for conventions and [`CLAUDE.md`](./CLAUDE.md) for the enforced rules.

See [CHANGELOG.md](./CHANGELOG.md) for release notes.

## License

MIT — see [LICENSE](./LICENSE).
30 changes: 30 additions & 0 deletions jsonl/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2026 dexpace and Omar Aljarrah.
// Licensed under the MIT License. See LICENSE in the repository root for details.

package jsonl_test

import (
"fmt"
"strings"

"github.com/dexpace/go-sdk/jsonl"
)

func ExampleDecode() {
type point struct {
N int `json:"n"`
}

stream := strings.NewReader("{\"n\":1}\n{\"n\":2}\n")

for p, err := range jsonl.Decode[point](stream) {
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(p.N)
}
// Output:
// 1
// 2
}
39 changes: 39 additions & 0 deletions pagination/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2026 dexpace and Omar Aljarrah.
// Licensed under the MIT License. See LICENSE in the repository root for details.

package pagination_test

import (
"context"
"fmt"

"github.com/dexpace/go-sdk/pagination"
)

func ExamplePager_Items() {
// fetch returns two fixed in-memory pages. The first page points to the
// second via its NextToken; the second has no NextToken, ending iteration.
fetch := func(_ context.Context, token string) (pagination.Page[string], error) {
switch token {
case "":
return pagination.Page[string]{Items: []string{"a", "b"}, NextToken: "page-2"}, nil
default:
return pagination.Page[string]{Items: []string{"c"}}, nil
}
}

pager := pagination.New(fetch)

var items []string
for item, err := range pager.Items(context.Background()) {
if err != nil {
fmt.Println("error:", err)
return
}
items = append(items, item)
}

fmt.Println(items)
// Output:
// [a b c]
}
26 changes: 26 additions & 0 deletions sse/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2026 dexpace and Omar Aljarrah.
// Licensed under the MIT License. See LICENSE in the repository root for details.

package sse_test

import (
"fmt"
"strings"

"github.com/dexpace/go-sdk/sse"
)

func ExampleParse() {
stream := strings.NewReader("data: hello\n\ndata: world\n\n")

for event, err := range sse.Parse(stream) {
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(event.Data)
}
// Output:
// hello
// world
}
26 changes: 26 additions & 0 deletions webhook/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2026 dexpace and Omar Aljarrah.
// Licensed under the MIT License. See LICENSE in the repository root for details.

package webhook_test

import (
"fmt"

"github.com/dexpace/go-sdk/webhook"
)

func ExampleVerifier_Verify() {
secret := []byte("shared-secret")
payload := []byte(`{"event":"ping"}`)

// The sender computes the signature over the payload with the shared secret.
signature := webhook.Sign(secret, payload)

// The receiver verifies the payload against the signature.
verifier := webhook.NewVerifier(secret)
err := verifier.Verify(payload, signature)

fmt.Println(err == nil)
// Output:
// true
}
Loading