client.go 3.09 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package api

import (
Jeffrey Morgan's avatar
Jeffrey Morgan committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
6
7
	"bytes"
	"context"
	"encoding/json"
Michael Yang's avatar
Michael Yang committed
8
	"errors"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
9
10
11
	"fmt"
	"io"
	"net/http"
Bruce MacDonald's avatar
Bruce MacDonald committed
12
	"sync"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
13
14
15
)

type Client struct {
Bruce MacDonald's avatar
Bruce MacDonald committed
16
17
	URL  string
	HTTP http.Client
Jeffrey Morgan's avatar
Jeffrey Morgan committed
18
19
20
21
22
23
24
25
26
}

func checkError(resp *http.Response, body []byte) error {
	if resp.StatusCode >= 200 && resp.StatusCode < 400 {
		return nil
	}

	apiError := Error{Code: int32(resp.StatusCode)}

Michael Yang's avatar
Michael Yang committed
27
	if err := json.Unmarshal(body, &apiError); err != nil {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
28
29
30
31
32
33
34
		// Use the full body as the message if we fail to decode a response.
		apiError.Message = string(body)
	}

	return apiError
}

Bruce MacDonald's avatar
Bruce MacDonald committed
35
func (c *Client) stream(ctx context.Context, method string, path string, reqData any, callback func(data []byte)) error {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	var reqBody io.Reader
	var data []byte
	var err error
	if reqData != nil {
		data, err = json.Marshal(reqData)
		if err != nil {
			return err
		}
		reqBody = bytes.NewReader(data)
	}

	url := fmt.Sprintf("%s%s", c.URL, path)

	req, err := http.NewRequestWithContext(ctx, method, url, reqBody)
	if err != nil {
		return err
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
54
55
56
57
58
59
60
61
62
63
64
65
66
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	res, err := c.HTTP.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	reader := bufio.NewReader(res.Body)

	for {
		line, err := reader.ReadBytes('\n')
Michael Yang's avatar
Michael Yang committed
67
68
69
70
71
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case err != nil:
			return err
Bruce MacDonald's avatar
Bruce MacDonald committed
72
		}
Michael Yang's avatar
Michael Yang committed
73

Bruce MacDonald's avatar
Bruce MacDonald committed
74
75
		if err := checkError(res, line); err != nil {
			return err
Jeffrey Morgan's avatar
Jeffrey Morgan committed
76
		}
Michael Yang's avatar
Michael Yang committed
77

Jeffrey Morgan's avatar
Jeffrey Morgan committed
78
79
80
81
82
83
84
85
86
87
		callback(bytes.TrimSuffix(line, []byte("\n")))
	}
}

func (c *Client) do(ctx context.Context, method string, path string, reqData any, respData any) error {
	var reqBody io.Reader
	var data []byte
	var err error
	if reqData != nil {
		data, err = json.Marshal(reqData)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
88
89
90
		if err != nil {
			return err
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
91
92
93
94
95
96
97
98
		reqBody = bytes.NewReader(data)
	}

	url := fmt.Sprintf("%s%s", c.URL, path)

	req, err := http.NewRequestWithContext(ctx, method, url, reqBody)
	if err != nil {
		return err
Jeffrey Morgan's avatar
Jeffrey Morgan committed
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	respObj, err := c.HTTP.Do(req)
	if err != nil {
		return err
	}
	defer respObj.Body.Close()

	respBody, err := io.ReadAll(respObj.Body)
	if err != nil {
		return err
	}

	if err := checkError(respObj, respBody); err != nil {
		return err
	}

	if len(respBody) > 0 && respData != nil {
		if err := json.Unmarshal(respBody, respData); err != nil {
			return err
		}
	}
	return nil
}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
126

Michael Yang's avatar
Michael Yang committed
127
func (c *Client) Generate(ctx context.Context, req *GenerateRequest, callback func(bts []byte)) (*GenerateResponse, error) {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
128
	var res GenerateResponse
Michael Yang's avatar
Michael Yang committed
129
	if err := c.stream(ctx, http.MethodPost, "/api/generate", req, callback); err != nil {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
130
131
132
133
134
		return nil, err
	}

	return &res, nil
}
Bruce MacDonald's avatar
Bruce MacDonald committed
135

Bruce MacDonald's avatar
Bruce MacDonald committed
136
137
138
func (c *Client) Pull(ctx context.Context, req *PullRequest, callback func(progress PullProgress)) error {
	var wg sync.WaitGroup
	wg.Add(1)
Bruce MacDonald's avatar
Bruce MacDonald committed
139
140
	if err := c.stream(ctx, http.MethodPost, "/api/pull", req, func(progressBytes []byte) {
		var progress PullProgress
Michael Yang's avatar
Michael Yang committed
141
		if err := json.Unmarshal(progressBytes, &progress); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
142
143
144
			fmt.Println(err)
			return
		}
Bruce MacDonald's avatar
Bruce MacDonald committed
145
146
147
		if progress.Completed >= progress.Total {
			wg.Done()
		}
Bruce MacDonald's avatar
Bruce MacDonald committed
148
		callback(progress)
Bruce MacDonald's avatar
Bruce MacDonald committed
149
	}); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
150
		return err
Bruce MacDonald's avatar
Bruce MacDonald committed
151
152
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
153
154
	wg.Wait()
	return nil
Bruce MacDonald's avatar
Bruce MacDonald committed
155
}