hook0

package module
v2.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 21 Imported by: 0

README

Hook0 Go SDK

A webhook SDK whose go.mod has no require at all


How the Hook0 Go SDK sits between your application and your users

Go Reference License


What is this?

The Go SDK for Hook0, the open source Webhooks-as-a-Service platform for SaaS applications. It sends events, declares the event types your application uses, verifies the signature of a webhook you receive, and calls every operation the API declares through generated, documented types.

go.mod carries no require. The SDK reaches the network with net/http, verifies signatures with crypto/hmac, and reads what the API answers with encoding/json. Adding it to a project drags nothing else in, and the CI job fails the day that stops being true.

Features

  • Send events - under an ID the client mints, so a retry cannot duplicate one
  • Declare event types - upsert the ones your application emits, in one call
  • Verify signatures - HMAC-SHA256 over a bilateral clock window
  • The whole API, typed - one type per schema, one error value per problem, one method per operation
  • Bounded everywhere - attempts, backoff, timeouts, payload and answer, all yours to set
  • Zero dependencies - the standard library and nothing else, enforced in CI

Quick Start

1. Install
go get github.com/hook0/hook0-go/v2

The package is hook0 while the module path ends in go, so import it under its own name if your tooling does not fill that in: import hook0 "github.com/hook0/hook0-go/v2". Go 1.25.13 or later, which is the floor go.mod declares.

2. Send an event
package main

import (
	"context"
	"log"

	hook0 "github.com/hook0/hook0-go/v2"
)

func main() {
	client := hook0.NewClient(
		"https://app.hook0.com/api/v1",
		applicationId,
		token,
		hook0.DefaultOptions(),
	)

	eventId, err := client.SendEvent(context.Background(), hook0.Event{
		EventType:          "billing.invoice.paid",
		Payload:            `{"invoice": "in_123"}`,
		PayloadContentType: "application/json",
		Labels:             map[string]string{"environment": "production"},
	})
	if err != nil {
		log.Fatalf("event not sent: %v", err)
	}

	log.Printf("ingested as %s", eventId)
}

Configuration

Every bound one send is held to is yours to set, and every one has a default.

Bound Default What it holds back
MaxAttempts 4 requests one send issues, capped at 16 whatever a policy says
InitialBackoff 100 ms the ceiling of the wait before the first retry
MaxBackoff 2 s the ceiling no single wait between attempts crosses
MaxTotalDelay 5 s the budget every wait of one send shares
RequestTimeout 10 s how long one attempt is given
MaxPayloadBytes 1 MiB the payload, refused before a socket is opened
MaxResponseBytes 8 MiB the body read off a socket
MaxHeadBytes 16 KiB the head of an answer, every line taken together
MaxResponseHeaders 64 header lines one answer may carry
MaxHeaderBytes 64 KiB one header line

Every default comes from clients/conformance/bounds.json, the corpus every Hook0 SDK reads. A number changed there fails every SDK still carrying the old one, so no two of them can bound different things.

The last three bound what the other end may cost you. A server that is broken or hostile can otherwise stream a head, a header or a body of any length into your process.

options := hook0.DefaultOptions()
options.RetryPolicy = hook0.RetryPolicy{
	MaxAttempts:    4,
	InitialBackoff: 100 * time.Millisecond,
	MaxBackoff:     2 * time.Second,
	MaxTotalDelay:  5 * time.Second,
}
options.RequestTimeout = 10 * time.Second
options.MaxPayloadBytes = 1024 * 1024
options.MaxResponseBytes = 8 * 1024 * 1024

client := hook0.NewClient(apiURL, applicationId, token, options)

Usage

Sending is idempotent, and retried

SendEvent sends every event under an ID it knows, either the one set on the event or a UUIDv7 it mints when the event carries none. Passing no ID does not mean the ID comes from Hook0. The value comes from the client, travels with the request, and is what SendEvent answers.

That is what makes a retry safe. Hook0 keys events on their ID, so a request repeated after a network failure or a server error ingests the event once rather than twice. Without a client-chosen ID, the repeated request would create a second event and deliver it to every subscriber.

