1
0
mirror of https://github.com/taigrr/wtf synced 2026-03-26 22:32:18 -07:00

go mod vendor update

Signed-off-by: Chris Cummer <chriscummer@me.com>
This commit is contained in:
Chris Cummer
2019-12-14 08:52:34 -08:00
parent 703619bf0a
commit 3d4059de02
665 changed files with 104373 additions and 59789 deletions

View File

@@ -4,102 +4,224 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/google/go-querystring/query"
)
//ArticlesResource implements the APIResource interface
//for devto articles.
// ArticlesResource implements the APIResource interface
// for devto articles.
type ArticlesResource struct {
API *Client
}
//List will return the articles uploaded to devto, the result
//can be narrowed down, filtered or enhanced using query
// List will return the articles uploaded to devto, the result
// can be narrowed down, filtered or enhanced using query
// parameters as specified on the documentation.
//See: https://docs.dev.to/api/#tag/articles/paths/~1articles/get
func (ar *ArticlesResource) List(ctx context.Context, opt ArticleListOptions) ([]Article, error) {
var l []Article
// See: https://docs.dev.to/api/#tag/articles/paths/~1articles/get
func (ar *ArticlesResource) List(ctx context.Context, opt ArticleListOptions) ([]ListedArticle, error) {
q, err := query.Values(opt)
if err != nil {
return nil, err
}
req, _ := ar.API.NewRequest(http.MethodGet, fmt.Sprintf("api/articles?%s", q.Encode()), nil)
res, _ := ar.API.HTTPClient.Do(req)
cont, err := ioutil.ReadAll(res.Body)
req, err := ar.API.NewRequest(http.MethodGet, fmt.Sprintf("api/articles?%s", q.Encode()), nil)
if err != nil {
return nil, err
}
json.Unmarshal(cont, &l)
return l, nil
}
//Find will retrieve an Article matching the ID passed.
func (ar *ArticlesResource) Find(ctx context.Context, id uint32) (Article, error) {
var art Article
req, _ := ar.API.NewRequest(http.MethodGet, fmt.Sprintf("api/articles/%d", id), nil)
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return art, err
return nil, err
}
cont, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
if nonSuccessfulResponse(res) {
return nil, unmarshalErrorResponse(res)
}
var articles []ListedArticle
if err := json.NewDecoder(res.Body).Decode(&articles); err != nil {
return nil, err
}
return articles, nil
}
// ListForTag is a convenience method for retrieving articles
// for a particular tag, calling the base List method.
func (ar *ArticlesResource) ListForTag(ctx context.Context, tag string, page int) ([]ListedArticle, error) {
return ar.List(ctx, ArticleListOptions{Tags: tag, Page: page})
}
// ListForUser is a convenience method for retrieving articles
// written by a particular user, calling the base List method.
func (ar *ArticlesResource) ListForUser(ctx context.Context, username string, page int) ([]ListedArticle, error) {
return ar.List(ctx, ArticleListOptions{Username: username, Page: page})
}
// ListMyPublishedArticles lists all published articles
// written by the user authenticated with this client,
// erroring if the caller is not authenticated. Articles in
// the response will be listed in reverse chronological order
// by their publication times.
//
// If opts is nil, then no query parameters will be sent; the
// page number will be 1 and the page size will be 30
// articles.
func (ar *ArticlesResource) ListMyPublishedArticles(ctx context.Context, opts *MyArticlesOptions) ([]ListedArticle, error) {
return ar.listMyArticles(ctx, "api/articles/me/published", opts)
}
// ListMyUnpublishedArticles lists all unpublished articles
// written by the user authenticated with this client,
// erroring if the caller is not authenticated. Articles in
// the response will be listed in reverse chronological order
// by their creation times.
//
// If opts is nil, then no query parameters will be sent; the
// page number will be 1 and the page size will be 30
// articles.
func (ar *ArticlesResource) ListMyUnpublishedArticles(ctx context.Context, opts *MyArticlesOptions) ([]ListedArticle, error) {
return ar.listMyArticles(ctx, "api/articles/me/unpublished", opts)
}
// ListAllMyArticles lists all articles written by the user
// authenticated with this client, erroring if the caller is
// not authenticated. Articles in the response will be listed
// in reverse chronological order by their creation times,
// with unpublished articles listed before published articles.
//
// If opts is nil, then no query parameters will be sent; the
// page number will be 1 and the page size will be 30
// articles.
func (ar *ArticlesResource) ListAllMyArticles(ctx context.Context, opts *MyArticlesOptions) ([]ListedArticle, error) {
return ar.listMyArticles(ctx, "api/articles/me/all", opts)
}
// listMyArticles serves for handling roundtrips to the
// /api/articles/me/* endpoints, requesting articles from the
// endpoint passed in, and returning a list of articles.
func (ar *ArticlesResource) listMyArticles(
ctx context.Context,
endpoint string,
opts *MyArticlesOptions,
) ([]ListedArticle, error) {
if ar.API.Config.InsecureOnly {
return nil, ErrProtectedEndpoint
}
req, err := ar.API.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return art, err
return nil, err
}
req.Header.Add(APIKeyHeader, ar.API.Config.APIKey)
if opts != nil {
q, err := query.Values(opts)
if err != nil {
return nil, err
}
req.URL.RawQuery = q.Encode()
}
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if nonSuccessfulResponse(res) {
return nil, unmarshalErrorResponse(res)
}
var articles []ListedArticle
if err := json.NewDecoder(res.Body).Decode(&articles); err != nil {
return nil, err
}
return articles, nil
}
// Find will retrieve an Article matching the ID passed.
func (ar *ArticlesResource) Find(ctx context.Context, id uint32) (Article, error) {
req, err := ar.API.NewRequest(http.MethodGet, fmt.Sprintf("api/articles/%d", id), nil)
if err != nil {
return Article{}, err
}
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return Article{}, err
}
defer res.Body.Close()
if nonSuccessfulResponse(res) {
return Article{}, unmarshalErrorResponse(res)
}
var art Article
if err := json.NewDecoder(res.Body).Decode(&art); err != nil {
return Article{}, err
}
json.Unmarshal(cont, &art)
return art, nil
}
//New will create a new article on dev.to
func (ar *ArticlesResource) New(ctx context.Context, a Article) (Article, error) {
// New will create a new article on dev.to
func (ar *ArticlesResource) New(ctx context.Context, u ArticleUpdate) (Article, error) {
if ar.API.Config.InsecureOnly {
return a, ErrProtectedEndpoint
return Article{}, ErrProtectedEndpoint
}
cont, err := json.Marshal(a)
cont, err := json.Marshal(&u)
if err != nil {
return a, err
return Article{}, err
}
req, err := ar.API.NewRequest(http.MethodPost, "api/articles", strings.NewReader(string(cont)))
if err != nil {
return a, err
return Article{}, err
}
req.Header.Add(APIKeyHeader, ar.API.Config.APIKey)
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return a, err
return Article{}, err
}
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return a, err
defer res.Body.Close()
if nonSuccessfulResponse(res) {
return Article{}, unmarshalErrorResponse(res)
}
var a Article
if err := json.NewDecoder(res.Body).Decode(&a); err != nil {
return Article{}, err
}
json.Unmarshal(content, &a)
return a, nil
}
func (ar *ArticlesResource) Update(ctx context.Context, a Article) (Article, error) {
// Update will mutate the resource by id, and all the changes
// performed to the Article will be applied, thus validation
// on the API side.
func (ar *ArticlesResource) Update(ctx context.Context, u ArticleUpdate, id uint32) (Article, error) {
if ar.API.Config.InsecureOnly {
return a, ErrProtectedEndpoint
return Article{}, ErrProtectedEndpoint
}
cont, err := json.Marshal(a)
cont, err := json.Marshal(&u)
if err != nil {
return a, err
return Article{}, err
}
req, err := ar.API.NewRequest(http.MethodPut, fmt.Sprintf("api/articles/%d", a.ID), strings.NewReader(string(cont)))
req, err := ar.API.NewRequest(http.MethodPut, fmt.Sprintf("api/articles/%d", id), strings.NewReader(string(cont)))
if err != nil {
return a, err
return Article{}, err
}
req.Header.Add(APIKeyHeader, ar.API.Config.APIKey)
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return a, err
return Article{}, err
}
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return a, err
defer res.Body.Close()
if nonSuccessfulResponse(res) {
return Article{}, unmarshalErrorResponse(res)
}
var a Article
if err := json.NewDecoder(res.Body).Decode(&a); err != nil {
return Article{}, err
}
json.Unmarshal(content, &a)
return a, nil
}

View File

@@ -2,25 +2,25 @@ package devto
import "errors"
//Confugration errors
// Configuration errors
var (
ErrMissingRequiredParameter = errors.New("a required parameter is missing")
)
//Config contains the elements required to initialize a
// Config contains the elements required to initialize a
// devto client.
type Config struct {
APIKey string
InsecureOnly bool
}
//NewConfig build a devto configuration instance with the
//required parameters.
// NewConfig build a devto configuration instance with the
// required parameters.
//
//It takes a boolean (p) as first parameter to indicate if
//you need access to endpoints which require authentication,
//and a API key as second paramenter, if p is set to true and
//you don't provide an API key, it will return an error.
// It takes a boolean (p) as first parameter to indicate if
// you need access to endpoints which require authentication,
// and a API key as second parameter, if p is set to true and
// you don't provide an API key, it will return an error.
func NewConfig(p bool, k string) (c *Config, err error) {
if p == true && k == "" {
return nil, ErrMissingRequiredParameter

View File

@@ -15,7 +15,7 @@ const (
APIKeyHeader string = "api-key"
)
//devto client errors
// devto client errors
var (
ErrMissingConfig = errors.New("missing configuration")
ErrProtectedEndpoint = errors.New("to use this resource you need to provide an authentication method")
@@ -25,8 +25,8 @@ type httpClient interface {
Do(req *http.Request) (res *http.Response, err error)
}
//Client is the main data structure for performing actions
//against dev.to API
// Client is the main data structure for performing actions
// against dev.to API
type Client struct {
Context context.Context
BaseURL *url.URL
@@ -35,8 +35,8 @@ type Client struct {
Articles *ArticlesResource
}
//NewClient takes a context, a configuration pointer and optionally a
//base http client (bc) to build an Client instance.
// NewClient takes a context, a configuration pointer and optionally a
// base http client (bc) to build an Client instance.
func NewClient(ctx context.Context, conf *Config, bc httpClient, bu string) (dev *Client, err error) {
if bc == nil {
bc = http.DefaultClient
@@ -54,7 +54,10 @@ func NewClient(ctx context.Context, conf *Config, bc httpClient, bu string) (dev
bu = BaseURL
}
u, _ := url.Parse(bu)
u, err := url.Parse(bu)
if err != nil {
return nil, err
}
c := &Client{
Context: ctx,
@@ -66,7 +69,7 @@ func NewClient(ctx context.Context, conf *Config, bc httpClient, bu string) (dev
return c, nil
}
//NewRequest build the request relative to the client BaseURL
// NewRequest build the request relative to the client BaseURL
func (c *Client) NewRequest(method string, uri string, body io.Reader) (*http.Request, error) {
u, err := url.Parse(uri)
if err != nil {

View File

@@ -0,0 +1,10 @@
// Package devto is a wrapper around dev.to REST API
//
// Where programmers share ideas and help each other grow.
// It is an online community for sharing and discovering great ideas,
// having debates, and making friends. Anyone can share articles,
// questions, discussions, etc. as long as they have the rights to the
// words they are sharing. Cross-posting from your own blog is welcome.
//
// See: https://docs.dev.to/api
package devto

View File

@@ -1,12 +1,14 @@
package devto
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
)
//User contains information about a devto account
// User contains information about a devto account
type User struct {
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
@@ -17,8 +19,8 @@ type User struct {
ProfileImage90 *WebURL `json:"profile_image_90,omitempty"`
}
//Organization describes a company or group that
//publishes content to devto.
// Organization describes a company or group that
// publishes content to devto.
type Organization struct {
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
@@ -27,40 +29,161 @@ type Organization struct {
ProfileImage90 *WebURL `json:"profile_image_90,omitempty"`
}
//Tags are a group of topics related to an article
type Tags []string
//Article contains all the information related to a single
//information resource from devto.
type Article struct {
TypeOf string `json:"type_of,omitempty"`
ID uint32 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
CoverImage *WebURL `json:"cover_image,omitempty"`
SocialImage *WebURL `json:"social_image,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
EditedAt *time.Time `json:"edited_at,omitempty"`
CrossPostedAt *time.Time `json:"crossposted_at,omitempty"`
LastCommentAt *time.Time `json:"last_comment_at,omitempty"`
TagList Tags `json:"tag_list,omitempty"`
Tags string `json:"tags,omitempty"`
Slug string `json:"slug,omitempty"`
Path *WebURL `json:"path,omitempty"`
URL *WebURL `json:"url,omitempty"`
CanonicalURL *WebURL `json:"canonical_url,omitempty"`
CommentsCount uint `json:"comments_count,omitempty"`
PositiveReactionsCount uint `json:"positive_reactions_count,omitempty"`
PublishedTimestamp *time.Time `json:"published_timestamp,omitempty"`
User User `json:"user,omitempty"`
Organization Organization `json:"organization,omitempty"`
BodyHTML string `json:"body_html,omitempty"`
BodyMarkdown string `json:"body_markdown,omitempty"`
Published bool `json:"published,omitempty"`
// FlareTag represents an article's flare tag, if the article
// has one.
type FlareTag struct {
Name string `json:"name"`
BGColorHex string `json:"bg_color_hex"`
TextColorHex string `json:"text_color_hex"`
}
//ArticleListOptions holds the query values to pass as
//query string parameter to the Articles List action.
// Tags are a group of topics related to an article
type Tags []string
// This deserialization logic is so that if a listed article
// originates from the /articles endpoint instead of
// /articles/me/*, its Published field is returned as true,
// since /articles exclusively returns articles that have been
// published.
type listedArticleJSON struct {
TypeOf string `json:"type_of,omitempty"`
ID uint32 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
CoverImage *WebURL `json:"cover_image,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
PublishedTimestamp string `json:"published_timestamp,omitempty"`
TagList Tags `json:"tag_list,omitempty"`
Slug string `json:"slug,omitempty"`
Path string `json:"path,omitempty"`
URL *WebURL `json:"url,omitempty"`
CanonicalURL *WebURL `json:"canonical_url,omitempty"`
CommentsCount uint `json:"comments_count,omitempty"`
PositiveReactionsCount uint `json:"positive_reactions_count,omitempty"`
User User `json:"user,omitempty"`
Organization *Organization `json:"organization,omitempty"`
FlareTag *FlareTag `json:"flare_tag,omitempty"`
BodyMarkdown string `json:"body_markdown,omitempty"`
Published *bool `json:"published,omitempty"`
}
func (j *listedArticleJSON) listedArticle() ListedArticle {
a := ListedArticle{
TypeOf: j.TypeOf,
ID: j.ID,
Title: j.Title,
Description: j.Description,
CoverImage: j.CoverImage,
PublishedAt: j.PublishedAt,
PublishedTimestamp: j.PublishedTimestamp,
TagList: j.TagList,
Slug: j.Slug,
Path: j.Path,
URL: j.URL,
CanonicalURL: j.CanonicalURL,
CommentsCount: j.CommentsCount,
PositiveReactionsCount: j.PositiveReactionsCount,
User: j.User,
Organization: j.Organization,
FlareTag: j.FlareTag,
BodyMarkdown: j.BodyMarkdown,
}
if j.Published != nil {
a.Published = *j.Published
} else {
// "published" currently is included in the API
// response for dev.to's /articles/me/* endpoints,
// but not in /articles, so we are setting this
// to true since /articles only returns articles
// that are published.
a.Published = true
}
return a
}
// ListedArticle represents an article returned from one of
// the list articles endpoints (/articles, /articles/me/*).
type ListedArticle struct {
TypeOf string `json:"type_of,omitempty"`
ID uint32 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
CoverImage *WebURL `json:"cover_image,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
PublishedTimestamp string `json:"published_timestamp,omitempty"`
TagList Tags `json:"tag_list,omitempty"`
Slug string `json:"slug,omitempty"`
Path string `json:"path,omitempty"`
URL *WebURL `json:"url,omitempty"`
CanonicalURL *WebURL `json:"canonical_url,omitempty"`
CommentsCount uint `json:"comments_count,omitempty"`
PositiveReactionsCount uint `json:"positive_reactions_count,omitempty"`
User User `json:"user,omitempty"`
Organization *Organization `json:"organization,omitempty"`
FlareTag *FlareTag `json:"flare_tag,omitempty"`
// Only present in "/articles/me/*" endpoints
BodyMarkdown string `json:"body_markdown,omitempty"`
Published bool `json:"published,omitempty"`
}
// UnmarshalJSON implements the JSON Unmarshaler interface.
func (a *ListedArticle) UnmarshalJSON(b []byte) error {
var j listedArticleJSON
if err := json.Unmarshal(b, &j); err != nil {
return err
}
*a = j.listedArticle()
return nil
}
// Article contains all the information related to a single
// information resource from devto.
type Article struct {
TypeOf string `json:"type_of,omitempty"`
ID uint32 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
CoverImage *WebURL `json:"cover_image,omitempty"`
SocialImage *WebURL `json:"social_image,omitempty"`
ReadablePublishDate string `json:"readable_publish_date"`
Published bool `json:"published,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
EditedAt *time.Time `json:"edited_at,omitempty"`
CrossPostedAt *time.Time `json:"crossposted_at,omitempty"`
LastCommentAt *time.Time `json:"last_comment_at,omitempty"`
TagList string `json:"tag_list,omitempty"`
Tags Tags `json:"tags,omitempty"`
Slug string `json:"slug,omitempty"`
Path *WebURL `json:"path,omitempty"`
URL *WebURL `json:"url,omitempty"`
CanonicalURL *WebURL `json:"canonical_url,omitempty"`
CommentsCount uint `json:"comments_count,omitempty"`
PositiveReactionsCount uint `json:"positive_reactions_count,omitempty"`
User User `json:"user,omitempty"`
BodyHTML string `json:"body_html,omitempty"`
BodyMarkdown string `json:"body_markdown,omitempty"`
}
// ArticleUpdate represents an update to an article; it is
// used as the payload in POST and PUT requests for writing
// articles.
type ArticleUpdate struct {
Title string `json:"title"`
BodyMarkdown string `json:"body_markdown"`
Published bool `json:"published"`
Series *string `json:"series"`
MainImage string `json:"main_image,omitempty"`
CanonicalURL string `json:"canonical_url,omitempty"`
Description string `json:"description,omitempty"`
Tags []string `json:"tags,omitempty"`
OrganizationID int32 `json:"organization_id,omitempty"`
}
// ArticleListOptions holds the query values to pass as
// query string parameter to the Articles List action.
type ArticleListOptions struct {
Tags string `url:"tag,omitempty"`
Username string `url:"username,omitempty"`
@@ -69,12 +192,21 @@ type ArticleListOptions struct {
Page int `url:"page,omitempty"`
}
// MyArticlesOptions defines pagination options used as query
// params in the dev.to "list my articles" endpoints.
type MyArticlesOptions struct {
Page int `url:"page,omitempty"`
PerPage int `url:"per_page,omitempty"`
}
// WebURL is a class embed to override default unmarshal
// behavior.
type WebURL struct {
*url.URL
}
//UnmarshalJSON overrides the default unmarshal behaviour
//for URL
// UnmarshalJSON overrides the default unmarshal behaviour
// for URL
func (s *WebURL) UnmarshalJSON(b []byte) error {
c := string(b)
c = strings.Trim(c, "\"")
@@ -85,3 +217,14 @@ func (s *WebURL) UnmarshalJSON(b []byte) error {
s.URL = uri
return nil
}
// ErrorResponse is an error returned from a dev.to API
// endpoint.
type ErrorResponse struct {
ErrorMessage string `json:"error"`
Status int `json:"status"`
}
func (e *ErrorResponse) Error() string {
return fmt.Sprintf(`%d error: "%s"`, e.Status, e.ErrorMessage)
}

View File

@@ -0,0 +1,38 @@
package devto
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
// nonSuccessfulResponse indicates that the status code for
// the HTTP response passed in was not one of the "success"
// (2XX) status codes
func nonSuccessfulResponse(res *http.Response) bool { return res.StatusCode/100 != 2 }
// attempt to deserialize the error response; if it succeeds,
// the error will be an ErrorResponse, otherwise it will be
// an error indicating that the error response could not be
// deserialized.
func unmarshalErrorResponse(res *http.Response) error {
var e ErrorResponse
if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
return fmt.Errorf(
`unexpected error deserializing %d response: "%v"`,
res.StatusCode,
err,
)
}
return &e
}
func decodeResponse(r *http.Response) []byte {
c, err := ioutil.ReadAll(r.Body)
if err != nil {
return []byte("")
}
defer r.Body.Close()
return c
}