From dc62ffd84a6edc9f15111f490ecb0e382fc12933 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Sun, 5 May 2024 16:25:46 +0200 Subject: [PATCH 01/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Client=201/n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/dependabot.yaml | 10 +++++ client.go | 86 +++++++++++++++++++++++++++++++++++++++++ client_test.go | 25 ++++++++++++ go.mod | 5 +++ version.go | 20 ++++++++++ 5 files changed, 146 insertions(+) create mode 100644 .github/dependabot.yaml create mode 100644 client.go create mode 100644 client_test.go create mode 100644 go.mod create mode 100644 version.go diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..731490b --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,10 @@ +version: 2 +updates: + + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + timezone: "Europe/Kyiv" + day: "friday" + time: "18:00" diff --git a/client.go b/client.go new file mode 100644 index 0000000..86fdb91 --- /dev/null +++ b/client.go @@ -0,0 +1,86 @@ +package glide + +import ( + "net/http" + "net/url" +) + +// Client is a minimal 'Glide' client. +type Client struct { + ApiKey string + UserAgent string + BaseURL *url.URL + + httpClient *http.Client +} + +type ClientOption func(*Client) error + +// NewClient instantiates a new Client. +func NewClient(options ...ClientOption) (*Client, error) { + options = append([]ClientOption{ + WithApiKey(envApiKey), + WithUserAgent(envUserAgent), + WithBaseURL(envBaseUrl), + WithHttpClient(http.DefaultClient), + }, options...) + + client := &Client{} + for _, option := range options { + if err := option(client); err != nil { + return nil, err + } + } + + return client, nil +} + +// WithApiKey replaces the api key. +// Default value: 'development'. +// Env variable 'GLIDE_API_KEY'. +func WithApiKey(apiKey string) ClientOption { + return func(client *Client) error { + client.ApiKey = apiKey + return nil + } +} + +// WithUserAgent replaces the 'User-Agent' header. +// Default value: 'glide-go/0.1.0'. +// Env variable: 'GLIDE_USER_AGENT'. +func WithUserAgent(userAgent string) ClientOption { + return func(client *Client) error { + client.UserAgent = userAgent + return nil + } +} + +// WithBaseURL replaces the 'base' Url. +// Default value: 'https://api.einstack.com'. +// Env variable: 'GLIDE_BASE_URL'. +func WithBaseURL(baseURL string) ClientOption { + return func(client *Client) error { + parsed, err := url.Parse(baseURL) + if err != nil { + return err + } + + client.BaseURL = parsed + return nil + } +} + +// WithHttpClient replaces the 'HTTP' client. +// Default value: 'http.DefaultClient'. +func WithHttpClient(httpClient *http.Client) ClientOption { + return func(client *Client) error { + client.httpClient = httpClient + return nil + } +} + +// Health returns nil if the service is healthy. +func (c *Client) Health() error { + // TODO. + return nil +} diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..b7755e3 --- /dev/null +++ b/client_test.go @@ -0,0 +1,25 @@ +package glide_test + +import ( + "github.com/einstack/glide-go" + "testing" +) + +func TestNewClient(t *testing.T) { + if _, err := glide.NewClient( + glide.WithApiKey("testing"), + glide.WithUserAgent("Axiston/1.0"), + ); err != nil { + t.Error(err) + } +} + +func TestClient_Health(t *testing.T) { + client, _ := glide.NewClient( + glide.WithApiKey("testing"), + ) + + if err := client.Health(); err != nil { + t.Error(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..257346a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +// https://go.dev/ref/mod + +module github.com/einstack/glide-go + +go 1.22.2 diff --git a/version.go b/version.go new file mode 100644 index 0000000..283c4e1 --- /dev/null +++ b/version.go @@ -0,0 +1,20 @@ +package glide + +import ( + "fmt" + "os" +) + +// Version is a supported API version. +var Version string = "0.1.0" + +var envApiKey string = getEnv("GLIDE_API_KEY", "development") +var envUserAgent string = getEnv("GLIDE_USER_AGENT", fmt.Sprintf("glide-go/%s", Version)) +var envBaseUrl string = getEnv("GLIDE_BASE_URL", "https://api.einstack.com") + +func getEnv(key, df string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return df +} From e1103a65bb5945334ff347e64c838848c42a088d Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Sun, 5 May 2024 17:25:53 +0200 Subject: [PATCH 02/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Client=202/n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 14 +++++++++----- client_test.go | 9 ++++++--- language.go | 36 ++++++++++++++++++++++++++++++++++++ language_test.go | 27 +++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 language.go create mode 100644 language_test.go diff --git a/client.go b/client.go index 86fdb91..7d151c2 100644 --- a/client.go +++ b/client.go @@ -1,17 +1,19 @@ package glide import ( + "context" "net/http" "net/url" ) // Client is a minimal 'Glide' client. type Client struct { - ApiKey string - UserAgent string - BaseURL *url.URL - + ApiKey string + UserAgent string + BaseURL *url.URL httpClient *http.Client + + Language LanguageSvc } type ClientOption func(*Client) error @@ -26,6 +28,8 @@ func NewClient(options ...ClientOption) (*Client, error) { }, options...) client := &Client{} + client.Language = &language{client} + for _, option := range options { if err := option(client); err != nil { return nil, err @@ -80,7 +84,7 @@ func WithHttpClient(httpClient *http.Client) ClientOption { } // Health returns nil if the service is healthy. -func (c *Client) Health() error { +func (c *Client) Health(ctx context.Context) error { // TODO. return nil } diff --git a/client_test.go b/client_test.go index b7755e3..21bee6c 100644 --- a/client_test.go +++ b/client_test.go @@ -1,14 +1,16 @@ package glide_test import ( - "github.com/einstack/glide-go" + "context" "testing" + + "github.com/einstack/glide-go" ) func TestNewClient(t *testing.T) { if _, err := glide.NewClient( glide.WithApiKey("testing"), - glide.WithUserAgent("Axiston/1.0"), + glide.WithUserAgent("Einstack/1.0"), ); err != nil { t.Error(err) } @@ -19,7 +21,8 @@ func TestClient_Health(t *testing.T) { glide.WithApiKey("testing"), ) - if err := client.Health(); err != nil { + ctx := context.Background() + if err := client.Health(ctx); err != nil { t.Error(err) } } diff --git a/language.go b/language.go new file mode 100644 index 0000000..b63cdc0 --- /dev/null +++ b/language.go @@ -0,0 +1,36 @@ +package glide + +import "context" + +// RouterConfig TODO. +type RouterConfig struct { +} + +// ChatRequest TODO. +type ChatRequest struct { +} + +// ChatResponse TODO. +type ChatResponse struct { +} + +// LanguageSvc implements APIs for '/v1/language' endpoints. +type LanguageSvc interface { + List(ctx context.Context) ([]RouterConfig, error) + Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) + // TODO. ChatStream(ctx context.Context) (<-chan ChatResponse, error) +} + +type language struct { + client *Client +} + +func (impl *language) List(ctx context.Context) ([]RouterConfig, error) { + // TODO. + return nil, nil +} + +func (impl *language) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { + // TODO. + return nil, nil +} diff --git a/language_test.go b/language_test.go new file mode 100644 index 0000000..f9311cf --- /dev/null +++ b/language_test.go @@ -0,0 +1,27 @@ +package glide_test + +import ( + "context" + "testing" + + "github.com/einstack/glide-go" +) + +func TestLanguage_List(t *testing.T) { + client, _ := glide.NewClient() + + ctx := context.Background() + if _, err := client.Language.List(ctx); err != nil { + t.Error(err) + } +} + +func TestLanguage_Chat(t *testing.T) { + client, _ := glide.NewClient() + + ctx := context.Background() + req := glide.ChatRequest{} + if _, err := client.Language.Chat(ctx, req); err != nil { + t.Error(err) + } +} From e3d0efbf8b1d40b0b6d3f497259cfe8f97585ef9 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Sun, 5 May 2024 21:17:11 +0200 Subject: [PATCH 03/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Client=203/n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yaml | 33 +++++++++++++++++ Makefile | 11 ++++++ client.go | 71 ++++++++++++++++++++++++++++++++++-- language.go | 14 +++++-- language_test.go | 2 +- variable.go | 24 ++++++++++++ version.go | 20 ---------- 7 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 Makefile create mode 100644 variable.go delete mode 100644 version.go diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..b7dbc25 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,33 @@ +on: + push: + branches: + - main + pull_request: + branches: + - main + +name: Build + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + version: [ 1.22 ] + runs-on: ${{ matrix.os }} + steps: + + - name: Check out + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.version }} + check-latest: true + + - name: Run Make:lint + run: make lint + + - name: Run Make:test + run: make test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8a5d9de --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +lint: ## Lint the source code + @echo "🧹 Cleaning go.mod.." + @go mod tidy + @echo "🧹 Formatting files.." + @go fmt ./... + @echo "🧹 Vetting go.mod.." + @go vet ./... + +test: ## Run tests + @echo "⏱️ Running tests.." + @go test -v -count=1 -race -shuffle=on -coverprofile=coverage.txt ./... diff --git a/client.go b/client.go index 7d151c2..37d5f6e 100644 --- a/client.go +++ b/client.go @@ -1,7 +1,12 @@ package glide import ( + "bytes" "context" + "encoding/json" + "errors" + "fmt" + "io" "net/http" "net/url" ) @@ -50,7 +55,7 @@ func WithApiKey(apiKey string) ClientOption { } // WithUserAgent replaces the 'User-Agent' header. -// Default value: 'glide-go/0.1.0'. +// Default value: 'Glide/0.1.0 (Go; Ver. 1.22.2)'. // Env variable: 'GLIDE_USER_AGENT'. func WithUserAgent(userAgent string) ClientOption { return func(client *Client) error { @@ -60,7 +65,7 @@ func WithUserAgent(userAgent string) ClientOption { } // WithBaseURL replaces the 'base' Url. -// Default value: 'https://api.einstack.com'. +// Default value: 'http://127.0.0.1:9099/'. // Env variable: 'GLIDE_BASE_URL'. func WithBaseURL(baseURL string) ClientOption { return func(client *Client) error { @@ -83,8 +88,68 @@ func WithHttpClient(httpClient *http.Client) ClientOption { } } +// Build instantiates a new http.Request. +func (c *Client) Build(ctx context.Context, method, path string, data any) (*http.Request, error) { + abs, err := c.BaseURL.Parse(path) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, method, abs.String(), nil) + if err != nil { + return nil, err + } + + if data != nil { + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(data); err != nil { + return nil, err + } + + req.Body = io.NopCloser(buf) + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", c.UserAgent) + req.Header.Set("Authorization", "Bearer "+c.ApiKey) + + return req, nil +} + +// Send sends an http.Request and decodes http.Response into ret. +func (c *Client) Send(r *http.Request, ret any) (*http.Response, error) { + resp, err := c.httpClient.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode > http.StatusBadRequest { + // TODO: Decode into ErrorResponse. + reason := fmt.Sprintf("status code: %d", resp.StatusCode) + return nil, errors.New(reason) + } + + if resp.StatusCode != http.StatusNoContent && ret != nil && resp.Body != nil { + if err = json.NewDecoder(resp.Body).Decode(ret); err != nil { + return nil, err + } + } + + return resp, nil +} + // Health returns nil if the service is healthy. func (c *Client) Health(ctx context.Context) error { - // TODO. + req, err := c.Build(ctx, http.MethodGet, "/v1/health/", nil) + if err != nil { + return err + } + + if _, err := c.Send(req, nil); err != nil { + return err + } + return nil } diff --git a/language.go b/language.go index b63cdc0..58567ca 100644 --- a/language.go +++ b/language.go @@ -1,6 +1,8 @@ package glide -import "context" +import ( + "context" +) // RouterConfig TODO. type RouterConfig struct { @@ -10,6 +12,12 @@ type RouterConfig struct { type ChatRequest struct { } +// NewChatRequest instantiates a new ChatRequest. +func NewChatRequest() ChatRequest { + // TODO. + return ChatRequest{} +} + // ChatResponse TODO. type ChatResponse struct { } @@ -25,12 +33,12 @@ type language struct { client *Client } -func (impl *language) List(ctx context.Context) ([]RouterConfig, error) { +func (svc *language) List(ctx context.Context) ([]RouterConfig, error) { // TODO. return nil, nil } -func (impl *language) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { +func (svc *language) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { // TODO. return nil, nil } diff --git a/language_test.go b/language_test.go index f9311cf..39e93c7 100644 --- a/language_test.go +++ b/language_test.go @@ -20,7 +20,7 @@ func TestLanguage_Chat(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - req := glide.ChatRequest{} + req := glide.NewChatRequest() if _, err := client.Language.Chat(ctx, req); err != nil { t.Error(err) } diff --git a/variable.go b/variable.go new file mode 100644 index 0000000..9c4631d --- /dev/null +++ b/variable.go @@ -0,0 +1,24 @@ +package glide + +import ( + "fmt" + "os" +) + +// ClientVersion is the current version of this client. +var ClientVersion string = "0.1.0" + +// GoVersion is the required version of the Go runtime. +var GoVersion string = "0.1.0" + +var envApiKey string = getEnv("GLIDE_API_KEY", "development") +var userAgent = fmt.Sprintf("Glide/%s (Go; Ver %s)", ClientVersion, GoVersion) +var envUserAgent string = getEnv("GLIDE_USER_AGENT", userAgent) +var envBaseUrl string = getEnv("GLIDE_BASE_URL", "http://127.0.0.1:9099/") + +func getEnv(key, df string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return df +} diff --git a/version.go b/version.go deleted file mode 100644 index 283c4e1..0000000 --- a/version.go +++ /dev/null @@ -1,20 +0,0 @@ -package glide - -import ( - "fmt" - "os" -) - -// Version is a supported API version. -var Version string = "0.1.0" - -var envApiKey string = getEnv("GLIDE_API_KEY", "development") -var envUserAgent string = getEnv("GLIDE_USER_AGENT", fmt.Sprintf("glide-go/%s", Version)) -var envBaseUrl string = getEnv("GLIDE_BASE_URL", "https://api.einstack.com") - -func getEnv(key, df string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - return df -} From 60b4222d53e75a15b68abba2a204bb4c854bd548 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 6 May 2024 22:02:49 +0200 Subject: [PATCH 04/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Client=203/n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 8 +++++--- language.go | 10 +++++++++- language_test.go | 13 +++++++++++-- variable.go | 12 ++++++------ 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 37d5f6e..9a450cf 100644 --- a/client.go +++ b/client.go @@ -44,8 +44,7 @@ func NewClient(options ...ClientOption) (*Client, error) { return client, nil } -// WithApiKey replaces the api key. -// Default value: 'development'. +// WithApiKey attaches the api key. // Env variable 'GLIDE_API_KEY'. func WithApiKey(apiKey string) ClientOption { return func(client *Client) error { @@ -112,7 +111,10 @@ func (c *Client) Build(ctx context.Context, method, path string, data any) (*htt req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", c.UserAgent) - req.Header.Set("Authorization", "Bearer "+c.ApiKey) + + if len(c.ApiKey) > 0 { + req.Header.Set("Authorization", "Bearer "+c.ApiKey) + } return req, nil } diff --git a/language.go b/language.go index 58567ca..c6f136e 100644 --- a/language.go +++ b/language.go @@ -24,9 +24,12 @@ type ChatResponse struct { // LanguageSvc implements APIs for '/v1/language' endpoints. type LanguageSvc interface { + // List retrieves a list of all router configs. List(ctx context.Context) ([]RouterConfig, error) + // Chat sends a single chat request to a specified router and retrieves the response. Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) - // TODO. ChatStream(ctx context.Context) (<-chan ChatResponse, error) + // ChatStream establishes a WebSocket connection for streaming chat messages from a specified router. + ChatStream(ctx context.Context) error } type language struct { @@ -42,3 +45,8 @@ func (svc *language) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, // TODO. return nil, nil } + +func (svc *language) ChatStream(ctx context.Context) error { + // TODO. + return nil +} diff --git a/language_test.go b/language_test.go index 39e93c7..1d00996 100644 --- a/language_test.go +++ b/language_test.go @@ -9,8 +9,8 @@ import ( func TestLanguage_List(t *testing.T) { client, _ := glide.NewClient() - ctx := context.Background() + if _, err := client.Language.List(ctx); err != nil { t.Error(err) } @@ -18,10 +18,19 @@ func TestLanguage_List(t *testing.T) { func TestLanguage_Chat(t *testing.T) { client, _ := glide.NewClient() - ctx := context.Background() + req := glide.NewChatRequest() if _, err := client.Language.Chat(ctx, req); err != nil { t.Error(err) } } + +func TestLanguage_ChatStream(t *testing.T) { + client, _ := glide.NewClient() + ctx := context.Background() + + if err := client.Language.ChatStream(ctx); err != nil { + t.Error(err) + } +} diff --git a/variable.go b/variable.go index 9c4631d..ed04a11 100644 --- a/variable.go +++ b/variable.go @@ -6,15 +6,15 @@ import ( ) // ClientVersion is the current version of this client. -var ClientVersion string = "0.1.0" +var ClientVersion = "0.1.0" // GoVersion is the required version of the Go runtime. -var GoVersion string = "0.1.0" +var GoVersion = "1.22.2" -var envApiKey string = getEnv("GLIDE_API_KEY", "development") -var userAgent = fmt.Sprintf("Glide/%s (Go; Ver %s)", ClientVersion, GoVersion) -var envUserAgent string = getEnv("GLIDE_USER_AGENT", userAgent) -var envBaseUrl string = getEnv("GLIDE_BASE_URL", "http://127.0.0.1:9099/") +var envApiKey = getEnv("GLIDE_API_KEY", "") +var userAgent = fmt.Sprintf("Glide/%s (Go; Ver. %s)", ClientVersion, GoVersion) +var envUserAgent = getEnv("GLIDE_USER_AGENT", userAgent) +var envBaseUrl = getEnv("GLIDE_BASE_URL", "http://127.0.0.1:9099/") func getEnv(key, df string) string { if value, ok := os.LookupEnv(key); ok { From 06b30ce39270552bea5b56a93458b914421dd74d Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Thu, 16 May 2024 10:58:45 +0200 Subject: [PATCH 05/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Client=205/n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yaml | 4 +-- .github/workflows/publish.yaml | 22 ++++++++++++ .gitignore | 1 - language.go | 45 ++++++++++++------------ schema.go | 63 ++++++++++++++++++++++++++++++++++ variable.go => vars.go | 8 +++-- 6 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/publish.yaml create mode 100644 schema.go rename variable.go => vars.go (70%) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b7dbc25..4e66c36 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,3 +1,5 @@ +name: Build + on: push: branches: @@ -6,8 +8,6 @@ on: branches: - main -name: Build - jobs: build: strategy: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..8735f1b --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,22 @@ +name: Publish + +on: + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest ] + version: [ 1.22 ] + runs-on: ${{ matrix.os }} + steps: + + - name: Check out + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.version }} + check-latest: true diff --git a/.gitignore b/.gitignore index b3a6792..02674d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # OS Thumbs.db .DS_Store -*.pdb # Editors .vs/ diff --git a/language.go b/language.go index c6f136e..b32c661 100644 --- a/language.go +++ b/language.go @@ -2,26 +2,9 @@ package glide import ( "context" + "net/http" ) -// RouterConfig TODO. -type RouterConfig struct { -} - -// ChatRequest TODO. -type ChatRequest struct { -} - -// NewChatRequest instantiates a new ChatRequest. -func NewChatRequest() ChatRequest { - // TODO. - return ChatRequest{} -} - -// ChatResponse TODO. -type ChatResponse struct { -} - // LanguageSvc implements APIs for '/v1/language' endpoints. type LanguageSvc interface { // List retrieves a list of all router configs. @@ -37,13 +20,31 @@ type language struct { } func (svc *language) List(ctx context.Context) ([]RouterConfig, error) { - // TODO. - return nil, nil + req, err := svc.client.Build(ctx, http.MethodGet, "/v1/list", nil) + if err != nil { + return nil, err + } + + var resp *RouterList + if _, err := svc.client.Send(req, resp); err != nil { + return nil, err + } + + return resp.Routers, nil } func (svc *language) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { - // TODO. - return nil, nil + req2, err := svc.client.Build(ctx, http.MethodPost, "/v1/chat", req) + if err != nil { + return nil, err + } + + var resp *ChatResponse + if _, err := svc.client.Send(req2, resp); err != nil { + return nil, err + } + + return resp, nil } func (svc *language) ChatStream(ctx context.Context) error { diff --git a/schema.go b/schema.go new file mode 100644 index 0000000..fc9710f --- /dev/null +++ b/schema.go @@ -0,0 +1,63 @@ +package glide + +// https://github.com/EinStack/glide/tree/develop/pkg/api/schemas + +// RouterConfig TODO. +type RouterConfig any + +// RouterList TODO. +type RouterList struct { + Routers []RouterConfig `json:"routers"` +} + +// ChatRequest TODO. +type ChatRequest struct { + Message ChatMessage `json:"message" validate:"required"` + MessageHistory []ChatMessage `json:"message_history"` + OverrideParams *OverrideChatRequest `json:"override_params,omitempty"` +} + +type OverrideChatRequest struct { + ModelID string `json:"model_id" validate:"required"` + Message ChatMessage `json:"message" validate:"required"` +} + +// NewChatRequest instantiates a new ChatRequest. +func NewChatRequest() ChatRequest { + // TODO. + return ChatRequest{} +} + +// ChatResponse TODO. +type ChatResponse struct { + ID string `json:"id,omitempty"` + Created int `json:"created_at,omitempty"` + Provider string `json:"provider_id,omitempty"` + RouterID string `json:"router_id,omitempty"` + ModelID string `json:"model_id,omitempty"` + ModelName string `json:"model_name,omitempty"` + Cached bool `json:"cached,omitempty"` + ModelResponse ModelResponse `json:"model_response,omitempty"` +} + +type ModelResponse struct { + Metadata map[string]string `json:"metadata,omitempty"` + Message ChatMessage `json:"message"` + TokenUsage TokenUsage `json:"token_usage"` +} + +type TokenUsage struct { + PromptTokens int `json:"prompt_tokens"` + ResponseTokens int `json:"response_tokens"` + TotalTokens int `json:"total_tokens"` +} + +type ChatMessage struct { + // The role of the author of this message. One of system, user, or assistant. + Role string `json:"role" validate:"required"` + // The content of the message. + Content string `json:"content" validate:"required"` + // The name of the author of this message. May contain a-z, A-Z, 0-9, and underscores, + // with a maximum length of 64 characters. + Name string `json:"name,omitempty"` +} diff --git a/variable.go b/vars.go similarity index 70% rename from variable.go rename to vars.go index ed04a11..2cf808c 100644 --- a/variable.go +++ b/vars.go @@ -6,13 +6,15 @@ import ( ) // ClientVersion is the current version of this client. -var ClientVersion = "0.1.0" +var clientVersion = "0.1.0" // GoVersion is the required version of the Go runtime. -var GoVersion = "1.22.2" +var goVersion = "1.22.2" + +// userAgent is a default User-Agent header value. +var userAgent = fmt.Sprintf("Glide/%s (Go; Ver. %s)", clientVersion, goVersion) var envApiKey = getEnv("GLIDE_API_KEY", "") -var userAgent = fmt.Sprintf("Glide/%s (Go; Ver. %s)", ClientVersion, GoVersion) var envUserAgent = getEnv("GLIDE_USER_AGENT", userAgent) var envBaseUrl = getEnv("GLIDE_BASE_URL", "http://127.0.0.1:9099/") From 8b6c49bffc52f20b8b1f8dfe309b4b61ef742069 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Sun, 16 Jun 2024 10:00:00 +0200 Subject: [PATCH 06/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Error,=20Stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ client.go | 41 ++++++++++++++++++++++++++------- error.go | 14 +++++++++++ go.mod | 2 ++ language.go | 39 ++++++++++++++++++------------- language_chat.go | 41 +++++++++++++++++++++++++++++++++ schema.go => language_schema.go | 0 language_test.go | 6 +++-- 8 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 error.go create mode 100644 language_chat.go rename schema.go => language_schema.go (100%) diff --git a/.gitignore b/.gitignore index 02674d7..4b24e35 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ Thumbs.db vendor/ *.test go.work +go.sum +go.work.sum *.out # Output diff --git a/client.go b/client.go index 9a450cf..3a35514 100644 --- a/client.go +++ b/client.go @@ -4,11 +4,11 @@ import ( "bytes" "context" "encoding/json" - "errors" - "fmt" "io" "net/http" "net/url" + + "github.com/gorilla/websocket" ) // Client is a minimal 'Glide' client. @@ -18,7 +18,7 @@ type Client struct { BaseURL *url.URL httpClient *http.Client - Language LanguageSvc + Language Language } type ClientOption func(*Client) error @@ -33,7 +33,7 @@ func NewClient(options ...ClientOption) (*Client, error) { }, options...) client := &Client{} - client.Language = &language{client} + client.Language = &languageSvc{client} for _, option := range options { if err := option(client); err != nil { @@ -127,10 +127,15 @@ func (c *Client) Send(r *http.Request, ret any) (*http.Response, error) { } defer resp.Body.Close() - if resp.StatusCode > http.StatusBadRequest { - // TODO: Decode into ErrorResponse. - reason := fmt.Sprintf("status code: %d", resp.StatusCode) - return nil, errors.New(reason) + if resp.StatusCode >= http.StatusBadRequest { + var errorResp Error + err := json.NewDecoder(resp.Body).Decode(&errorResp) + if err != nil { + return nil, err + } + + errorResp.Status = resp.StatusCode + return nil, &errorResp } if resp.StatusCode != http.StatusNoContent && ret != nil && resp.Body != nil { @@ -142,6 +147,26 @@ func (c *Client) Send(r *http.Request, ret any) (*http.Response, error) { return resp, nil } +// Upgrade establishes the WebSocket connection. +func (c *Client) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { + abs, err := c.BaseURL.Parse(path) + if err != nil { + return nil, err + } + + header := http.Header{} + if len(c.ApiKey) > 0 { + header.Set("Authorization", "Bearer "+c.ApiKey) + } + + conn, _, err := websocket.DefaultDialer.DialContext(ctx, abs.String(), header) + if err != nil { + return nil, err + } + + return conn, nil +} + // Health returns nil if the service is healthy. func (c *Client) Health(ctx context.Context) error { req, err := c.Build(ctx, http.MethodGet, "/v1/health/", nil) diff --git a/error.go b/error.go new file mode 100644 index 0000000..329be44 --- /dev/null +++ b/error.go @@ -0,0 +1,14 @@ +package glide + +import "fmt" + +// Error that may occur during the processing of API request. +type Error struct { + Name string `json:"name"` + Message string `json:"message"` + Status int `json:"status,omitempty"` +} + +func (e *Error) Error() string { + return fmt.Sprintf("%s: %s", e.Name, e.Message) +} diff --git a/go.mod b/go.mod index 257346a..a64688c 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module github.com/einstack/glide-go go 1.22.2 + +require github.com/gorilla/websocket v1.5.3 // indirect diff --git a/language.go b/language.go index b32c661..78a52b4 100644 --- a/language.go +++ b/language.go @@ -2,52 +2,59 @@ package glide import ( "context" + "fmt" "net/http" ) -// LanguageSvc implements APIs for '/v1/language' endpoints. -type LanguageSvc interface { +// Language implements APIs for '/v1/language' endpoints. +type Language interface { // List retrieves a list of all router configs. - List(ctx context.Context) ([]RouterConfig, error) + List(ctx context.Context) (*RouterList, error) // Chat sends a single chat request to a specified router and retrieves the response. - Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) + Chat(ctx context.Context, router string, req ChatRequest) (*ChatResponse, error) // ChatStream establishes a WebSocket connection for streaming chat messages from a specified router. - ChatStream(ctx context.Context) error + ChatStream(ctx context.Context, router string) (Chat, error) } -type language struct { +type languageSvc struct { client *Client } -func (svc *language) List(ctx context.Context) ([]RouterConfig, error) { - req, err := svc.client.Build(ctx, http.MethodGet, "/v1/list", nil) +func (svc *languageSvc) List(ctx context.Context) (*RouterList, error) { + httpReq, err := svc.client.Build(ctx, http.MethodGet, "/v1/list", nil) if err != nil { return nil, err } var resp *RouterList - if _, err := svc.client.Send(req, resp); err != nil { + if _, err := svc.client.Send(httpReq, resp); err != nil { return nil, err } - return resp.Routers, nil + return resp, nil } -func (svc *language) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { - req2, err := svc.client.Build(ctx, http.MethodPost, "/v1/chat", req) +func (svc *languageSvc) Chat(ctx context.Context, router string, req ChatRequest) (*ChatResponse, error) { + path := fmt.Sprintf("/v1/%s/chat", router) + httpReq, err := svc.client.Build(ctx, http.MethodPost, path, req) if err != nil { return nil, err } var resp *ChatResponse - if _, err := svc.client.Send(req2, resp); err != nil { + if _, err := svc.client.Send(httpReq, resp); err != nil { return nil, err } return resp, nil } -func (svc *language) ChatStream(ctx context.Context) error { - // TODO. - return nil +func (svc *languageSvc) ChatStream(ctx context.Context, router string) (Chat, error) { + path := fmt.Sprintf("/v1/%s/chatStream", router) + conn, err := svc.client.Upgrade(ctx, path) + if err != nil { + return nil, err + } + + return newChatService(conn), nil } diff --git a/language_chat.go b/language_chat.go new file mode 100644 index 0000000..271a818 --- /dev/null +++ b/language_chat.go @@ -0,0 +1,41 @@ +package glide + +import ( + "context" + "github.com/gorilla/websocket" + "io" +) + +// Chat is a streaming (`WebSocket`) chat connection. +type Chat interface { + io.Closer + + // Send TODO. + Send(ctx context.Context) error + + // Recv TODO. + Recv(ctx context.Context) error +} + +type chatService struct { + conn *websocket.Conn +} + +func newChatService(conn *websocket.Conn) *chatService { + return &chatService{conn: conn} +} + +func (svc *chatService) Send(ctx context.Context) error { + // TODO. + panic("implement me") +} + +func (svc *chatService) Recv(ctx context.Context) error { + // TODO. + panic("implement me") +} + +// Close closes the underlying connection without sending or waiting for a close message. +func (svc *chatService) Close() error { + return svc.conn.Close() +} diff --git a/schema.go b/language_schema.go similarity index 100% rename from schema.go rename to language_schema.go diff --git a/language_test.go b/language_test.go index 1d00996..3f3e7f8 100644 --- a/language_test.go +++ b/language_test.go @@ -7,6 +7,8 @@ import ( "github.com/einstack/glide-go" ) +var router = "myrouter" + func TestLanguage_List(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() @@ -21,7 +23,7 @@ func TestLanguage_Chat(t *testing.T) { ctx := context.Background() req := glide.NewChatRequest() - if _, err := client.Language.Chat(ctx, req); err != nil { + if _, err := client.Language.Chat(ctx, router, req); err != nil { t.Error(err) } } @@ -30,7 +32,7 @@ func TestLanguage_ChatStream(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - if err := client.Language.ChatStream(ctx); err != nil { + if _, err := client.Language.ChatStream(ctx, router); err != nil { t.Error(err) } } From f4b1b84c551c9c3a40c19239ad6e7c72405a2544 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Tue, 18 Jun 2024 22:12:10 +0200 Subject: [PATCH 07/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Before=20move=20to?= =?UTF-8?q?=20Config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 15 +++++++++------ error.go | 14 +++++++++++++- examples/hello.go | 5 +++++ go.mod | 2 +- language_chat.go | 3 ++- language_test.go | 6 +++--- 6 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 examples/hello.go diff --git a/client.go b/client.go index 3a35514..e7ea164 100644 --- a/client.go +++ b/client.go @@ -18,7 +18,7 @@ type Client struct { BaseURL *url.URL httpClient *http.Client - Language Language + Lang Language } type ClientOption func(*Client) error @@ -33,7 +33,7 @@ func NewClient(options ...ClientOption) (*Client, error) { }, options...) client := &Client{} - client.Language = &languageSvc{client} + client.Lang = &languageSvc{client} for _, option := range options { if err := option(client); err != nil { @@ -128,14 +128,17 @@ func (c *Client) Send(r *http.Request, ret any) (*http.Response, error) { defer resp.Body.Close() if resp.StatusCode >= http.StatusBadRequest { - var errorResp Error - err := json.NewDecoder(resp.Body).Decode(&errorResp) - if err != nil { + if resp.Body == nil { + return nil, NewError() + } + + var errorResp *Error + if err := json.NewDecoder(resp.Body).Decode(errorResp); err != nil { return nil, err } errorResp.Status = resp.StatusCode - return nil, &errorResp + return nil, errorResp } if resp.StatusCode != http.StatusNoContent && ret != nil && resp.Body != nil { diff --git a/error.go b/error.go index 329be44..e75b4c4 100644 --- a/error.go +++ b/error.go @@ -1,6 +1,9 @@ package glide -import "fmt" +import ( + "fmt" + "net/http" +) // Error that may occur during the processing of API request. type Error struct { @@ -9,6 +12,15 @@ type Error struct { Status int `json:"status,omitempty"` } +// NewError instantiates a default Error. +func NewError() error { + return &Error{ + Name: "unrecognized_error", + Message: "", + Status: http.StatusInternalServerError, + } +} + func (e *Error) Error() string { return fmt.Sprintf("%s: %s", e.Name, e.Message) } diff --git a/examples/hello.go b/examples/hello.go new file mode 100644 index 0000000..7905807 --- /dev/null +++ b/examples/hello.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/go.mod b/go.mod index a64688c..26d9a29 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ module github.com/einstack/glide-go go 1.22.2 -require github.com/gorilla/websocket v1.5.3 // indirect +require github.com/gorilla/websocket v1.5.3 diff --git a/language_chat.go b/language_chat.go index 271a818..b07e695 100644 --- a/language_chat.go +++ b/language_chat.go @@ -2,8 +2,9 @@ package glide import ( "context" - "github.com/gorilla/websocket" "io" + + "github.com/gorilla/websocket" ) // Chat is a streaming (`WebSocket`) chat connection. diff --git a/language_test.go b/language_test.go index 3f3e7f8..440a13c 100644 --- a/language_test.go +++ b/language_test.go @@ -13,7 +13,7 @@ func TestLanguage_List(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - if _, err := client.Language.List(ctx); err != nil { + if _, err := client.Lang.List(ctx); err != nil { t.Error(err) } } @@ -23,7 +23,7 @@ func TestLanguage_Chat(t *testing.T) { ctx := context.Background() req := glide.NewChatRequest() - if _, err := client.Language.Chat(ctx, router, req); err != nil { + if _, err := client.Lang.Chat(ctx, router, req); err != nil { t.Error(err) } } @@ -32,7 +32,7 @@ func TestLanguage_ChatStream(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - if _, err := client.Language.ChatStream(ctx, router); err != nil { + if _, err := client.Lang.ChatStream(ctx, router); err != nil { t.Error(err) } } From 0c0e2349bd7421c3b612dbb9028d6cbe4fd451ac Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Tue, 18 Jun 2024 22:47:50 +0200 Subject: [PATCH 08/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Move=20to=20Config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + README.md | 28 ++++++- client.go | 116 +++-------------------------- config.go | 102 +++++++++++++++++++++++++ config_test.go | 1 + examples/hello.go | 25 +++++++ language.go => lang.go | 13 ++-- language_schema.go => lang_chat.go | 46 ++++++------ lang_list.go | 11 +++ language_chat.go => lang_stream.go | 2 + language_test.go => lang_test.go | 5 +- 11 files changed, 216 insertions(+), 136 deletions(-) create mode 100644 config.go create mode 100644 config_test.go rename language.go => lang.go (78%) rename language_schema.go => lang_chat.go (50%) create mode 100644 lang_list.go rename language_chat.go => lang_stream.go (91%) rename language_test.go => lang_test.go (94%) diff --git a/.gitignore b/.gitignore index 4b24e35..ff5780c 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ env/ logs/ *.log *.log* + +# Coverage +coverage.txt diff --git a/README.md b/README.md index 037f298..bd287aa 100644 --- a/README.md +++ b/README.md @@ -25,4 +25,30 @@ go get github.com/einstack/glide-go ## Usage -... +For a full example take a look at [`hello.go`](examples/hello.go). + +```go +package main + +import ( + "context" + "log" + + "github.com/einstack/glide-go" +) + +func main() { + client, err := glide.NewClient() + if err != nil { + log.Fatal(err) + } + + req := glide.NewChatRequest("Hello") + resp, err := client.Lang.Chat(ctx, "myrouter", req) + if err != nil { + log.Fatal(err) + } + + println("response: ", resp.Content()) +} +``` diff --git a/client.go b/client.go index e7ea164..0f2ce67 100644 --- a/client.go +++ b/client.go @@ -1,23 +1,14 @@ package glide import ( - "bytes" "context" - "encoding/json" - "io" "net/http" "net/url" - - "github.com/gorilla/websocket" ) // Client is a minimal 'Glide' client. type Client struct { - ApiKey string - UserAgent string - BaseURL *url.URL - httpClient *http.Client - + cfg *config Lang Language } @@ -32,8 +23,8 @@ func NewClient(options ...ClientOption) (*Client, error) { WithHttpClient(http.DefaultClient), }, options...) - client := &Client{} - client.Lang = &languageSvc{client} + client := &Client{cfg: &config{}} + client.Lang = &languageSvc{client.cfg} for _, option := range options { if err := option(client); err != nil { @@ -45,27 +36,27 @@ func NewClient(options ...ClientOption) (*Client, error) { } // WithApiKey attaches the api key. -// Env variable 'GLIDE_API_KEY'. +// Use environment variable 'GLIDE_API_KEY' to override. func WithApiKey(apiKey string) ClientOption { return func(client *Client) error { - client.ApiKey = apiKey + client.cfg.apiKey = apiKey return nil } } // WithUserAgent replaces the 'User-Agent' header. // Default value: 'Glide/0.1.0 (Go; Ver. 1.22.2)'. -// Env variable: 'GLIDE_USER_AGENT'. +// Use environment variable 'GLIDE_USER_AGENT' to override. func WithUserAgent(userAgent string) ClientOption { return func(client *Client) error { - client.UserAgent = userAgent + client.cfg.userAgent = userAgent return nil } } // WithBaseURL replaces the 'base' Url. // Default value: 'http://127.0.0.1:9099/'. -// Env variable: 'GLIDE_BASE_URL'. +// Use environment variable 'GLIDE_BASE_URL' to override. func WithBaseURL(baseURL string) ClientOption { return func(client *Client) error { parsed, err := url.Parse(baseURL) @@ -73,7 +64,7 @@ func WithBaseURL(baseURL string) ClientOption { return err } - client.BaseURL = parsed + client.cfg.baseURL = parsed return nil } } @@ -82,102 +73,19 @@ func WithBaseURL(baseURL string) ClientOption { // Default value: 'http.DefaultClient'. func WithHttpClient(httpClient *http.Client) ClientOption { return func(client *Client) error { - client.httpClient = httpClient + client.cfg.httpClient = httpClient return nil } } -// Build instantiates a new http.Request. -func (c *Client) Build(ctx context.Context, method, path string, data any) (*http.Request, error) { - abs, err := c.BaseURL.Parse(path) - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, method, abs.String(), nil) - if err != nil { - return nil, err - } - - if data != nil { - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(data); err != nil { - return nil, err - } - - req.Body = io.NopCloser(buf) - req.Header.Set("Content-Type", "application/json") - } - - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", c.UserAgent) - - if len(c.ApiKey) > 0 { - req.Header.Set("Authorization", "Bearer "+c.ApiKey) - } - - return req, nil -} - -// Send sends an http.Request and decodes http.Response into ret. -func (c *Client) Send(r *http.Request, ret any) (*http.Response, error) { - resp, err := c.httpClient.Do(r) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode >= http.StatusBadRequest { - if resp.Body == nil { - return nil, NewError() - } - - var errorResp *Error - if err := json.NewDecoder(resp.Body).Decode(errorResp); err != nil { - return nil, err - } - - errorResp.Status = resp.StatusCode - return nil, errorResp - } - - if resp.StatusCode != http.StatusNoContent && ret != nil && resp.Body != nil { - if err = json.NewDecoder(resp.Body).Decode(ret); err != nil { - return nil, err - } - } - - return resp, nil -} - -// Upgrade establishes the WebSocket connection. -func (c *Client) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { - abs, err := c.BaseURL.Parse(path) - if err != nil { - return nil, err - } - - header := http.Header{} - if len(c.ApiKey) > 0 { - header.Set("Authorization", "Bearer "+c.ApiKey) - } - - conn, _, err := websocket.DefaultDialer.DialContext(ctx, abs.String(), header) - if err != nil { - return nil, err - } - - return conn, nil -} - // Health returns nil if the service is healthy. func (c *Client) Health(ctx context.Context) error { - req, err := c.Build(ctx, http.MethodGet, "/v1/health/", nil) + req, err := c.cfg.Build(ctx, http.MethodGet, "/v1/health/", nil) if err != nil { return err } - if _, err := c.Send(req, nil); err != nil { + if _, err := c.cfg.Send(req, nil); err != nil { return err } diff --git a/config.go b/config.go new file mode 100644 index 0000000..339a1df --- /dev/null +++ b/config.go @@ -0,0 +1,102 @@ +package glide + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/gorilla/websocket" +) + +type config struct { + apiKey string + userAgent string + baseURL *url.URL + httpClient *http.Client +} + +// Build instantiates a new http.Request. +func (c *config) Build(ctx context.Context, method, path string, data any) (*http.Request, error) { + abs, err := c.baseURL.Parse(path) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, method, abs.String(), nil) + if err != nil { + return nil, err + } + + if data != nil { + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(data); err != nil { + return nil, err + } + + req.Body = io.NopCloser(buf) + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", c.userAgent) + + if len(c.apiKey) > 0 { + req.Header.Set("Authorization", "Bearer "+c.apiKey) + } + + return req, nil +} + +// Send sends an http.Request and decodes http.Response into ret. +func (c *config) Send(r *http.Request, ret any) (*http.Response, error) { + resp, err := c.httpClient.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode >= http.StatusBadRequest { + if resp.Body == nil { + return nil, NewError() + } + + var errorResp *Error + if err := json.NewDecoder(resp.Body).Decode(errorResp); err != nil { + return nil, err + } + + errorResp.Status = resp.StatusCode + return nil, errorResp + } + + if resp.StatusCode != http.StatusNoContent && ret != nil && resp.Body != nil { + if err = json.NewDecoder(resp.Body).Decode(ret); err != nil { + return nil, err + } + } + + return resp, nil +} + +// Upgrade establishes the WebSocket connection. +func (c *config) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { + abs, err := c.baseURL.Parse(path) + if err != nil { + return nil, err + } + + header := http.Header{} + if len(c.apiKey) > 0 { + header.Set("Authorization", "Bearer "+c.apiKey) + } + + conn, _, err := websocket.DefaultDialer.DialContext(ctx, abs.String(), header) + if err != nil { + return nil, err + } + + return conn, nil +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..767864d --- /dev/null +++ b/config_test.go @@ -0,0 +1 @@ +package glide diff --git a/examples/hello.go b/examples/hello.go index 7905807..e6d6cee 100644 --- a/examples/hello.go +++ b/examples/hello.go @@ -1,5 +1,30 @@ package main +import ( + "context" + "log" + + "github.com/einstack/glide-go" +) + +var router = "myrouter" + func main() { + client, err := glide.NewClient() + if err != nil { + log.Fatal(err) + } + + ctx := context.Background() + if err := client.Health(ctx); err != nil { + log.Fatal(err) + } + + req := glide.NewChatRequest("Hello") + resp, err := client.Lang.Chat(ctx, router, req) + if err != nil { + log.Fatal(err) + } + println("response: ", resp.Content()) } diff --git a/language.go b/lang.go similarity index 78% rename from language.go rename to lang.go index 78a52b4..a05cad0 100644 --- a/language.go +++ b/lang.go @@ -17,17 +17,17 @@ type Language interface { } type languageSvc struct { - client *Client + config *config } func (svc *languageSvc) List(ctx context.Context) (*RouterList, error) { - httpReq, err := svc.client.Build(ctx, http.MethodGet, "/v1/list", nil) + httpReq, err := svc.config.Build(ctx, http.MethodGet, "/v1/list", nil) if err != nil { return nil, err } var resp *RouterList - if _, err := svc.client.Send(httpReq, resp); err != nil { + if _, err := svc.config.Send(httpReq, resp); err != nil { return nil, err } @@ -36,13 +36,13 @@ func (svc *languageSvc) List(ctx context.Context) (*RouterList, error) { func (svc *languageSvc) Chat(ctx context.Context, router string, req ChatRequest) (*ChatResponse, error) { path := fmt.Sprintf("/v1/%s/chat", router) - httpReq, err := svc.client.Build(ctx, http.MethodPost, path, req) + httpReq, err := svc.config.Build(ctx, http.MethodPost, path, req) if err != nil { return nil, err } var resp *ChatResponse - if _, err := svc.client.Send(httpReq, resp); err != nil { + if _, err := svc.config.Send(httpReq, resp); err != nil { return nil, err } @@ -50,8 +50,9 @@ func (svc *languageSvc) Chat(ctx context.Context, router string, req ChatRequest } func (svc *languageSvc) ChatStream(ctx context.Context, router string) (Chat, error) { + // TODO: Change schema to ws/wss. path := fmt.Sprintf("/v1/%s/chatStream", router) - conn, err := svc.client.Upgrade(ctx, path) + conn, err := svc.config.Upgrade(ctx, path) if err != nil { return nil, err } diff --git a/language_schema.go b/lang_chat.go similarity index 50% rename from language_schema.go rename to lang_chat.go index fc9710f..fd448e7 100644 --- a/language_schema.go +++ b/lang_chat.go @@ -2,33 +2,25 @@ package glide // https://github.com/EinStack/glide/tree/develop/pkg/api/schemas -// RouterConfig TODO. -type RouterConfig any - -// RouterList TODO. -type RouterList struct { - Routers []RouterConfig `json:"routers"` -} - -// ChatRequest TODO. +// ChatRequest is a unified chat request across all language models. type ChatRequest struct { - Message ChatMessage `json:"message" validate:"required"` - MessageHistory []ChatMessage `json:"message_history"` - OverrideParams *OverrideChatRequest `json:"override_params,omitempty"` + Message ChatMessage `json:"message"` + MessageHistory *[]ChatMessage `json:"message_history,omitempty"` + OverrideParams *map[string]OverrideChatRequest `json:"override_params,omitempty"` } +// OverrideChatRequest is an override of a single chat request. type OverrideChatRequest struct { - ModelID string `json:"model_id" validate:"required"` - Message ChatMessage `json:"message" validate:"required"` + Message ChatMessage `json:"message"` } // NewChatRequest instantiates a new ChatRequest. -func NewChatRequest() ChatRequest { - // TODO. - return ChatRequest{} +func NewChatRequest(content string) ChatRequest { + message := ChatMessage{Content: content} + return ChatRequest{Message: message} } -// ChatResponse TODO. +// ChatResponse is a unified chat response across all language models. type ChatResponse struct { ID string `json:"id,omitempty"` Created int `json:"created_at,omitempty"` @@ -40,24 +32,34 @@ type ChatResponse struct { ModelResponse ModelResponse `json:"model_response,omitempty"` } +// Content returns the content of the response. +func (r *ChatResponse) Content() string { + return r.ModelResponse.Message.Content +} + +// ModelResponse is unified response from the provider. type ModelResponse struct { Metadata map[string]string `json:"metadata,omitempty"` Message ChatMessage `json:"message"` TokenUsage TokenUsage `json:"token_usage"` } +// TokenUsage is a list of prompt, response and total token usage. type TokenUsage struct { PromptTokens int `json:"prompt_tokens"` ResponseTokens int `json:"response_tokens"` TotalTokens int `json:"total_tokens"` } +// ChatMessage is content and role of the message. type ChatMessage struct { - // The role of the author of this message. One of system, user, or assistant. - Role string `json:"role" validate:"required"` + // The role of the author of this message. + // One of system, user, or assistant. + Role *string `json:"role"` // The content of the message. - Content string `json:"content" validate:"required"` - // The name of the author of this message. May contain a-z, A-Z, 0-9, and underscores, + Content string `json:"content"` + // The name of the author of this message. + // May contain a-z, A-Z, 0-9, and underscores, // with a maximum length of 64 characters. Name string `json:"name,omitempty"` } diff --git a/lang_list.go b/lang_list.go new file mode 100644 index 0000000..49ec53e --- /dev/null +++ b/lang_list.go @@ -0,0 +1,11 @@ +package glide + +// https://github.com/EinStack/glide/tree/develop/pkg/api/schemas + +// RouterList is a list of all router configurations. +type RouterList struct { + Routers []RouterConfig `json:"routers"` +} + +// RouterConfig is a single router configuration. +type RouterConfig any diff --git a/language_chat.go b/lang_stream.go similarity index 91% rename from language_chat.go rename to lang_stream.go index b07e695..8a0ab5a 100644 --- a/language_chat.go +++ b/lang_stream.go @@ -7,6 +7,8 @@ import ( "github.com/gorilla/websocket" ) +// https://github.com/EinStack/glide/tree/develop/pkg/api/schemas + // Chat is a streaming (`WebSocket`) chat connection. type Chat interface { io.Closer diff --git a/language_test.go b/lang_test.go similarity index 94% rename from language_test.go rename to lang_test.go index 440a13c..ace0178 100644 --- a/language_test.go +++ b/lang_test.go @@ -2,9 +2,8 @@ package glide_test import ( "context" - "testing" - "github.com/einstack/glide-go" + "testing" ) var router = "myrouter" @@ -22,7 +21,7 @@ func TestLanguage_Chat(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - req := glide.NewChatRequest() + req := glide.NewChatRequest("Hello") if _, err := client.Lang.Chat(ctx, router, req); err != nil { t.Error(err) } From f0998d9abdeb1b0b66837566aaf90f1cea1f19bd Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Wed, 19 Jun 2024 10:14:12 +0200 Subject: [PATCH 09/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Expose=20cfg=20field?= =?UTF-8?q?s,=20ret=20health=20bool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 37 +++++++++++++++++++++++++++++++------ client_test.go | 2 +- config.go | 8 +++++++- config_test.go | 1 - examples/hello.go | 2 +- lang.go | 1 - 6 files changed, 40 insertions(+), 11 deletions(-) delete mode 100644 config_test.go diff --git a/client.go b/client.go index 0f2ce67..dd0e78a 100644 --- a/client.go +++ b/client.go @@ -78,16 +78,41 @@ func WithHttpClient(httpClient *http.Client) ClientOption { } } -// Health returns nil if the service is healthy. -func (c *Client) Health(ctx context.Context) error { +// ApiKey returns the provided API key, empty string otherwise. +func (c *Client) ApiKey() string { + return c.cfg.apiKey +} + +// UserAgent returns the used 'User-Agent' header value. +func (c *Client) UserAgent() string { + return c.cfg.userAgent +} + +// BaseURL returns the used 'base url.URL'. +func (c *Client) BaseURL() url.URL { + return *c.cfg.baseURL +} + +// HttpClient returns the underlying http.Client. +func (c *Client) HttpClient() *http.Client { + return c.cfg.httpClient +} + +// Health returns true if the service is healthy. +func (c *Client) Health(ctx context.Context) (*bool, error) { + type Health struct { + Healthy bool `json:"healthy"` + } + req, err := c.cfg.Build(ctx, http.MethodGet, "/v1/health/", nil) if err != nil { - return err + return nil, err } - if _, err := c.cfg.Send(req, nil); err != nil { - return err + var resp Health + if _, err := c.cfg.Send(req, resp); err != nil { + return nil, err } - return nil + return &resp.Healthy, nil } diff --git a/client_test.go b/client_test.go index 21bee6c..da8bbc3 100644 --- a/client_test.go +++ b/client_test.go @@ -22,7 +22,7 @@ func TestClient_Health(t *testing.T) { ) ctx := context.Background() - if err := client.Health(ctx); err != nil { + if _, err := client.Health(ctx); err != nil { t.Error(err) } } diff --git a/config.go b/config.go index 339a1df..eaa343a 100644 --- a/config.go +++ b/config.go @@ -83,7 +83,13 @@ func (c *config) Send(r *http.Request, ret any) (*http.Response, error) { // Upgrade establishes the WebSocket connection. func (c *config) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { - abs, err := c.baseURL.Parse(path) + wsBaseURL := c.baseURL + wsBaseURL.Scheme = "ws" + if c.baseURL.Scheme == "https" { + wsBaseURL.Scheme = "wss" + } + + abs, err := wsBaseURL.Parse(path) if err != nil { return nil, err } diff --git a/config_test.go b/config_test.go deleted file mode 100644 index 767864d..0000000 --- a/config_test.go +++ /dev/null @@ -1 +0,0 @@ -package glide diff --git a/examples/hello.go b/examples/hello.go index e6d6cee..e4582cf 100644 --- a/examples/hello.go +++ b/examples/hello.go @@ -16,7 +16,7 @@ func main() { } ctx := context.Background() - if err := client.Health(ctx); err != nil { + if _, err := client.Health(ctx); err != nil { log.Fatal(err) } diff --git a/lang.go b/lang.go index a05cad0..752b325 100644 --- a/lang.go +++ b/lang.go @@ -50,7 +50,6 @@ func (svc *languageSvc) Chat(ctx context.Context, router string, req ChatRequest } func (svc *languageSvc) ChatStream(ctx context.Context, router string) (Chat, error) { - // TODO: Change schema to ws/wss. path := fmt.Sprintf("/v1/%s/chatStream", router) conn, err := svc.config.Upgrade(ctx, path) if err != nil { From b8c6713b7f1fe6071187e4d48d8f3b3e19a1554b Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Wed, 19 Jun 2024 12:19:48 +0200 Subject: [PATCH 10/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Merge=20list/chat,?= =?UTF-8?q?=20fix=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client_test.go | 4 ++-- lang_list.go | 11 ----------- lang_chat.go => lang_schema.go | 8 ++++++++ lang_stream.go | 7 +++---- lang_test.go | 13 +++++++++---- 5 files changed, 22 insertions(+), 21 deletions(-) delete mode 100644 lang_list.go rename lang_chat.go => lang_schema.go (91%) diff --git a/client_test.go b/client_test.go index da8bbc3..1143c60 100644 --- a/client_test.go +++ b/client_test.go @@ -12,7 +12,7 @@ func TestNewClient(t *testing.T) { glide.WithApiKey("testing"), glide.WithUserAgent("Einstack/1.0"), ); err != nil { - t.Error(err) + t.Fatal(err) } } @@ -23,6 +23,6 @@ func TestClient_Health(t *testing.T) { ctx := context.Background() if _, err := client.Health(ctx); err != nil { - t.Error(err) + t.Fatal(err) } } diff --git a/lang_list.go b/lang_list.go deleted file mode 100644 index 49ec53e..0000000 --- a/lang_list.go +++ /dev/null @@ -1,11 +0,0 @@ -package glide - -// https://github.com/EinStack/glide/tree/develop/pkg/api/schemas - -// RouterList is a list of all router configurations. -type RouterList struct { - Routers []RouterConfig `json:"routers"` -} - -// RouterConfig is a single router configuration. -type RouterConfig any diff --git a/lang_chat.go b/lang_schema.go similarity index 91% rename from lang_chat.go rename to lang_schema.go index fd448e7..2546063 100644 --- a/lang_chat.go +++ b/lang_schema.go @@ -2,6 +2,14 @@ package glide // https://github.com/EinStack/glide/tree/develop/pkg/api/schemas +// RouterList is a list of all router configurations. +type RouterList struct { + Routers []RouterConfig `json:"routers"` +} + +// RouterConfig is a single router configuration. +type RouterConfig map[string]any + // ChatRequest is a unified chat request across all language models. type ChatRequest struct { Message ChatMessage `json:"message"` diff --git a/lang_stream.go b/lang_stream.go index 8a0ab5a..53df70c 100644 --- a/lang_stream.go +++ b/lang_stream.go @@ -7,16 +7,14 @@ import ( "github.com/gorilla/websocket" ) -// https://github.com/EinStack/glide/tree/develop/pkg/api/schemas - // Chat is a streaming (`WebSocket`) chat connection. type Chat interface { io.Closer - // Send TODO. + // Send attempts to send the provided chat request. Send(ctx context.Context) error - // Recv TODO. + // Recv attempts to receive the next chat response. Recv(ctx context.Context) error } @@ -24,6 +22,7 @@ type chatService struct { conn *websocket.Conn } +// newChatService instantiates a new chatService. func newChatService(conn *websocket.Conn) *chatService { return &chatService{conn: conn} } diff --git a/lang_test.go b/lang_test.go index ace0178..5b0082a 100644 --- a/lang_test.go +++ b/lang_test.go @@ -13,7 +13,7 @@ func TestLanguage_List(t *testing.T) { ctx := context.Background() if _, err := client.Lang.List(ctx); err != nil { - t.Error(err) + t.Fatal(err) } } @@ -23,7 +23,7 @@ func TestLanguage_Chat(t *testing.T) { req := glide.NewChatRequest("Hello") if _, err := client.Lang.Chat(ctx, router, req); err != nil { - t.Error(err) + t.Fatal(err) } } @@ -31,7 +31,12 @@ func TestLanguage_ChatStream(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - if _, err := client.Lang.ChatStream(ctx, router); err != nil { - t.Error(err) + chat, err := client.Lang.ChatStream(ctx, router) + if err != nil { + t.Fatal(err) + } + + if err := chat.Close(); err != nil { + t.Fatal(err) } } From 1b140e0a3f3ab6b02897b6b8b7526bbb2c980eb8 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 24 Jun 2024 20:14:03 +0200 Subject: [PATCH 11/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20WithRawBaseURL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 45 +++++++++++++++++++++++++++------------------ client_test.go | 1 + config.go | 3 ++- lang_test.go | 3 ++- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/client.go b/client.go index dd0e78a..8aacdd7 100644 --- a/client.go +++ b/client.go @@ -8,8 +8,8 @@ import ( // Client is a minimal 'Glide' client. type Client struct { - cfg *config - Lang Language + config *config + Lang Language } type ClientOption func(*Client) error @@ -19,12 +19,12 @@ func NewClient(options ...ClientOption) (*Client, error) { options = append([]ClientOption{ WithApiKey(envApiKey), WithUserAgent(envUserAgent), - WithBaseURL(envBaseUrl), + WithRawBaseURL(envBaseUrl), WithHttpClient(http.DefaultClient), }, options...) - client := &Client{cfg: &config{}} - client.Lang = &languageSvc{client.cfg} + client := &Client{config: &config{}} + client.Lang = &languageSvc{client.config} for _, option := range options { if err := option(client); err != nil { @@ -39,7 +39,7 @@ func NewClient(options ...ClientOption) (*Client, error) { // Use environment variable 'GLIDE_API_KEY' to override. func WithApiKey(apiKey string) ClientOption { return func(client *Client) error { - client.cfg.apiKey = apiKey + client.config.apiKey = apiKey return nil } } @@ -49,22 +49,31 @@ func WithApiKey(apiKey string) ClientOption { // Use environment variable 'GLIDE_USER_AGENT' to override. func WithUserAgent(userAgent string) ClientOption { return func(client *Client) error { - client.cfg.userAgent = userAgent + client.config.userAgent = userAgent return nil } } -// WithBaseURL replaces the 'base' Url. +// WithRawBaseURL parses and replaces the base URL. // Default value: 'http://127.0.0.1:9099/'. // Use environment variable 'GLIDE_BASE_URL' to override. -func WithBaseURL(baseURL string) ClientOption { +func WithRawBaseURL(rawBaseURL string) ClientOption { return func(client *Client) error { - parsed, err := url.Parse(baseURL) + baseURL, err := url.Parse(rawBaseURL) if err != nil { return err } - client.cfg.baseURL = parsed + client.config.baseURL = baseURL + return nil + } +} + +// WithBaseURL replaces the base URL. +// Also see WithRawBaseURL. +func WithBaseURL(baseURL url.URL) ClientOption { + return func(client *Client) error { + client.config.baseURL = &baseURL return nil } } @@ -73,29 +82,29 @@ func WithBaseURL(baseURL string) ClientOption { // Default value: 'http.DefaultClient'. func WithHttpClient(httpClient *http.Client) ClientOption { return func(client *Client) error { - client.cfg.httpClient = httpClient + client.config.httpClient = httpClient return nil } } // ApiKey returns the provided API key, empty string otherwise. func (c *Client) ApiKey() string { - return c.cfg.apiKey + return c.config.apiKey } // UserAgent returns the used 'User-Agent' header value. func (c *Client) UserAgent() string { - return c.cfg.userAgent + return c.config.userAgent } // BaseURL returns the used 'base url.URL'. func (c *Client) BaseURL() url.URL { - return *c.cfg.baseURL + return *c.config.baseURL } // HttpClient returns the underlying http.Client. func (c *Client) HttpClient() *http.Client { - return c.cfg.httpClient + return c.config.httpClient } // Health returns true if the service is healthy. @@ -104,13 +113,13 @@ func (c *Client) Health(ctx context.Context) (*bool, error) { Healthy bool `json:"healthy"` } - req, err := c.cfg.Build(ctx, http.MethodGet, "/v1/health/", nil) + req, err := c.config.Build(ctx, http.MethodGet, "/v1/health/", nil) if err != nil { return nil, err } var resp Health - if _, err := c.cfg.Send(req, resp); err != nil { + if _, err := c.config.Send(req, resp); err != nil { return nil, err } diff --git a/client_test.go b/client_test.go index 1143c60..d70d198 100644 --- a/client_test.go +++ b/client_test.go @@ -10,6 +10,7 @@ import ( func TestNewClient(t *testing.T) { if _, err := glide.NewClient( glide.WithApiKey("testing"), + glide.WithRawBaseURL("http://127.0.0.1:9098/"), glide.WithUserAgent("Einstack/1.0"), ); err != nil { t.Fatal(err) diff --git a/config.go b/config.go index eaa343a..95db0cc 100644 --- a/config.go +++ b/config.go @@ -84,9 +84,10 @@ func (c *config) Send(r *http.Request, ret any) (*http.Response, error) { // Upgrade establishes the WebSocket connection. func (c *config) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { wsBaseURL := c.baseURL - wsBaseURL.Scheme = "ws" if c.baseURL.Scheme == "https" { wsBaseURL.Scheme = "wss" + } else if c.baseURL.Scheme == "http" { + wsBaseURL.Scheme = "ws" } abs, err := wsBaseURL.Parse(path) diff --git a/lang_test.go b/lang_test.go index 5b0082a..a801b72 100644 --- a/lang_test.go +++ b/lang_test.go @@ -2,8 +2,9 @@ package glide_test import ( "context" - "github.com/einstack/glide-go" "testing" + + "github.com/einstack/glide-go" ) var router = "myrouter" From 63bd1c253284916d3fb9274b30bf37ee4a0f2bad Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 24 Jun 2024 22:58:23 +0200 Subject: [PATCH 12/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Reorganize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- client.go | 40 ++++++++++++++++--------- config.go => config/config.go | 44 ++++++++++++++++------------ error.go => config/error.go | 2 +- examples/hello.go | 3 +- lang_schema.go => lang/schema.go | 2 +- lang.go => lang/service.go | 11 +++++-- lang_test.go => lang/service_test.go | 5 ++-- lang_stream.go => lang/stream.go | 2 +- 9 files changed, 70 insertions(+), 42 deletions(-) rename config.go => config/config.go (66%) rename error.go => config/error.go (97%) rename lang_schema.go => lang/schema.go (99%) rename lang.go => lang/service.go (87%) rename lang_test.go => lang/service_test.go (88%) rename lang_stream.go => lang/stream.go (98%) diff --git a/README.md b/README.md index bd287aa..5fe0a8c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ import ( "log" "github.com/einstack/glide-go" + "github.com/einstack/glide-go/lang" ) func main() { @@ -43,7 +44,7 @@ func main() { log.Fatal(err) } - req := glide.NewChatRequest("Hello") + req := lang.NewChatRequest("Hello") resp, err := client.Lang.Chat(ctx, "myrouter", req) if err != nil { log.Fatal(err) diff --git a/client.go b/client.go index 8aacdd7..aad8945 100644 --- a/client.go +++ b/client.go @@ -4,14 +4,19 @@ import ( "context" "net/http" "net/url" + + "github.com/einstack/glide-go/config" + "github.com/einstack/glide-go/lang" ) -// Client is a minimal 'Glide' client. +// Client is an Einstack Glide client. type Client struct { - config *config - Lang Language + config *config.Config + Lang lang.Language } +// ClientOption is a functional option type for Client. +// Also see NewClient. type ClientOption func(*Client) error // NewClient instantiates a new Client. @@ -23,8 +28,8 @@ func NewClient(options ...ClientOption) (*Client, error) { WithHttpClient(http.DefaultClient), }, options...) - client := &Client{config: &config{}} - client.Lang = &languageSvc{client.config} + client := &Client{config: config.NewConfig()} + client.Lang = lang.NewLanguage(client.config) for _, option := range options { if err := option(client); err != nil { @@ -35,11 +40,18 @@ func NewClient(options ...ClientOption) (*Client, error) { return client, nil } +// NewClientFromConfig instantiates a new Client. +func NewClientFromConfig(config *config.Config) (*Client, error) { + client := &Client{config: config} + client.Lang = lang.NewLanguage(client.config) + return client, nil +} + // WithApiKey attaches the api key. // Use environment variable 'GLIDE_API_KEY' to override. func WithApiKey(apiKey string) ClientOption { return func(client *Client) error { - client.config.apiKey = apiKey + client.config.ApiKey = apiKey return nil } } @@ -49,7 +61,7 @@ func WithApiKey(apiKey string) ClientOption { // Use environment variable 'GLIDE_USER_AGENT' to override. func WithUserAgent(userAgent string) ClientOption { return func(client *Client) error { - client.config.userAgent = userAgent + client.config.UserAgent = userAgent return nil } } @@ -64,7 +76,7 @@ func WithRawBaseURL(rawBaseURL string) ClientOption { return err } - client.config.baseURL = baseURL + client.config.BaseURL = baseURL return nil } } @@ -73,7 +85,7 @@ func WithRawBaseURL(rawBaseURL string) ClientOption { // Also see WithRawBaseURL. func WithBaseURL(baseURL url.URL) ClientOption { return func(client *Client) error { - client.config.baseURL = &baseURL + client.config.BaseURL = &baseURL return nil } } @@ -82,29 +94,29 @@ func WithBaseURL(baseURL url.URL) ClientOption { // Default value: 'http.DefaultClient'. func WithHttpClient(httpClient *http.Client) ClientOption { return func(client *Client) error { - client.config.httpClient = httpClient + client.config.HttpClient = httpClient return nil } } // ApiKey returns the provided API key, empty string otherwise. func (c *Client) ApiKey() string { - return c.config.apiKey + return c.config.ApiKey } // UserAgent returns the used 'User-Agent' header value. func (c *Client) UserAgent() string { - return c.config.userAgent + return c.config.UserAgent } // BaseURL returns the used 'base url.URL'. func (c *Client) BaseURL() url.URL { - return *c.config.baseURL + return *c.config.BaseURL } // HttpClient returns the underlying http.Client. func (c *Client) HttpClient() *http.Client { - return c.config.httpClient + return c.config.HttpClient } // Health returns true if the service is healthy. diff --git a/config.go b/config/config.go similarity index 66% rename from config.go rename to config/config.go index 95db0cc..ea231dd 100644 --- a/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -package glide +package config import ( "bytes" @@ -11,16 +11,22 @@ import ( "github.com/gorilla/websocket" ) -type config struct { - apiKey string - userAgent string - baseURL *url.URL - httpClient *http.Client +// Config is an http.Client wrapper. +type Config struct { + ApiKey string + UserAgent string + BaseURL *url.URL + HttpClient *http.Client +} + +// NewConfig instantiates a new Config. +func NewConfig() *Config { + return &Config{} } // Build instantiates a new http.Request. -func (c *config) Build(ctx context.Context, method, path string, data any) (*http.Request, error) { - abs, err := c.baseURL.Parse(path) +func (c *Config) Build(ctx context.Context, method, path string, data any) (*http.Request, error) { + abs, err := c.BaseURL.Parse(path) if err != nil { return nil, err } @@ -41,18 +47,18 @@ func (c *config) Build(ctx context.Context, method, path string, data any) (*htt } req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", c.userAgent) + req.Header.Set("User-Agent", c.UserAgent) - if len(c.apiKey) > 0 { - req.Header.Set("Authorization", "Bearer "+c.apiKey) + if len(c.ApiKey) > 0 { + req.Header.Set("Authorization", "Bearer "+c.ApiKey) } return req, nil } // Send sends an http.Request and decodes http.Response into ret. -func (c *config) Send(r *http.Request, ret any) (*http.Response, error) { - resp, err := c.httpClient.Do(r) +func (c *Config) Send(r *http.Request, ret any) (*http.Response, error) { + resp, err := c.HttpClient.Do(r) if err != nil { return nil, err } @@ -82,11 +88,11 @@ func (c *config) Send(r *http.Request, ret any) (*http.Response, error) { } // Upgrade establishes the WebSocket connection. -func (c *config) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { - wsBaseURL := c.baseURL - if c.baseURL.Scheme == "https" { +func (c *Config) Upgrade(ctx context.Context, path string) (*websocket.Conn, error) { + wsBaseURL := c.BaseURL + if c.BaseURL.Scheme == "https" { wsBaseURL.Scheme = "wss" - } else if c.baseURL.Scheme == "http" { + } else if c.BaseURL.Scheme == "http" { wsBaseURL.Scheme = "ws" } @@ -96,8 +102,8 @@ func (c *config) Upgrade(ctx context.Context, path string) (*websocket.Conn, err } header := http.Header{} - if len(c.apiKey) > 0 { - header.Set("Authorization", "Bearer "+c.apiKey) + if len(c.ApiKey) > 0 { + header.Set("Authorization", "Bearer "+c.ApiKey) } conn, _, err := websocket.DefaultDialer.DialContext(ctx, abs.String(), header) diff --git a/error.go b/config/error.go similarity index 97% rename from error.go rename to config/error.go index e75b4c4..95ec72c 100644 --- a/error.go +++ b/config/error.go @@ -1,4 +1,4 @@ -package glide +package config import ( "fmt" diff --git a/examples/hello.go b/examples/hello.go index e4582cf..02c57c5 100644 --- a/examples/hello.go +++ b/examples/hello.go @@ -5,6 +5,7 @@ import ( "log" "github.com/einstack/glide-go" + "github.com/einstack/glide-go/lang" ) var router = "myrouter" @@ -20,7 +21,7 @@ func main() { log.Fatal(err) } - req := glide.NewChatRequest("Hello") + req := lang.NewChatRequest("Hello") resp, err := client.Lang.Chat(ctx, router, req) if err != nil { log.Fatal(err) diff --git a/lang_schema.go b/lang/schema.go similarity index 99% rename from lang_schema.go rename to lang/schema.go index 2546063..1fb9d84 100644 --- a/lang_schema.go +++ b/lang/schema.go @@ -1,4 +1,4 @@ -package glide +package lang // https://github.com/EinStack/glide/tree/develop/pkg/api/schemas diff --git a/lang.go b/lang/service.go similarity index 87% rename from lang.go rename to lang/service.go index 752b325..8f3bf05 100644 --- a/lang.go +++ b/lang/service.go @@ -1,9 +1,11 @@ -package glide +package lang import ( "context" "fmt" "net/http" + + "github.com/einstack/glide-go/config" ) // Language implements APIs for '/v1/language' endpoints. @@ -17,7 +19,12 @@ type Language interface { } type languageSvc struct { - config *config + config *config.Config +} + +// NewLanguage instantiates a new Language service. +func NewLanguage(config *config.Config) Language { + return &languageSvc{config: config} } func (svc *languageSvc) List(ctx context.Context) (*RouterList, error) { diff --git a/lang_test.go b/lang/service_test.go similarity index 88% rename from lang_test.go rename to lang/service_test.go index a801b72..e8026c0 100644 --- a/lang_test.go +++ b/lang/service_test.go @@ -1,10 +1,11 @@ -package glide_test +package lang_test import ( "context" "testing" "github.com/einstack/glide-go" + "github.com/einstack/glide-go/lang" ) var router = "myrouter" @@ -22,7 +23,7 @@ func TestLanguage_Chat(t *testing.T) { client, _ := glide.NewClient() ctx := context.Background() - req := glide.NewChatRequest("Hello") + req := lang.NewChatRequest("Hello") if _, err := client.Lang.Chat(ctx, router, req); err != nil { t.Fatal(err) } diff --git a/lang_stream.go b/lang/stream.go similarity index 98% rename from lang_stream.go rename to lang/stream.go index 53df70c..913c26b 100644 --- a/lang_stream.go +++ b/lang/stream.go @@ -1,4 +1,4 @@ -package glide +package lang import ( "context" From 1dff397b81f58c5822395a17e7e2363387f4d229 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 24 Jun 2024 23:11:13 +0200 Subject: [PATCH 13/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Test,=20error=20mess?= =?UTF-8?q?age?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client_test.go | 9 +++++++++ config/error.go | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client_test.go b/client_test.go index d70d198..f8a3d5a 100644 --- a/client_test.go +++ b/client_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/einstack/glide-go" + "github.com/einstack/glide-go/config" ) func TestNewClient(t *testing.T) { @@ -17,6 +18,14 @@ func TestNewClient(t *testing.T) { } } +func TestNewClientFromConfig(t *testing.T) { + if _, err := glide.NewClientFromConfig( + config.NewConfig(), + ); err != nil { + t.Fatal(err) + } +} + func TestClient_Health(t *testing.T) { client, _ := glide.NewClient( glide.WithApiKey("testing"), diff --git a/config/error.go b/config/error.go index 95ec72c..7935205 100644 --- a/config/error.go +++ b/config/error.go @@ -16,7 +16,7 @@ type Error struct { func NewError() error { return &Error{ Name: "unrecognized_error", - Message: "", + Message: "There is no response body and the status code is in the range of 400-599.", Status: http.StatusInternalServerError, } } From 3990455e57bcc2651a30c8569fc74b08ba7a7a64 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 24 Jun 2024 23:14:24 +0200 Subject: [PATCH 14/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Oops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index aad8945..f0c217d 100644 --- a/client.go +++ b/client.go @@ -130,7 +130,7 @@ func (c *Client) Health(ctx context.Context) (*bool, error) { return nil, err } - var resp Health + var resp *Health if _, err := c.config.Send(req, resp); err != nil { return nil, err } From 4d7c014dabb531a8e1c06b2a5e4b16f1dca8dd83 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 24 Jun 2024 23:27:05 +0200 Subject: [PATCH 15/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20runtime.Version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vars.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vars.go b/vars.go index 2cf808c..4e4e7c0 100644 --- a/vars.go +++ b/vars.go @@ -3,13 +3,21 @@ package glide import ( "fmt" "os" + "runtime" + "strings" ) // ClientVersion is the current version of this client. var clientVersion = "0.1.0" // GoVersion is the required version of the Go runtime. -var goVersion = "1.22.2" +var goVersion = getVersion() + +func getVersion() string { + version := runtime.Version() + after, _ := strings.CutPrefix(version, "go") + return after +} // userAgent is a default User-Agent header value. var userAgent = fmt.Sprintf("Glide/%s (Go; Ver. %s)", clientVersion, goVersion) From ef55bd99e36a356fc86a0684cee9f7a147fb5ef1 Mon Sep 17 00:00:00 2001 From: Oleh Martsokha Date: Mon, 24 Jun 2024 23:48:10 +0200 Subject: [PATCH 16/16] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Badges,=20move=20env?= =?UTF-8?q?=20vars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++++++- client.go | 7 ------- config/config.go | 8 +++++++- vars.go => config/env.go | 8 ++++---- examples/hello.go | 3 ++- 5 files changed, 19 insertions(+), 14 deletions(-) rename vars.go => config/env.go (98%) diff --git a/README.md b/README.md index 5fe0a8c..5423a1e 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ Discord Glide Docs ArtifactHub +
+ + Github Action + + Go Reference --- @@ -50,6 +55,6 @@ func main() { log.Fatal(err) } - println("response: ", resp.Content()) + log.Println("response: ", resp.Content()) } ``` diff --git a/client.go b/client.go index f0c217d..d1fc0be 100644 --- a/client.go +++ b/client.go @@ -21,13 +21,6 @@ type ClientOption func(*Client) error // NewClient instantiates a new Client. func NewClient(options ...ClientOption) (*Client, error) { - options = append([]ClientOption{ - WithApiKey(envApiKey), - WithUserAgent(envUserAgent), - WithRawBaseURL(envBaseUrl), - WithHttpClient(http.DefaultClient), - }, options...) - client := &Client{config: config.NewConfig()} client.Lang = lang.NewLanguage(client.config) diff --git a/config/config.go b/config/config.go index ea231dd..be326bf 100644 --- a/config/config.go +++ b/config/config.go @@ -21,7 +21,13 @@ type Config struct { // NewConfig instantiates a new Config. func NewConfig() *Config { - return &Config{} + parseBaseURL, _ := url.Parse(envBaseUrl) + return &Config{ + ApiKey: envApiKey, + UserAgent: envUserAgent, + BaseURL: parseBaseURL, + HttpClient: http.DefaultClient, + } } // Build instantiates a new http.Request. diff --git a/vars.go b/config/env.go similarity index 98% rename from vars.go rename to config/env.go index 4e4e7c0..a171a60 100644 --- a/vars.go +++ b/config/env.go @@ -1,4 +1,4 @@ -package glide +package config import ( "fmt" @@ -13,15 +13,15 @@ var clientVersion = "0.1.0" // GoVersion is the required version of the Go runtime. var goVersion = getVersion() +// userAgent is a default User-Agent header value. +var userAgent = fmt.Sprintf("Glide/%s (Go; Ver. %s)", clientVersion, goVersion) + func getVersion() string { version := runtime.Version() after, _ := strings.CutPrefix(version, "go") return after } -// userAgent is a default User-Agent header value. -var userAgent = fmt.Sprintf("Glide/%s (Go; Ver. %s)", clientVersion, goVersion) - var envApiKey = getEnv("GLIDE_API_KEY", "") var envUserAgent = getEnv("GLIDE_USER_AGENT", userAgent) var envBaseUrl = getEnv("GLIDE_BASE_URL", "http://127.0.0.1:9099/") diff --git a/examples/hello.go b/examples/hello.go index 02c57c5..d9e98be 100644 --- a/examples/hello.go +++ b/examples/hello.go @@ -17,6 +17,7 @@ func main() { } ctx := context.Background() + log.Println(client.UserAgent()) if _, err := client.Health(ctx); err != nil { log.Fatal(err) } @@ -27,5 +28,5 @@ func main() { log.Fatal(err) } - println("response: ", resp.Content()) + log.Println("response: ", resp.Content()) }