diff --git a/lemmy.go b/lemmy.go index 3c7e00a..43b50ee 100644 --- a/lemmy.go +++ b/lemmy.go @@ -32,20 +32,17 @@ func NewWithClient(baseURL string, client *http.Client) (*Client, error) { return nil, err } u = u.JoinPath("/api/v3") - return &Client{baseURL: u, client: client}, nil } -// ClientLogin logs in to Lemmy by sending an HTTP request to the -// login endpoint. It stores the returned token in the client -// for future use. +// ClientLogin logs in to Lemmy by calling the Lemmy endpoint, +// and stores the returned token for use in future requests. func (c *Client) ClientLogin(ctx context.Context, l types.Login) error { lr, err := c.Login(ctx, l) if err != nil { return err } - - c.Token = lr.JWT.MustValue() + c.Token = lr.JWT.ValueOrEmpty() return nil } @@ -91,7 +88,7 @@ func (c *Client) req(ctx context.Context, method string, path string, data, resp } // getReq makes a get request to the Lemmy server. -// It is separate from req() because it uses query +// It's separate from req() because it uses query // parameters rather than a JSON request body. func (c *Client) getReq(ctx context.Context, method string, path string, data, resp any) (*http.Response, error) { data = c.setAuth(data) @@ -129,7 +126,7 @@ func (c *Client) getReq(ctx context.Context, method string, path string, data, r return res, nil } -// resError returns an error if the given response is an error +// resError returns an error if the the response contains an error func resError(res *http.Response, lr types.LemmyResponse) error { if lr.Error.IsValid() { return types.LemmyError{ diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 0000000..f8ec744 --- /dev/null +++ b/types/errors.go @@ -0,0 +1,25 @@ +package types + +import ( + "fmt" + "net/http" +) + +// HTTPError represents an error caused by a non-200 HTTP status code +type HTTPError struct { + Code int +} + +func (he HTTPError) Error() string { + return fmt.Sprintf("%d %s", he.Code, http.StatusText(he.Code)) +} + +// LemmyError represents an error returned by the Lemmy API +type LemmyError struct { + ErrStr string + Code int +} + +func (le LemmyError) Error() string { + return fmt.Sprintf("%d %s: %s", le.Code, http.StatusText(le.Code), le.ErrStr) +} diff --git a/types/optional.go b/types/optional.go index b808519..36d2b27 100644 --- a/types/optional.go +++ b/types/optional.go @@ -10,41 +10,50 @@ import ( var ErrOptionalEmpty = errors.New("optional value is empty") +// Optional represents an optional value type Optional[T any] struct { value *T } +// NewOptional creates an optional with value v func NewOptional[T any](v T) Optional[T] { return Optional[T]{value: &v} } +// NewOptionalPtr creates an optional with the value pointed to by v func NewOptionalPtr[T any](v *T) Optional[T] { return Optional[T]{value: v} } +// NewOptionalNil creates a new nil optional value func NewOptionalNil[T any]() Optional[T] { return Optional[T]{} } +// Set sets the value of the optional func (o Optional[T]) Set(v T) Optional[T] { o.value = &v return o } +// SetPtr sets the value of the optional to the value bointed to by v func (o Optional[T]) SetPtr(v *T) Optional[T] { o.value = v return o } +// SetNil sets the optional value to nil func (o Optional[T]) SetNil() Optional[T] { o.value = nil return o } +// IsValid returns true if the value of the optional is not nil func (o Optional[T]) IsValid() bool { return o.value != nil } +// MustValue returns the value in the optional. It panics if the value is nil. func (o Optional[T]) MustValue() T { if o.value == nil { panic("optional value is nil") @@ -52,6 +61,7 @@ func (o Optional[T]) MustValue() T { return *o.value } +// Value returns the value in the optional. func (o Optional[T]) Value() (T, error) { if o.value != nil { return *o.value, ErrOptionalEmpty @@ -59,6 +69,7 @@ func (o Optional[T]) Value() (T, error) { return *new(T), nil } +// ValueOr returns the value inside the optional if it exists, or else it returns fallback func (o Optional[T]) ValueOr(fallback T) T { if o.value != nil { return *o.value @@ -66,6 +77,7 @@ func (o Optional[T]) ValueOr(fallback T) T { return fallback } +// ValueOrEmpty returns the value inside the optional if it exists, or else it returns the zero value of T func (o Optional[T]) ValueOrEmpty() T { if o.value != nil { return *o.value @@ -74,10 +86,12 @@ func (o Optional[T]) ValueOrEmpty() T { return value } +// MarshalJSON encodes the optional value as JSON func (o Optional[T]) MarshalJSON() ([]byte, error) { return json.Marshal(o.value) } +// UnmarshalJSON decodes JSON into the optional value func (o *Optional[T]) UnmarshalJSON(b []byte) error { if bytes.Equal(b, []byte("null")) { o.value = nil @@ -88,6 +102,7 @@ func (o *Optional[T]) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, o.value) } +// EncodeValues encodes the optional as a URL query parameter func (o Optional[T]) EncodeValues(key string, v *url.Values) error { s := o.String() if s != "" { @@ -96,6 +111,7 @@ func (o Optional[T]) EncodeValues(key string, v *url.Values) error { return nil } +// String returns the string representation of the optional value func (o Optional[T]) String() string { if o.value == nil { return "" @@ -103,6 +119,7 @@ func (o Optional[T]) String() string { return fmt.Sprint(*o.value) } +// GoString returns the Go representation of the optional value func (o Optional[T]) GoString() string { if o.value == nil { return "nil" diff --git a/types/time.go b/types/time.go new file mode 100644 index 0000000..14cbd74 --- /dev/null +++ b/types/time.go @@ -0,0 +1,38 @@ +package types + +import ( + "encoding/json" + "time" +) + +// LemmyTime represents a time value returned by the Lemmy server +type LemmyTime struct { + time.Time +} + +// MarshalJSON encodes the Lemmy time to its JSON value +func (lt LemmyTime) MarshalJSON() ([]byte, error) { + return json.Marshal(lt.Time.Format("2006-01-02T15:04:05")) +} + +// UnmarshalJSON decodes JSON into the Lemmy time struct +func (lt *LemmyTime) UnmarshalJSON(b []byte) error { + var timeStr string + err := json.Unmarshal(b, &timeStr) + if err != nil { + return err + } + + if timeStr == "" { + lt.Time = time.Unix(0, 0) + return nil + } + + t, err := time.Parse("2006-01-02T15:04:05", timeStr) + if err != nil { + return err + } + lt.Time = t + + return nil +} diff --git a/types/types.go b/types/types.go index c8edabc..1275830 100644 --- a/types/types.go +++ b/types/types.go @@ -1,66 +1,19 @@ package types -import ( - "encoding/json" - "fmt" - "net/http" - "time" -) - +// EmptyResponse is a response without any fields. +// It embeds LemmyResponse to capture any errors. type EmptyResponse struct { LemmyResponse } +// EmptyData is a request without any fields. It contains +// an Auth field so that the auth token is sent to the server. type EmptyData struct { Auth string `json:"auth" url:"auth,omitempty"` } +// LemmyResponse is embedded in all response structs +// to capture any errors sent by the Lemmy server. type LemmyResponse struct { Error Optional[string] `json:"error" url:"error,omitempty"` } - -type HTTPError struct { - Code int -} - -func (he HTTPError) Error() string { - return fmt.Sprintf("%d %s", he.Code, http.StatusText(he.Code)) -} - -type LemmyError struct { - ErrStr string - Code int -} - -func (le LemmyError) Error() string { - return fmt.Sprintf("%d %s: %s", le.Code, http.StatusText(le.Code), le.ErrStr) -} - -type LemmyTime struct { - time.Time -} - -func (lt LemmyTime) MarshalJSON() ([]byte, error) { - return json.Marshal(lt.Time.Format("2006-01-02T15:04:05")) -} - -func (lt *LemmyTime) UnmarshalJSON(b []byte) error { - var timeStr string - err := json.Unmarshal(b, &timeStr) - if err != nil { - return err - } - - if timeStr == "" { - lt.Time = time.Unix(0, 0) - return nil - } - - t, err := time.Parse("2006-01-02T15:04:05", timeStr) - if err != nil { - return err - } - - lt.Time = t - return nil -}