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 internal/generate/templates/no_resptype_body_method.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
}{{end}}{{if .IsAppJSON}}
// Encode the request body as json.
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(params.Body); err != nil {
if err := c.newJSONEncoder(b).Encode(params.Body); err != nil {
return fmt.Errorf("encoding json body request failed: %v", err)
}{{else}}
b := params.Body{{end}}
Expand Down
4 changes: 2 additions & 2 deletions internal/generate/templates/resptype_body_method.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
}{{end}}{{if .IsAppJSON}}
// Encode the request body as json.
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(params.Body); err != nil {
if err := c.newJSONEncoder(b).Encode(params.Body); err != nil {
return nil, fmt.Errorf("encoding json body request failed: %v", err)
}{{else}}
b := params.Body{{end}}
Expand Down Expand Up @@ -44,7 +44,7 @@
}

var body {{.ResponseType}}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/generate/templates/resptype_method.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
}

var body {{.ResponseType}}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down
12 changes: 6 additions & 6 deletions internal/generate/test_utils/paths_output
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (c *Client) IpPoolList(ctx context.Context, params IpPoolListParams, ) (*Ip
}

var body IpPoolResultsPage
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down Expand Up @@ -90,7 +90,7 @@ func (c *Client) IpPoolCreate(ctx context.Context, params IpPoolCreateParams, )
}
// Encode the request body as json.
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(params.Body); err != nil {
if err := c.newJSONEncoder(b).Encode(params.Body); err != nil {
return nil, fmt.Errorf("encoding json body request failed: %v", err)
}

Expand Down Expand Up @@ -127,7 +127,7 @@ func (c *Client) IpPoolCreate(ctx context.Context, params IpPoolCreateParams, )
}

var body IpPool
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down Expand Up @@ -174,7 +174,7 @@ func (c *Client) IpPoolView(ctx context.Context, params IpPoolViewParams, ) (*Ip
}

var body IpPool
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand All @@ -189,7 +189,7 @@ func (c *Client) IpPoolUpdate(ctx context.Context, params IpPoolUpdateParams, )
}
// Encode the request body as json.
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(params.Body); err != nil {
if err := c.newJSONEncoder(b).Encode(params.Body); err != nil {
return nil, fmt.Errorf("encoding json body request failed: %v", err)
}

Expand Down Expand Up @@ -227,7 +227,7 @@ func (c *Client) IpPoolUpdate(ctx context.Context, params IpPoolUpdateParams, )
}

var body IpPool
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down
12 changes: 6 additions & 6 deletions internal/generate/test_utils/paths_output_expected
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (c *Client) IpPoolList(ctx context.Context, params IpPoolListParams, ) (*Ip
}

var body IpPoolResultsPage
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down Expand Up @@ -90,7 +90,7 @@ func (c *Client) IpPoolCreate(ctx context.Context, params IpPoolCreateParams, )
}
// Encode the request body as json.
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(params.Body); err != nil {
if err := c.newJSONEncoder(b).Encode(params.Body); err != nil {
return nil, fmt.Errorf("encoding json body request failed: %v", err)
}

Expand Down Expand Up @@ -127,7 +127,7 @@ func (c *Client) IpPoolCreate(ctx context.Context, params IpPoolCreateParams, )
}

var body IpPool
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down Expand Up @@ -174,7 +174,7 @@ func (c *Client) IpPoolView(ctx context.Context, params IpPoolViewParams, ) (*Ip
}

var body IpPool
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand All @@ -189,7 +189,7 @@ func (c *Client) IpPoolUpdate(ctx context.Context, params IpPoolUpdateParams, )
}
// Encode the request body as json.
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(params.Body); err != nil {
if err := c.newJSONEncoder(b).Encode(params.Body); err != nil {
return nil, fmt.Errorf("encoding json body request failed: %v", err)
}

Expand Down Expand Up @@ -227,7 +227,7 @@ func (c *Client) IpPoolUpdate(ctx context.Context, params IpPoolUpdateParams, )
}

var body IpPool
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
if err := c.newJSONDecoder(resp.Body).Decode(&body); err != nil {
return nil, fmt.Errorf("error decoding response body: %v", err)
}

Expand Down
55 changes: 55 additions & 0 deletions oxide/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package oxide

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -40,6 +41,37 @@ const (
defaultConfigDir = ".config" + string(filepath.Separator) + "oxide"
)

// JSONEncoder is an interface for encoding values to an output stream.
type JSONEncoder interface {
Encode(v any) error
}

// JSONDecoder is an interface for decoding values from an input stream.
type JSONDecoder interface {
Decode(v any) error
}

// JSONCodec is an interface that provides methods for creating JSON encoders and decoders.
type JSONCodec interface {
NewEncoder(w io.Writer) JSONEncoder
NewDecoder(r io.Reader) JSONDecoder
}