Retrying is limited to what could end differently. A request that got no answer, a server error and an instance saying it is being reached faster than it accepts are all retried. A 429 naming a spent quota is not, because a quota clears when a plan changes or a day turns, and no send can wait for that. A Retry-After the answer carries is honoured, clamped to what is left of the delay budget. A retried request Hook0 answers with EventAlreadyIngested reports success, since an earlier attempt of that same send reached the API. The same answer to a first attempt is a genuine conflict, and is reported as an error.

Declaring the event types you use
created, err := client.UpsertEventTypes(ctx, []string{
	"billing.invoice.paid",
	"billing.invoice.voided",
})

Only the ones your application does not declare yet are created, and those are what comes back.

Every failure is a value you can match

Every reason a delivery or a send is refused is a value errors.Is names: ErrSignatureUnreadable, ErrHeaderNotDelivered, ErrSignatureMismatch, ErrSignatureOutsideTolerance, ErrPayloadTooLarge. A problem the API reported also arrives as a *generated.ProblemError, carrying the status and the document it answered.

Two clocks, two bounds

The context bounds the whole send, retries and waits included. The request timeout bounds one attempt.


Development

clients/go/generated/ is written by hook0-sdkgen from the OpenAPI snapshot the API commits, and is rewritten whole on every regeneration. A hand edit there is reverted the next time anyone regenerates, and the drift guard says so before that. Change the generator, then run:

UPDATE_SDK=go cargo test -p hook0-sdkgen sdk_targets

Everything beside it, the transport, the retry loop and the signature verification, is hand-written and never regenerated, and so is every _test.go file.

What a send retries, the bounds it is held to and how a signature is verified are dictated by the shared corpus at clients/conformance, which every SDK's suite reads, so a verdict changed there fails this client until it agrees again.

Every case runs against a real Hook0 over a loopback socket. Nothing here stands in for a part of the client.

gofmt -l .
go vet ./...
go test ./...

License

The Hook0 Go SDK is free and open source, released under the MIT License. Use it, change it, ship it, in open source and in commercial work alike, as long as the copyright notice travels with it.

Hook0 itself is open source too. Read what Hook0 is, visit hook0.com, join the community, or write to support@hook0.com.

Maintained by David Sferruzza and François-Guillaume Ribreau.

Documentation

Overview

Package hook0 is the Go SDK for Hook0, an open source Webhooks-as-a-Service platform.

Two halves live here. This one is hand-written: sending an event, upserting the event types an application uses, and verifying that a webhook came from Hook0 unchanged. The other is generated from the OpenAPI snapshot the API commits — one type per schema it declares, one error value per problem it can report, one method per operation — and is reached through the generated package beside this one, over the transport this one exports.

Sending an event is idempotent, and retried

SendEvent sends every event under an identifier this client knows: the one set on the Event, or a UUIDv7 it generates when the event carries none. Passing none does not mean the identifier comes from Hook0 — the value comes from here, travels with the request, and is what SendEvent answers.

That is what makes retrying safe. Hook0 keys events on that identifier, so a request repeated after a network failure or a server error ingests the event once rather than twice; without a client-chosen identifier, a repeated request would create a second event and deliver it to every subscriber. It also gives the answer to a retry its meaning: EventAlreadyIngested in reply to a repeated request says an earlier attempt of that same send reached the API, so the send succeeded. The same answer to a first attempt is a genuine conflict and is reported as one.

Only what could end differently is retried: a request that got no answer, a server error, and an instance saying it is being reached faster than it accepts. What the API refuses outright — a quota that is spent, a payload it will not read — is answered as is, since repeating it would only spend the same round trip again. The verdict for every problem the API can report is written down in the conformance corpus committed beside this module, which the suite here reads.

A send is bounded on five axes, each of them the caller's to set: the size of the payload, which is refused before a socket is opened; how long one attempt is given; how many attempts are made; how long a single wait between them may be; and how long every wait of one send may add up to.

Index

Constants

