Skip to content

Commit c5a9eb5

Browse files
authored
Merge pull request #9 from mutablelogic/v1
Various updates
2 parents 5838d37 + bb8ebdd commit c5a9eb5

29 files changed

+1357
-41
lines changed

README.md

+160-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,161 @@
11
# go-client
2-
REST API Client for various services
2+
3+
This repository contains a generic HTTP client which can be adapted to provide:
4+
5+
* General HTTP methods for GET and POST of data
6+
* Ability to send and receive JSON, plaintext and XML data
7+
* Ability to send files and data of type `multipart/form-data`
8+
* Ability to send data of type `application/x-www-form-urlencoded`
9+
* Debugging capabilities to see the request and response data
10+
11+
## Basic Usage
12+
13+
The following example shows how to decode a response from a GET request
14+
to a JSON endpoint:
15+
16+
```go
17+
package main
18+
19+
import (
20+
client "github.com/mutablelogic/go-client"
21+
)
22+
23+
func main() {
24+
// Create a new client
25+
c := client.New(client.OptEndpoint("https://api.example.com/api/v1"))
26+
27+
// Send a GET request, populating a struct with the response
28+
var response struct {
29+
Message string `json:"message"`
30+
}
31+
if err := c.Do(nil, &response, OptPath("test")); err != nil {
32+
// Handle error
33+
}
34+
35+
// Print the response
36+
fmt.Println(response.Message)
37+
}
38+
```
39+
40+
Various options can be passed to the client `New` method to control its behaviour:
41+
42+
* `OptEndpoint(value string)` sets the endpoint for all requests
43+
* `OptTimeout(value time.Duration)` sets the timeout on any request, which defaults to 10 seconds
44+
* `OptUserAgent(value string)` sets the user agent string on each API request
45+
* `OptTrace(w io.Writer, verbose bool)` allows you to debug the request and response data.
46+
When `verbose` is set to true, it also displays the payloads
47+
* `OptStrict()` turns on strict content type checking on anything returned from the API
48+
* `OptRateLimit(value float32)` sets the limit on number of requests per second and the API will sleep to regulate
49+
the rate limit when exceeded
50+
* `OptReqToken(value Token)` sets a request token for all client requests. This can be overridden by the client
51+
for individual requests using `OptToken`
52+
* `OptSkipVerify()` skips TLS certificate domain verification
53+
* `OptHeader(key, value string)` appends a custom header to each request
54+
55+
## Usage with a payload
56+
57+
The first argument to the `Do` method is the payload to send to the server, when set. You can create a payload
58+
using the following methods:
59+
60+
* `client.NewRequest(accept string)` returns a new empty payload which defaults to GET. The accept parameter is the
61+
accepted mime-type of the response.
62+
* `client.NewJSONRequest(payload any, accept string)` returns a new request with a JSON payload which defaults to GET.
63+
* `client.NewMultipartRequest(payload any, accept string)` returns a new request with a Multipart Form data payload which
64+
defaults to POST.
65+
* `client.NewFormRequest(payload any, accept string)` returns a new request with a Form data payload which defaults to POST.
66+
67+
For example,
68+
69+
```go
70+
package main
71+
72+
import (
73+
client "github.com/mutablelogic/go-client"
74+
)
75+
76+
func main() {
77+
// Create a new client
78+
c := client.New(client.OptEndpoint("https://api.example.com/api/v1"))
79+
80+
// Send a GET request, populating a struct with the response
81+
var request struct {
82+
Prompt string `json:"prompt"`
83+
}
84+
var response struct {
85+
Reply string `json:"reply"`
86+
}
87+
request.Prompt = "Hello, world!"
88+
payload := client.NewJSONRequest(request, "application/json")
89+
if err := c.Do(payload, &response, OptPath("test")); err != nil {
90+
// Handle error
91+
}
92+
93+
// Print the response
94+
fmt.Println(response.Reply)
95+
}
96+
```
97+
98+
You can also implement your own payload by implementing the `Payload` interface:
99+
100+
```go
101+
type Payload interface {
102+
io.Reader
103+
104+
// The method to use to send the payload
105+
Method() string
106+
107+
// The content type of the payload
108+
Type() string
109+
110+
// The content type which is accepted as a response, or empty string if any
111+
Accept() string
112+
}
113+
```
114+
115+
### Request options
116+
117+
The signature of the `Do` method is:
118+
119+
```go
120+
type Client interface {
121+
// Perform request and wait for response
122+
Do(in Payload, out any, opts ...RequestOpt) error
123+
124+
// Perform request and wait for response, with context for cancellation
125+
DoWithContext(ctx context.Context, in Payload, out any, opts ...RequestOpt) error
126+
}
127+
```
128+
129+
Various options can be passed to modify each individual request when using the `Do` method:
130+
131+
* `OptReqEndpoint(value string)` sets the endpoint for the request
132+
* `OptPath(value ...string)` appends path elements onto a request endpoint
133+
* `OptToken(value Token)` adds an authorization header (overrides the client OptReqToken option)
134+
* `OptQuery(value url.Values)` sets the query parameters to a request
135+
* `OptHeader(key, value string)` appends a custom header to the request
136+
137+
138+
### Authentication
139+
140+
The authentication token can be set as follows:
141+
142+
```go
143+
package main
144+
145+
import (
146+
client "github.com/mutablelogic/go-client"
147+
)
148+
149+
func main() {
150+
// Create a new client
151+
c := client.New(
152+
client.OptEndpoint("https://api.example.com/api/v1"),
153+
client.OptReqToken(client.Token{
154+
Scheme: "Bearer",
155+
Value: os.GetEnv("API_TOKEN"),
156+
}),
157+
)
158+
159+
// ...
160+
}
161+
```

cmd/cli/flags.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func NewFlags(name string, args []string, register ...FlagsRegister) (*Flags, er
4848
}
4949

5050
// Create a writer
51-
flags.writer = tablewriter.New(os.Stdout)
51+
flags.writer = tablewriter.New(os.Stdout, tablewriter.OptOutputText())
5252

5353
// Return success
5454
return flags, nil

cmd/cli/main.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
func main() {
1515
name := path.Base(os.Args[0])
16-
flags, err := NewFlags(name, os.Args[1:], OpenAIFlags, MistralFlags, ElevenlabsFlags, HomeAssistantFlags)
16+
flags, err := NewFlags(name, os.Args[1:], OpenAIFlags, MistralFlags, ElevenlabsFlags, HomeAssistantFlags, NewsAPIFlags)
1717
if err != nil {
1818
if err != flag.ErrHelp {
1919
fmt.Fprintln(os.Stderr, err)
@@ -65,6 +65,12 @@ func main() {
6565
os.Exit(1)
6666
}
6767

68+
cmd, err = NewsAPIRegister(cmd, opts, flags)
69+
if err != nil {
70+
fmt.Fprintln(os.Stderr, err)
71+
os.Exit(1)
72+
}
73+
6874
// Run command
6975
if err := Run(cmd, flags); err != nil {
7076
if errors.Is(err, flag.ErrHelp) {

cmd/cli/newsapi.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
// Package imports
5+
"github.com/mutablelogic/go-client/pkg/client"
6+
"github.com/mutablelogic/go-client/pkg/newsapi"
7+
)
8+
9+
/////////////////////////////////////////////////////////////////////
10+
// REGISTER FUNCTIONS
11+
12+
func NewsAPIFlags(flags *Flags) {
13+
flags.String("news-api-key", "${NEWSAPI_KEY}", "NewsAPI key")
14+
flags.String("q", "", "Search query")
15+
}
16+
17+
func NewsAPIRegister(cmd []Client, opts []client.ClientOpt, flags *Flags) ([]Client, error) {
18+
newsapi, err := newsapi.New(flags.GetString("news-api-key"), opts...)
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
// Register commands
24+
cmd = append(cmd, Client{
25+
ns: "newsapi",
26+
cmd: []Command{
27+
{Name: "sources", Description: "Return news sources", MinArgs: 2, MaxArgs: 2, Fn: newsAPISources(newsapi, flags)},
28+
{Name: "headlines", Description: "Return news headlines", MinArgs: 2, MaxArgs: 2, Fn: newsAPIHeadlines(newsapi, flags)},
29+
{Name: "articles", Description: "Return news articles", MinArgs: 2, MaxArgs: 2, Fn: newsAPIArticles(newsapi, flags)},
30+
},
31+
})
32+
33+
// Return success
34+
return cmd, nil
35+
}
36+
37+
/////////////////////////////////////////////////////////////////////
38+
// API CALL FUNCTIONS
39+
40+
func newsAPISources(client *newsapi.Client, flags *Flags) CommandFn {
41+
return func() error {
42+
if sources, err := client.Sources(); err != nil {
43+
return err
44+
} else {
45+
return flags.Write(sources)
46+
}
47+
}
48+
}
49+
50+
func newsAPIHeadlines(client *newsapi.Client, flags *Flags) CommandFn {
51+
return func() error {
52+
opts := []newsapi.Opt{}
53+
if q := flags.GetString("q"); q != "" {
54+
opts = append(opts, newsapi.OptQuery(q))
55+
}
56+
if articles, err := client.Headlines(opts...); err != nil {
57+
return err
58+
} else {
59+
return flags.Write(articles)
60+
}
61+
}
62+
}
63+
64+
func newsAPIArticles(client *newsapi.Client, flags *Flags) CommandFn {
65+
return func() error {
66+
opts := []newsapi.Opt{}
67+
if q := flags.GetString("q"); q != "" {
68+
opts = append(opts, newsapi.OptQuery(q))
69+
}
70+
if articles, err := client.Articles(opts...); err != nil {
71+
return err
72+
} else {
73+
return flags.Write(articles)
74+
}
75+
}
76+
}

go.mod

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
module github.com/mutablelogic/go-client
22

3-
go 1.21.5
3+
go 1.22
4+
5+
toolchain go1.22.3
46

57
require (
8+
github.com/andreburgaud/crypt2go v1.5.0
69
github.com/djthorpe/go-errors v1.0.3
7-
github.com/djthorpe/go-tablewriter v0.0.0-20240507095049-af1f79a27517
10+
github.com/djthorpe/go-tablewriter v0.0.2
811
github.com/pkg/errors v0.9.1
912
github.com/stretchr/testify v1.9.0
13+
github.com/xdg-go/pbkdf2 v1.0.0
1014
)
1115

1216
require (
1317
github.com/davecgh/go-spew v1.1.1 // indirect
18+
github.com/mattn/go-runewidth v0.0.15 // indirect
1419
github.com/pmezard/go-difflib v1.0.0 // indirect
20+
github.com/rivo/uniseg v0.4.7 // indirect
1521
gopkg.in/yaml.v3 v3.0.1 // indirect
1622
)

go.sum

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
github.com/andreburgaud/crypt2go v1.5.0 h1:7hz8l9WjaMEtAUL4+nMm64Of7HzUr1H4JhmNof7BCLc=
2+
github.com/andreburgaud/crypt2go v1.5.0/go.mod h1:ZEu8s+aLbZdRNdSHr//o6gCSMYKgT24sjNX6r4uAI8U=
13
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
github.com/djthorpe/go-errors v1.0.3 h1:GZeMPkC1mx2vteXLI/gvxZS0Ee9zxzwD1mcYyKU5jD0=
46
github.com/djthorpe/go-errors v1.0.3/go.mod h1:HtfrZnMd6HsX75Mtbv9Qcnn0BqOrrFArvCaj3RMnZhY=
5-
github.com/djthorpe/go-tablewriter v0.0.0-20240507095049-af1f79a27517 h1:UOUbtOLKMmupwpbYq1YG28pXDHJOlrznNX2vavHGhKk=
6-
github.com/djthorpe/go-tablewriter v0.0.0-20240507095049-af1f79a27517/go.mod h1:M0TwrksgC73IdXcj9ZXtpuw2Tkl7jdQi/mFPOo1I/4M=
7+
github.com/djthorpe/go-tablewriter v0.0.2 h1:3TuafWr/m+7/Cfq2CrpkewVIp11fEgr80VxokPWhOVA=
8+
github.com/djthorpe/go-tablewriter v0.0.2/go.mod h1:e+aurk5hOhszDXN42nlnZZ8nBT+8vU6/Qro1avS2cMI=
9+
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
10+
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
711
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
812
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
913
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1014
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
16+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
17+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
1118
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
1219
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
20+
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
21+
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
1322
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1423
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1524
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

pkg/bitwarden/auth.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package bitwarden
2+
3+
///////////////////////////////////////////////////////////////////////////////
4+
// TYPES
5+
6+
type Register struct {
7+
Name string `json:"name"`
8+
Email string `json:"email"`
9+
MasterPasswordHash string `json:"masterPasswordHash"`
10+
MasterPasswordHint string `json:"masterPasswordHint"`
11+
Key string `json:"key"`
12+
Kdf uint `json:"kdf"`
13+
KdfIterations uint `json:"kdfIterations"`
14+
}
15+
16+
///////////////////////////////////////////////////////////////////////////////
17+
// PUBLIC METHODS
18+
19+
func (c *Client) Register(name, email, password string) {
20+
// TODO
21+
}

0 commit comments

Comments
 (0)