Files
go-unifi/unifi/requests.go
Mateusz Filipowicz dca894e8e7 feat: use Client interface instead of client struct when interacting with UniFi SDK (#21)
* feat: use Client interface instead of client struct when interacting with UniFi SDK

Breaking change!

* chore: linting

* chore: linting
2025-02-17 09:38:41 +01:00

149 lines
4.6 KiB
Go

package unifi
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
)
// marshalRequest marshals the request body to an io.Reader. Returns nil if reqBody is nil.
func marshalRequest(reqBody interface{}) (io.Reader, error) {
if reqBody == nil {
return nil, nil //nolint: nilnil
}
reqBytes, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}
return bytes.NewReader(reqBytes), nil
}
// buildRequestURL constructs the full URL for a given apiPath using the client's baseURL and apiPaths.
func (c *client) buildRequestURL(apiPath string) (*url.URL, error) {
reqURL, err := url.Parse(apiPath)
if err != nil {
return nil, err
}
if !strings.HasPrefix(apiPath, "/") && !reqURL.IsAbs() {
reqURL.Path = path.Join(c.apiPaths.ApiPath, reqURL.Path)
}
return c.baseURL.ResolveReference(reqURL), nil
}
// validateRequestBody validates the request body if validation is enabled.
func (c *client) validateRequestBody(reqBody interface{}) error {
if reqBody != nil && c.validationMode != DisableValidation {
if err := c.validator.Validate(reqBody); err != nil {
err = fmt.Errorf("failed validating request body: %w", err)
if c.validationMode == HardValidation {
return err
} else {
fmt.Println(err)
}
}
}
return nil
}
// newRequestContext creates a new context for the request with a timeout if specified.
func (c *client) newRequestContext() (context.Context, context.CancelFunc) {
ctx := context.Background()
if c.timeout != 0 {
return context.WithTimeout(ctx, c.timeout)
}
return ctx, func() {}
}
// Do performs an HTTP request using the given method, apiPath, request body, and decodes the response into respBody.
// It validates the request body, applies interceptors, and decodes the HTTP response into respBody if provided.
// It returns an error if the request or response handling fails.
func (c *client) Do(ctx context.Context, method, apiPath string, reqBody interface{}, respBody interface{}) error {
if err := c.validateRequestBody(reqBody); err != nil {
return err
}
reqReader, err := marshalRequest(reqBody)
if err != nil {
return fmt.Errorf("unable to marshal request: %w", err)
}
url, err := c.buildRequestURL(apiPath)
if err != nil {
return fmt.Errorf("unable to create request URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, method, url.String(), reqReader)
if err != nil {
return fmt.Errorf("unable to create request: %s %s %w", method, apiPath, err)
}
if c.useLocking {
c.lock.Lock()
defer c.lock.Unlock()
}
for _, interceptor := range c.interceptors {
if err := interceptor.InterceptRequest(req); err != nil {
return err
}
}
resp, err := c.http.Do(req)
if err != nil {
return fmt.Errorf("unable to perform request: %s %s %w", method, apiPath, err)
}
defer resp.Body.Close()
for _, interceptor := range c.interceptors {
if err := interceptor.InterceptResponse(resp); err != nil {
return err
}
}
if err := c.errorHandler.HandleError(resp); err != nil {
return err
}
if respBody == nil || resp.ContentLength == 0 {
return nil
}
err = json.NewDecoder(resp.Body).Decode(respBody)
if err != nil {
return fmt.Errorf("unable to decode body: %s %s %w", method, apiPath, err)
}
return nil
}
// Get sends an HTTP GET request to the specified API path with the provided request body,
// and decodes the HTTP response into respBody.
// It is a convenience wrapper around Do.
func (c *client) Get(ctx context.Context, apiPath string, reqBody interface{}, respBody interface{}) error {
return c.Do(ctx, http.MethodGet, apiPath, reqBody, respBody)
}
// Post sends an HTTP POST request to the specified API path with the provided request body,
// and decodes the HTTP response into respBody.
// It is a convenience wrapper around Do.
func (c *client) Post(ctx context.Context, apiPath string, reqBody interface{}, respBody interface{}) error {
return c.Do(ctx, http.MethodPost, apiPath, reqBody, respBody)
}
// Put sends an HTTP PUT request to the specified API path with the provided request body,
// and decodes the HTTP response into respBody.
// It is a convenience wrapper around Do.
func (c *client) Put(ctx context.Context, apiPath string, reqBody interface{}, respBody interface{}) error {
return c.Do(ctx, http.MethodPut, apiPath, reqBody, respBody)
}
// Delete sends an HTTP DELETE request to the specified API path with the provided request body,
// and decodes the HTTP response into respBody.
// It is a convenience wrapper around Do.
func (c *client) Delete(ctx context.Context, apiPath string, reqBody interface{}, respBody interface{}) error {
return c.Do(ctx, http.MethodDelete, apiPath, reqBody, respBody)
}