View Source
const (
	// DefaultMaxPayloadBytes is the largest event payload the client agrees to send.
	//
	// Hook0's API refuses request bodies above 2 MiB, so a payload above 1 MiB is already at risk of
	// being refused once the JSON envelope around it — metadata, labels, identifiers — is counted.
	// The client rules such an event out rather than spending a round trip, and every retry after
	// it, on a request that cannot be accepted.
	DefaultMaxPayloadBytes = 1024 * 1024

	// MaxAttemptsCap is the most attempts a retry policy can ever make, whatever MaxAttempts says.
	//
	// A policy is configuration, and configuration can be wrong; this cap keeps a mistyped
	// MaxAttempts from turning one send into an unbounded series of requests.
	MaxAttemptsCap = 16

	// AlreadyIngested is the identifier Hook0 gives the problem it answers when an event identifier
	// is already taken.
	AlreadyIngested = "EventAlreadyIngested"

	// RateLimited is the identifier Hook0 gives the problem it answers when requests are reaching
	// the instance faster than it accepts them.
	//
	// It shares its status with the quota problems, and is the only one of them worth repeating: a
	// quota clears when a plan changes or a day turns, neither of which happens inside the seconds a
	// send is given, while pacing clears on its own and the answer says when.
	RateLimited = "RateLimited"
)
View Source
const (
	// DefaultRequestTimeout is the longest one attempt at reaching the API is given before it is
	// abandoned.
	//
	// Ten seconds is far above what ingesting an event takes when the API is healthy, and short
	// enough that a stuck connection does not hold a caller for a noticeable time.
	DefaultRequestTimeout = 10 * time.Second

	// DefaultMaxResponseBytes is the largest response body read off a socket.
	DefaultMaxResponseBytes int64 = 8 * 1024 * 1024

	// MaxResponseHeaders is the most headers read out of one answer, and MaxHeaderBytes the longest
	// one of them may be.
	//
	// The head of an answer is written by the other end, so it is bounded like the body: a server
	// that is broken or hostile can otherwise spend a caller's memory on headers alone. Both are
	// the numbers the conformance corpus names, so that no two SDKs bound different things.
	MaxResponseHeaders = 64
	MaxHeaderBytes     = 64 * 1024

	// MaxHeadBytes is the largest whole head an answer may carry, every line counted together.
	//
	// This is the one that bounds what a head costs, because it bounds the total: a line count and
	// a size per line multiply, and the two above admit sixty-four lines of sixty-four kilobytes
	// between them. They earn their place by refusing early, on the line that crosses them rather
	// than at the end of the head; this one sets the ceiling.
	//
	// Sixteen kilobytes is the ceiling of the strictest runtime any target runs on, which is what
	// makes it a number every target can apply in library code. It is applied here rather than left
	// to MaxResponseHeaderBytes below: that one is an outer wall, set far above this so that what
	// refuses an abusive head is this client's own ceiling rather than whatever the runtime of the
	// day happens to allow.
	MaxHeadBytes = 16 * 1024
)

Variables

View Source
var (
	// ErrPayloadTooLarge is an event whose payload is larger than the client agrees to send. It is
	// answered before a socket is opened, so nothing was sent when a caller sees it.
	ErrPayloadTooLarge = errors.New("the event payload is larger than this client sends")

	// ErrInvalidEventType is an event type that does not read as `service.resource_type.verb`.
	ErrInvalidEventType = errors.New("the event type does not have a valid syntax")

	// ErrUnreachable is a request that got no answer: a connection refused or reset, an attempt out
	// of time, a body that stopped mid-way.
	//
	// It is the one failure of a send that could end differently, which is why it is told apart
	// from the others rather than grouped with them under the type that carries them all. None of
	// these says whether the API acted on the request, which is exactly why a send carries an
	// identifier the client chose itself.
	ErrUnreachable = errors.New("the API could not be reached")

	// ErrAnswerAboveABound is an answer that crossed a ceiling this client set for itself: a body,
	// a header, or a number of headers above what it agrees to read.
	//
	// Repeating the request draws the same answer, so it is reported rather than retried: a client
	// that retries it reads the oversized answer four times and then blames the network.
	ErrAnswerAboveABound = errors.New("the API answered more than this client reads")

	// ErrUnusableAPIURL is an API URL no request can be sent to. Nothing was sent when a caller
	// sees it, and building the same request again would fail the same way.
	ErrUnusableAPIURL = errors.New("the API URL is not one a request can be sent to")

	// ErrSignatureUnreadable is a signature header this client cannot read whole: a part it needs
	// that is missing, a moment that is not a number of seconds, a code that is not hexadecimal.
	ErrSignatureUnreadable = errors.New("the signature header cannot be read")

	// ErrHeaderNotDelivered is a header the signature says it covers that the request did not
	// carry. Signing over an absent value would let a sender drop a header and keep the signature
	// valid, so this is refused before any code is computed.
	ErrHeaderNotDelivered = errors.New("a header the signature covers was not delivered")

	// ErrSignatureMismatch is a code that is not the one the subscription secret produces.
	ErrSignatureMismatch = errors.New("the signature does not match what the subscription secret produces")

	// ErrSignatureOutsideTolerance is a moment sitting further from now than the caller accepts, in
	// either direction: a delivery captured and replayed later, and one dated in the future by a
	// clock that is ahead or by a sender widening its own acceptance window, are refused alike.
	ErrSignatureOutsideTolerance = errors.New("the signature's moment sits outside the tolerance accepted")
)

