mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
* chore: Rename ProjectHistory to ProjectVersion Version more accurately represents version storage. This forks from the WorkspaceHistory name, but I think it's easier to understand Workspace history. * Rename files * Standardize tests a bit more * Remove Server struct from coderdtest * Improve test coverage for workspace history * Fix linting errors * Fix coderd test leak * Fix coderd test leak * Improve workspace history logs * Standardize test structure for codersdk * Fix linting errors * Fix WebSocket compression * Update coderd/workspaces.go Co-authored-by: Bryan <bryan@coder.com> * Add test for listing project parameters * Cache npm dependencies with setup node * Remove windows npm cache key Co-authored-by: Bryan <bryan@coder.com>
124 lines
2.7 KiB
Go
124 lines
2.7 KiB
Go
package codersdk
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/httpapi"
|
|
"github.com/coder/coder/httpmw"
|
|
)
|
|
|
|
// New creates a Coder client for the provided URL.
|
|
func New(serverURL *url.URL) *Client {
|
|
return &Client{
|
|
URL: serverURL,
|
|
httpClient: &http.Client{},
|
|
}
|
|
}
|
|
|
|
// Client is an HTTP caller for methods to the Coder API.
|
|
type Client struct {
|
|
URL *url.URL
|
|
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// SetSessionToken applies the provided token to the current client.
|
|
func (c *Client) SetSessionToken(token string) error {
|
|
if c.httpClient.Jar == nil {
|
|
var err error
|
|
c.httpClient.Jar, err = cookiejar.New(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
c.httpClient.Jar.SetCookies(c.URL, []*http.Cookie{{
|
|
Name: httpmw.AuthCookie,
|
|
Value: token,
|
|
}})
|
|
return nil
|
|
}
|
|
|
|
// request performs an HTTP request with the body provided.
|
|
// The caller is responsible for closing the response body.
|
|
func (c *Client) request(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
|
|
serverURL, err := c.URL.Parse(path)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("parse url: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if body != nil {
|
|
enc := json.NewEncoder(&buf)
|
|
enc.SetEscapeHTML(false)
|
|
err = enc.Encode(body)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("encode body: %w", err)
|
|
}
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, serverURL.String(), &buf)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("create request: %w", err)
|
|
}
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("do: %w", err)
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
// readBodyAsError reads the response as an httpapi.Message, and
|
|
// wraps it in a codersdk.Error type for easy marshaling.
|
|
func readBodyAsError(res *http.Response) error {
|
|
var m httpapi.Response
|
|
err := json.NewDecoder(res.Body).Decode(&m)
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
// If no body is sent, we'll just provide the status code.
|
|
return &Error{
|
|
statusCode: res.StatusCode,
|
|
}
|
|
}
|
|
return xerrors.Errorf("decode body: %w", err)
|
|
}
|
|
return &Error{
|
|
Response: m,
|
|
statusCode: res.StatusCode,
|
|
}
|
|
}
|
|
|
|
// Error represents an unaccepted or invalid request to the API.
|
|
type Error struct {
|
|
httpapi.Response
|
|
|
|
statusCode int
|
|
}
|
|
|
|
func (e *Error) StatusCode() int {
|
|
return e.statusCode
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
var builder strings.Builder
|
|
_, _ = fmt.Fprintf(&builder, "status code %d: %s", e.statusCode, e.Message)
|
|
for _, err := range e.Errors {
|
|
_, _ = fmt.Fprintf(&builder, "\n\t%s: %s", err.Field, err.Code)
|
|
}
|
|
return builder.String()
|
|
}
|