nexus

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2024 License: MIT Imports: 18 Imported by: 18

Documentation

Overview

Package nexus provides client and server implementations of the Nexus HTTP API

Index

Examples

Constants

View Source
const (

	// HeaderOperationID is the unique ID returned by the StartOperation response for async operations.
	// Must be set on callback headers to support completing operations before the start response is received.
	HeaderOperationID = "nexus-operation-id"

	// HeaderRequestTimeout is the total time to complete a Nexus HTTP request.
	HeaderRequestTimeout = "request-timeout"
	// HeaderOperationTimeout is the total time to complete a Nexus operation.
	// Unlike HeaderRequestTimeout, this applies to the whole operation, not just a single HTTP request.
	HeaderOperationTimeout = "operation-timeout"
)
View Source
const (
	StatusUpstreamTimeout = 520
)

Variables

View Source
var ErrOperationStillRunning = errors.New("operation still running")

ErrOperationStillRunning indicates that an operation is still running while trying to get its result.

Functions

func ExecuteOperation added in v0.0.2

func ExecuteOperation[I, O any](ctx context.Context, client *HTTPClient, operation OperationReference[I, O], input I, request ExecuteOperationOptions) (O, error)

ExecuteOperation is the type safe version of HTTPClient.ExecuteOperation. It accepts input of type I and returns output of type O, removing the need to consume the LazyValue returned by the client method.

ref := NewOperationReference[MyInput, MyOutput]("my-operation")
out, err := ExecuteOperation(ctx, client, ref, MyInput{}, options) // returns MyOutput, error

func NewCompletionHTTPHandler

func NewCompletionHTTPHandler(options CompletionHandlerOptions) http.Handler

NewCompletionHTTPHandler constructs an http.Handler from given options for handling operation completion requests.

func NewCompletionHTTPRequest

func NewCompletionHTTPRequest(ctx context.Context, url string, completion OperationCompletion) (*http.Request, error)

NewCompletionHTTPRequest creates an HTTP request deliver an operation completion to a given URL.

func NewHTTPHandler

func NewHTTPHandler(options HandlerOptions) http.Handler

NewHTTPHandler constructs an http.Handler from given options for handling Nexus service requests.

Types

type CancelOperationOptions

type CancelOperationOptions struct {
	// Header contains the request header fields either received by the server or to be sent by the client.
	//
	// Header will always be non empty in server methods and can be optionally set in the client API.
	//
	// Header values set here will overwrite any SDK-provided values for the same key.
	Header Header
}

CancelOperationOptions are options for the CancelOperation client and server APIs.

type ClientStartOperationResult added in v0.0.2

type ClientStartOperationResult[T any] struct {
	// Set when start completes synchronously and successfully.
	//
	// If T is a [LazyValue], ensure that your consume it or read the underlying content in its entirety and close it to
	// free up the underlying connection.
	Successful T
	// Set when the handler indicates that it started an asynchronous operation.
	// The attached handle can be used to perform actions such as cancel the operation or get its result.
	Pending *OperationHandle[T]
	// Links contain information about the operations done by the handler.
	Links []Link
}

ClientStartOperationResult is the return type of HTTPClient.StartOperation. One and only one of Successful or Pending will be non-nil.

func StartOperation added in v0.0.2

func StartOperation[I, O any](ctx context.Context, client *HTTPClient, operation OperationReference[I, O], input I, request StartOperationOptions) (*ClientStartOperationResult[O], error)

StartOperation is the type safe version of HTTPClient.StartOperation. It accepts input of type I and returns a ClientStartOperationResult of type O, removing the need to consume the LazyValue returned by the client method.

type CompletionHandler

type CompletionHandler interface {
	CompleteOperation(context.Context, *CompletionRequest) error
}

A CompletionHandler can receive operation completion requests as delivered via the callback URL provided in start-operation requests.

type CompletionHandlerOptions

type CompletionHandlerOptions struct {
	// Handler for completion requests.
	Handler CompletionHandler
	// A stuctured logging handler.
	// Defaults to slog.Default().
	Logger *slog.Logger
	// A [Serializer] to customize handler serialization behavior.
	// By default the handler handles, JSONables, byte slices, and nil.
	Serializer Serializer
	// A [FailureConverter] to convert a [Failure] instance to and from an [error]. Defaults to
	// [DefaultFailureConverter].
	FailureConverter FailureConverter
}

CompletionHandlerOptions are options for NewCompletionHTTPHandler.

type CompletionRequest

type CompletionRequest struct {
	// The original HTTP request.
	HTTPRequest *http.Request
	// State of the operation.
	State OperationState
	// OperationID is the ID of the operation. Used when a completion callback is received before a started response.
	OperationID string
	// StartTime is the time the operation started. Used when a completion callback is received before a started response.
	StartTime time.Time
	// Links are used to link back to the operation when a completion callback is received before a started response.
	Links []Link
	// Parsed from request and set if State is failed or canceled.
	Error error
	// Extracted from request and set if State is succeeded.
	Result *LazyValue
}

CompletionRequest is input for CompletionHandler.CompleteOperation.

type Content added in v0.0.2

type Content struct {
	// Header that should include information on how to deserialize this content.
	// Headers constructed by the framework always have lower case keys.
	// User provided keys are considered case-insensitive by the framework.
	Header Header
	// Data contains request or response data. May be nil for empty data.
	Data []byte
}

A Content is a container for a Header and a byte slice. It is used by the SDK's Serializer interface implementations.

type ExecuteOperationOptions

type ExecuteOperationOptions struct {
	// Callback URL to provide to the handle for receiving async operation completions. Optional.
	// Even though Client.ExecuteOperation waits for operation completion, some applications may want to set this
	// callback as a fallback mechanism.
	CallbackURL string
	// Optional header fields set by a client that are required to be attached to the callback request when an
	// asynchronous operation completes.
	CallbackHeader Header
	// Request ID that may be used by the server handler to dedupe this start request.
	// By default a v4 UUID will be generated by the client.
	RequestID string
	// Links contain arbitrary caller information. Handlers may use these links as
	// metadata on resources associated with and operation.
	Links []Link
	// Header to attach to start and get-result requests. Optional.
	//
	// Header values set here will overwrite any SDK-provided values for the same key.
	//
	// Header keys with the "content-" prefix are reserved for [Serializer] headers and should not be set in the
	// client API; they are not available to server [Handler] and [Operation] implementations.
	Header Header
	// Duration to wait for operation completion.
	//
	// ⚠ NOTE: unlike GetOperationResultOptions.Wait, zero and negative values are considered effectively infinite.
	Wait time.Duration
}

ExecuteOperationOptions are options for HTTPClient.ExecuteOperation.

type Failure

type Failure struct {
	// A simple text message.
	Message string `json:"message"`
	// A key-value mapping for additional context. Useful for decoding the 'details' field, if needed.
	Metadata map[string]string `json:"metadata,omitempty"`
	// Additional JSON serializable structured data.
	Details json.RawMessage `json:"details,omitempty"`
}

A Failure represents failed handler invocations as well as `failed` or `canceled` operation results. Failures shouldn't typically be constructed directly. The SDK APIs take a FailureConverter instance that can translate language errors to and from Failure instances.

type FailureConverter added in v0.1.0

type FailureConverter interface {
	// ErrorToFailure converts an [error] to a [Failure].
	// Implementors should take a best-effort approach and never fail this method.
	// Note that the provided error may be nil.
	ErrorToFailure(error) Failure
	// ErrorToFailure converts a [Failure] to an [error].
	// Implementors should take a best-effort approach and never fail this method.
	FailureToError(Failure) error
}

FailureConverter is used by the framework to transform [error] instances to and from Failure instances. To customize conversion logic, implement this interface and provide your implementation to framework methods such as [NewClient] and NewHTTPHandler. By default the SDK translates only error messages, losing type information and struct fields.

func DefaultFailureConverter added in v0.1.0

func DefaultFailureConverter() FailureConverter

DefaultFailureConverter returns the SDK's default FailureConverter implementation. Arbitrary errors are converted to a simple Failure object with just the Message popluated and FailureError instances to their underlying Failure instance. Failure instances are converted to FailureError to allow access to the full failure metadata and details if available.

type FailureError added in v0.1.0

type FailureError struct {
	// The underlying Failure object this error represents.
	Failure Failure
}

An error that directly represents a wire representation of Failure. The SDK will convert to this error by default unless the FailureConverter instance is customized.

func (*FailureError) Error added in v0.1.0

func (e *FailureError) Error() string

Error implements the error interface.

type GetOperationInfoOptions

type GetOperationInfoOptions struct {
	// Header contains the request header fields either received by the server or to be sent by the client.
	//
	// Header will always be non empty in server methods and can be optionally set in the client API.
	//
	// Header values set here will overwrite any SDK-provided values for the same key.
	Header Header
}

GetOperationInfoOptions are options for the GetOperationInfo client and server APIs.

type GetOperationResultOptions

type GetOperationResultOptions struct {
	// Header contains the request header fields either received by the server or to be sent by the client.
	//
	// Header will always be non empty in server methods and can be optionally set in the client API.
	//
	// Header values set here will overwrite any SDK-provided values for the same key.
	Header Header
	// If non-zero, reflects the duration the caller has indicated that it wants to wait for operation completion,
	// turning the request into a long poll.
	Wait time.Duration
}

GetOperationResultOptions are options for the GetOperationResult client and server APIs.

type HTTPClient added in v0.1.0

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

An HTTPClient makes Nexus service requests as defined in the Nexus HTTP API.

It can start a new operation and get an OperationHandle to an existing, asynchronous operation.

Use an OperationHandle to cancel, get the result of, and get information about asynchronous operations.

OperationHandles can be obtained either by starting new operations or by calling HTTPClient.NewHandle for existing operations.

func NewHTTPClient added in v0.1.0

func NewHTTPClient(options HTTPClientOptions) (*HTTPClient, error)

NewHTTPClient creates a new HTTPClient from provided HTTPClientOptions. BaseURL and Service are required.

func (*HTTPClient) ExecuteOperation added in v0.1.0

func (c *HTTPClient) ExecuteOperation(ctx context.Context, operation string, input any, options ExecuteOperationOptions) (*LazyValue, error)

ExecuteOperation is a helper for starting an operation and waiting for its completion.

For asynchronous operations, the client will long poll for their result, issuing one or more requests until the wait period provided via ExecuteOperationOptions exceeds, in which case an ErrOperationStillRunning error is returned.

The wait time is capped to the deadline of the provided context. Make sure to handle both context deadline errors and ErrOperationStillRunning.

Note that the wait period is enforced by the server and may not be respected if the server is misbehaving. Set the context deadline to the max allowed wait period to ensure this call returns in a timely fashion.

⚠️ If this method completes successfully, the returned response's body must be read in its entirety and closed to free up the underlying connection.

Example
package main

import (
	"context"
	"fmt"

	"github.com/nexus-rpc/sdk-go/nexus"
)

type MyStruct struct {
	Field string
}

var ctx = context.Background()
var client *nexus.HTTPClient

func main() {
	response, err := client.ExecuteOperation(ctx, "operation name", MyStruct{Field: "value"}, nexus.ExecuteOperationOptions{})
	if err != nil {
		// handle nexus.UnsuccessfulOperationError, nexus.ErrOperationStillRunning and, context.DeadlineExceeded
	}
	// must close the returned response body and read it until EOF to free up the underlying connection
	var output MyStruct
	_ = response.Consume(&output)
	fmt.Printf("Got response: %v\n", output)
}
Output:

func (*HTTPClient) NewHandle added in v0.1.0

func (c *HTTPClient) NewHandle(operation string, operationID string) (*OperationHandle[*LazyValue], error)

NewHandle gets a handle to an asynchronous operation by name and ID. Does not incur a trip to the server. Fails if provided an empty operation or ID.

func (*HTTPClient) StartOperation added in v0.1.0

func (c *HTTPClient) StartOperation(
	ctx context.Context,
	operation string,
	input any,
	options StartOperationOptions,
) (*ClientStartOperationResult[*LazyValue], error)

StartOperation calls the configured Nexus endpoint to start an operation.

This method has the following possible outcomes:

  1. The operation completes successfully. The result of this call will be set as a LazyValue in ClientStartOperationResult.Successful and must be consumed to free up the underlying connection.

  2. The operation was started and the handler has indicated that it will complete asynchronously. An OperationHandle will be returned as ClientStartOperationResult.Pending, which can be used to perform actions such as getting its result.

  3. The operation was unsuccessful. The returned result will be nil and error will be an UnsuccessfulOperationError.

  4. Any other error.

Example
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/nexus-rpc/sdk-go/nexus"
)

type MyStruct struct {
	Field string
}

var ctx = context.Background()
var client *nexus.HTTPClient

func main() {
	result, err := client.StartOperation(ctx, "example", MyStruct{Field: "value"}, nexus.StartOperationOptions{})
	if err != nil {
		var unsuccessfulOperationError *nexus.UnsuccessfulOperationError
		if errors.As(err, &unsuccessfulOperationError) { // operation failed or canceled
			fmt.Printf("Operation unsuccessful with state: %s, failure message: %s\n", unsuccessfulOperationError.State, unsuccessfulOperationError.Cause.Error())
		}
		var handlerError *nexus.HandlerError
		if errors.As(err, &handlerError) {
			fmt.Printf("Handler returned an error, type: %s, failure message: %s\n", handlerError.Type, handlerError.Cause.Error())
		}
		// most other errors should be returned as *nexus.UnexpectedResponseError
	}
	if result.Successful != nil { // operation successful
		response := result.Successful
		// must consume the response to free up the underlying connection
		var output MyStruct
		_ = response.Consume(&output)
		fmt.Printf("Got response: %v\n", output)
	} else { // operation started asynchronously
		handle := result.Pending
		fmt.Printf("Started asynchronous operation with ID: %s\n", handle.ID)
	}
}
Output:

type HTTPClientOptions added in v0.1.0

type HTTPClientOptions struct {
	// Base URL for all requests. Required.
	BaseURL string
	// Service name. Required.
	Service string
	// A function for making HTTP requests.
	// Defaults to [http.DefaultClient.Do].
	HTTPCaller func(*http.Request) (*http.Response, error)
	// A [Serializer] to customize client serialization behavior.
	// By default the client handles JSONables, byte slices, and nil.
	Serializer Serializer
	// A [FailureConverter] to convert a [Failure] instance to and from an [error]. Defaults to
	// [DefaultFailureConverter].
	FailureConverter FailureConverter
}

HTTPClientOptions are options for creating an HTTPClient.

type Handler

type Handler interface {
	// StartOperation handles requests for starting an operation. Return [HandlerStartOperationResultSync] to
	// respond successfully - inline, or [HandlerStartOperationResultAsync] to indicate that an asynchronous
	// operation was started. Return an [UnsuccessfulOperationError] to indicate that an operation completed as
	// failed or canceled.
	StartOperation(ctx context.Context, service, operation string, input *LazyValue, options StartOperationOptions) (HandlerStartOperationResult[any], error)
	// GetOperationResult handles requests to get the result of an asynchronous operation. Return non error result
	// to respond successfully - inline, or error with [ErrOperationStillRunning] to indicate that an asynchronous
	// operation is still running. Return an [UnsuccessfulOperationError] to indicate that an operation completed as
	// failed or canceled.
	//
	// When [GetOperationResultOptions.Wait] is greater than zero, this request should be treated as a long poll.
	// Long poll requests have a server side timeout, configurable via [HandlerOptions.GetResultTimeout], and exposed
	// via context deadline. The context deadline is decoupled from the application level Wait duration.
	//
	// It is the implementor's responsiblity to respect the client's wait duration and return in a timely fashion.
	// Consider using a derived context that enforces the wait timeout when implementing this method and return
	// [ErrOperationStillRunning] when that context expires as shown in the example.
	GetOperationResult(ctx context.Context, service, operation, operationID string, options GetOperationResultOptions) (any, error)
	// GetOperationInfo handles requests to get information about an asynchronous operation.
	GetOperationInfo(ctx context.Context, service, operation, operationID string, options GetOperationInfoOptions) (*OperationInfo, error)
	// CancelOperation handles requests to cancel an asynchronous operation.
	// Cancelation in Nexus is:
	//  1. asynchronous - returning from this method only ensures that cancelation is delivered, it may later be
	//  ignored by the underlying operation implemention.
	//  2. idempotent - implementors should ignore duplicate cancelations for the same operation.
	CancelOperation(ctx context.Context, service, operation, operationID string, options CancelOperationOptions) error
	// contains filtered or unexported methods
}

A Handler must implement all of the Nexus service endpoints as defined in the Nexus HTTP API.

Handler implementations must embed the UnimplementedHandler.

All Handler methods can return a HandlerError to fail requests with a custom HandlerErrorType and structured Failure. Arbitrary errors from handler methods are turned into HandlerErrorTypeInternal,their details are logged and hidden from the caller.

Example
package main

import (
	"context"
	"net"
	"net/http"
	"time"

	"github.com/nexus-rpc/sdk-go/nexus"
)

type myHandler struct {
	nexus.UnimplementedHandler
}

type MyResult struct {
	Field string `json:"field"`
}

// StartOperation implements the Handler interface.
func (h *myHandler) StartOperation(ctx context.Context, service, operation string, input *nexus.LazyValue, options nexus.StartOperationOptions) (nexus.HandlerStartOperationResult[any], error) {
	if err := h.authorize(ctx, options.Header); err != nil {
		return nil, err
	}
	return &nexus.HandlerStartOperationResultAsync{OperationID: "meaningful-id"}, nil
}

// GetOperationResult implements the Handler interface.
func (h *myHandler) GetOperationResult(ctx context.Context, service, operation, operationID string, options nexus.GetOperationResultOptions) (any, error) {
	if err := h.authorize(ctx, options.Header); err != nil {
		return nil, err
	}
	if options.Wait > 0 { // request is a long poll
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, options.Wait)
		defer cancel()

		result, err := h.pollOperation(ctx, options.Wait)
		if err != nil {
			// Translate deadline exceeded to "OperationStillRunning", this may or may not be semantically correct for
			// your application.
			// Some applications may want to "peek" the current status instead of performing this blind conversion if
			// the wait time is exceeded and the request's context deadline has not yet exceeded.
			if ctx.Err() != nil {
				return nil, nexus.ErrOperationStillRunning
			}
			// Optionally translate to operation failure (could also result in canceled state).
			// Optionally expose the error details to the caller.
			return nil, nexus.NewFailedOperationError(err)
		}
		return result, nil
	} else {
		result, err := h.peekOperation(ctx)
		if err != nil {
			// Optionally translate to operation failure (could also result in canceled state).
			return nil, nexus.NewFailedOperationError(err)
		}
		return result, nil
	}
}

func (h *myHandler) CancelOperation(ctx context.Context, service, operation, operationID string, options nexus.CancelOperationOptions) error {
	// Handlers must implement this.
	panic("unimplemented")
}

func (h *myHandler) GetOperationInfo(ctx context.Context, service, operation, operationID string, options nexus.GetOperationInfoOptions) (*nexus.OperationInfo, error) {
	// Handlers must implement this.
	panic("unimplemented")
}

func (h *myHandler) pollOperation(ctx context.Context, wait time.Duration) (*MyResult, error) {
	panic("unimplemented")
}

func (h *myHandler) peekOperation(ctx context.Context) (*MyResult, error) {
	panic("unimplemented")
}

func (h *myHandler) authorize(_ context.Context, header nexus.Header) error {
	// Authorization for demo purposes
	if header.Get("Authorization") != "Bearer top-secret" {
		return nexus.HandlerErrorf(nexus.HandlerErrorTypeUnauthorized, "unauthorized")
	}
	return nil
}

func main() {
	handler := &myHandler{}
	httpHandler := nexus.NewHTTPHandler(nexus.HandlerOptions{Handler: handler})

	listener, _ := net.Listen("tcp", "localhost:0")
	defer listener.Close()
	_ = http.Serve(listener, httpHandler)
}
Output:

type HandlerError

type HandlerError struct {
	// Error Type. Defaults to HandlerErrorTypeInternal.
	Type HandlerErrorType
	// The underlying cause for this error.
	Cause error
}

HandlerError is a special error that can be returned from Handler methods for failing a request with a custom status code and failure message.

func HandlerErrorf added in v0.0.2

func HandlerErrorf(typ HandlerErrorType, format string, args ...any) *HandlerError

HandlerErrorf creates a HandlerError with the given type using fmt.Errorf to construct the cause.

func (*HandlerError) Error

func (e *HandlerError) Error() string

Error implements the error interface.

func (*HandlerError) Unwrap added in v0.1.0

func (e *HandlerError) Unwrap() error

Unwrap returns the cause for use with utilities in the errors package.

type HandlerErrorType added in v0.0.2

type HandlerErrorType string
const (
	// The server cannot or will not process the request due to an apparent client error.
	HandlerErrorTypeBadRequest HandlerErrorType = "BAD_REQUEST"
	// The client did not supply valid authentication credentials for this request.
	HandlerErrorTypeUnauthenticated HandlerErrorType = "UNAUTHENTICATED"
	// The caller does not have permission to execute the specified operation.
	HandlerErrorTypeUnauthorized HandlerErrorType = "UNAUTHORIZED"
	// The requested resource could not be found but may be available in the future. Subsequent requests by the client
	// are permissible.
	HandlerErrorTypeNotFound HandlerErrorType = "NOT_FOUND"
	// Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.
	HandlerErrorTypeResourceExhausted HandlerErrorType = "RESOURCE_EXHAUSTED"
	// An internal error occured.
	HandlerErrorTypeInternal HandlerErrorType = "INTERNAL"
	// The server either does not recognize the request method, or it lacks the ability to fulfill the request.
	HandlerErrorTypeNotImplemented HandlerErrorType = "NOT_IMPLEMENTED"
	// The service is currently unavailable.
	HandlerErrorTypeUnavailable HandlerErrorType = "UNAVAILABLE"
	// Used by gateways to report that a request to an upstream server has timed out.
	HandlerErrorTypeUpstreamTimeout HandlerErrorType = "UPSTREAM_TIMEOUT"
)

type HandlerOptions

type HandlerOptions struct {
	// Handler for handling service requests.
	Handler Handler
	// A stuctured logger.
	// Defaults to slog.Default().
	Logger *slog.Logger
	// Max duration to allow waiting for a single get result request.
	// Enforced if provided for requests with the wait query parameter set.
	//
	// Defaults to one minute.
	GetResultTimeout time.Duration
	// A [Serializer] to customize handler serialization behavior.
	// By default the handler handles JSONables, byte slices, and nil.
	Serializer Serializer
	// A [FailureConverter] to convert a [Failure] instance to and from an [error].
	// Defaults to [DefaultFailureConverter].
	FailureConverter FailureConverter
}

HandlerOptions are options for NewHTTPHandler.

type HandlerStartOperationResult added in v0.0.2

type HandlerStartOperationResult[T any] interface {
	// contains filtered or unexported methods
}

An HandlerStartOperationResult is the return type from the Handler StartOperation and Operation Start methods. It has two implementations: HandlerStartOperationResultSync and HandlerStartOperationResultAsync.

type HandlerStartOperationResultAsync added in v0.0.2

type HandlerStartOperationResultAsync struct {
	OperationID string
	Links       []Link
}

HandlerStartOperationResultAsync indicates that an operation has been accepted and will complete asynchronously.

type HandlerStartOperationResultSync added in v0.0.2

type HandlerStartOperationResultSync[T any] struct {
	Value T
}

HandlerStartOperationResultSync indicates that an operation completed successfully.

type Header map[string]string

Header is a mapping of string to string. It is used throughout the framework to transmit metadata. The keys should be in lower case form.

func (Header) Get added in v0.0.2

func (h Header) Get(k string) string

Get is a case-insensitive key lookup from the header map.

func (Header) Set added in v0.0.12

func (h Header) Set(k, v string)

Set sets the header key to the given value transforming the key to its lower case form.

type LazyValue added in v0.0.2

type LazyValue struct {
	Reader *Reader
	// contains filtered or unexported fields
}

A LazyValue holds a value encoded in an underlying Reader.

⚠️ When a LazyValue is returned from a client - if directly accessing the Reader - it must be read it in its entirety and closed to free up the associated HTTP connection. Otherwise the LazyValue.Consume method must be called.

⚠️ When a LazyValue is passed to a server handler, it must not be used after the returning from the handler method.

func NewLazyValue added in v0.0.8

func NewLazyValue(serializer Serializer, reader *Reader) *LazyValue

Create a new LazyValue from a given serializer and reader.

func (*LazyValue) Consume added in v0.0.2

func (l *LazyValue) Consume(v any) error

Consume consumes the lazy value, decodes it from the underlying Reader, and stores the result in the value pointed to by v.

var v int
err := lazyValue.Consume(&v)
type Link struct {
	// URL information about the link.
	// It must be URL percent-encoded.
	URL *url.URL
	// Type can describe an actual data type for decoding the URL.
	// Valid chars: alphanumeric, '_', '.', '/'
	Type string
}

Link contains an URL and a Type that can be used to decode the URL. Links can contain any arbitrary information as a percent-encoded URL. It can be used to pass information about the caller to the handler, or vice-versa.

type NoValue added in v0.0.2

type NoValue *struct{}

NoValue is a marker type for an operations that do not accept any input or return a value (nil).

nexus.NewSyncOperation("my-empty-operation", func(context.Context, nexus.NoValue, options, nexus.StartOperationOptions) (nexus.NoValue, error) {
	return nil, nil
)}

type Operation added in v0.0.2

type Operation[I, O any] interface {
	RegisterableOperation
	OperationReference[I, O]

	// Start handles requests for starting an operation. Return [HandlerStartOperationResultSync] to respond
	// successfully - inline, or [HandlerStartOperationResultAsync] to indicate that an asynchronous operation was
	// started. Return an [UnsuccessfulOperationError] to indicate that an operation completed as failed or
	// canceled.
	Start(context.Context, I, StartOperationOptions) (HandlerStartOperationResult[O], error)
	// GetResult handles requests to get the result of an asynchronous operation. Return non error result to respond
	// successfully - inline, or error with [ErrOperationStillRunning] to indicate that an asynchronous operation is
	// still running. Return an [UnsuccessfulOperationError] to indicate that an operation completed as failed or
	// canceled.
	//
	// When [GetOperationResultOptions.Wait] is greater than zero, this request should be treated as a long poll.
	// Long poll requests have a server side timeout, configurable via [HandlerOptions.GetResultTimeout], and exposed
	// via context deadline. The context deadline is decoupled from the application level Wait duration.
	//
	// It is the implementor's responsiblity to respect the client's wait duration and return in a timely fashion.
	// Consider using a derived context that enforces the wait timeout when implementing this method and return
	// [ErrOperationStillRunning] when that context expires as shown in the [Handler] example.
	GetResult(context.Context, string, GetOperationResultOptions) (O, error)
	// GetInfo handles requests to get information about an asynchronous operation.
	GetInfo(context.Context, string, GetOperationInfoOptions) (*OperationInfo, error)
	// Cancel handles requests to cancel an asynchronous operation.
	// Cancelation in Nexus is:
	//  1. asynchronous - returning from this method only ensures that cancelation is delivered, it may later be
	//  ignored by the underlying operation implemention.
	//  2. idempotent - implementors should ignore duplicate cancelations for the same operation.
	Cancel(context.Context, string, CancelOperationOptions) error
}

Operation is a handler for a single operation.

Operation implementations must embed the UnimplementedOperation.

All Operation methods can return a HandlerError to fail requests with a custom HandlerErrorType and structured Failure. Arbitrary errors from handler methods are turned into HandlerErrorTypeInternal,their details are logged and hidden from the caller.

func NewSyncOperation added in v0.0.2

func NewSyncOperation[I, O any](name string, handler func(context.Context, I, StartOperationOptions) (O, error)) Operation[I, O]

NewSyncOperation is a helper for creating a synchronous-only Operation from a given name and handler function.

type OperationCompletion

type OperationCompletion interface {
	// contains filtered or unexported methods
}

OperationCompletion is input for NewCompletionHTTPRequest. It has two implementations: OperationCompletionSuccessful and OperationCompletionUnsuccessful.

type OperationCompletionSuccessful

type OperationCompletionSuccessful struct {
	// Header to send in the completion request.
	// Note that this is a Nexus header, not an HTTP header.
	Header Header

	// A [Reader] that may be directly set on the completion or constructed when instantiating via
	// [NewOperationCompletionSuccessful].
	// Automatically closed when the completion is delivered.
	Reader *Reader
	// OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
	OperationID string
	// StartTime is the time the operation started. Used when a completion callback is received before a started response.
	StartTime time.Time
	// Links are used to link back to the operation when a completion callback is received before a started response.
	Links []Link
}

OperationCompletionSuccessful is input for NewCompletionHTTPRequest, used to deliver successful operation results.

func NewOperationCompletionSuccessful

func NewOperationCompletionSuccessful(result any, options OperationCompletionSuccessfulOptions) (*OperationCompletionSuccessful, error)

NewOperationCompletionSuccessful constructs an OperationCompletionSuccessful from a given result.

type OperationCompletionSuccessfulOptions added in v0.0.11

type OperationCompletionSuccessfulOptions struct {
	// Optional serializer for the result. Defaults to the SDK's default Serializer, which handles JSONables, byte
	// slices and nils.
	Serializer Serializer
	// OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
	OperationID string
	// StartTime is the time the operation started. Used when a completion callback is received before a started response.
	StartTime time.Time
	// Links are used to link back to the operation when a completion callback is received before a started response.
	Links []Link
}

OperationCompletionSuccessfulOptions are options for NewOperationCompletionSuccessful.

type OperationCompletionUnsuccessful

type OperationCompletionUnsuccessful struct {
	// Header to send in the completion request.
	// Note that this is a Nexus header, not an HTTP header.
	Header Header
	// State of the operation, should be failed or canceled.
	State OperationState
	// OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
	OperationID string
	// StartTime is the time the operation started. Used when a completion callback is received before a started response.
	StartTime time.Time
	// Links are used to link back to the operation when a completion callback is received before a started response.
	Links []Link
	// Failure object to send with the completion.
	Failure Failure
}

OperationCompletionUnsuccessful is input for NewCompletionHTTPRequest, used to deliver unsuccessful operation results.

func NewOperationCompletionUnsuccessful added in v0.1.0

NewOperationCompletionUnsuccessful constructs an OperationCompletionUnsuccessful from a given error.

type OperationCompletionUnsuccessfulOptions added in v0.1.0

type OperationCompletionUnsuccessfulOptions struct {
	// A [FailureConverter] to convert a [Failure] instance to and from an [error]. Defaults to
	// [DefaultFailureConverter].
	FailureConverter FailureConverter
	// OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
	OperationID string
	// StartTime is the time the operation started. Used when a completion callback is received before a started response.
	StartTime time.Time
	// Links are used to link back to the operation when a completion callback is received before a started response.
	Links []Link
}

OperationCompletionUnsuccessfulOptions are options for NewOperationCompletionUnsuccessful.

type OperationHandle

type OperationHandle[T any] struct {
	// Name of the Operation this handle represents.
	Operation string
	// Handler generated ID for this handle's operation.
	ID string
	// contains filtered or unexported fields
}

An OperationHandle is used to cancel operations and get their result and status.

func NewHandle added in v0.0.2

func NewHandle[I, O any](client *HTTPClient, operation OperationReference[I, O], operationID string) (*OperationHandle[O], error)

NewHandle is the type safe version of HTTPClient.NewHandle. The [Handle.GetResult] method will return an output of type O.

func (*OperationHandle[T]) Cancel

func (h *OperationHandle[T]) Cancel(ctx context.Context, options CancelOperationOptions) error

Cancel requests to cancel an asynchronous operation.

Cancelation is asynchronous and may be not be respected by the operation's implementation.

func (*OperationHandle[T]) GetInfo

GetInfo gets operation information, issuing a network request to the service handler.

func (*OperationHandle[T]) GetResult

func (h *OperationHandle[T]) GetResult(ctx context.Context, options GetOperationResultOptions) (T, error)

GetResult gets the result of an operation, issuing a network request to the service handler.

By default, GetResult returns (nil, ErrOperationStillRunning) immediately after issuing a call if the operation has not yet completed.

Callers may set GetOperationResultOptions.Wait to a value greater than 0 to alter this behavior, causing the client to long poll for the result issuing one or more requests until the provided wait period exceeds, in which case (nil, ErrOperationStillRunning) is returned.

The wait time is capped to the deadline of the provided context. Make sure to handle both context deadline errors and ErrOperationStillRunning.

Note that the wait period is enforced by the server and may not be respected if the server is misbehaving. Set the context deadline to the max allowed wait period to ensure this call returns in a timely fashion.

⚠️ If a LazyValue is returned (as indicated by T), it must be consumed to free up the underlying connection.

type OperationInfo

type OperationInfo struct {
	// ID of the operation.
	ID string `json:"id"`
	// State of the operation.
	State OperationState `json:"state"`
}

OperationInfo conveys information about an operation.

type OperationReference added in v0.0.2

type OperationReference[I, O any] interface {
	Name() string
	// InputType the generic input type I for this operation.
	InputType() reflect.Type
	// OutputType the generic out type O for this operation.
	OutputType() reflect.Type
	// contains filtered or unexported methods
}

OperationReference provides a typed interface for invoking operations. Every Operation is also an OperationReference. Callers may create references using NewOperationReference when the implementation is not available.

func NewOperationReference added in v0.0.2

func NewOperationReference[I, O any](name string) OperationReference[I, O]

NewOperationReference creates an OperationReference with the provided type parameters and name. It provides typed interface for invoking operations when the implementation is not available to the caller.

type OperationState

type OperationState string

OperationState represents the variable states of an operation.

const (
	// "running" operation state. Indicates an operation is started and not yet completed.
	OperationStateRunning OperationState = "running"
	// "succeeded" operation state. Indicates an operation completed successfully.
	OperationStateSucceeded OperationState = "succeeded"
	// "failed" operation state. Indicates an operation completed as failed.
	OperationStateFailed OperationState = "failed"
	// "canceled" operation state. Indicates an operation completed as canceled.
	OperationStateCanceled OperationState = "canceled"
)

type Reader added in v0.0.2

type Reader struct {
	// ReaderCloser contains request or response data. May be nil for empty data.
	io.ReadCloser
	// Header that should include information on how to deserialize this content.
	// Headers constructed by the framework always have lower case keys.
	// User provided keys are considered case-insensitive by the framework.
	Header Header
}

A Reader is a container for a Header and an io.Reader. It is used to stream inputs and outputs in the various client and server APIs.

type RegisterableOperation added in v0.0.2

type RegisterableOperation interface {
	// Name of the operation. Used for invocation and registration.
	Name() string
	// contains filtered or unexported methods
}

A RegisterableOperation is accepted in [OperationRegistry.Register]. Embed UnimplementedOperation to implement it.

type Serializer added in v0.0.2

type Serializer interface {
	// Serialize encodes a value into a [Content].
	Serialize(any) (*Content, error)
	// Deserialize decodes a [Content] into a given reference.
	Deserialize(*Content, any) error
}

Serializer is used by the framework to serialize/deserialize input and output. To customize serialization logic, implement this interface and provide your implementation to framework methods such as NewHTTPClient and NewHTTPHandler. By default, the SDK supports serialization of JSONables, byte slices, and nils.

func DefaultSerializer added in v0.1.0

func DefaultSerializer() Serializer

DefaultSerializer returns the SDK's default Serializer that handles serialization to and from JSONables, byte slices, and nil.

type Service added in v0.0.8

type Service struct {
	Name string
	// contains filtered or unexported fields
}

A Service is a container for a group of operations.

func NewService added in v0.0.8

func NewService(name string) *Service

NewService constructs a Service.

func (*Service) Operation added in v0.0.11

func (s *Service) Operation(name string) RegisterableOperation

Operation returns an operation by name or nil if not found.

func (*Service) Register added in v0.0.8

func (s *Service) Register(operations ...RegisterableOperation) error

Register one or more operations. Returns an error if duplicate operations were registered with the same name or when trying to register an operation with no name.

Can be called multiple times and is not thread safe.

type ServiceRegistry added in v0.0.8

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

A ServiceRegistry registers services and constructs a Handler that dispatches operations requests to those services.

func NewServiceRegistry added in v0.0.8

func NewServiceRegistry() *ServiceRegistry

func (*ServiceRegistry) NewHandler added in v0.0.8

func (r *ServiceRegistry) NewHandler() (Handler, error)

NewHandler creates a Handler that dispatches requests to registered operations based on their name.

func (*ServiceRegistry) Register added in v0.0.8

func (r *ServiceRegistry) Register(services ...*Service) error

Register one or more service. Returns an error if duplicate operations were registered with the same name or when trying to register a service with no name.

Can be called multiple times and is not thread safe.

type StartOperationOptions

type StartOperationOptions struct {
	// Header contains the request header fields either received by the server or to be sent by the client.
	//
	// Header will always be non empty in server methods and can be optionally set in the client API.
	//
	// Header values set here will overwrite any SDK-provided values for the same key.
	//
	// Header keys with the "content-" prefix are reserved for [Serializer] headers and should not be set in the
	// client API; they are not available to server [Handler] and [Operation] implementations.
	Header Header
	// Callbacks are used to deliver completion of async operations.
	// This value may optionally be set by the client and should be called by a handler upon completion if the started operation is async.
	//
	// Implement a [CompletionHandler] and expose it as an HTTP handler to handle async completions.
	CallbackURL string
	// Optional header fields set by a client that are required to be attached to the callback request when an
	// asynchronous operation completes.
	CallbackHeader Header
	// Request ID that may be used by the server handler to dedupe a start request.
	// By default a v4 UUID will be generated by the client.
	RequestID string
	// Links contain arbitrary caller information. Handlers may use these links as
	// metadata on resources associated with and operation.
	Links []Link
}

StartOperationOptions are options for the StartOperation client and server APIs.

type UnexpectedResponseError

type UnexpectedResponseError struct {
	// Error message.
	Message string
	// Optional failure that may have been emedded in the response.
	Failure *Failure
	// Additional transport specific details.
	// For HTTP, this would include the HTTP response. The response body will have already been read into memory and
	// does not need to be closed.
	Details any
}

Error that indicates a client encountered something unexpected in the server's response.

func (*UnexpectedResponseError) Error

func (e *UnexpectedResponseError) Error() string

Error implements the error interface.

type UnimplementedHandler

type UnimplementedHandler struct{}

UnimplementedHandler must be embedded into any Handler implementation for future compatibility. It implements all methods on the Handler interface, returning unimplemented errors if they are not implemented by the embedding type.

func (UnimplementedHandler) CancelOperation

func (h UnimplementedHandler) CancelOperation(ctx context.Context, service, operation, operationID string, options CancelOperationOptions) error

CancelOperation implements the Handler interface.

func (UnimplementedHandler) GetOperationInfo

func (h UnimplementedHandler) GetOperationInfo(ctx context.Context, service, operation, operationID string, options GetOperationInfoOptions) (*OperationInfo, error)

GetOperationInfo implements the Handler interface.

func (UnimplementedHandler) GetOperationResult

func (h UnimplementedHandler) GetOperationResult(ctx context.Context, service, operation, operationID string, options GetOperationResultOptions) (any, error)

GetOperationResult implements the Handler interface.

func (UnimplementedHandler) StartOperation

func (h UnimplementedHandler) StartOperation(ctx context.Context, service, operation string, input *LazyValue, options StartOperationOptions) (HandlerStartOperationResult[any], error)

StartOperation implements the Handler interface.

type UnimplementedOperation added in v0.0.2

type UnimplementedOperation[I, O any] struct{}

UnimplementedOperation must be embedded into any Operation implementation for future compatibility. It implements all methods on the Operation interface except for `Name`, returning unimplemented errors if they are not implemented by the embedding type.

func (*UnimplementedOperation[I, O]) Cancel added in v0.0.2

Cancel implements Operation.

func (*UnimplementedOperation[I, O]) GetInfo added in v0.0.2

GetInfo implements Operation.

func (*UnimplementedOperation[I, O]) GetResult added in v0.0.2

GetResult implements Operation.

func (*UnimplementedOperation[I, O]) InputType added in v0.0.11

func (*UnimplementedOperation[I, O]) InputType() reflect.Type

func (*UnimplementedOperation[I, O]) OutputType added in v0.0.11

func (*UnimplementedOperation[I, O]) OutputType() reflect.Type

func (*UnimplementedOperation[I, O]) Start added in v0.0.2

Start implements Operation.

type UnsuccessfulOperationError

type UnsuccessfulOperationError struct {
	// State of the operation. Only [OperationStateFailed] and [OperationStateCanceled] are valid.
	State OperationState
	// The underlying cause for this error.
	Cause error
}

UnsuccessfulOperationError represents "failed" and "canceled" operation results.

func NewCanceledOperationError added in v0.1.0

func NewCanceledOperationError(err error) *UnsuccessfulOperationError

NewFailedOperationError is shorthand for constructing an UnsuccessfulOperationError with State set to OperationStateCanceled and the given err as the Cause.

func NewFailedOperationError added in v0.1.0

func NewFailedOperationError(err error) *UnsuccessfulOperationError

NewFailedOperationError is shorthand for constructing an UnsuccessfulOperationError with State set to OperationStateFailed and the given err as the Cause.

func (*UnsuccessfulOperationError) Error

Error implements the error interface.

func (*UnsuccessfulOperationError) Unwrap added in v0.1.0

func (e *UnsuccessfulOperationError) Unwrap() error

Unwrap returns the cause for use with utilities in the errors package.

Jump to

Keyboard shortcuts

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