The reasons this client refuses to do what it was asked, as values errors.Is compares against.

A caller that only wants to know whether to try again reads the sentinel; a caller that wants the numbers reads the error the sentinel is wrapped in.

Functions

func GenerateEventId

func GenerateEventId() string

GenerateEventId answers a UUIDv7, the shape of identifier Hook0 mints when it is the one choosing.

Its leading 48 bits are the current time in milliseconds, so identifiers generated in sequence are ordered, which is what keeps the index they end up in from being written all over.

func VerifyWebhookSignature

func VerifyWebhookSignature(
	signature string,
	payload []byte,
	headers http.Header,
	subscriptionSecret string,
	tolerance time.Duration,
) error

VerifyWebhookSignature verifies a webhook against the current moment.

See VerifyWebhookSignatureAt for what each argument is.

func VerifyWebhookSignatureAt

func VerifyWebhookSignatureAt(
	signature string,
	payload []byte,
	headers http.Header,
	subscriptionSecret string,
	tolerance time.Duration,
	currentTime time.Time,
) error

VerifyWebhookSignatureAt verifies a webhook against a moment the caller names.

  • signature: the value of the `X-Hook0-Signature` header.
  • payload: the raw body of the webhook request.
  • headers: the headers of the webhook request.
  • subscriptionSecret: the signing secret of the subscription the webhook was delivered for.
  • tolerance: how far, in either direction, the moment the signature names may sit from currentTime. Five minutes is a reasonable trade-off between tolerating clock drift and bounding how long a captured delivery can be replayed.
  • currentTime: what to hold the signature's moment against.

Every reason a webhook is refused is one of the sentinels this package declares, so errors.Is tells a missing header from a code that does not match from a moment out of the window.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the Hook0 client, built once and shared wherever an application sends events.

func NewClient

func NewClient(apiURL string, applicationId string, token string, options Options) *Client

NewClient builds a client reaching an instance of the API.

  • apiURL: base API URL of a Hook0 instance, such as https://app.hook0.com/api/v1.
  • applicationId: identifier of the Hook0 application events are sent to.
  • token: an authentication token valid for that application.
  • options: the bounds one send is held to.

func (*Client) APIURL

func (c *Client) APIURL() string

APIURL is the base API URL this client reaches.

func (*Client) ApplicationId

func (c *Client) ApplicationId() string

ApplicationId is the application this client sends events to.

func (*Client) Options

func (c *Client) Options() Options

Options is the bounds one send is held to.

func (*Client) SendEvent

func (c *Client) SendEvent(ctx context.Context, event Event) (string, error)

SendEvent sends an event, and answers the identifier it was sent under.

func (*Client) Transport

func (c *Client) Transport() *Transport

Transport is what this client issues its requests through, which is also what a generated operation group is built on.

func (*Client) UpsertEventTypes

func (c *Client) UpsertEventTypes(ctx context.Context, eventTypes []string) ([]string, error)

UpsertEventTypes creates the event types the application does not declare yet, and answers those.

type Event

type Event struct {
	// EventType is the type of the event, as the application declares it.
	EventType string
	// Payload is what the event carries.
	Payload string
	// PayloadContentType says how to read the payload.
	PayloadContentType string
	// Labels are what Hook0 routes the event by.
	Labels map[string]string
	// Metadata is anything else worth carrying, nil when there is none.
	Metadata map[string]string
	// OccurredAt is when the event happened; the zero moment means now.
	OccurredAt time.Time
	// EventId is what to key the event on, empty when the client is to choose.
	EventId string
}

Event is an event to send to Hook0.