func defaultJSONCodec() JSONCodec {
return stdJSONCodec{}
}

// stdJSONCodec is the default implementation using the standard library.
type stdJSONCodec struct{}

func (stdJSONCodec) NewEncoder(w io.Writer) JSONEncoder {
return json.NewEncoder(w)
}

func (stdJSONCodec) NewDecoder(r io.Reader) JSONDecoder {
return json.NewDecoder(r)
}

// Config is the configuration that can be set on a Client.
type Config struct {
// Base URL of the Oxide API including the scheme. For example,
Expand Down Expand Up @@ -69,6 +101,10 @@ type Config struct {
// config.toml file for authentication. Will be overridden by
// the Profile field.
UseDefaultProfile bool

// A custom JSON codec for encoding and decoding. If not provided,
// the standard library's json.NewEncoder and json.NewDecoder will be used.
JSONCodec JSONCodec
}

// Client which conforms to the OpenAPI3 specification for this service.
Expand All @@ -85,6 +121,9 @@ type Client struct {

// The user agent string to add to every API request.
userAgent string

// JSON codec for encoding and decoding.
jsonCodec JSONCodec
}

type authCredentials struct {
Expand All @@ -106,6 +145,7 @@ func NewClient(cfg *Config) (*Client, error) {
profile := os.Getenv(ProfileEnvVar)
useDefaultProfile := false
userAgent := defaultUserAgent()
jsonCodec := defaultJSONCodec()
httpClient := &http.Client{
Timeout: 600 * time.Second,
}
Expand Down Expand Up @@ -138,6 +178,10 @@ func NewClient(cfg *Config) (*Client, error) {
if cfg.HTTPClient != nil {
httpClient = cfg.HTTPClient
}

if cfg.JSONCodec != nil {
jsonCodec = cfg.JSONCodec
}
}

if (profile != "" || useDefaultProfile) && (host != "" || token != "") {
Expand Down Expand Up @@ -187,6 +231,7 @@ func NewClient(cfg *Config) (*Client, error) {
host: host,
userAgent: userAgent,
client: httpClient,
jsonCodec: jsonCodec,
}

return client, nil
Expand Down Expand Up @@ -291,6 +336,16 @@ func parseBaseURL(baseURL string) (string, error) {
return b, nil
}

// newJSONEncoder creates a new JSON encoder for the given writer using the configured codec.
func (c *Client) newJSONEncoder(w io.Writer) JSONEncoder {
return c.jsonCodec.NewEncoder(w)
}

// newJSONDecoder creates a new JSON decoder for the given reader using the configured codec.
func (c *Client) newJSONDecoder(r io.Reader) JSONDecoder {
return c.jsonCodec.NewDecoder(r)
}

// buildRequest creates an HTTP request to interact with the Oxide API.
func (c *Client) buildRequest(ctx context.Context, body io.Reader, method, uri string, params, queries map[string]string) (*http.Request, error) {
// Create the request.
Expand Down
40 changes: 40 additions & 0 deletions oxide/lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ import (
"github.com/stretchr/testify/require"
)

// customJSONCodec is a custom JSON codec for testing custom codec functionality.
type customJSONCodec struct{}

func (customJSONCodec) NewEncoder(w io.Writer) JSONEncoder {
return json.NewEncoder(w)
}

func (customJSONCodec) NewDecoder(r io.Reader) JSONDecoder {
return json.NewDecoder(r)
}

func Test_buildRequest(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -189,6 +200,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with valid client from env": {
Expand All @@ -203,6 +215,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with valid client from env and config": {
Expand All @@ -225,6 +238,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 500 * time.Second,
},
userAgent: "bob",
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with config, overrides env": {
Expand All @@ -245,6 +259,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with profile": {
Expand All @@ -261,6 +276,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with profile from env": {
Expand All @@ -278,6 +294,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with default profile": {
Expand All @@ -294,6 +311,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with config dir and default profile": {
Expand All @@ -310,6 +328,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with config dir and profile": {
Expand All @@ -326,6 +345,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with profile, overrides env": {
Expand All @@ -346,6 +366,7 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with host and token from different sources ": {
Expand All @@ -364,6 +385,25 @@ func Test_NewClient(t *testing.T) {
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: defaultJSONCodec(),
},
},
"succeeds with custom json codec": {
config: func(string) *Config {
return &Config{
Host: "http://localhost",
Token: "foo",
JSONCodec: customJSONCodec{},
}
},
expectedClient: &Client{
host: "http://localhost/",
token: "foo",
client: &http.Client{
Timeout: 600 * time.Second,
},
userAgent: defaultUserAgent(),
jsonCodec: customJSONCodec{},
},
},
"fails with missing address using config": {
Expand Down
Loading