EventId is the caller's to set when it already has one to key the event on. Left empty, the client generates a UUIDv7, sends it and answers it — which is what lets it repeat a request without risking a second copy of the event being ingested and delivered to every subscriber.

type EventType

type EventType struct {
	// Service is the leading segment.
	Service string
	// ResourceType is the middle segment.
	ResourceType string
	// Verb is the trailing segment.
	Verb string
}

EventType is an event type, read out of the `service.resource_type.verb` it is written as.

func ParseEventType

func ParseEventType(written string) (EventType, error)

ParseEventType reads an event type, refusing one that does not name all three of its parts.

func (EventType) String

func (e EventType) String() string

String writes an event type the way the API reads one.

type EventTypeError

type EventTypeError struct {
	// EventType is the one that was asked for.
	EventType string
	// Detail is what went wrong, in the words a caller is given.
	Detail string
	// Err is the reason underneath, nil when there is none to name.
	Err error
}

EventTypeError is an event type this client would not use or could not create.

func (*EventTypeError) Error

func (e *EventTypeError) Error() string

Error says which event type failed, and why.

func (*EventTypeError) Unwrap

func (e *EventTypeError) Unwrap() error

Unwrap answers the reason underneath, which is what lets errors.Is name it.

type Options

type Options struct {
	// RetryPolicy is how the attempts of one send are spaced out.
	RetryPolicy RetryPolicy
	// RequestTimeout is how long one attempt is given.
	RequestTimeout time.Duration
	// MaxPayloadBytes is the largest payload sent, refused before a socket is opened.
	MaxPayloadBytes int
	// MaxResponseBytes is the largest answer read off a socket.
	MaxResponseBytes int64
}

Options is every bound a client applies to one send.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions is the bounds a client applies when the caller names none.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts is how many attempts a single send makes at most, the first one included. One
	// disables retrying, and nothing above MaxAttemptsCap is honoured.
	MaxAttempts int
	// InitialBackoff is the ceiling of the delay before the first retry.
	InitialBackoff time.Duration
	// MaxBackoff is the ceiling no single delay ever exceeds, however many retries were made.
	MaxBackoff time.Duration
	// MaxTotalDelay is the budget all the delays of one send share.
	MaxTotalDelay time.Duration
}

RetryPolicy says how a client spaces out the attempts of a single send.

The delay before a retry doubles from InitialBackoff and is capped by MaxBackoff; the delay actually waited is then drawn anywhere between zero and that ceiling, so that emitters which failed at the same moment do not come back at the same moment. Retrying stops as soon as the delays of the send would add up to more than MaxTotalDelay.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy is four attempts spread over at most five seconds.

Three retries absorb the blips a webhook emitter meets in production — a connection reset, a rolling deployment answering 503 — without holding the caller for long, and the five-second budget bounds what the worst send costs whatever the individual delays turn out to be.

func DisabledRetryPolicy

func DisabledRetryPolicy() RetryPolicy

DisabledRetryPolicy never retries: one attempt, and the caller hears what it answered.

func (RetryPolicy) Attempts

func (p RetryPolicy) Attempts() int

Attempts is how many attempts this policy actually makes: MaxAttempts, brought back inside one and MaxAttemptsCap.

func (RetryPolicy) BackoffCeiling

func (p RetryPolicy) BackoffCeiling(retry int) time.Duration

BackoffCeiling is the ceiling of the delay before retry number retry, where one is the first retry.

It doubles from InitialBackoff and never exceeds MaxBackoff, so the ceilings of successive retries never decrease.

func (RetryPolicy) Delays

func (p RetryPolicy) Delays(draws []float64) []time.Duration

Delays is what this policy waits between the attempts of one send, one per retry, given one draw in [0, 1) per retry.

Each delay lands between zero and the ceiling of its retry, and the schedule is cut short as soon as the next delay would spend more than MaxTotalDelay. There are therefore at most Attempts() - 1 delays, and they add up to at most MaxTotalDelay.

A draw that is missing or is not a finite number is read as one, which asks for the whole ceiling: an unusable source of randomness makes the client wait longer, never less.

type SendError

type SendError struct {
	// EventId is the identifier the request carried, whether the caller chose it or this client
	// generated it.
	EventId string
	// Attempts is how many requests were issued, the first one included. Zero when the send was
	// refused before any socket was opened.
	Attempts int
	// Waited is how much of the delay budget the retries spent.
	Waited time.Duration
	// Detail is what the last attempt ran into, in the words a caller is given.
	Detail string
	// Err is the reason underneath, nil when the failure is only what the API answered.
	Err error
}

SendError is what a send that did not ingest an event answers with.

It says how many attempts were made and how long they spent waiting, which is the difference between a transient outage this client rode out and a request the API will never accept. What went wrong underneath is under Unwrap, so errors.Is finds it.

func (*SendError) Error

func (e *SendError) Error() string

Error says what went wrong, and what it cost.

func (*SendError) Unwrap

func (e *SendError) Unwrap() error

Unwrap answers the reason underneath, which is what lets errors.Is name it.

type Signature

type Signature struct {
	// Timestamp is the moment the delivery was signed, in whole seconds since the epoch.
	Timestamp int64
	// CoveredHeaders names the headers the stronger scheme covers, in the order it covers them and
	// lowercased.
	CoveredHeaders []string
	// BodyCode is the `v0` code, nil when the signature offers none.
	BodyCode []byte
	// HeadersCode is the `v1` code, nil when the signature offers none.
	HeadersCode []byte
}

Signature is a signature header, read into the pieces a verification needs.

func ParseSignature

func ParseSignature(signature string) (*Signature, error)

ParseSignature reads a signature header, refusing anything it cannot read whole.

func (*Signature) Verify

func (s *Signature) Verify(payload []byte, coveredValues []string, subscriptionSecret string) bool

Verify reports whether the code this signature carries is the one the secret produces.

The stronger scheme wins when both are offered, and the comparison is made in constant time: one that gave up at the first differing byte would say, by how long it took, how much of a guess was right.

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport issues one request and reads the answer.

It answers the shape the generated package declares, so a generated operation group is built on one of these directly.

func NewTransport

func NewTransport(baseURL string, token string, timeout time.Duration, maxResponseBytes int64) *Transport

NewTransport builds a transport reaching an instance of the API with a token valid for it.

A timeout or a ceiling that names nothing is the default rather than no bound at all: a transport with no timeout is one a single hung connection holds forever.

func (*Transport) Deliver

func (t *Transport) Deliver(
	ctx context.Context,
	method string,
	path string,
	query url.Values,
	body any,
) (int, http.Header, []byte, error)

Deliver issues one request and answers the status, the headers, and the body.

It is Request with what the answer carried beside its body, which is what a client reads when the API names how long to wait before the request becomes servable again.

func (*Transport) Request

func (t *Transport) Request(
	ctx context.Context,
	method string,
	path string,
	query url.Values,
	body any,
) (int, []byte, error)

Request issues one request and answers the status, the body, and why it got neither.

A refusal is an answer: the status and the body are what say whether repeating the request could end differently, so they are answered rather than raised over. Only a request that got no answer at all is an error here.

This is the shape the generated package declares, which reads what the API sent and nothing about how it was sent. A caller that also needs the headers — the delay a paced instance names beside a refusal is one — asks Deliver for them.

type TransportError

type TransportError struct {
	// Detail says what went wrong, in the words a caller is given.
	Detail string
	// Err is the nature of the failure, and under it whatever the standard library reported.
	Err error
}

TransportError is a request that produced no answer to read.

Several natures of failure land here — a connection that was refused or reset, an answer above a ceiling this client set for itself, a URL nothing can be sent to — and only the first of them could end differently. What a send retries is therefore decided by errors.Is against ErrUnreachable, ErrAnswerAboveABound and ErrUnusableAPIURL, never by this type: a client deciding by the type spends four attempts on a mistyped API URL and then hands its caller a message that accuses the network.

func (*TransportError) Error

func (e *TransportError) Error() string

Error says why the API was not reached.

func (*TransportError) Unwrap

func (e *TransportError) Unwrap() error

Unwrap answers what the standard library reported.

Directories

Path Synopsis
Package generated carries everything the API document describes: one type per schema it declares, one closed list of constants per enumeration it names, one error value per problem it can report, and one method per operation, grouped by the entity its operation id names.
Package generated carries everything the API document describes: one type per schema it declares, one closed list of constants per enumeration it names, one error value per problem it can report, and one method per operation, grouped by the entity its operation id names.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL