├── .fernignore
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── client
├── client.go
└── client_test.go
├── core
├── api_error.go
├── http.go
└── request_option.go
├── document.go
├── document
└── client.go
├── entity_types.go
├── environments.go
├── errors.go
├── examples
├── conversations.go
├── entity_types.go
└── user_graph.go
├── file_param.go
├── go.mod
├── go.sum
├── graph.go
├── graph
├── client
│ ├── base_edge.go
│ ├── base_entity.go
│ ├── client.go
│ ├── ontology.go
│ └── schema.go
├── edge
│ └── client.go
├── episode.go
├── episode
│ └── client.go
└── node
│ └── client.go
├── group.go
├── group
└── client.go
├── internal
├── caller.go
├── caller_test.go
├── error_decoder.go
├── error_decoder_test.go
├── extra_properties.go
├── extra_properties_test.go
├── http.go
├── query.go
├── query_test.go
├── retrier.go
├── retrier_test.go
├── stringer.go
└── time.go
├── memory.go
├── memory
└── client.go
├── option
└── request_option.go
├── pointer.go
├── types.go
├── user.go
└── user
└── client.go
/.fernignore:
--------------------------------------------------------------------------------
1 | # Specify files that shouldn't be modified by Fern
2 |
3 | README.md
4 |
5 | .gitignore
6 | examples/
7 | graph/client/base_entity.go
8 | graph/client/base_edge.go
9 | graph/client/schema.go
10 | graph/client/ontology.go
11 | entity_types.go
12 | LICENSE
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push]
4 |
5 | jobs:
6 | compile:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout repo
10 | uses: actions/checkout@v3
11 |
12 | - name: Set up go
13 | uses: actions/setup-go@v4
14 |
15 | - name: Compile
16 | run: go build ./...
17 | test:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout repo
21 | uses: actions/checkout@v3
22 |
23 | - name: Set up go
24 | uses: actions/setup-go@v4
25 |
26 | - name: Test
27 | run: go test ./...
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Zep Go Library
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Zep: Long-Term Memory for AI Assistants.
11 |
12 | Recall, understand, and extract data from chat histories. Power personalized AI experiences.
13 |
14 |
15 |
19 |
20 |
21 |
22 |
26 |
27 |
31 |
32 |
33 |
34 | Documentation |
35 | LangChain |
36 | Discord
37 | www.getzep.com
38 |
39 |
40 |
41 |
42 | The Zep Go library provides convenient access to the Zep Cloud API from Go.
43 |
44 | ## Requirements
45 |
46 | This module requires Go version >= 1.13.
47 |
48 | # Installation
49 |
50 | Run the following command to use the Zep Go library in your module:
51 |
52 | ```sh
53 | go get github.com/getzep/zep-go/v2
54 | ```
55 |
56 | ## Initialize Client
57 |
58 | ```go
59 | import (
60 | "github.com/getzep/zep-go/v2"
61 | zepclient "github.com/getzep/zep-go/v2/client"
62 | "github.com/getzep/zep-go/v2/option"
63 | )
64 |
65 | client := zepclient.NewClient(
66 | // this api key is `api_secret` line from zep.yaml of your local server or your Zep cloud api-key
67 | option.WithAPIKey(""),
68 | // use this to connect to your Zep server locally, not necessary for Zep Cloud
69 | option.WithBaseURL("http://localhost:8000/api/v2"),
70 | )
71 | ```
72 |
73 | ## Add Memory
74 |
75 | ```go
76 | _, err = client.Memory.Add(ctx, "session_id", &zep.AddMemoryRequest{
77 | Messages: []*zep.Message{
78 | {
79 | Role: zep.String("customer"),
80 | Content: zep.String("Hello, can I buy some shoes?"),
81 | RoleType: zep.RoleTypeUserRole.Ptr(),
82 | },
83 | },
84 | })
85 | ```
86 |
87 | ## Get Memory
88 |
89 | ```go
90 | memory, err := client.Memory.Get(ctx, "session_id", nil)
91 | ```
92 |
93 | ## Optionals
94 |
95 | This library models optional primitives and enum types as pointers. This is primarily meant to distinguish
96 | default zero values from explicit values (e.g. `false` for `bool` and `""` for `string`). A collection of
97 | helper functions are provided to easily map a primitive or enum to its pointer-equivalent (e.g. `zep.Int`).
98 |
99 | ## Request Options
100 |
101 | A variety of request options are included to adapt the behavior of the library, which includes
102 | configuring authorization tokens, or providing your own instrumented `*http.Client`. Both of
103 | these options are shown below:
104 |
105 | ```go
106 | client := zepclient.NewClient(
107 | option.WithAPIKey(""),
108 | option.WithHTTPClient(
109 | &http.Client{
110 | Timeout: 5 * time.Second,
111 | },
112 | ),
113 | )
114 | ```
115 |
116 | These request options can either be specified on the client so that they're applied on _every_
117 | request (shown above), or for an individual request like so:
118 |
119 | ```go
120 | _, _ = client.Memory.Get(ctx, "session_id", &zep.MemoryGetRequest{}, option.WithAPIKey(""))
121 | ```
122 |
123 | > Providing your own `*http.Client` is recommended. Otherwise, the `http.DefaultClient` will be used,
124 | > and your client will wait indefinitely for a response (unless the per-request, context-based timeout
125 | > is used).
126 |
127 | ## Automatic Retries
128 |
129 | The Zep Go client is instrumented with automatic retries with exponential backoff. A request will be
130 | retried as long as the request is deemed retriable and the number of retry attempts has not grown larger
131 | than the configured retry limit (default: 2).
132 |
133 | A request is deemed retriable when any of the following HTTP status codes is returned:
134 |
135 | - [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
136 | - [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) (Conflict)
137 | - [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
138 | - [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors)
139 |
140 | You can use the `option.WithMaxAttempts` option to configure the maximum retry limit to
141 | your liking. For example, if you want to disable retries for the client entirely, you can
142 | set this value to 1 like so:
143 |
144 | ```go
145 | client := zepclient.NewClient(
146 | option.WithMaxAttempts(1),
147 | )
148 | ```
149 |
150 | This can be done for an individual request, too:
151 |
152 | ```go
153 | _, _ = client.Memory.Get(ctx, "session_id", &zep.MemoryGetRequest{}, option.WithMaxAttempts(1))
154 | ```
155 |
156 | ## Errors
157 |
158 | Structured error types are returned from API calls that return non-success status codes. For example,
159 | you can check if the error was due to a bad request (i.e. status code 400) with the following:
160 |
161 | ```go
162 | _, err := client.Memory.Get(ctx, "session_id", &zep.MemoryGetRequest{})
163 | if err != nil {
164 | if badRequestErr, ok := err.(*zep.BadRequestError);
165 | // Do something with the bad request ...
166 | }
167 | return err
168 | }
169 | ```
170 |
171 | These errors are also compatible with the `errors.Is` and `errors.As` APIs, so you can access the error
172 | like so:
173 |
174 | ```go
175 | _, err := client.Memory.Get(ctx, "session_id", &zep.MemoryGetRequest{})
176 | if err != nil {
177 | var badRequestErr *zep.BadRequestError
178 | if errors.As(err, badRequestErr) {
179 | // Do something with the bad request ...
180 | }
181 | return err
182 | }
183 | ```
184 |
185 | If you'd like to wrap the errors with additional information and still retain the ability
186 | to access the type with `errors.Is` and `errors.As`, you can use the `%w` directive:
187 |
188 | ```go
189 | _, err := client.Memory.Get(ctx, "session_id", &zep.MemoryGetRequest{})
190 | if err != nil {
191 | return fmt.Errorf("failed to get memory: %w", err)
192 | }
193 | ```
194 |
195 | ## Contributing
196 |
197 | While we value open-source contributions to this SDK, this library is generated programmatically.
198 | Additions made directly to this library would have to be moved over to our generation code,
199 | otherwise they would be overwritten upon the next generated release. Feel free to open a PR as
200 | a proof of concept, but know that we will not be able to merge it as-is. We suggest opening
201 | an issue first to discuss with us!
202 |
203 | On the other hand, contributions to the `README.md` are always very welcome!
204 |
--------------------------------------------------------------------------------
/client/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package client
4 |
5 | import (
6 | core "github.com/getzep/zep-go/v2/core"
7 | document "github.com/getzep/zep-go/v2/document"
8 | graphclient "github.com/getzep/zep-go/v2/graph/client"
9 | group "github.com/getzep/zep-go/v2/group"
10 | internal "github.com/getzep/zep-go/v2/internal"
11 | memory "github.com/getzep/zep-go/v2/memory"
12 | option "github.com/getzep/zep-go/v2/option"
13 | user "github.com/getzep/zep-go/v2/user"
14 | http "net/http"
15 | os "os"
16 | )
17 |
18 | type Client struct {
19 | baseURL string
20 | caller *internal.Caller
21 | header http.Header
22 |
23 | Document *document.Client
24 | Graph *graphclient.Client
25 | Memory *memory.Client
26 | Group *group.Client
27 | User *user.Client
28 | }
29 |
30 | func NewClient(opts ...option.RequestOption) *Client {
31 | options := core.NewRequestOptions(opts...)
32 | if options.APIKey == "" {
33 | options.APIKey = os.Getenv("ZEP_API_KEY")
34 | }
35 | return &Client{
36 | baseURL: options.BaseURL,
37 | caller: internal.NewCaller(
38 | &internal.CallerParams{
39 | Client: options.HTTPClient,
40 | MaxAttempts: options.MaxAttempts,
41 | },
42 | ),
43 | header: options.ToHeader(),
44 | Document: document.NewClient(opts...),
45 | Graph: graphclient.NewClient(opts...),
46 | Memory: memory.NewClient(opts...),
47 | Group: group.NewClient(opts...),
48 | User: user.NewClient(opts...),
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/client/client_test.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package client
4 |
5 | import (
6 | option "github.com/getzep/zep-go/v2/option"
7 | assert "github.com/stretchr/testify/assert"
8 | http "net/http"
9 | testing "testing"
10 | time "time"
11 | )
12 |
13 | func TestNewClient(t *testing.T) {
14 | t.Run("default", func(t *testing.T) {
15 | c := NewClient()
16 | assert.Empty(t, c.baseURL)
17 | })
18 |
19 | t.Run("base url", func(t *testing.T) {
20 | c := NewClient(
21 | option.WithBaseURL("test.co"),
22 | )
23 | assert.Equal(t, "test.co", c.baseURL)
24 | })
25 |
26 | t.Run("http client", func(t *testing.T) {
27 | httpClient := &http.Client{
28 | Timeout: 5 * time.Second,
29 | }
30 | c := NewClient(
31 | option.WithHTTPClient(httpClient),
32 | )
33 | assert.Empty(t, c.baseURL)
34 | })
35 |
36 | t.Run("http header", func(t *testing.T) {
37 | header := make(http.Header)
38 | header.Set("X-API-Tenancy", "test")
39 | c := NewClient(
40 | option.WithHTTPHeader(header),
41 | )
42 | assert.Empty(t, c.baseURL)
43 | assert.Equal(t, "test", c.header.Get("X-API-Tenancy"))
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/core/api_error.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import "fmt"
4 |
5 | // APIError is a lightweight wrapper around the standard error
6 | // interface that preserves the status code from the RPC, if any.
7 | type APIError struct {
8 | err error
9 |
10 | StatusCode int `json:"-"`
11 | }
12 |
13 | // NewAPIError constructs a new API error.
14 | func NewAPIError(statusCode int, err error) *APIError {
15 | return &APIError{
16 | err: err,
17 | StatusCode: statusCode,
18 | }
19 | }
20 |
21 | // Unwrap returns the underlying error. This also makes the error compatible
22 | // with errors.As and errors.Is.
23 | func (a *APIError) Unwrap() error {
24 | if a == nil {
25 | return nil
26 | }
27 | return a.err
28 | }
29 |
30 | // Error returns the API error's message.
31 | func (a *APIError) Error() string {
32 | if a == nil || (a.err == nil && a.StatusCode == 0) {
33 | return ""
34 | }
35 | if a.err == nil {
36 | return fmt.Sprintf("%d", a.StatusCode)
37 | }
38 | if a.StatusCode == 0 {
39 | return a.err.Error()
40 | }
41 | return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error())
42 | }
43 |
--------------------------------------------------------------------------------
/core/http.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import "net/http"
4 |
5 | // HTTPClient is an interface for a subset of the *http.Client.
6 | type HTTPClient interface {
7 | Do(*http.Request) (*http.Response, error)
8 | }
9 |
--------------------------------------------------------------------------------
/core/request_option.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package core
4 |
5 | import (
6 | fmt "fmt"
7 | http "net/http"
8 | url "net/url"
9 | )
10 |
11 | // RequestOption adapts the behavior of the client or an individual request.
12 | type RequestOption interface {
13 | applyRequestOptions(*RequestOptions)
14 | }
15 |
16 | // RequestOptions defines all of the possible request options.
17 | //
18 | // This type is primarily used by the generated code and is not meant
19 | // to be used directly; use the option package instead.
20 | type RequestOptions struct {
21 | BaseURL string
22 | HTTPClient HTTPClient
23 | HTTPHeader http.Header
24 | BodyProperties map[string]interface{}
25 | QueryParameters url.Values
26 | MaxAttempts uint
27 | APIKey string
28 | }
29 |
30 | // NewRequestOptions returns a new *RequestOptions value.
31 | //
32 | // This function is primarily used by the generated code and is not meant
33 | // to be used directly; use RequestOption instead.
34 | func NewRequestOptions(opts ...RequestOption) *RequestOptions {
35 | options := &RequestOptions{
36 | HTTPHeader: make(http.Header),
37 | BodyProperties: make(map[string]interface{}),
38 | QueryParameters: make(url.Values),
39 | }
40 | for _, opt := range opts {
41 | opt.applyRequestOptions(options)
42 | }
43 | return options
44 | }
45 |
46 | // ToHeader maps the configured request options into a http.Header used
47 | // for the request(s).
48 | func (r *RequestOptions) ToHeader() http.Header {
49 | header := r.cloneHeader()
50 | if r.APIKey != "" {
51 | header.Set("Authorization", fmt.Sprintf("Api-Key %v", r.APIKey))
52 | }
53 | return header
54 | }
55 |
56 | func (r *RequestOptions) cloneHeader() http.Header {
57 | headers := r.HTTPHeader.Clone()
58 | headers.Set("X-Fern-Language", "Go")
59 | headers.Set("X-Fern-SDK-Name", "github.com/getzep/zep-go/v2")
60 | headers.Set("X-Fern-SDK-Version", "v2.14.0")
61 | headers.Set("User-Agent", "github.com/getzep/zep-go/2.14.0")
62 | return headers
63 | }
64 |
65 | // BaseURLOption implements the RequestOption interface.
66 | type BaseURLOption struct {
67 | BaseURL string
68 | }
69 |
70 | func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) {
71 | opts.BaseURL = b.BaseURL
72 | }
73 |
74 | // HTTPClientOption implements the RequestOption interface.
75 | type HTTPClientOption struct {
76 | HTTPClient HTTPClient
77 | }
78 |
79 | func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) {
80 | opts.HTTPClient = h.HTTPClient
81 | }
82 |
83 | // HTTPHeaderOption implements the RequestOption interface.
84 | type HTTPHeaderOption struct {
85 | HTTPHeader http.Header
86 | }
87 |
88 | func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) {
89 | opts.HTTPHeader = h.HTTPHeader
90 | }
91 |
92 | // BodyPropertiesOption implements the RequestOption interface.
93 | type BodyPropertiesOption struct {
94 | BodyProperties map[string]interface{}
95 | }
96 |
97 | func (b *BodyPropertiesOption) applyRequestOptions(opts *RequestOptions) {
98 | opts.BodyProperties = b.BodyProperties
99 | }
100 |
101 | // QueryParametersOption implements the RequestOption interface.
102 | type QueryParametersOption struct {
103 | QueryParameters url.Values
104 | }
105 |
106 | func (q *QueryParametersOption) applyRequestOptions(opts *RequestOptions) {
107 | opts.QueryParameters = q.QueryParameters
108 | }
109 |
110 | // MaxAttemptsOption implements the RequestOption interface.
111 | type MaxAttemptsOption struct {
112 | MaxAttempts uint
113 | }
114 |
115 | func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) {
116 | opts.MaxAttempts = m.MaxAttempts
117 | }
118 |
119 | // APIKeyOption implements the RequestOption interface.
120 | type APIKeyOption struct {
121 | APIKey string
122 | }
123 |
124 | func (a *APIKeyOption) applyRequestOptions(opts *RequestOptions) {
125 | opts.APIKey = a.APIKey
126 | }
127 |
--------------------------------------------------------------------------------
/document.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package zep
4 |
5 | import (
6 | json "encoding/json"
7 | fmt "fmt"
8 | internal "github.com/getzep/zep-go/v2/internal"
9 | )
10 |
11 | type CreateDocumentCollectionRequest struct {
12 | Description *string `json:"description,omitempty" url:"-"`
13 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"`
14 | }
15 |
16 | type GetDocumentListRequest struct {
17 | DocumentIDs []string `json:"document_ids,omitempty" url:"-"`
18 | UUIDs []string `json:"uuids,omitempty" url:"-"`
19 | }
20 |
21 | type DocumentSearchPayload struct {
22 | // Limit the number of returned documents
23 | Limit *int `json:"-" url:"limit,omitempty"`
24 | // Document metadata to filter on.
25 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"`
26 | MinScore *float64 `json:"min_score,omitempty" url:"-"`
27 | // The lambda parameter for the MMR Reranking Algorithm.
28 | MmrLambda *float64 `json:"mmr_lambda,omitempty" url:"-"`
29 | // The type of search to perform. Defaults to "similarity". Must be one of "similarity" or "mmr".
30 | SearchType *SearchType `json:"search_type,omitempty" url:"-"`
31 | // The search text.
32 | Text *string `json:"text,omitempty" url:"-"`
33 | }
34 |
35 | type ApidataDocument struct {
36 | Content *string `json:"content,omitempty" url:"content,omitempty"`
37 | CreatedAt *string `json:"created_at,omitempty" url:"created_at,omitempty"`
38 | DocumentID *string `json:"document_id,omitempty" url:"document_id,omitempty"`
39 | Embedding []float64 `json:"embedding,omitempty" url:"embedding,omitempty"`
40 | IsEmbedded *bool `json:"is_embedded,omitempty" url:"is_embedded,omitempty"`
41 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"`
42 | UpdatedAt *string `json:"updated_at,omitempty" url:"updated_at,omitempty"`
43 | UUID *string `json:"uuid,omitempty" url:"uuid,omitempty"`
44 |
45 | extraProperties map[string]interface{}
46 | rawJSON json.RawMessage
47 | }
48 |
49 | func (a *ApidataDocument) GetContent() *string {
50 | if a == nil {
51 | return nil
52 | }
53 | return a.Content
54 | }
55 |
56 | func (a *ApidataDocument) GetCreatedAt() *string {
57 | if a == nil {
58 | return nil
59 | }
60 | return a.CreatedAt
61 | }
62 |
63 | func (a *ApidataDocument) GetDocumentID() *string {
64 | if a == nil {
65 | return nil
66 | }
67 | return a.DocumentID
68 | }
69 |
70 | func (a *ApidataDocument) GetEmbedding() []float64 {
71 | if a == nil {
72 | return nil
73 | }
74 | return a.Embedding
75 | }
76 |
77 | func (a *ApidataDocument) GetIsEmbedded() *bool {
78 | if a == nil {
79 | return nil
80 | }
81 | return a.IsEmbedded
82 | }
83 |
84 | func (a *ApidataDocument) GetMetadata() map[string]interface{} {
85 | if a == nil {
86 | return nil
87 | }
88 | return a.Metadata
89 | }
90 |
91 | func (a *ApidataDocument) GetUpdatedAt() *string {
92 | if a == nil {
93 | return nil
94 | }
95 | return a.UpdatedAt
96 | }
97 |
98 | func (a *ApidataDocument) GetUUID() *string {
99 | if a == nil {
100 | return nil
101 | }
102 | return a.UUID
103 | }
104 |
105 | func (a *ApidataDocument) GetExtraProperties() map[string]interface{} {
106 | return a.extraProperties
107 | }
108 |
109 | func (a *ApidataDocument) UnmarshalJSON(data []byte) error {
110 | type unmarshaler ApidataDocument
111 | var value unmarshaler
112 | if err := json.Unmarshal(data, &value); err != nil {
113 | return err
114 | }
115 | *a = ApidataDocument(value)
116 | extraProperties, err := internal.ExtractExtraProperties(data, *a)
117 | if err != nil {
118 | return err
119 | }
120 | a.extraProperties = extraProperties
121 | a.rawJSON = json.RawMessage(data)
122 | return nil
123 | }
124 |
125 | func (a *ApidataDocument) String() string {
126 | if len(a.rawJSON) > 0 {
127 | if value, err := internal.StringifyJSON(a.rawJSON); err == nil {
128 | return value
129 | }
130 | }
131 | if value, err := internal.StringifyJSON(a); err == nil {
132 | return value
133 | }
134 | return fmt.Sprintf("%#v", a)
135 | }
136 |
137 | type ApidataDocumentCollection struct {
138 | CreatedAt *string `json:"created_at,omitempty" url:"created_at,omitempty"`
139 | Description *string `json:"description,omitempty" url:"description,omitempty"`
140 | DocumentCount *int `json:"document_count,omitempty" url:"document_count,omitempty"`
141 | DocumentEmbeddedCount *int `json:"document_embedded_count,omitempty" url:"document_embedded_count,omitempty"`
142 | EmbeddingDimensions *int `json:"embedding_dimensions,omitempty" url:"embedding_dimensions,omitempty"`
143 | EmbeddingModelName *string `json:"embedding_model_name,omitempty" url:"embedding_model_name,omitempty"`
144 | IsAutoEmbedded *bool `json:"is_auto_embedded,omitempty" url:"is_auto_embedded,omitempty"`
145 | IsIndexed *bool `json:"is_indexed,omitempty" url:"is_indexed,omitempty"`
146 | IsNormalized *bool `json:"is_normalized,omitempty" url:"is_normalized,omitempty"`
147 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"`
148 | Name *string `json:"name,omitempty" url:"name,omitempty"`
149 | UpdatedAt *string `json:"updated_at,omitempty" url:"updated_at,omitempty"`
150 | UUID *string `json:"uuid,omitempty" url:"uuid,omitempty"`
151 |
152 | extraProperties map[string]interface{}
153 | rawJSON json.RawMessage
154 | }
155 |
156 | func (a *ApidataDocumentCollection) GetCreatedAt() *string {
157 | if a == nil {
158 | return nil
159 | }
160 | return a.CreatedAt
161 | }
162 |
163 | func (a *ApidataDocumentCollection) GetDescription() *string {
164 | if a == nil {
165 | return nil
166 | }
167 | return a.Description
168 | }
169 |
170 | func (a *ApidataDocumentCollection) GetDocumentCount() *int {
171 | if a == nil {
172 | return nil
173 | }
174 | return a.DocumentCount
175 | }
176 |
177 | func (a *ApidataDocumentCollection) GetDocumentEmbeddedCount() *int {
178 | if a == nil {
179 | return nil
180 | }
181 | return a.DocumentEmbeddedCount
182 | }
183 |
184 | func (a *ApidataDocumentCollection) GetEmbeddingDimensions() *int {
185 | if a == nil {
186 | return nil
187 | }
188 | return a.EmbeddingDimensions
189 | }
190 |
191 | func (a *ApidataDocumentCollection) GetEmbeddingModelName() *string {
192 | if a == nil {
193 | return nil
194 | }
195 | return a.EmbeddingModelName
196 | }
197 |
198 | func (a *ApidataDocumentCollection) GetIsAutoEmbedded() *bool {
199 | if a == nil {
200 | return nil
201 | }
202 | return a.IsAutoEmbedded
203 | }
204 |
205 | func (a *ApidataDocumentCollection) GetIsIndexed() *bool {
206 | if a == nil {
207 | return nil
208 | }
209 | return a.IsIndexed
210 | }
211 |
212 | func (a *ApidataDocumentCollection) GetIsNormalized() *bool {
213 | if a == nil {
214 | return nil
215 | }
216 | return a.IsNormalized
217 | }
218 |
219 | func (a *ApidataDocumentCollection) GetMetadata() map[string]interface{} {
220 | if a == nil {
221 | return nil
222 | }
223 | return a.Metadata
224 | }
225 |
226 | func (a *ApidataDocumentCollection) GetName() *string {
227 | if a == nil {
228 | return nil
229 | }
230 | return a.Name
231 | }
232 |
233 | func (a *ApidataDocumentCollection) GetUpdatedAt() *string {
234 | if a == nil {
235 | return nil
236 | }
237 | return a.UpdatedAt
238 | }
239 |
240 | func (a *ApidataDocumentCollection) GetUUID() *string {
241 | if a == nil {
242 | return nil
243 | }
244 | return a.UUID
245 | }
246 |
247 | func (a *ApidataDocumentCollection) GetExtraProperties() map[string]interface{} {
248 | return a.extraProperties
249 | }
250 |
251 | func (a *ApidataDocumentCollection) UnmarshalJSON(data []byte) error {
252 | type unmarshaler ApidataDocumentCollection
253 | var value unmarshaler
254 | if err := json.Unmarshal(data, &value); err != nil {
255 | return err
256 | }
257 | *a = ApidataDocumentCollection(value)
258 | extraProperties, err := internal.ExtractExtraProperties(data, *a)
259 | if err != nil {
260 | return err
261 | }
262 | a.extraProperties = extraProperties
263 | a.rawJSON = json.RawMessage(data)
264 | return nil
265 | }
266 |
267 | func (a *ApidataDocumentCollection) String() string {
268 | if len(a.rawJSON) > 0 {
269 | if value, err := internal.StringifyJSON(a.rawJSON); err == nil {
270 | return value
271 | }
272 | }
273 | if value, err := internal.StringifyJSON(a); err == nil {
274 | return value
275 | }
276 | return fmt.Sprintf("%#v", a)
277 | }
278 |
279 | type ApidataDocumentSearchResponse struct {
280 | CurrentPage *int `json:"current_page,omitempty" url:"current_page,omitempty"`
281 | QueryVector []float64 `json:"query_vector,omitempty" url:"query_vector,omitempty"`
282 | ResultCount *int `json:"result_count,omitempty" url:"result_count,omitempty"`
283 | Results []*ApidataDocumentWithScore `json:"results,omitempty" url:"results,omitempty"`
284 | TotalPages *int `json:"total_pages,omitempty" url:"total_pages,omitempty"`
285 |
286 | extraProperties map[string]interface{}
287 | rawJSON json.RawMessage
288 | }
289 |
290 | func (a *ApidataDocumentSearchResponse) GetCurrentPage() *int {
291 | if a == nil {
292 | return nil
293 | }
294 | return a.CurrentPage
295 | }
296 |
297 | func (a *ApidataDocumentSearchResponse) GetQueryVector() []float64 {
298 | if a == nil {
299 | return nil
300 | }
301 | return a.QueryVector
302 | }
303 |
304 | func (a *ApidataDocumentSearchResponse) GetResultCount() *int {
305 | if a == nil {
306 | return nil
307 | }
308 | return a.ResultCount
309 | }
310 |
311 | func (a *ApidataDocumentSearchResponse) GetResults() []*ApidataDocumentWithScore {
312 | if a == nil {
313 | return nil
314 | }
315 | return a.Results
316 | }
317 |
318 | func (a *ApidataDocumentSearchResponse) GetTotalPages() *int {
319 | if a == nil {
320 | return nil
321 | }
322 | return a.TotalPages
323 | }
324 |
325 | func (a *ApidataDocumentSearchResponse) GetExtraProperties() map[string]interface{} {
326 | return a.extraProperties
327 | }
328 |
329 | func (a *ApidataDocumentSearchResponse) UnmarshalJSON(data []byte) error {
330 | type unmarshaler ApidataDocumentSearchResponse
331 | var value unmarshaler
332 | if err := json.Unmarshal(data, &value); err != nil {
333 | return err
334 | }
335 | *a = ApidataDocumentSearchResponse(value)
336 | extraProperties, err := internal.ExtractExtraProperties(data, *a)
337 | if err != nil {
338 | return err
339 | }
340 | a.extraProperties = extraProperties
341 | a.rawJSON = json.RawMessage(data)
342 | return nil
343 | }
344 |
345 | func (a *ApidataDocumentSearchResponse) String() string {
346 | if len(a.rawJSON) > 0 {
347 | if value, err := internal.StringifyJSON(a.rawJSON); err == nil {
348 | return value
349 | }
350 | }
351 | if value, err := internal.StringifyJSON(a); err == nil {
352 | return value
353 | }
354 | return fmt.Sprintf("%#v", a)
355 | }
356 |
357 | type ApidataDocumentWithScore struct {
358 | Content *string `json:"content,omitempty" url:"content,omitempty"`
359 | CreatedAt *string `json:"created_at,omitempty" url:"created_at,omitempty"`
360 | DocumentID *string `json:"document_id,omitempty" url:"document_id,omitempty"`
361 | Embedding []float64 `json:"embedding,omitempty" url:"embedding,omitempty"`
362 | IsEmbedded *bool `json:"is_embedded,omitempty" url:"is_embedded,omitempty"`
363 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"`
364 | Score *float64 `json:"score,omitempty" url:"score,omitempty"`
365 | UpdatedAt *string `json:"updated_at,omitempty" url:"updated_at,omitempty"`
366 | UUID *string `json:"uuid,omitempty" url:"uuid,omitempty"`
367 |
368 | extraProperties map[string]interface{}
369 | rawJSON json.RawMessage
370 | }
371 |
372 | func (a *ApidataDocumentWithScore) GetContent() *string {
373 | if a == nil {
374 | return nil
375 | }
376 | return a.Content
377 | }
378 |
379 | func (a *ApidataDocumentWithScore) GetCreatedAt() *string {
380 | if a == nil {
381 | return nil
382 | }
383 | return a.CreatedAt
384 | }
385 |
386 | func (a *ApidataDocumentWithScore) GetDocumentID() *string {
387 | if a == nil {
388 | return nil
389 | }
390 | return a.DocumentID
391 | }
392 |
393 | func (a *ApidataDocumentWithScore) GetEmbedding() []float64 {
394 | if a == nil {
395 | return nil
396 | }
397 | return a.Embedding
398 | }
399 |
400 | func (a *ApidataDocumentWithScore) GetIsEmbedded() *bool {
401 | if a == nil {
402 | return nil
403 | }
404 | return a.IsEmbedded
405 | }
406 |
407 | func (a *ApidataDocumentWithScore) GetMetadata() map[string]interface{} {
408 | if a == nil {
409 | return nil
410 | }
411 | return a.Metadata
412 | }
413 |
414 | func (a *ApidataDocumentWithScore) GetScore() *float64 {
415 | if a == nil {
416 | return nil
417 | }
418 | return a.Score
419 | }
420 |
421 | func (a *ApidataDocumentWithScore) GetUpdatedAt() *string {
422 | if a == nil {
423 | return nil
424 | }
425 | return a.UpdatedAt
426 | }
427 |
428 | func (a *ApidataDocumentWithScore) GetUUID() *string {
429 | if a == nil {
430 | return nil
431 | }
432 | return a.UUID
433 | }
434 |
435 | func (a *ApidataDocumentWithScore) GetExtraProperties() map[string]interface{} {
436 | return a.extraProperties
437 | }
438 |
439 | func (a *ApidataDocumentWithScore) UnmarshalJSON(data []byte) error {
440 | type unmarshaler ApidataDocumentWithScore
441 | var value unmarshaler
442 | if err := json.Unmarshal(data, &value); err != nil {
443 | return err
444 | }
445 | *a = ApidataDocumentWithScore(value)
446 | extraProperties, err := internal.ExtractExtraProperties(data, *a)
447 | if err != nil {
448 | return err
449 | }
450 | a.extraProperties = extraProperties
451 | a.rawJSON = json.RawMessage(data)
452 | return nil
453 | }
454 |
455 | func (a *ApidataDocumentWithScore) String() string {
456 | if len(a.rawJSON) > 0 {
457 | if value, err := internal.StringifyJSON(a.rawJSON); err == nil {
458 | return value
459 | }
460 | }
461 | if value, err := internal.StringifyJSON(a); err == nil {
462 | return value
463 | }
464 | return fmt.Sprintf("%#v", a)
465 | }
466 |
467 | type CreateDocumentRequest struct {
468 | Content string `json:"content" url:"content"`
469 | DocumentID *string `json:"document_id,omitempty" url:"document_id,omitempty"`
470 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"`
471 |
472 | extraProperties map[string]interface{}
473 | rawJSON json.RawMessage
474 | }
475 |
476 | func (c *CreateDocumentRequest) GetContent() string {
477 | if c == nil {
478 | return ""
479 | }
480 | return c.Content
481 | }
482 |
483 | func (c *CreateDocumentRequest) GetDocumentID() *string {
484 | if c == nil {
485 | return nil
486 | }
487 | return c.DocumentID
488 | }
489 |
490 | func (c *CreateDocumentRequest) GetMetadata() map[string]interface{} {
491 | if c == nil {
492 | return nil
493 | }
494 | return c.Metadata
495 | }
496 |
497 | func (c *CreateDocumentRequest) GetExtraProperties() map[string]interface{} {
498 | return c.extraProperties
499 | }
500 |
501 | func (c *CreateDocumentRequest) UnmarshalJSON(data []byte) error {
502 | type unmarshaler CreateDocumentRequest
503 | var value unmarshaler
504 | if err := json.Unmarshal(data, &value); err != nil {
505 | return err
506 | }
507 | *c = CreateDocumentRequest(value)
508 | extraProperties, err := internal.ExtractExtraProperties(data, *c)
509 | if err != nil {
510 | return err
511 | }
512 | c.extraProperties = extraProperties
513 | c.rawJSON = json.RawMessage(data)
514 | return nil
515 | }
516 |
517 | func (c *CreateDocumentRequest) String() string {
518 | if len(c.rawJSON) > 0 {
519 | if value, err := internal.StringifyJSON(c.rawJSON); err == nil {
520 | return value
521 | }
522 | }
523 | if value, err := internal.StringifyJSON(c); err == nil {
524 | return value
525 | }
526 | return fmt.Sprintf("%#v", c)
527 | }
528 |
529 | type UpdateDocumentListRequest struct {
530 | DocumentID *string `json:"document_id,omitempty" url:"document_id,omitempty"`
531 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"`
532 | UUID string `json:"uuid" url:"uuid"`
533 |
534 | extraProperties map[string]interface{}
535 | rawJSON json.RawMessage
536 | }
537 |
538 | func (u *UpdateDocumentListRequest) GetDocumentID() *string {
539 | if u == nil {
540 | return nil
541 | }
542 | return u.DocumentID
543 | }
544 |
545 | func (u *UpdateDocumentListRequest) GetMetadata() map[string]interface{} {
546 | if u == nil {
547 | return nil
548 | }
549 | return u.Metadata
550 | }
551 |
552 | func (u *UpdateDocumentListRequest) GetUUID() string {
553 | if u == nil {
554 | return ""
555 | }
556 | return u.UUID
557 | }
558 |
559 | func (u *UpdateDocumentListRequest) GetExtraProperties() map[string]interface{} {
560 | return u.extraProperties
561 | }
562 |
563 | func (u *UpdateDocumentListRequest) UnmarshalJSON(data []byte) error {
564 | type unmarshaler UpdateDocumentListRequest
565 | var value unmarshaler
566 | if err := json.Unmarshal(data, &value); err != nil {
567 | return err
568 | }
569 | *u = UpdateDocumentListRequest(value)
570 | extraProperties, err := internal.ExtractExtraProperties(data, *u)
571 | if err != nil {
572 | return err
573 | }
574 | u.extraProperties = extraProperties
575 | u.rawJSON = json.RawMessage(data)
576 | return nil
577 | }
578 |
579 | func (u *UpdateDocumentListRequest) String() string {
580 | if len(u.rawJSON) > 0 {
581 | if value, err := internal.StringifyJSON(u.rawJSON); err == nil {
582 | return value
583 | }
584 | }
585 | if value, err := internal.StringifyJSON(u); err == nil {
586 | return value
587 | }
588 | return fmt.Sprintf("%#v", u)
589 | }
590 |
591 | type UpdateDocumentCollectionRequest struct {
592 | Description *string `json:"description,omitempty" url:"-"`
593 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"`
594 | }
595 |
596 | type UpdateDocumentRequest struct {
597 | DocumentID *string `json:"document_id,omitempty" url:"-"`
598 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"`
599 | }
600 |
--------------------------------------------------------------------------------
/entity_types.go:
--------------------------------------------------------------------------------
1 | package zep
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type BaseEntity struct{}
9 |
10 | type EntityDefinition interface {
11 | // This is a marker interface - any struct embedding BaseEntity will satisfy it
12 | isEntityDefinition()
13 | }
14 |
15 | // isEntityDefinition is a marker method of EntityDefinition interface
16 | func (BaseEntity) isEntityDefinition() {}
17 |
18 | // UnmarshalNodeAttributes unmarshals a map[string]interface{} into a struct
19 | // that embeds BaseEntity
20 | func UnmarshalNodeAttributes(attributes map[string]interface{}, dest EntityDefinition) error {
21 | jsonData, err := json.Marshal(attributes)
22 | if err != nil {
23 | return fmt.Errorf("error marshaling node attributes: %w", err)
24 | }
25 |
26 | err = json.Unmarshal(jsonData, dest)
27 | if err != nil {
28 | return fmt.Errorf("error unmarshaling to struct: %w", err)
29 | }
30 |
31 | return nil
32 | }
33 |
34 | type BaseEdge struct{}
35 |
36 | type EdgeDefinitionWithSourceTargets struct {
37 | EdgeModel EdgeDefinition `json:"edge_model"`
38 | SourceTargets []EntityEdgeSourceTarget `json:"source_targets,omitempty"`
39 | }
40 |
41 | type EdgeDefinition interface {
42 | // This is a marker interface - any struct embedding BaseEdge will satisfy it
43 | isEdgeDefinition()
44 | }
45 |
46 | // isEdgeDefinition is a marker method of EntityDefinition interface
47 | func (BaseEdge) isEdgeDefinition() {}
48 |
49 | // UnmarshalEdgeAttributes unmarshals a map[string]interface{} into a struct
50 | // that embeds BaseEdge
51 | func UnmarshalEdgeAttributes(attributes map[string]interface{}, dest EdgeDefinition) error {
52 | jsonData, err := json.Marshal(attributes)
53 | if err != nil {
54 | return fmt.Errorf("error marshaling node attributes: %w", err)
55 | }
56 |
57 | err = json.Unmarshal(jsonData, dest)
58 | if err != nil {
59 | return fmt.Errorf("error unmarshaling to struct: %w", err)
60 | }
61 |
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/environments.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package zep
4 |
5 | // Environments defines all of the API environments.
6 | // These values can be used with the WithBaseURL
7 | // RequestOption to override the client's default environment,
8 | // if any.
9 | var Environments = struct {
10 | Default string
11 | }{
12 | Default: "https://api.getzep.com/api/v2",
13 | }
14 |
--------------------------------------------------------------------------------
/errors.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package zep
4 |
5 | import (
6 | json "encoding/json"
7 | core "github.com/getzep/zep-go/v2/core"
8 | )
9 |
10 | // Bad Request
11 | type BadRequestError struct {
12 | *core.APIError
13 | Body *APIError
14 | }
15 |
16 | func (b *BadRequestError) UnmarshalJSON(data []byte) error {
17 | var body *APIError
18 | if err := json.Unmarshal(data, &body); err != nil {
19 | return err
20 | }
21 | b.StatusCode = 400
22 | b.Body = body
23 | return nil
24 | }
25 |
26 | func (b *BadRequestError) MarshalJSON() ([]byte, error) {
27 | return json.Marshal(b.Body)
28 | }
29 |
30 | func (b *BadRequestError) Unwrap() error {
31 | return b.APIError
32 | }
33 |
34 | // Conflict
35 | type ConflictError struct {
36 | *core.APIError
37 | Body *APIError
38 | }
39 |
40 | func (c *ConflictError) UnmarshalJSON(data []byte) error {
41 | var body *APIError
42 | if err := json.Unmarshal(data, &body); err != nil {
43 | return err
44 | }
45 | c.StatusCode = 409
46 | c.Body = body
47 | return nil
48 | }
49 |
50 | func (c *ConflictError) MarshalJSON() ([]byte, error) {
51 | return json.Marshal(c.Body)
52 | }
53 |
54 | func (c *ConflictError) Unwrap() error {
55 | return c.APIError
56 | }
57 |
58 | // Internal Server Error
59 | type InternalServerError struct {
60 | *core.APIError
61 | Body *APIError
62 | }
63 |
64 | func (i *InternalServerError) UnmarshalJSON(data []byte) error {
65 | var body *APIError
66 | if err := json.Unmarshal(data, &body); err != nil {
67 | return err
68 | }
69 | i.StatusCode = 500
70 | i.Body = body
71 | return nil
72 | }
73 |
74 | func (i *InternalServerError) MarshalJSON() ([]byte, error) {
75 | return json.Marshal(i.Body)
76 | }
77 |
78 | func (i *InternalServerError) Unwrap() error {
79 | return i.APIError
80 | }
81 |
82 | // Not Found
83 | type NotFoundError struct {
84 | *core.APIError
85 | Body *APIError
86 | }
87 |
88 | func (n *NotFoundError) UnmarshalJSON(data []byte) error {
89 | var body *APIError
90 | if err := json.Unmarshal(data, &body); err != nil {
91 | return err
92 | }
93 | n.StatusCode = 404
94 | n.Body = body
95 | return nil
96 | }
97 |
98 | func (n *NotFoundError) MarshalJSON() ([]byte, error) {
99 | return json.Marshal(n.Body)
100 | }
101 |
102 | func (n *NotFoundError) Unwrap() error {
103 | return n.APIError
104 | }
105 |
106 | // Unauthorized
107 | type UnauthorizedError struct {
108 | *core.APIError
109 | Body *APIError
110 | }
111 |
112 | func (u *UnauthorizedError) UnmarshalJSON(data []byte) error {
113 | var body *APIError
114 | if err := json.Unmarshal(data, &body); err != nil {
115 | return err
116 | }
117 | u.StatusCode = 401
118 | u.Body = body
119 | return nil
120 | }
121 |
122 | func (u *UnauthorizedError) MarshalJSON() ([]byte, error) {
123 | return json.Marshal(u.Body)
124 | }
125 |
126 | func (u *UnauthorizedError) Unwrap() error {
127 | return u.APIError
128 | }
129 |
--------------------------------------------------------------------------------
/examples/conversations.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/getzep/zep-go/v2"
4 |
5 | var history = [][]zep.Message{
6 | // Conversation 1: Japan
7 | {
8 | {
9 | Role: zep.String("Paul"),
10 | RoleType: "user",
11 | Content: "Hi, I'm planning a vacation to Japan. Can you give me some advice?",
12 | },
13 | {
14 | Role: zep.String("assistant"),
15 | RoleType: "assistant",
16 | Content: "Of course! Japan is a fascinating destination. Are you more interested in modern cities like Tokyo, or historical sites like Kyoto?",
17 | },
18 | {
19 | Role: zep.String("Paul"),
20 | RoleType: "user",
21 | Content: "I think I'd like to experience both. Can you suggest an itinerary?",
22 | },
23 | {
24 | Role: zep.String("assistant"),
25 | RoleType: "assistant",
26 | Content: "Certainly! You could start with 3 days in Tokyo, then take the bullet train to Kyoto for 3 days. This way, you'll experience both the modern and traditional aspects of Japan.",
27 | },
28 | {
29 | Role: zep.String("Paul"),
30 | RoleType: "user",
31 | Content: "That sounds perfect! I booked a flight on Nov 17th! it departs at 5 pm (flight number GC1234). It cost me $700.",
32 | },
33 | },
34 | // Conversation 2: Italy
35 | {
36 | {
37 | Role: zep.String("Paul"),
38 | RoleType: "user",
39 | Content: "I'm thinking about visiting Italy next summer. Any recommendations?",
40 | },
41 | {
42 | Role: zep.String("assistant"),
43 | RoleType: "assistant",
44 | Content: "Italy is a wonderful choice! Are you more interested in art and history, or would you prefer to focus on food and wine experiences?",
45 | },
46 | {
47 | Role: zep.String("Paul"),
48 | RoleType: "user",
49 | Content: "I love both, but I think I'm leaning towards the food and wine experiences.",
50 | },
51 | {
52 | Role: zep.String("assistant"),
53 | RoleType: "assistant",
54 | Content: "Great! In that case, you might want to consider regions like Tuscany or Emilia-Romagna. Would you like more information about these areas?",
55 | },
56 | {
57 | Role: zep.String("Paul"),
58 | RoleType: "user",
59 | Content: "Yes, please tell me more about Tuscany. What are some must-try dishes and wines there?",
60 | },
61 | },
62 | {
63 | {
64 | Role: zep.String("Paul"),
65 | RoleType: "user",
66 | Content: "Apples are my favorite fruit",
67 | },
68 | {
69 | Role: zep.String("Paul"),
70 | RoleType: "user",
71 | Content: "now bananas are my favorite fruit",
72 | },
73 | {
74 | Role: zep.String("Paul"),
75 | RoleType: "user",
76 | Content: "Eric Clapton is my favorite guitarist",
77 | },
78 | },
79 | // Conversation 3: US Road Trip
80 | }
81 |
--------------------------------------------------------------------------------
/examples/entity_types.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/getzep/zep-go/v2"
9 | zepclient "github.com/getzep/zep-go/v2/client"
10 | "github.com/getzep/zep-go/v2/option"
11 | )
12 |
13 | func entityTypes() {
14 | apiKey := os.Getenv("ZEP_API_KEY")
15 | if apiKey == "" {
16 | fmt.Println("ZEP_API_KEY environment variable is not set")
17 | return
18 | }
19 |
20 | client := zepclient.NewClient(
21 | option.WithAPIKey(apiKey),
22 | )
23 |
24 | ctx := context.Background()
25 |
26 | type TravelingTo struct {
27 | zep.BaseEdge `name:"TRAVELING_TO" description:"A traveling to edge is an edge that connects two nodes"`
28 | TravelDate string `description:"The destination of the travel" json:"travel_date,omitempty"`
29 | Purpose string `description:"The distance of the travel" json:"purpose,omitempty"`
30 | }
31 |
32 | type BookedFlight struct {
33 | zep.BaseEdge `name:"BOOKED_FLIGHT" description:"A booked flight edge is an edge that connects two nodes"`
34 | FlightNumber string `description:"The flight number of the flight" json:"flight_number,omitempty"`
35 | Departure string `description:"The departure time of the flight" json:"departure,omitempty"`
36 | Arrival string `description:"The arrival time of the flight" json:"arrival,omitempty"`
37 | Price float64 `description:"The price of the flight" json:"price,omitempty"`
38 | }
39 |
40 | type Destination struct {
41 | zep.BaseEntity `name:"Destination" description:"Travel destination"`
42 | DestinationName string `description:"The name of the destination" json:"destination_name,omitempty"`
43 | Country string `description:"The country of the destination" json:"country,omitempty"`
44 | Latitude float64 `description:"The latitude of the destination" json:"latitude,omitempty"`
45 | Longitude float64 `description:"The longitude of the destination" json:"longitude,omitempty"`
46 | AirportCode string `description:"The airport code of the destination" json:"airport_code,omitempty"`
47 | AirportIATACode string `description:"The airport IATA code of the destination" json:"airport_iata_code,omitempty"`
48 | }
49 |
50 | _, err := client.Graph.SetEntityTypes(
51 | ctx,
52 | []zep.EntityDefinition{
53 | Destination{},
54 | },
55 | []zep.EdgeDefinitionWithSourceTargets{
56 | {
57 | EdgeModel: BookedFlight{},
58 | },
59 | {
60 | EdgeModel: TravelingTo{},
61 | SourceTargets: []zep.EntityEdgeSourceTarget{
62 | {
63 | Source: zep.String("User"),
64 | Target: zep.String("Destination"),
65 | },
66 | },
67 | },
68 | },
69 | )
70 | if err != nil {
71 | fmt.Printf("Error setting entity types with base entity: %v\n", err)
72 | return
73 | }
74 |
75 | searchFilters := zep.SearchFilters{NodeLabels: []string{"Destination"}}
76 | searchResults, err := client.Graph.Search(
77 | ctx,
78 | &zep.GraphSearchQuery{
79 | UserID: zep.String(""),
80 | Query: "destination",
81 | Scope: zep.GraphSearchScopeNodes.Ptr(),
82 | SearchFilters: &searchFilters,
83 | },
84 | )
85 | if err != nil {
86 | fmt.Printf("Error searching graph: %v\n", err)
87 | return
88 | }
89 |
90 | var destinations []Destination
91 | for _, node := range searchResults.Nodes {
92 | var destination Destination
93 | err := zep.UnmarshalNodeAttributes(node.Attributes, &destination)
94 | if err != nil {
95 | fmt.Printf("Error converting node to struct: %v\n", err)
96 | continue
97 | }
98 |
99 | destinations = append(destinations, destination)
100 | }
101 |
102 | for _, destination := range destinations {
103 | fmt.Printf("Destination Country: %s\n", destination.Country)
104 | fmt.Printf("Destination Name: %s\n", destination.DestinationName)
105 | }
106 |
107 | var travelingToRelations []TravelingTo
108 | for _, edge := range searchResults.Edges {
109 | var travelingToRelation TravelingTo
110 | err := zep.UnmarshalEdgeAttributes(edge.Attributes, &travelingToRelation)
111 | if err != nil {
112 | fmt.Printf("Error converting edge to struct: %v\n", err)
113 | continue
114 | }
115 |
116 | travelingToRelations = append(travelingToRelations, travelingToRelation)
117 | }
118 |
119 | for _, travelingToRelation := range travelingToRelations {
120 | fmt.Printf("Traveling to destination: %s\n", travelingToRelation.TravelDate)
121 | fmt.Printf("Traveling to distance: %s\n", travelingToRelation.Purpose)
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/examples/user_graph.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "time"
8 |
9 | "github.com/google/uuid"
10 |
11 | "github.com/getzep/zep-go/v2"
12 | zepclient "github.com/getzep/zep-go/v2/client"
13 | "github.com/getzep/zep-go/v2/graph"
14 | "github.com/getzep/zep-go/v2/option"
15 | )
16 |
17 | func main() {
18 | apiKey := os.Getenv("ZEP_API_KEY")
19 | if apiKey == "" {
20 | fmt.Println("ZEP_API_KEY environment variable is not set")
21 | return
22 | }
23 |
24 | client := zepclient.NewClient(
25 | option.WithAPIKey(apiKey),
26 | )
27 |
28 | ctx := context.Background()
29 |
30 | userID := uuid.New().String()
31 | sessionID := uuid.New().String()
32 |
33 | // Create a user
34 | userRequest := &zep.CreateUserRequest{
35 | UserID: userID,
36 | FirstName: zep.String("Paul"),
37 | }
38 | _, err := client.User.Add(ctx, userRequest)
39 | if err != nil {
40 | fmt.Printf("Error creating user: %v\n", err)
41 | return
42 | }
43 | fmt.Printf("User %s created\n", userID)
44 |
45 | // Create a session
46 | _, err = client.Memory.AddSession(ctx, &zep.CreateSessionRequest{
47 | SessionID: sessionID,
48 | UserID: userID,
49 | })
50 | if err != nil {
51 | fmt.Printf("Error creating session: %v\n", err)
52 | return
53 | }
54 | fmt.Printf("Session %s created\n", sessionID)
55 |
56 | // Add messages to the session
57 | for _, message := range history[0] {
58 | _, err = client.Memory.Add(ctx, sessionID, &zep.AddMemoryRequest{
59 | Messages: []*zep.Message{
60 | {Role: message.Role, RoleType: message.RoleType, Content: message.Content},
61 | },
62 | ReturnContext: zep.Bool(true),
63 | })
64 | if err != nil {
65 | fmt.Printf("Error adding message: %v\n", err)
66 | return
67 | }
68 | }
69 |
70 | fmt.Println("Waiting for the graph to be updated...")
71 | time.Sleep(10 * time.Second)
72 |
73 | fmt.Println("Getting memory for session")
74 | sessionMemory, err := client.Memory.Get(ctx, sessionID, nil)
75 | if err != nil {
76 | fmt.Printf("Error getting session memory: %v\n", err)
77 | return
78 | }
79 | fmt.Printf("%+v\n", sessionMemory)
80 |
81 | fmt.Println("Searching user memory...")
82 | searchResults, err := client.Memory.SearchSessions(ctx, &zep.SessionSearchQuery{
83 | UserID: zep.String(userID),
84 | Text: "What is the weather in San Francisco?",
85 | SearchScope: zep.SearchScopeFacts.Ptr(),
86 | })
87 | if err != nil {
88 | fmt.Printf("Error searching sessions: %v\n", err)
89 | return
90 | }
91 | fmt.Printf("%+v\n", searchResults)
92 |
93 | fmt.Println("Getting episodes for user")
94 | episodeResult, err := client.Graph.Episode.GetByUserID(ctx, userID, &graph.EpisodeGetByUserIDRequest{
95 | Lastn: zep.Int(3),
96 | })
97 | if err != nil {
98 | fmt.Printf("Error getting episodes: %v\n", err)
99 | return
100 | }
101 | fmt.Printf("Episodes for user %s:\n", userID)
102 | fmt.Printf("%+v\n", episodeResult.Episodes)
103 |
104 | if len(episodeResult.Episodes) > 0 {
105 | episode, err := client.Graph.Episode.Get(ctx, episodeResult.Episodes[0].UUID)
106 | if err != nil {
107 | fmt.Printf("Error getting episode: %v\n", err)
108 | return
109 | }
110 | fmt.Printf("%+v\n", episode)
111 | }
112 |
113 | edges, err := client.Graph.Edge.GetByUserID(ctx, userID, &zep.GraphEdgesRequest{})
114 | if err != nil {
115 | fmt.Printf("Error getting edges: %v\n", err)
116 | return
117 | }
118 | fmt.Printf("Edges for user %s:\n", userID)
119 | fmt.Printf("%+v\n", edges)
120 |
121 | if len(edges) > 0 {
122 | edge, err := client.Graph.Edge.Get(ctx, edges[0].UUID)
123 | if err != nil {
124 | fmt.Printf("Error getting edge: %v\n", err)
125 | return
126 | }
127 | fmt.Printf("%+v\n", edge)
128 | }
129 |
130 | nodes, err := client.Graph.Node.GetByUserID(ctx, userID, &zep.GraphNodesRequest{})
131 | if err != nil {
132 | fmt.Printf("Error getting nodes: %v\n", err)
133 | return
134 | }
135 | fmt.Printf("Nodes for user %s:\n", userID)
136 | fmt.Printf("%+v\n", nodes)
137 |
138 | if len(nodes) > 0 {
139 | node, err := client.Graph.Node.Get(ctx, nodes[0].UUID)
140 | if err != nil {
141 | fmt.Printf("Error getting node: %v\n", err)
142 | return
143 | }
144 | fmt.Printf("%+v\n", node)
145 | }
146 |
147 | fmt.Println("Searching user graph memory...")
148 | graphSearchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
149 | UserID: zep.String(userID),
150 | Query: "What is the weather in San Francisco?",
151 | })
152 | if err != nil {
153 | fmt.Printf("Error searching graph: %v\n", err)
154 | return
155 | }
156 | fmt.Printf("%+v\n", graphSearchResults.Edges)
157 |
158 | fmt.Println("Adding a new text episode to the graph...")
159 | _, err = client.Graph.Add(ctx, &zep.AddDataRequest{
160 | UserID: zep.String(userID),
161 | Type: zep.GraphDataTypeText,
162 | Data: "The user is an avid fan of Eric Clapton",
163 | })
164 | if err != nil {
165 | fmt.Printf("Error adding text episode: %v\n", err)
166 | return
167 | }
168 | fmt.Println("Text episode added")
169 |
170 | fmt.Println("Adding a new JSON episode to the graph...")
171 | jsonString := `{"name": "Eric Clapton", "age": 78, "genre": "Rock"}`
172 | _, err = client.Graph.Add(ctx, &zep.AddDataRequest{
173 | UserID: zep.String(userID),
174 | Type: zep.GraphDataTypeJSON,
175 | Data: jsonString,
176 | })
177 | if err != nil {
178 | fmt.Printf("Error adding JSON episode: %v\n", err)
179 | return
180 | }
181 | fmt.Println("JSON episode added")
182 |
183 | fmt.Println("Adding a new message episode to the graph...")
184 | message := "Paul (user): I went to Eric Clapton concert last night"
185 | _, err = client.Graph.Add(ctx, &zep.AddDataRequest{
186 | UserID: zep.String(userID),
187 | Type: zep.GraphDataTypeMessage,
188 | Data: message,
189 | })
190 | if err != nil {
191 | fmt.Printf("Error adding message episode: %v\n", err)
192 | return
193 | }
194 | fmt.Println("Message episode added")
195 |
196 | fmt.Println("Waiting for the graph to be updated...")
197 | time.Sleep(30 * time.Second)
198 |
199 | fmt.Println("Getting nodes from the graph...")
200 | updatedNodes, err := client.Graph.Node.GetByUserID(ctx, userID, &zep.GraphNodesRequest{})
201 | if err != nil {
202 | fmt.Printf("Error getting updated nodes: %v\n", err)
203 | return
204 | }
205 | fmt.Printf("%+v\n", updatedNodes)
206 |
207 | fmt.Println("Finding Eric Clapton in the graph...")
208 | var claptonNode *zep.EntityNode
209 | for _, node := range updatedNodes {
210 | if node.Name == "Eric Clapton" {
211 | claptonNode = node
212 | break
213 | }
214 | }
215 | fmt.Printf("%+v\n", claptonNode)
216 |
217 | if claptonNode != nil {
218 | fmt.Println("Performing Eric Clapton centered edge search...")
219 | edgeSearchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
220 | UserID: zep.String(userID),
221 | Query: "Eric Clapton",
222 | CenterNodeUUID: &claptonNode.UUID,
223 | Scope: zep.GraphSearchScopeEdges.Ptr(),
224 | })
225 | if err != nil {
226 | fmt.Printf("Error performing edge search: %v\n", err)
227 | return
228 | }
229 | fmt.Printf("%+v\n", edgeSearchResults.Edges)
230 |
231 | fmt.Println("Performing Eric Clapton centered node search...")
232 | nodeSearchResults, err := client.Graph.Search(ctx, &zep.GraphSearchQuery{
233 | UserID: zep.String(userID),
234 | Query: "Eric Clapton",
235 | CenterNodeUUID: &claptonNode.UUID,
236 | Scope: zep.GraphSearchScopeNodes.Ptr(),
237 | })
238 | if err != nil {
239 | fmt.Printf("Error performing node search: %v\n", err)
240 | return
241 | }
242 | fmt.Printf("%+v\n", nodeSearchResults.Nodes)
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/file_param.go:
--------------------------------------------------------------------------------
1 | package zep
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | // FileParam is a file type suitable for multipart/form-data uploads.
8 | type FileParam struct {
9 | io.Reader
10 | filename string
11 | contentType string
12 | }
13 |
14 | // FileParamOption adapts the behavior of the FileParam. No options are
15 | // implemented yet, but this interface allows for future extensibility.
16 | type FileParamOption interface {
17 | apply()
18 | }
19 |
20 | // NewFileParam returns a *FileParam type suitable for multipart/form-data uploads. All file
21 | // upload endpoints accept a simple io.Reader, which is usually created by opening a file
22 | // via os.Open.
23 | //
24 | // However, some endpoints require additional metadata about the file such as a specific
25 | // Content-Type or custom filename. FileParam makes it easier to create the correct type
26 | // signature for these endpoints.
27 | func NewFileParam(
28 | reader io.Reader,
29 | filename string,
30 | contentType string,
31 | opts ...FileParamOption,
32 | ) *FileParam {
33 | return &FileParam{
34 | Reader: reader,
35 | filename: filename,
36 | contentType: contentType,
37 | }
38 | }
39 |
40 | func (f *FileParam) Name() string { return f.filename }
41 | func (f *FileParam) ContentType() string { return f.contentType }
42 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/getzep/zep-go/v2
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/google/uuid v1.4.0
7 | github.com/stretchr/testify v1.7.0
8 | gopkg.in/yaml.v3 v3.0.1 // indirect
9 | )
10 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
4 | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
8 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
9 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15 |
--------------------------------------------------------------------------------
/graph/client/base_edge.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "reflect"
5 |
6 | "github.com/getzep/zep-go/v2"
7 | )
8 |
9 | // BaseEdgeMetadata contains information extracted from BaseEdge struct tags
10 | type BaseEdgeMetadata struct {
11 | Description string
12 | Name string
13 | }
14 |
15 | // ExtractBaseEdgeMetadata tries to find an embedded BaseEdge by reflection
16 | // and extract metadata from direct struct tags
17 | func ExtractBaseEdgeMetadata(entity zep.EdgeDefinition) (BaseEdgeMetadata, bool) {
18 | v := reflect.ValueOf(entity)
19 |
20 | if v.Kind() == reflect.Ptr {
21 | v = v.Elem()
22 | }
23 |
24 | if v.Kind() != reflect.Struct {
25 | return BaseEdgeMetadata{}, false
26 | }
27 |
28 | t := v.Type()
29 |
30 | // Look for BaseEdge field and its struct tags
31 | for i := 0; i < t.NumField(); i++ {
32 | field := t.Field(i)
33 |
34 | // Check if this is the BaseEdge field
35 | if field.Type.Name() == "BaseEdge" && field.Anonymous {
36 | metadata := BaseEdgeMetadata{}
37 | foundName := false
38 |
39 | // Extract name tag directly
40 | nameTag := field.Tag.Get("name")
41 | if nameTag != "" {
42 | metadata.Name = nameTag
43 | foundName = true
44 | }
45 |
46 | // Extract description tag directly
47 | descTag := field.Tag.Get("description")
48 | if descTag != "" {
49 | metadata.Description = descTag
50 | }
51 |
52 | // Name is required, description is optional
53 | if foundName {
54 | return metadata, true
55 | }
56 | }
57 | }
58 |
59 | return BaseEdgeMetadata{}, false
60 | }
61 |
--------------------------------------------------------------------------------
/graph/client/base_entity.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "reflect"
5 |
6 | "github.com/getzep/zep-go/v2"
7 | )
8 |
9 | // BaseEntityMetadata contains information extracted from BaseEntity struct tags
10 | type BaseEntityMetadata struct {
11 | Description string
12 | Name string
13 | }
14 |
15 | // ExtractBaseEntityMetadata tries to find an embedded BaseEntity by reflection
16 | // and extract metadata from direct struct tags
17 | func ExtractBaseEntityMetadata(entity zep.EntityDefinition) (BaseEntityMetadata, bool) {
18 | v := reflect.ValueOf(entity)
19 |
20 | if v.Kind() == reflect.Ptr {
21 | v = v.Elem()
22 | }
23 |
24 | if v.Kind() != reflect.Struct {
25 | return BaseEntityMetadata{}, false
26 | }
27 |
28 | t := v.Type()
29 |
30 | // Look for BaseEntity field and its struct tags
31 | for i := 0; i < t.NumField(); i++ {
32 | field := t.Field(i)
33 |
34 | // Check if this is the BaseEntity field
35 | if field.Type.Name() == "BaseEntity" && field.Anonymous {
36 | metadata := BaseEntityMetadata{}
37 | foundName := false
38 |
39 | // Extract name tag directly
40 | nameTag := field.Tag.Get("name")
41 | if nameTag != "" {
42 | metadata.Name = nameTag
43 | foundName = true
44 | }
45 |
46 | // Extract description tag directly
47 | descTag := field.Tag.Get("description")
48 | if descTag != "" {
49 | metadata.Description = descTag
50 | }
51 |
52 | // Name is required, description is optional
53 | if foundName {
54 | return metadata, true
55 | }
56 | }
57 | }
58 |
59 | return BaseEntityMetadata{}, false
60 | }
61 |
--------------------------------------------------------------------------------
/graph/client/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package client
4 |
5 | import (
6 | context "context"
7 | v2 "github.com/getzep/zep-go/v2"
8 | core "github.com/getzep/zep-go/v2/core"
9 | edge "github.com/getzep/zep-go/v2/graph/edge"
10 | episode "github.com/getzep/zep-go/v2/graph/episode"
11 | node "github.com/getzep/zep-go/v2/graph/node"
12 | internal "github.com/getzep/zep-go/v2/internal"
13 | option "github.com/getzep/zep-go/v2/option"
14 | http "net/http"
15 | os "os"
16 | )
17 |
18 | type Client struct {
19 | baseURL string
20 | caller *internal.Caller
21 | header http.Header
22 |
23 | Edge *edge.Client
24 | Episode *episode.Client
25 | Node *node.Client
26 | }
27 |
28 | func NewClient(opts ...option.RequestOption) *Client {
29 | options := core.NewRequestOptions(opts...)
30 | if options.APIKey == "" {
31 | options.APIKey = os.Getenv("ZEP_API_KEY")
32 | }
33 | return &Client{
34 | baseURL: options.BaseURL,
35 | caller: internal.NewCaller(
36 | &internal.CallerParams{
37 | Client: options.HTTPClient,
38 | MaxAttempts: options.MaxAttempts,
39 | },
40 | ),
41 | header: options.ToHeader(),
42 | Edge: edge.NewClient(opts...),
43 | Episode: episode.NewClient(opts...),
44 | Node: node.NewClient(opts...),
45 | }
46 | }
47 |
48 | // Returns all entity types for a project.
49 | func (c *Client) ListEntityTypes(
50 | ctx context.Context,
51 | opts ...option.RequestOption,
52 | ) (*v2.EntityTypeResponse, error) {
53 | options := core.NewRequestOptions(opts...)
54 | baseURL := internal.ResolveBaseURL(
55 | options.BaseURL,
56 | c.baseURL,
57 | "https://api.getzep.com/api/v2",
58 | )
59 | endpointURL := baseURL + "/entity-types"
60 | headers := internal.MergeHeaders(
61 | c.header.Clone(),
62 | options.ToHeader(),
63 | )
64 | errorCodes := internal.ErrorCodes{
65 | 404: func(apiError *core.APIError) error {
66 | return &v2.NotFoundError{
67 | APIError: apiError,
68 | }
69 | },
70 | 500: func(apiError *core.APIError) error {
71 | return &v2.InternalServerError{
72 | APIError: apiError,
73 | }
74 | },
75 | }
76 |
77 | var response *v2.EntityTypeResponse
78 | if err := c.caller.Call(
79 | ctx,
80 | &internal.CallParams{
81 | URL: endpointURL,
82 | Method: http.MethodGet,
83 | Headers: headers,
84 | MaxAttempts: options.MaxAttempts,
85 | BodyProperties: options.BodyProperties,
86 | QueryParameters: options.QueryParameters,
87 | Client: options.HTTPClient,
88 | Response: &response,
89 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
90 | },
91 | ); err != nil {
92 | return nil, err
93 | }
94 | return response, nil
95 | }
96 |
97 | // Sets the entity types for a project, replacing any existing ones.
98 | func (c *Client) SetEntityTypesInternal(
99 | ctx context.Context,
100 | request *v2.EntityTypeRequest,
101 | opts ...option.RequestOption,
102 | ) (*v2.SuccessResponse, error) {
103 | options := core.NewRequestOptions(opts...)
104 | baseURL := internal.ResolveBaseURL(
105 | options.BaseURL,
106 | c.baseURL,
107 | "https://api.getzep.com/api/v2",
108 | )
109 | endpointURL := baseURL + "/entity-types"
110 | headers := internal.MergeHeaders(
111 | c.header.Clone(),
112 | options.ToHeader(),
113 | )
114 | headers.Set("Content-Type", "application/json")
115 | errorCodes := internal.ErrorCodes{
116 | 400: func(apiError *core.APIError) error {
117 | return &v2.BadRequestError{
118 | APIError: apiError,
119 | }
120 | },
121 | 500: func(apiError *core.APIError) error {
122 | return &v2.InternalServerError{
123 | APIError: apiError,
124 | }
125 | },
126 | }
127 |
128 | var response *v2.SuccessResponse
129 | if err := c.caller.Call(
130 | ctx,
131 | &internal.CallParams{
132 | URL: endpointURL,
133 | Method: http.MethodPut,
134 | Headers: headers,
135 | MaxAttempts: options.MaxAttempts,
136 | BodyProperties: options.BodyProperties,
137 | QueryParameters: options.QueryParameters,
138 | Client: options.HTTPClient,
139 | Request: request,
140 | Response: &response,
141 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
142 | },
143 | ); err != nil {
144 | return nil, err
145 | }
146 | return response, nil
147 | }
148 |
149 | // Add data to the graph.
150 | func (c *Client) Add(
151 | ctx context.Context,
152 | request *v2.AddDataRequest,
153 | opts ...option.RequestOption,
154 | ) (*v2.Episode, error) {
155 | options := core.NewRequestOptions(opts...)
156 | baseURL := internal.ResolveBaseURL(
157 | options.BaseURL,
158 | c.baseURL,
159 | "https://api.getzep.com/api/v2",
160 | )
161 | endpointURL := baseURL + "/graph"
162 | headers := internal.MergeHeaders(
163 | c.header.Clone(),
164 | options.ToHeader(),
165 | )
166 | headers.Set("Content-Type", "application/json")
167 | errorCodes := internal.ErrorCodes{
168 | 400: func(apiError *core.APIError) error {
169 | return &v2.BadRequestError{
170 | APIError: apiError,
171 | }
172 | },
173 | 500: func(apiError *core.APIError) error {
174 | return &v2.InternalServerError{
175 | APIError: apiError,
176 | }
177 | },
178 | }
179 |
180 | var response *v2.Episode
181 | if err := c.caller.Call(
182 | ctx,
183 | &internal.CallParams{
184 | URL: endpointURL,
185 | Method: http.MethodPost,
186 | Headers: headers,
187 | MaxAttempts: options.MaxAttempts,
188 | BodyProperties: options.BodyProperties,
189 | QueryParameters: options.QueryParameters,
190 | Client: options.HTTPClient,
191 | Request: request,
192 | Response: &response,
193 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
194 | },
195 | ); err != nil {
196 | return nil, err
197 | }
198 | return response, nil
199 | }
200 |
201 | // Add data to the graph in batch mode, processing episodes concurrently. Use only for data that is insensitive to processing order.
202 | func (c *Client) AddBatch(
203 | ctx context.Context,
204 | request *v2.AddDataBatchRequest,
205 | opts ...option.RequestOption,
206 | ) ([]*v2.Episode, error) {
207 | options := core.NewRequestOptions(opts...)
208 | baseURL := internal.ResolveBaseURL(
209 | options.BaseURL,
210 | c.baseURL,
211 | "https://api.getzep.com/api/v2",
212 | )
213 | endpointURL := baseURL + "/graph-batch"
214 | headers := internal.MergeHeaders(
215 | c.header.Clone(),
216 | options.ToHeader(),
217 | )
218 | headers.Set("Content-Type", "application/json")
219 | errorCodes := internal.ErrorCodes{
220 | 400: func(apiError *core.APIError) error {
221 | return &v2.BadRequestError{
222 | APIError: apiError,
223 | }
224 | },
225 | 500: func(apiError *core.APIError) error {
226 | return &v2.InternalServerError{
227 | APIError: apiError,
228 | }
229 | },
230 | }
231 |
232 | var response []*v2.Episode
233 | if err := c.caller.Call(
234 | ctx,
235 | &internal.CallParams{
236 | URL: endpointURL,
237 | Method: http.MethodPost,
238 | Headers: headers,
239 | MaxAttempts: options.MaxAttempts,
240 | BodyProperties: options.BodyProperties,
241 | QueryParameters: options.QueryParameters,
242 | Client: options.HTTPClient,
243 | Request: request,
244 | Response: &response,
245 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
246 | },
247 | ); err != nil {
248 | return nil, err
249 | }
250 | return response, nil
251 | }
252 |
253 | // Add a fact triple for a user or group
254 | func (c *Client) AddFactTriple(
255 | ctx context.Context,
256 | request *v2.AddTripleRequest,
257 | opts ...option.RequestOption,
258 | ) (*v2.AddTripleResponse, error) {
259 | options := core.NewRequestOptions(opts...)
260 | baseURL := internal.ResolveBaseURL(
261 | options.BaseURL,
262 | c.baseURL,
263 | "https://api.getzep.com/api/v2",
264 | )
265 | endpointURL := baseURL + "/graph/add-fact-triple"
266 | headers := internal.MergeHeaders(
267 | c.header.Clone(),
268 | options.ToHeader(),
269 | )
270 | headers.Set("Content-Type", "application/json")
271 | errorCodes := internal.ErrorCodes{
272 | 400: func(apiError *core.APIError) error {
273 | return &v2.BadRequestError{
274 | APIError: apiError,
275 | }
276 | },
277 | 500: func(apiError *core.APIError) error {
278 | return &v2.InternalServerError{
279 | APIError: apiError,
280 | }
281 | },
282 | }
283 |
284 | var response *v2.AddTripleResponse
285 | if err := c.caller.Call(
286 | ctx,
287 | &internal.CallParams{
288 | URL: endpointURL,
289 | Method: http.MethodPost,
290 | Headers: headers,
291 | MaxAttempts: options.MaxAttempts,
292 | BodyProperties: options.BodyProperties,
293 | QueryParameters: options.QueryParameters,
294 | Client: options.HTTPClient,
295 | Request: request,
296 | Response: &response,
297 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
298 | },
299 | ); err != nil {
300 | return nil, err
301 | }
302 | return response, nil
303 | }
304 |
305 | // Perform a graph search query.
306 | func (c *Client) Search(
307 | ctx context.Context,
308 | request *v2.GraphSearchQuery,
309 | opts ...option.RequestOption,
310 | ) (*v2.GraphSearchResults, error) {
311 | options := core.NewRequestOptions(opts...)
312 | baseURL := internal.ResolveBaseURL(
313 | options.BaseURL,
314 | c.baseURL,
315 | "https://api.getzep.com/api/v2",
316 | )
317 | endpointURL := baseURL + "/graph/search"
318 | headers := internal.MergeHeaders(
319 | c.header.Clone(),
320 | options.ToHeader(),
321 | )
322 | headers.Set("Content-Type", "application/json")
323 | errorCodes := internal.ErrorCodes{
324 | 400: func(apiError *core.APIError) error {
325 | return &v2.BadRequestError{
326 | APIError: apiError,
327 | }
328 | },
329 | 500: func(apiError *core.APIError) error {
330 | return &v2.InternalServerError{
331 | APIError: apiError,
332 | }
333 | },
334 | }
335 |
336 | var response *v2.GraphSearchResults
337 | if err := c.caller.Call(
338 | ctx,
339 | &internal.CallParams{
340 | URL: endpointURL,
341 | Method: http.MethodPost,
342 | Headers: headers,
343 | MaxAttempts: options.MaxAttempts,
344 | BodyProperties: options.BodyProperties,
345 | QueryParameters: options.QueryParameters,
346 | Client: options.HTTPClient,
347 | Request: request,
348 | Response: &response,
349 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
350 | },
351 | ); err != nil {
352 | return nil, err
353 | }
354 | return response, nil
355 | }
356 |
--------------------------------------------------------------------------------
/graph/client/ontology.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/getzep/zep-go/v2"
8 | )
9 |
10 | // SetOntology sets entity end edge types for the project, replacing any existing entity/edge types set for the project.
11 | // It takes a slice of EntityDefinition, which is satisfied by any struct that embeds BaseEntity, and a slice of EdgeDefinition, which is satisfied by any struct that embeds BaseEdge
12 | func (c *Client) SetOntology(ctx context.Context, entities []zep.EntityDefinition, edges []zep.EdgeDefinitionWithSourceTargets) (*zep.SuccessResponse, error) {
13 | return c.SetEntityTypes(ctx, entities, edges)
14 | }
15 |
16 | // SetEntityTypes sets entity end edge types for the project, replacing any existing entity/edge types set for the project.
17 | // It takes a slice of EntityDefinition, which is satisfied by any struct that embeds BaseEntity, and a slice of EdgeDefinition, which is satisfied by any struct that embeds BaseEdge
18 | func (c *Client) SetEntityTypes(ctx context.Context, entities []zep.EntityDefinition, edges []zep.EdgeDefinitionWithSourceTargets) (*zep.SuccessResponse, error) {
19 | var entitySchemas []*zep.EntityType
20 | var edgeSchemas []*zep.EdgeType
21 |
22 | for i, entityStruct := range entities {
23 | // Try to extract metadata from embedded BaseEntity struct tags
24 | metadata, found := ExtractBaseEntityMetadata(entityStruct)
25 | if !found {
26 | return nil, fmt.Errorf("entity at index %d does not have a BaseEntity with required name tag", i)
27 | }
28 |
29 | // Name is always from the struct tag
30 | entityName := metadata.Name
31 |
32 | // Extract entity schema as usual
33 | entitySchema, err := ExtractEntitySchema(entityStruct, entityName)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | entityProperties := make([]*zep.EntityProperty, 0, len(entitySchema.Properties))
39 | for name, property := range entitySchema.Properties {
40 | entityProperties = append(entityProperties, &zep.EntityProperty{
41 | Name: name,
42 | Type: zep.EntityPropertyType(property.Type),
43 | Description: property.Description,
44 | })
45 | }
46 |
47 | // If description is not provided in struct tag, use a default or empty string
48 | description := metadata.Description
49 | if description == "" {
50 | description = fmt.Sprintf("Entity type for %s", entityName)
51 | }
52 |
53 | entityType := &zep.EntityType{
54 | Name: entityName,
55 | Description: description,
56 | Properties: entityProperties,
57 | }
58 |
59 | entitySchemas = append(entitySchemas, entityType)
60 | }
61 |
62 | for i, edgeWithSourceTargets := range edges {
63 | edgeStruct := edgeWithSourceTargets.EdgeModel
64 | // Try to extract metadata from embedded BaseEntity struct tags
65 | metadata, found := ExtractBaseEdgeMetadata(edgeStruct)
66 | if !found {
67 | return nil, fmt.Errorf("entity at index %d does not have a BaseEdge with required name tag", i)
68 | }
69 |
70 | // Name is always from the struct tag
71 | edgeName := metadata.Name
72 |
73 | // Extract entity schema as usual
74 | edgeSchema, err := ExtractEdgeSchema(edgeStruct, edgeName)
75 | if err != nil {
76 | return nil, err
77 | }
78 |
79 | entityProperties := make([]*zep.EntityProperty, 0, len(edgeSchema.Properties))
80 | for name, property := range edgeSchema.Properties {
81 | entityProperties = append(entityProperties, &zep.EntityProperty{
82 | Name: name,
83 | Type: zep.EntityPropertyType(property.Type),
84 | Description: property.Description,
85 | })
86 | }
87 |
88 | // If description is not provided in struct tag, use a default or empty string
89 | description := metadata.Description
90 | if description == "" {
91 | description = fmt.Sprintf("Entity type for %s", edgeName)
92 | }
93 | var sourceTargets []*zep.EntityEdgeSourceTarget
94 | if edgeWithSourceTargets.SourceTargets != nil {
95 | for _, sourceTarget := range edgeWithSourceTargets.SourceTargets {
96 | sourceTargets = append(sourceTargets, &zep.EntityEdgeSourceTarget{
97 | Source: sourceTarget.Source,
98 | Target: sourceTarget.Target,
99 | })
100 | }
101 | }
102 | edgeType := &zep.EdgeType{
103 | Name: edgeName,
104 | Description: description,
105 | Properties: entityProperties,
106 | SourceTargets: sourceTargets,
107 | }
108 |
109 | edgeSchemas = append(edgeSchemas, edgeType)
110 | }
111 |
112 | request := &zep.EntityTypeRequest{
113 | EntityTypes: entitySchemas,
114 | EdgeTypes: edgeSchemas,
115 | }
116 | return c.SetEntityTypesInternal(ctx, request)
117 | }
118 |
--------------------------------------------------------------------------------
/graph/client/schema.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 | )
8 |
9 | // PropertyType defines the supported property types
10 | type PropertyType string
11 |
12 | const (
13 | Text PropertyType = "Text"
14 | Int PropertyType = "Int"
15 | Float PropertyType = "Float"
16 | Boolean PropertyType = "Boolean"
17 | )
18 |
19 | // IsValid checks if the property type is valid
20 | func (pt PropertyType) IsValid() bool {
21 | switch pt {
22 | case Text, Int, Float, Boolean:
23 | return true
24 | default:
25 | return false
26 | }
27 | }
28 |
29 | // Property represents a property definition for a schema
30 | type Property struct {
31 | Type PropertyType `json:"type"`
32 | Description string `json:"description"`
33 | }
34 |
35 | // Schema represents a schema for an entity or edge type
36 | type Schema struct {
37 | Name string `json:"name"`
38 | Properties map[string]Property `json:"properties"`
39 | }
40 |
41 | // Validate checks if the schema is valid
42 | func (s Schema) Validate() error {
43 | if s.Name == "" {
44 | return fmt.Errorf("schema name is required")
45 | }
46 |
47 | if len(s.Properties) == 0 {
48 | return fmt.Errorf("schema must have at least one property")
49 | }
50 |
51 | if len(s.Properties) > 10 {
52 | return fmt.Errorf("schema cannot have more than 10 properties")
53 | }
54 |
55 | for name, prop := range s.Properties {
56 | if name == "" {
57 | return fmt.Errorf("property name cannot be empty")
58 | }
59 |
60 | if !prop.Type.IsValid() {
61 | return fmt.Errorf("invalid property type for %s: %s", name, prop.Type)
62 | }
63 |
64 | if prop.Description == "" {
65 | return fmt.Errorf("property description is required for %s", name)
66 | }
67 | }
68 |
69 | return nil
70 | }
71 |
72 | // ExtractSchema extracts a schema from a struct using reflection
73 | func ExtractSchema(obj interface{}, name string) (Schema, error) {
74 | schema := Schema{
75 | Name: name,
76 | Properties: make(map[string]Property),
77 | }
78 |
79 | t := reflect.TypeOf(obj)
80 | if t.Kind() == reflect.Ptr {
81 | t = t.Elem()
82 | }
83 |
84 | if t.Kind() != reflect.Struct {
85 | return schema, fmt.Errorf("object must be a struct")
86 | }
87 |
88 | for i := 0; i < t.NumField(); i++ {
89 | field := t.Field(i)
90 |
91 | // Skip unexported fields
92 | if !field.IsExported() {
93 | continue
94 | }
95 |
96 | // Get the description tag
97 | tag := field.Tag.Get("description")
98 | if tag == "" || tag == "-" {
99 | continue
100 | }
101 |
102 | // Get the json tag for the field name
103 | jsonTag := field.Tag.Get("json")
104 | fieldName := field.Name
105 | if jsonTag != "" {
106 | parts := strings.Split(jsonTag, ",")
107 | if parts[0] != "" {
108 | fieldName = parts[0]
109 | }
110 | }
111 |
112 | // Parse the tag
113 | var propType PropertyType
114 | var description string = tag
115 |
116 | switch field.Type.Kind() {
117 | case reflect.String:
118 | propType = Text
119 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
120 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
121 | propType = Int
122 | case reflect.Float32, reflect.Float64:
123 | propType = Float
124 | case reflect.Bool:
125 | propType = Boolean
126 | default:
127 | // Skip unsupported types
128 | continue
129 | }
130 |
131 | // Add the property to the schema
132 | schema.Properties[fieldName] = Property{
133 | Type: propType,
134 | Description: description,
135 | }
136 | }
137 |
138 | // Validate the schema
139 | if err := schema.Validate(); err != nil {
140 | return schema, err
141 | }
142 |
143 | return schema, nil
144 | }
145 |
146 | // ExtractEntitySchema extracts an entity schema from a struct using reflection
147 | // This is a wrapper around ExtractSchema for backward compatibility
148 | func ExtractEntitySchema(entity interface{}, name string) (Schema, error) {
149 | return ExtractSchema(entity, name)
150 | }
151 |
152 | // ExtractEdgeSchema extracts an edge schema from a struct using reflection
153 | // This is a wrapper around ExtractSchema for backward compatibility
154 | func ExtractEdgeSchema(edge interface{}, name string) (Schema, error) {
155 | return ExtractSchema(edge, name)
156 | }
157 |
158 | // EntitySchema is an alias for Schema for backward compatibility
159 | type EntitySchema = Schema
160 |
161 | // EdgeSchema is an alias for Schema for backward compatibility
162 | type EdgeSchema = Schema
163 |
--------------------------------------------------------------------------------
/graph/edge/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package edge
4 |
5 | import (
6 | context "context"
7 | v2 "github.com/getzep/zep-go/v2"
8 | core "github.com/getzep/zep-go/v2/core"
9 | internal "github.com/getzep/zep-go/v2/internal"
10 | option "github.com/getzep/zep-go/v2/option"
11 | http "net/http"
12 | os "os"
13 | )
14 |
15 | type Client struct {
16 | baseURL string
17 | caller *internal.Caller
18 | header http.Header
19 | }
20 |
21 | func NewClient(opts ...option.RequestOption) *Client {
22 | options := core.NewRequestOptions(opts...)
23 | if options.APIKey == "" {
24 | options.APIKey = os.Getenv("ZEP_API_KEY")
25 | }
26 | return &Client{
27 | baseURL: options.BaseURL,
28 | caller: internal.NewCaller(
29 | &internal.CallerParams{
30 | Client: options.HTTPClient,
31 | MaxAttempts: options.MaxAttempts,
32 | },
33 | ),
34 | header: options.ToHeader(),
35 | }
36 | }
37 |
38 | // Returns all edges for a group.
39 | func (c *Client) GetByGroupID(
40 | ctx context.Context,
41 | // Group ID
42 | groupID string,
43 | request *v2.GraphEdgesRequest,
44 | opts ...option.RequestOption,
45 | ) ([]*v2.EntityEdge, error) {
46 | options := core.NewRequestOptions(opts...)
47 | baseURL := internal.ResolveBaseURL(
48 | options.BaseURL,
49 | c.baseURL,
50 | "https://api.getzep.com/api/v2",
51 | )
52 | endpointURL := internal.EncodeURL(
53 | baseURL+"/graph/edge/group/%v",
54 | groupID,
55 | )
56 | headers := internal.MergeHeaders(
57 | c.header.Clone(),
58 | options.ToHeader(),
59 | )
60 | headers.Set("Content-Type", "application/json")
61 | errorCodes := internal.ErrorCodes{
62 | 400: func(apiError *core.APIError) error {
63 | return &v2.BadRequestError{
64 | APIError: apiError,
65 | }
66 | },
67 | 500: func(apiError *core.APIError) error {
68 | return &v2.InternalServerError{
69 | APIError: apiError,
70 | }
71 | },
72 | }
73 |
74 | var response []*v2.EntityEdge
75 | if err := c.caller.Call(
76 | ctx,
77 | &internal.CallParams{
78 | URL: endpointURL,
79 | Method: http.MethodPost,
80 | Headers: headers,
81 | MaxAttempts: options.MaxAttempts,
82 | BodyProperties: options.BodyProperties,
83 | QueryParameters: options.QueryParameters,
84 | Client: options.HTTPClient,
85 | Request: request,
86 | Response: &response,
87 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
88 | },
89 | ); err != nil {
90 | return nil, err
91 | }
92 | return response, nil
93 | }
94 |
95 | // Returns all edges for a user.
96 | func (c *Client) GetByUserID(
97 | ctx context.Context,
98 | // User ID
99 | userID string,
100 | request *v2.GraphEdgesRequest,
101 | opts ...option.RequestOption,
102 | ) ([]*v2.EntityEdge, error) {
103 | options := core.NewRequestOptions(opts...)
104 | baseURL := internal.ResolveBaseURL(
105 | options.BaseURL,
106 | c.baseURL,
107 | "https://api.getzep.com/api/v2",
108 | )
109 | endpointURL := internal.EncodeURL(
110 | baseURL+"/graph/edge/user/%v",
111 | userID,
112 | )
113 | headers := internal.MergeHeaders(
114 | c.header.Clone(),
115 | options.ToHeader(),
116 | )
117 | headers.Set("Content-Type", "application/json")
118 | errorCodes := internal.ErrorCodes{
119 | 400: func(apiError *core.APIError) error {
120 | return &v2.BadRequestError{
121 | APIError: apiError,
122 | }
123 | },
124 | 500: func(apiError *core.APIError) error {
125 | return &v2.InternalServerError{
126 | APIError: apiError,
127 | }
128 | },
129 | }
130 |
131 | var response []*v2.EntityEdge
132 | if err := c.caller.Call(
133 | ctx,
134 | &internal.CallParams{
135 | URL: endpointURL,
136 | Method: http.MethodPost,
137 | Headers: headers,
138 | MaxAttempts: options.MaxAttempts,
139 | BodyProperties: options.BodyProperties,
140 | QueryParameters: options.QueryParameters,
141 | Client: options.HTTPClient,
142 | Request: request,
143 | Response: &response,
144 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
145 | },
146 | ); err != nil {
147 | return nil, err
148 | }
149 | return response, nil
150 | }
151 |
152 | // Returns a specific edge by its UUID.
153 | func (c *Client) Get(
154 | ctx context.Context,
155 | // Edge UUID
156 | _uuid string,
157 | opts ...option.RequestOption,
158 | ) (*v2.EntityEdge, error) {
159 | options := core.NewRequestOptions(opts...)
160 | baseURL := internal.ResolveBaseURL(
161 | options.BaseURL,
162 | c.baseURL,
163 | "https://api.getzep.com/api/v2",
164 | )
165 | endpointURL := internal.EncodeURL(
166 | baseURL+"/graph/edge/%v",
167 | _uuid,
168 | )
169 | headers := internal.MergeHeaders(
170 | c.header.Clone(),
171 | options.ToHeader(),
172 | )
173 | errorCodes := internal.ErrorCodes{
174 | 400: func(apiError *core.APIError) error {
175 | return &v2.BadRequestError{
176 | APIError: apiError,
177 | }
178 | },
179 | 404: func(apiError *core.APIError) error {
180 | return &v2.NotFoundError{
181 | APIError: apiError,
182 | }
183 | },
184 | 500: func(apiError *core.APIError) error {
185 | return &v2.InternalServerError{
186 | APIError: apiError,
187 | }
188 | },
189 | }
190 |
191 | var response *v2.EntityEdge
192 | if err := c.caller.Call(
193 | ctx,
194 | &internal.CallParams{
195 | URL: endpointURL,
196 | Method: http.MethodGet,
197 | Headers: headers,
198 | MaxAttempts: options.MaxAttempts,
199 | BodyProperties: options.BodyProperties,
200 | QueryParameters: options.QueryParameters,
201 | Client: options.HTTPClient,
202 | Response: &response,
203 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
204 | },
205 | ); err != nil {
206 | return nil, err
207 | }
208 | return response, nil
209 | }
210 |
211 | // Deletes an edge by UUID.
212 | func (c *Client) Delete(
213 | ctx context.Context,
214 | // Edge UUID
215 | __uuid string,
216 | opts ...option.RequestOption,
217 | ) (*v2.SuccessResponse, error) {
218 | options := core.NewRequestOptions(opts...)
219 | baseURL := internal.ResolveBaseURL(
220 | options.BaseURL,
221 | c.baseURL,
222 | "https://api.getzep.com/api/v2",
223 | )
224 | endpointURL := internal.EncodeURL(
225 | baseURL+"/graph/edge/%v",
226 | __uuid,
227 | )
228 | headers := internal.MergeHeaders(
229 | c.header.Clone(),
230 | options.ToHeader(),
231 | )
232 | errorCodes := internal.ErrorCodes{
233 | 400: func(apiError *core.APIError) error {
234 | return &v2.BadRequestError{
235 | APIError: apiError,
236 | }
237 | },
238 | 500: func(apiError *core.APIError) error {
239 | return &v2.InternalServerError{
240 | APIError: apiError,
241 | }
242 | },
243 | }
244 |
245 | var response *v2.SuccessResponse
246 | if err := c.caller.Call(
247 | ctx,
248 | &internal.CallParams{
249 | URL: endpointURL,
250 | Method: http.MethodDelete,
251 | Headers: headers,
252 | MaxAttempts: options.MaxAttempts,
253 | BodyProperties: options.BodyProperties,
254 | QueryParameters: options.QueryParameters,
255 | Client: options.HTTPClient,
256 | Response: &response,
257 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
258 | },
259 | ); err != nil {
260 | return nil, err
261 | }
262 | return response, nil
263 | }
264 |
--------------------------------------------------------------------------------
/graph/episode.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package graph
4 |
5 | type EpisodeGetByGroupIDRequest struct {
6 | // The number of most recent episodes to retrieve.
7 | Lastn *int `json:"-" url:"lastn,omitempty"`
8 | }
9 |
10 | type EpisodeGetByUserIDRequest struct {
11 | // The number of most recent episodes entries to retrieve.
12 | Lastn *int `json:"-" url:"lastn,omitempty"`
13 | }
14 |
--------------------------------------------------------------------------------
/graph/episode/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package episode
4 |
5 | import (
6 | context "context"
7 | v2 "github.com/getzep/zep-go/v2"
8 | core "github.com/getzep/zep-go/v2/core"
9 | graph "github.com/getzep/zep-go/v2/graph"
10 | internal "github.com/getzep/zep-go/v2/internal"
11 | option "github.com/getzep/zep-go/v2/option"
12 | http "net/http"
13 | os "os"
14 | )
15 |
16 | type Client struct {
17 | baseURL string
18 | caller *internal.Caller
19 | header http.Header
20 | }
21 |
22 | func NewClient(opts ...option.RequestOption) *Client {
23 | options := core.NewRequestOptions(opts...)
24 | if options.APIKey == "" {
25 | options.APIKey = os.Getenv("ZEP_API_KEY")
26 | }
27 | return &Client{
28 | baseURL: options.BaseURL,
29 | caller: internal.NewCaller(
30 | &internal.CallerParams{
31 | Client: options.HTTPClient,
32 | MaxAttempts: options.MaxAttempts,
33 | },
34 | ),
35 | header: options.ToHeader(),
36 | }
37 | }
38 |
39 | // Returns episodes by group id.
40 | func (c *Client) GetByGroupID(
41 | ctx context.Context,
42 | // Group ID
43 | groupID string,
44 | request *graph.EpisodeGetByGroupIDRequest,
45 | opts ...option.RequestOption,
46 | ) (*v2.EpisodeResponse, error) {
47 | options := core.NewRequestOptions(opts...)
48 | baseURL := internal.ResolveBaseURL(
49 | options.BaseURL,
50 | c.baseURL,
51 | "https://api.getzep.com/api/v2",
52 | )
53 | endpointURL := internal.EncodeURL(
54 | baseURL+"/graph/episodes/group/%v",
55 | groupID,
56 | )
57 | queryParams, err := internal.QueryValues(request)
58 | if err != nil {
59 | return nil, err
60 | }
61 | if len(queryParams) > 0 {
62 | endpointURL += "?" + queryParams.Encode()
63 | }
64 | headers := internal.MergeHeaders(
65 | c.header.Clone(),
66 | options.ToHeader(),
67 | )
68 | errorCodes := internal.ErrorCodes{
69 | 400: func(apiError *core.APIError) error {
70 | return &v2.BadRequestError{
71 | APIError: apiError,
72 | }
73 | },
74 | 500: func(apiError *core.APIError) error {
75 | return &v2.InternalServerError{
76 | APIError: apiError,
77 | }
78 | },
79 | }
80 |
81 | var response *v2.EpisodeResponse
82 | if err := c.caller.Call(
83 | ctx,
84 | &internal.CallParams{
85 | URL: endpointURL,
86 | Method: http.MethodGet,
87 | Headers: headers,
88 | MaxAttempts: options.MaxAttempts,
89 | BodyProperties: options.BodyProperties,
90 | QueryParameters: options.QueryParameters,
91 | Client: options.HTTPClient,
92 | Response: &response,
93 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
94 | },
95 | ); err != nil {
96 | return nil, err
97 | }
98 | return response, nil
99 | }
100 |
101 | // Returns episodes by user id.
102 | func (c *Client) GetByUserID(
103 | ctx context.Context,
104 | // User ID
105 | userID string,
106 | request *graph.EpisodeGetByUserIDRequest,
107 | opts ...option.RequestOption,
108 | ) (*v2.EpisodeResponse, error) {
109 | options := core.NewRequestOptions(opts...)
110 | baseURL := internal.ResolveBaseURL(
111 | options.BaseURL,
112 | c.baseURL,
113 | "https://api.getzep.com/api/v2",
114 | )
115 | endpointURL := internal.EncodeURL(
116 | baseURL+"/graph/episodes/user/%v",
117 | userID,
118 | )
119 | queryParams, err := internal.QueryValues(request)
120 | if err != nil {
121 | return nil, err
122 | }
123 | if len(queryParams) > 0 {
124 | endpointURL += "?" + queryParams.Encode()
125 | }
126 | headers := internal.MergeHeaders(
127 | c.header.Clone(),
128 | options.ToHeader(),
129 | )
130 | errorCodes := internal.ErrorCodes{
131 | 400: func(apiError *core.APIError) error {
132 | return &v2.BadRequestError{
133 | APIError: apiError,
134 | }
135 | },
136 | 500: func(apiError *core.APIError) error {
137 | return &v2.InternalServerError{
138 | APIError: apiError,
139 | }
140 | },
141 | }
142 |
143 | var response *v2.EpisodeResponse
144 | if err := c.caller.Call(
145 | ctx,
146 | &internal.CallParams{
147 | URL: endpointURL,
148 | Method: http.MethodGet,
149 | Headers: headers,
150 | MaxAttempts: options.MaxAttempts,
151 | BodyProperties: options.BodyProperties,
152 | QueryParameters: options.QueryParameters,
153 | Client: options.HTTPClient,
154 | Response: &response,
155 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
156 | },
157 | ); err != nil {
158 | return nil, err
159 | }
160 | return response, nil
161 | }
162 |
163 | // Returns episodes by UUID
164 | func (c *Client) Get(
165 | ctx context.Context,
166 | // Episode UUID
167 | _uuid string,
168 | opts ...option.RequestOption,
169 | ) (*v2.Episode, error) {
170 | options := core.NewRequestOptions(opts...)
171 | baseURL := internal.ResolveBaseURL(
172 | options.BaseURL,
173 | c.baseURL,
174 | "https://api.getzep.com/api/v2",
175 | )
176 | endpointURL := internal.EncodeURL(
177 | baseURL+"/graph/episodes/%v",
178 | _uuid,
179 | )
180 | headers := internal.MergeHeaders(
181 | c.header.Clone(),
182 | options.ToHeader(),
183 | )
184 | errorCodes := internal.ErrorCodes{
185 | 400: func(apiError *core.APIError) error {
186 | return &v2.BadRequestError{
187 | APIError: apiError,
188 | }
189 | },
190 | 500: func(apiError *core.APIError) error {
191 | return &v2.InternalServerError{
192 | APIError: apiError,
193 | }
194 | },
195 | }
196 |
197 | var response *v2.Episode
198 | if err := c.caller.Call(
199 | ctx,
200 | &internal.CallParams{
201 | URL: endpointURL,
202 | Method: http.MethodGet,
203 | Headers: headers,
204 | MaxAttempts: options.MaxAttempts,
205 | BodyProperties: options.BodyProperties,
206 | QueryParameters: options.QueryParameters,
207 | Client: options.HTTPClient,
208 | Response: &response,
209 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
210 | },
211 | ); err != nil {
212 | return nil, err
213 | }
214 | return response, nil
215 | }
216 |
217 | // Deletes an episode by its UUID.
218 | func (c *Client) Delete(
219 | ctx context.Context,
220 | // Episode UUID
221 | __uuid string,
222 | opts ...option.RequestOption,
223 | ) (*v2.SuccessResponse, error) {
224 | options := core.NewRequestOptions(opts...)
225 | baseURL := internal.ResolveBaseURL(
226 | options.BaseURL,
227 | c.baseURL,
228 | "https://api.getzep.com/api/v2",
229 | )
230 | endpointURL := internal.EncodeURL(
231 | baseURL+"/graph/episodes/%v",
232 | __uuid,
233 | )
234 | headers := internal.MergeHeaders(
235 | c.header.Clone(),
236 | options.ToHeader(),
237 | )
238 | errorCodes := internal.ErrorCodes{
239 | 400: func(apiError *core.APIError) error {
240 | return &v2.BadRequestError{
241 | APIError: apiError,
242 | }
243 | },
244 | 404: func(apiError *core.APIError) error {
245 | return &v2.NotFoundError{
246 | APIError: apiError,
247 | }
248 | },
249 | 500: func(apiError *core.APIError) error {
250 | return &v2.InternalServerError{
251 | APIError: apiError,
252 | }
253 | },
254 | }
255 |
256 | var response *v2.SuccessResponse
257 | if err := c.caller.Call(
258 | ctx,
259 | &internal.CallParams{
260 | URL: endpointURL,
261 | Method: http.MethodDelete,
262 | Headers: headers,
263 | MaxAttempts: options.MaxAttempts,
264 | BodyProperties: options.BodyProperties,
265 | QueryParameters: options.QueryParameters,
266 | Client: options.HTTPClient,
267 | Response: &response,
268 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
269 | },
270 | ); err != nil {
271 | return nil, err
272 | }
273 | return response, nil
274 | }
275 |
276 | // Returns nodes and edges mentioned in an episode
277 | func (c *Client) GetNodesAndEdges(
278 | ctx context.Context,
279 | // Episode uuid
280 | ___uuid string,
281 | opts ...option.RequestOption,
282 | ) (*v2.EpisodeMentions, error) {
283 | options := core.NewRequestOptions(opts...)
284 | baseURL := internal.ResolveBaseURL(
285 | options.BaseURL,
286 | c.baseURL,
287 | "https://api.getzep.com/api/v2",
288 | )
289 | endpointURL := internal.EncodeURL(
290 | baseURL+"/graph/episodes/%v/mentions",
291 | ___uuid,
292 | )
293 | headers := internal.MergeHeaders(
294 | c.header.Clone(),
295 | options.ToHeader(),
296 | )
297 | errorCodes := internal.ErrorCodes{
298 | 400: func(apiError *core.APIError) error {
299 | return &v2.BadRequestError{
300 | APIError: apiError,
301 | }
302 | },
303 | 500: func(apiError *core.APIError) error {
304 | return &v2.InternalServerError{
305 | APIError: apiError,
306 | }
307 | },
308 | }
309 |
310 | var response *v2.EpisodeMentions
311 | if err := c.caller.Call(
312 | ctx,
313 | &internal.CallParams{
314 | URL: endpointURL,
315 | Method: http.MethodGet,
316 | Headers: headers,
317 | MaxAttempts: options.MaxAttempts,
318 | BodyProperties: options.BodyProperties,
319 | QueryParameters: options.QueryParameters,
320 | Client: options.HTTPClient,
321 | Response: &response,
322 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
323 | },
324 | ); err != nil {
325 | return nil, err
326 | }
327 | return response, nil
328 | }
329 |
--------------------------------------------------------------------------------
/graph/node/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package node
4 |
5 | import (
6 | context "context"
7 | v2 "github.com/getzep/zep-go/v2"
8 | core "github.com/getzep/zep-go/v2/core"
9 | internal "github.com/getzep/zep-go/v2/internal"
10 | option "github.com/getzep/zep-go/v2/option"
11 | http "net/http"
12 | os "os"
13 | )
14 |
15 | type Client struct {
16 | baseURL string
17 | caller *internal.Caller
18 | header http.Header
19 | }
20 |
21 | func NewClient(opts ...option.RequestOption) *Client {
22 | options := core.NewRequestOptions(opts...)
23 | if options.APIKey == "" {
24 | options.APIKey = os.Getenv("ZEP_API_KEY")
25 | }
26 | return &Client{
27 | baseURL: options.BaseURL,
28 | caller: internal.NewCaller(
29 | &internal.CallerParams{
30 | Client: options.HTTPClient,
31 | MaxAttempts: options.MaxAttempts,
32 | },
33 | ),
34 | header: options.ToHeader(),
35 | }
36 | }
37 |
38 | // Returns all nodes for a group.
39 | func (c *Client) GetByGroupID(
40 | ctx context.Context,
41 | // Group ID
42 | groupID string,
43 | request *v2.GraphNodesRequest,
44 | opts ...option.RequestOption,
45 | ) ([]*v2.EntityNode, error) {
46 | options := core.NewRequestOptions(opts...)
47 | baseURL := internal.ResolveBaseURL(
48 | options.BaseURL,
49 | c.baseURL,
50 | "https://api.getzep.com/api/v2",
51 | )
52 | endpointURL := internal.EncodeURL(
53 | baseURL+"/graph/node/group/%v",
54 | groupID,
55 | )
56 | headers := internal.MergeHeaders(
57 | c.header.Clone(),
58 | options.ToHeader(),
59 | )
60 | headers.Set("Content-Type", "application/json")
61 | errorCodes := internal.ErrorCodes{
62 | 400: func(apiError *core.APIError) error {
63 | return &v2.BadRequestError{
64 | APIError: apiError,
65 | }
66 | },
67 | 500: func(apiError *core.APIError) error {
68 | return &v2.InternalServerError{
69 | APIError: apiError,
70 | }
71 | },
72 | }
73 |
74 | var response []*v2.EntityNode
75 | if err := c.caller.Call(
76 | ctx,
77 | &internal.CallParams{
78 | URL: endpointURL,
79 | Method: http.MethodPost,
80 | Headers: headers,
81 | MaxAttempts: options.MaxAttempts,
82 | BodyProperties: options.BodyProperties,
83 | QueryParameters: options.QueryParameters,
84 | Client: options.HTTPClient,
85 | Request: request,
86 | Response: &response,
87 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
88 | },
89 | ); err != nil {
90 | return nil, err
91 | }
92 | return response, nil
93 | }
94 |
95 | // Returns all nodes for a user
96 | func (c *Client) GetByUserID(
97 | ctx context.Context,
98 | // User ID
99 | userID string,
100 | request *v2.GraphNodesRequest,
101 | opts ...option.RequestOption,
102 | ) ([]*v2.EntityNode, error) {
103 | options := core.NewRequestOptions(opts...)
104 | baseURL := internal.ResolveBaseURL(
105 | options.BaseURL,
106 | c.baseURL,
107 | "https://api.getzep.com/api/v2",
108 | )
109 | endpointURL := internal.EncodeURL(
110 | baseURL+"/graph/node/user/%v",
111 | userID,
112 | )
113 | headers := internal.MergeHeaders(
114 | c.header.Clone(),
115 | options.ToHeader(),
116 | )
117 | headers.Set("Content-Type", "application/json")
118 | errorCodes := internal.ErrorCodes{
119 | 400: func(apiError *core.APIError) error {
120 | return &v2.BadRequestError{
121 | APIError: apiError,
122 | }
123 | },
124 | 500: func(apiError *core.APIError) error {
125 | return &v2.InternalServerError{
126 | APIError: apiError,
127 | }
128 | },
129 | }
130 |
131 | var response []*v2.EntityNode
132 | if err := c.caller.Call(
133 | ctx,
134 | &internal.CallParams{
135 | URL: endpointURL,
136 | Method: http.MethodPost,
137 | Headers: headers,
138 | MaxAttempts: options.MaxAttempts,
139 | BodyProperties: options.BodyProperties,
140 | QueryParameters: options.QueryParameters,
141 | Client: options.HTTPClient,
142 | Request: request,
143 | Response: &response,
144 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
145 | },
146 | ); err != nil {
147 | return nil, err
148 | }
149 | return response, nil
150 | }
151 |
152 | // Returns all edges for a node
153 | func (c *Client) GetEdges(
154 | ctx context.Context,
155 | // Node UUID
156 | nodeUUID string,
157 | opts ...option.RequestOption,
158 | ) ([]*v2.EntityEdge, error) {
159 | options := core.NewRequestOptions(opts...)
160 | baseURL := internal.ResolveBaseURL(
161 | options.BaseURL,
162 | c.baseURL,
163 | "https://api.getzep.com/api/v2",
164 | )
165 | endpointURL := internal.EncodeURL(
166 | baseURL+"/graph/node/%v/entity-edges",
167 | nodeUUID,
168 | )
169 | headers := internal.MergeHeaders(
170 | c.header.Clone(),
171 | options.ToHeader(),
172 | )
173 | errorCodes := internal.ErrorCodes{
174 | 400: func(apiError *core.APIError) error {
175 | return &v2.BadRequestError{
176 | APIError: apiError,
177 | }
178 | },
179 | 500: func(apiError *core.APIError) error {
180 | return &v2.InternalServerError{
181 | APIError: apiError,
182 | }
183 | },
184 | }
185 |
186 | var response []*v2.EntityEdge
187 | if err := c.caller.Call(
188 | ctx,
189 | &internal.CallParams{
190 | URL: endpointURL,
191 | Method: http.MethodGet,
192 | Headers: headers,
193 | MaxAttempts: options.MaxAttempts,
194 | BodyProperties: options.BodyProperties,
195 | QueryParameters: options.QueryParameters,
196 | Client: options.HTTPClient,
197 | Response: &response,
198 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
199 | },
200 | ); err != nil {
201 | return nil, err
202 | }
203 | return response, nil
204 | }
205 |
206 | // Returns all episodes that mentioned a given node
207 | func (c *Client) GetEpisodes(
208 | ctx context.Context,
209 | // Node UUID
210 | nodeUUID string,
211 | opts ...option.RequestOption,
212 | ) (*v2.EpisodeResponse, error) {
213 | options := core.NewRequestOptions(opts...)
214 | baseURL := internal.ResolveBaseURL(
215 | options.BaseURL,
216 | c.baseURL,
217 | "https://api.getzep.com/api/v2",
218 | )
219 | endpointURL := internal.EncodeURL(
220 | baseURL+"/graph/node/%v/episodes",
221 | nodeUUID,
222 | )
223 | headers := internal.MergeHeaders(
224 | c.header.Clone(),
225 | options.ToHeader(),
226 | )
227 | errorCodes := internal.ErrorCodes{
228 | 400: func(apiError *core.APIError) error {
229 | return &v2.BadRequestError{
230 | APIError: apiError,
231 | }
232 | },
233 | 500: func(apiError *core.APIError) error {
234 | return &v2.InternalServerError{
235 | APIError: apiError,
236 | }
237 | },
238 | }
239 |
240 | var response *v2.EpisodeResponse
241 | if err := c.caller.Call(
242 | ctx,
243 | &internal.CallParams{
244 | URL: endpointURL,
245 | Method: http.MethodGet,
246 | Headers: headers,
247 | MaxAttempts: options.MaxAttempts,
248 | BodyProperties: options.BodyProperties,
249 | QueryParameters: options.QueryParameters,
250 | Client: options.HTTPClient,
251 | Response: &response,
252 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
253 | },
254 | ); err != nil {
255 | return nil, err
256 | }
257 | return response, nil
258 | }
259 |
260 | // Returns a specific node by its UUID.
261 | func (c *Client) Get(
262 | ctx context.Context,
263 | // Node UUID
264 | _uuid string,
265 | opts ...option.RequestOption,
266 | ) (*v2.EntityNode, error) {
267 | options := core.NewRequestOptions(opts...)
268 | baseURL := internal.ResolveBaseURL(
269 | options.BaseURL,
270 | c.baseURL,
271 | "https://api.getzep.com/api/v2",
272 | )
273 | endpointURL := internal.EncodeURL(
274 | baseURL+"/graph/node/%v",
275 | _uuid,
276 | )
277 | headers := internal.MergeHeaders(
278 | c.header.Clone(),
279 | options.ToHeader(),
280 | )
281 | errorCodes := internal.ErrorCodes{
282 | 400: func(apiError *core.APIError) error {
283 | return &v2.BadRequestError{
284 | APIError: apiError,
285 | }
286 | },
287 | 404: func(apiError *core.APIError) error {
288 | return &v2.NotFoundError{
289 | APIError: apiError,
290 | }
291 | },
292 | 500: func(apiError *core.APIError) error {
293 | return &v2.InternalServerError{
294 | APIError: apiError,
295 | }
296 | },
297 | }
298 |
299 | var response *v2.EntityNode
300 | if err := c.caller.Call(
301 | ctx,
302 | &internal.CallParams{
303 | URL: endpointURL,
304 | Method: http.MethodGet,
305 | Headers: headers,
306 | MaxAttempts: options.MaxAttempts,
307 | BodyProperties: options.BodyProperties,
308 | QueryParameters: options.QueryParameters,
309 | Client: options.HTTPClient,
310 | Response: &response,
311 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
312 | },
313 | ); err != nil {
314 | return nil, err
315 | }
316 | return response, nil
317 | }
318 |
--------------------------------------------------------------------------------
/group.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package zep
4 |
5 | import (
6 | json "encoding/json"
7 | fmt "fmt"
8 | internal "github.com/getzep/zep-go/v2/internal"
9 | )
10 |
11 | type CreateGroupRequest struct {
12 | Description *string `json:"description,omitempty" url:"-"`
13 | FactRatingInstruction *FactRatingInstruction `json:"fact_rating_instruction,omitempty" url:"-"`
14 | GroupID string `json:"group_id" url:"-"`
15 | Name *string `json:"name,omitempty" url:"-"`
16 | }
17 |
18 | type GetGroupsOrderedRequest struct {
19 | // Page number for pagination, starting from 1.
20 | PageNumber *int `json:"-" url:"pageNumber,omitempty"`
21 | // Number of groups to retrieve per page.
22 | PageSize *int `json:"-" url:"pageSize,omitempty"`
23 | }
24 |
25 | type Group struct {
26 | CreatedAt *string `json:"created_at,omitempty" url:"created_at,omitempty"`
27 | Description *string `json:"description,omitempty" url:"description,omitempty"`
28 | // Deprecated
29 | ExternalID *string `json:"external_id,omitempty" url:"external_id,omitempty"`
30 | FactRatingInstruction *FactRatingInstruction `json:"fact_rating_instruction,omitempty" url:"fact_rating_instruction,omitempty"`
31 | GroupID *string `json:"group_id,omitempty" url:"group_id,omitempty"`
32 | ID *int `json:"id,omitempty" url:"id,omitempty"`
33 | Name *string `json:"name,omitempty" url:"name,omitempty"`
34 | ProjectUUID *string `json:"project_uuid,omitempty" url:"project_uuid,omitempty"`
35 | UUID *string `json:"uuid,omitempty" url:"uuid,omitempty"`
36 |
37 | extraProperties map[string]interface{}
38 | rawJSON json.RawMessage
39 | }
40 |
41 | func (g *Group) GetCreatedAt() *string {
42 | if g == nil {
43 | return nil
44 | }
45 | return g.CreatedAt
46 | }
47 |
48 | func (g *Group) GetDescription() *string {
49 | if g == nil {
50 | return nil
51 | }
52 | return g.Description
53 | }
54 |
55 | func (g *Group) GetExternalID() *string {
56 | if g == nil {
57 | return nil
58 | }
59 | return g.ExternalID
60 | }
61 |
62 | func (g *Group) GetFactRatingInstruction() *FactRatingInstruction {
63 | if g == nil {
64 | return nil
65 | }
66 | return g.FactRatingInstruction
67 | }
68 |
69 | func (g *Group) GetGroupID() *string {
70 | if g == nil {
71 | return nil
72 | }
73 | return g.GroupID
74 | }
75 |
76 | func (g *Group) GetID() *int {
77 | if g == nil {
78 | return nil
79 | }
80 | return g.ID
81 | }
82 |
83 | func (g *Group) GetName() *string {
84 | if g == nil {
85 | return nil
86 | }
87 | return g.Name
88 | }
89 |
90 | func (g *Group) GetProjectUUID() *string {
91 | if g == nil {
92 | return nil
93 | }
94 | return g.ProjectUUID
95 | }
96 |
97 | func (g *Group) GetUUID() *string {
98 | if g == nil {
99 | return nil
100 | }
101 | return g.UUID
102 | }
103 |
104 | func (g *Group) GetExtraProperties() map[string]interface{} {
105 | return g.extraProperties
106 | }
107 |
108 | func (g *Group) UnmarshalJSON(data []byte) error {
109 | type unmarshaler Group
110 | var value unmarshaler
111 | if err := json.Unmarshal(data, &value); err != nil {
112 | return err
113 | }
114 | *g = Group(value)
115 | extraProperties, err := internal.ExtractExtraProperties(data, *g)
116 | if err != nil {
117 | return err
118 | }
119 | g.extraProperties = extraProperties
120 | g.rawJSON = json.RawMessage(data)
121 | return nil
122 | }
123 |
124 | func (g *Group) String() string {
125 | if len(g.rawJSON) > 0 {
126 | if value, err := internal.StringifyJSON(g.rawJSON); err == nil {
127 | return value
128 | }
129 | }
130 | if value, err := internal.StringifyJSON(g); err == nil {
131 | return value
132 | }
133 | return fmt.Sprintf("%#v", g)
134 | }
135 |
136 | type GroupListResponse struct {
137 | Groups []*Group `json:"groups,omitempty" url:"groups,omitempty"`
138 | RowCount *int `json:"row_count,omitempty" url:"row_count,omitempty"`
139 | TotalCount *int `json:"total_count,omitempty" url:"total_count,omitempty"`
140 |
141 | extraProperties map[string]interface{}
142 | rawJSON json.RawMessage
143 | }
144 |
145 | func (g *GroupListResponse) GetGroups() []*Group {
146 | if g == nil {
147 | return nil
148 | }
149 | return g.Groups
150 | }
151 |
152 | func (g *GroupListResponse) GetRowCount() *int {
153 | if g == nil {
154 | return nil
155 | }
156 | return g.RowCount
157 | }
158 |
159 | func (g *GroupListResponse) GetTotalCount() *int {
160 | if g == nil {
161 | return nil
162 | }
163 | return g.TotalCount
164 | }
165 |
166 | func (g *GroupListResponse) GetExtraProperties() map[string]interface{} {
167 | return g.extraProperties
168 | }
169 |
170 | func (g *GroupListResponse) UnmarshalJSON(data []byte) error {
171 | type unmarshaler GroupListResponse
172 | var value unmarshaler
173 | if err := json.Unmarshal(data, &value); err != nil {
174 | return err
175 | }
176 | *g = GroupListResponse(value)
177 | extraProperties, err := internal.ExtractExtraProperties(data, *g)
178 | if err != nil {
179 | return err
180 | }
181 | g.extraProperties = extraProperties
182 | g.rawJSON = json.RawMessage(data)
183 | return nil
184 | }
185 |
186 | func (g *GroupListResponse) String() string {
187 | if len(g.rawJSON) > 0 {
188 | if value, err := internal.StringifyJSON(g.rawJSON); err == nil {
189 | return value
190 | }
191 | }
192 | if value, err := internal.StringifyJSON(g); err == nil {
193 | return value
194 | }
195 | return fmt.Sprintf("%#v", g)
196 | }
197 |
198 | type UpdateGroupRequest struct {
199 | Description *string `json:"description,omitempty" url:"-"`
200 | FactRatingInstruction *FactRatingInstruction `json:"fact_rating_instruction,omitempty" url:"-"`
201 | Name *string `json:"name,omitempty" url:"-"`
202 | }
203 |
--------------------------------------------------------------------------------
/group/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package group
4 |
5 | import (
6 | context "context"
7 | v2 "github.com/getzep/zep-go/v2"
8 | core "github.com/getzep/zep-go/v2/core"
9 | internal "github.com/getzep/zep-go/v2/internal"
10 | option "github.com/getzep/zep-go/v2/option"
11 | http "net/http"
12 | os "os"
13 | )
14 |
15 | type Client struct {
16 | baseURL string
17 | caller *internal.Caller
18 | header http.Header
19 | }
20 |
21 | func NewClient(opts ...option.RequestOption) *Client {
22 | options := core.NewRequestOptions(opts...)
23 | if options.APIKey == "" {
24 | options.APIKey = os.Getenv("ZEP_API_KEY")
25 | }
26 | return &Client{
27 | baseURL: options.BaseURL,
28 | caller: internal.NewCaller(
29 | &internal.CallerParams{
30 | Client: options.HTTPClient,
31 | MaxAttempts: options.MaxAttempts,
32 | },
33 | ),
34 | header: options.ToHeader(),
35 | }
36 | }
37 |
38 | // Creates a new group.
39 | func (c *Client) Add(
40 | ctx context.Context,
41 | request *v2.CreateGroupRequest,
42 | opts ...option.RequestOption,
43 | ) (*v2.Group, error) {
44 | options := core.NewRequestOptions(opts...)
45 | baseURL := internal.ResolveBaseURL(
46 | options.BaseURL,
47 | c.baseURL,
48 | "https://api.getzep.com/api/v2",
49 | )
50 | endpointURL := baseURL + "/groups"
51 | headers := internal.MergeHeaders(
52 | c.header.Clone(),
53 | options.ToHeader(),
54 | )
55 | headers.Set("Content-Type", "application/json")
56 | errorCodes := internal.ErrorCodes{
57 | 400: func(apiError *core.APIError) error {
58 | return &v2.BadRequestError{
59 | APIError: apiError,
60 | }
61 | },
62 | 500: func(apiError *core.APIError) error {
63 | return &v2.InternalServerError{
64 | APIError: apiError,
65 | }
66 | },
67 | }
68 |
69 | var response *v2.Group
70 | if err := c.caller.Call(
71 | ctx,
72 | &internal.CallParams{
73 | URL: endpointURL,
74 | Method: http.MethodPost,
75 | Headers: headers,
76 | MaxAttempts: options.MaxAttempts,
77 | BodyProperties: options.BodyProperties,
78 | QueryParameters: options.QueryParameters,
79 | Client: options.HTTPClient,
80 | Request: request,
81 | Response: &response,
82 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
83 | },
84 | ); err != nil {
85 | return nil, err
86 | }
87 | return response, nil
88 | }
89 |
90 | // Returns all groups.
91 | func (c *Client) GetAllGroups(
92 | ctx context.Context,
93 | request *v2.GetGroupsOrderedRequest,
94 | opts ...option.RequestOption,
95 | ) (*v2.GroupListResponse, error) {
96 | options := core.NewRequestOptions(opts...)
97 | baseURL := internal.ResolveBaseURL(
98 | options.BaseURL,
99 | c.baseURL,
100 | "https://api.getzep.com/api/v2",
101 | )
102 | endpointURL := baseURL + "/groups-ordered"
103 | queryParams, err := internal.QueryValues(request)
104 | if err != nil {
105 | return nil, err
106 | }
107 | if len(queryParams) > 0 {
108 | endpointURL += "?" + queryParams.Encode()
109 | }
110 | headers := internal.MergeHeaders(
111 | c.header.Clone(),
112 | options.ToHeader(),
113 | )
114 | errorCodes := internal.ErrorCodes{
115 | 400: func(apiError *core.APIError) error {
116 | return &v2.BadRequestError{
117 | APIError: apiError,
118 | }
119 | },
120 | 500: func(apiError *core.APIError) error {
121 | return &v2.InternalServerError{
122 | APIError: apiError,
123 | }
124 | },
125 | }
126 |
127 | var response *v2.GroupListResponse
128 | if err := c.caller.Call(
129 | ctx,
130 | &internal.CallParams{
131 | URL: endpointURL,
132 | Method: http.MethodGet,
133 | Headers: headers,
134 | MaxAttempts: options.MaxAttempts,
135 | BodyProperties: options.BodyProperties,
136 | QueryParameters: options.QueryParameters,
137 | Client: options.HTTPClient,
138 | Response: &response,
139 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
140 | },
141 | ); err != nil {
142 | return nil, err
143 | }
144 | return response, nil
145 | }
146 |
147 | // Returns a group.
148 | func (c *Client) GetGroup(
149 | ctx context.Context,
150 | // The group_id of the group to get.
151 | groupID string,
152 | opts ...option.RequestOption,
153 | ) (*v2.Group, error) {
154 | options := core.NewRequestOptions(opts...)
155 | baseURL := internal.ResolveBaseURL(
156 | options.BaseURL,
157 | c.baseURL,
158 | "https://api.getzep.com/api/v2",
159 | )
160 | endpointURL := internal.EncodeURL(
161 | baseURL+"/groups/%v",
162 | groupID,
163 | )
164 | headers := internal.MergeHeaders(
165 | c.header.Clone(),
166 | options.ToHeader(),
167 | )
168 | errorCodes := internal.ErrorCodes{
169 | 404: func(apiError *core.APIError) error {
170 | return &v2.NotFoundError{
171 | APIError: apiError,
172 | }
173 | },
174 | 500: func(apiError *core.APIError) error {
175 | return &v2.InternalServerError{
176 | APIError: apiError,
177 | }
178 | },
179 | }
180 |
181 | var response *v2.Group
182 | if err := c.caller.Call(
183 | ctx,
184 | &internal.CallParams{
185 | URL: endpointURL,
186 | Method: http.MethodGet,
187 | Headers: headers,
188 | MaxAttempts: options.MaxAttempts,
189 | BodyProperties: options.BodyProperties,
190 | QueryParameters: options.QueryParameters,
191 | Client: options.HTTPClient,
192 | Response: &response,
193 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
194 | },
195 | ); err != nil {
196 | return nil, err
197 | }
198 | return response, nil
199 | }
200 |
201 | // Deletes a group.
202 | func (c *Client) Delete(
203 | ctx context.Context,
204 | // Group ID
205 | groupID string,
206 | opts ...option.RequestOption,
207 | ) (*v2.SuccessResponse, error) {
208 | options := core.NewRequestOptions(opts...)
209 | baseURL := internal.ResolveBaseURL(
210 | options.BaseURL,
211 | c.baseURL,
212 | "https://api.getzep.com/api/v2",
213 | )
214 | endpointURL := internal.EncodeURL(
215 | baseURL+"/groups/%v",
216 | groupID,
217 | )
218 | headers := internal.MergeHeaders(
219 | c.header.Clone(),
220 | options.ToHeader(),
221 | )
222 | errorCodes := internal.ErrorCodes{
223 | 400: func(apiError *core.APIError) error {
224 | return &v2.BadRequestError{
225 | APIError: apiError,
226 | }
227 | },
228 | 404: func(apiError *core.APIError) error {
229 | return &v2.NotFoundError{
230 | APIError: apiError,
231 | }
232 | },
233 | 500: func(apiError *core.APIError) error {
234 | return &v2.InternalServerError{
235 | APIError: apiError,
236 | }
237 | },
238 | }
239 |
240 | var response *v2.SuccessResponse
241 | if err := c.caller.Call(
242 | ctx,
243 | &internal.CallParams{
244 | URL: endpointURL,
245 | Method: http.MethodDelete,
246 | Headers: headers,
247 | MaxAttempts: options.MaxAttempts,
248 | BodyProperties: options.BodyProperties,
249 | QueryParameters: options.QueryParameters,
250 | Client: options.HTTPClient,
251 | Response: &response,
252 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
253 | },
254 | ); err != nil {
255 | return nil, err
256 | }
257 | return response, nil
258 | }
259 |
260 | // Updates information about a group.
261 | func (c *Client) Update(
262 | ctx context.Context,
263 | // Group ID
264 | groupID string,
265 | request *v2.UpdateGroupRequest,
266 | opts ...option.RequestOption,
267 | ) (*v2.Group, error) {
268 | options := core.NewRequestOptions(opts...)
269 | baseURL := internal.ResolveBaseURL(
270 | options.BaseURL,
271 | c.baseURL,
272 | "https://api.getzep.com/api/v2",
273 | )
274 | endpointURL := internal.EncodeURL(
275 | baseURL+"/groups/%v",
276 | groupID,
277 | )
278 | headers := internal.MergeHeaders(
279 | c.header.Clone(),
280 | options.ToHeader(),
281 | )
282 | headers.Set("Content-Type", "application/json")
283 | errorCodes := internal.ErrorCodes{
284 | 400: func(apiError *core.APIError) error {
285 | return &v2.BadRequestError{
286 | APIError: apiError,
287 | }
288 | },
289 | 404: func(apiError *core.APIError) error {
290 | return &v2.NotFoundError{
291 | APIError: apiError,
292 | }
293 | },
294 | 500: func(apiError *core.APIError) error {
295 | return &v2.InternalServerError{
296 | APIError: apiError,
297 | }
298 | },
299 | }
300 |
301 | var response *v2.Group
302 | if err := c.caller.Call(
303 | ctx,
304 | &internal.CallParams{
305 | URL: endpointURL,
306 | Method: http.MethodPatch,
307 | Headers: headers,
308 | MaxAttempts: options.MaxAttempts,
309 | BodyProperties: options.BodyProperties,
310 | QueryParameters: options.QueryParameters,
311 | Client: options.HTTPClient,
312 | Request: request,
313 | Response: &response,
314 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
315 | },
316 | ); err != nil {
317 | return nil, err
318 | }
319 | return response, nil
320 | }
321 |
322 | // Deprecated: Use Get Group Edges instead.
323 | func (c *Client) GetFacts(
324 | ctx context.Context,
325 | // The group_id of the group to get.
326 | groupID string,
327 | opts ...option.RequestOption,
328 | ) (*v2.FactsResponse, error) {
329 | options := core.NewRequestOptions(opts...)
330 | baseURL := internal.ResolveBaseURL(
331 | options.BaseURL,
332 | c.baseURL,
333 | "https://api.getzep.com/api/v2",
334 | )
335 | endpointURL := internal.EncodeURL(
336 | baseURL+"/groups/%v/facts",
337 | groupID,
338 | )
339 | headers := internal.MergeHeaders(
340 | c.header.Clone(),
341 | options.ToHeader(),
342 | )
343 | errorCodes := internal.ErrorCodes{
344 | 404: func(apiError *core.APIError) error {
345 | return &v2.NotFoundError{
346 | APIError: apiError,
347 | }
348 | },
349 | 500: func(apiError *core.APIError) error {
350 | return &v2.InternalServerError{
351 | APIError: apiError,
352 | }
353 | },
354 | }
355 |
356 | var response *v2.FactsResponse
357 | if err := c.caller.Call(
358 | ctx,
359 | &internal.CallParams{
360 | URL: endpointURL,
361 | Method: http.MethodGet,
362 | Headers: headers,
363 | MaxAttempts: options.MaxAttempts,
364 | BodyProperties: options.BodyProperties,
365 | QueryParameters: options.QueryParameters,
366 | Client: options.HTTPClient,
367 | Response: &response,
368 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
369 | },
370 | ); err != nil {
371 | return nil, err
372 | }
373 | return response, nil
374 | }
375 |
--------------------------------------------------------------------------------
/internal/caller.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "net/http"
11 | "net/url"
12 | "reflect"
13 | "strings"
14 |
15 | "github.com/getzep/zep-go/v2/core"
16 | )
17 |
18 | const (
19 | // contentType specifies the JSON Content-Type header value.
20 | contentType = "application/json"
21 | contentTypeHeader = "Content-Type"
22 | )
23 |
24 | // Caller calls APIs and deserializes their response, if any.
25 | type Caller struct {
26 | client core.HTTPClient
27 | retrier *Retrier
28 | }
29 |
30 | // CallerParams represents the parameters used to constrcut a new *Caller.
31 | type CallerParams struct {
32 | Client core.HTTPClient
33 | MaxAttempts uint
34 | }
35 |
36 | // NewCaller returns a new *Caller backed by the given parameters.
37 | func NewCaller(params *CallerParams) *Caller {
38 | var httpClient core.HTTPClient = http.DefaultClient
39 | if params.Client != nil {
40 | httpClient = params.Client
41 | }
42 | var retryOptions []RetryOption
43 | if params.MaxAttempts > 0 {
44 | retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts))
45 | }
46 | return &Caller{
47 | client: httpClient,
48 | retrier: NewRetrier(retryOptions...),
49 | }
50 | }
51 |
52 | // CallParams represents the parameters used to issue an API call.
53 | type CallParams struct {
54 | URL string
55 | Method string
56 | MaxAttempts uint
57 | Headers http.Header
58 | BodyProperties map[string]interface{}
59 | QueryParameters url.Values
60 | Client core.HTTPClient
61 | Request interface{}
62 | Response interface{}
63 | ResponseIsOptional bool
64 | ErrorDecoder ErrorDecoder
65 | }
66 |
67 | // Call issues an API call according to the given call parameters.
68 | func (c *Caller) Call(ctx context.Context, params *CallParams) error {
69 | url := buildURL(params.URL, params.QueryParameters)
70 | req, err := newRequest(
71 | ctx,
72 | url,
73 | params.Method,
74 | params.Headers,
75 | params.Request,
76 | params.BodyProperties,
77 | )
78 | if err != nil {
79 | return err
80 | }
81 |
82 | // If the call has been cancelled, don't issue the request.
83 | if err := ctx.Err(); err != nil {
84 | return err
85 | }
86 |
87 | client := c.client
88 | if params.Client != nil {
89 | // Use the HTTP client scoped to the request.
90 | client = params.Client
91 | }
92 |
93 | var retryOptions []RetryOption
94 | if params.MaxAttempts > 0 {
95 | retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts))
96 | }
97 |
98 | resp, err := c.retrier.Run(
99 | client.Do,
100 | req,
101 | params.ErrorDecoder,
102 | retryOptions...,
103 | )
104 | if err != nil {
105 | return err
106 | }
107 |
108 | // Close the response body after we're done.
109 | defer resp.Body.Close()
110 |
111 | // Check if the call was cancelled before we return the error
112 | // associated with the call and/or unmarshal the response data.
113 | if err := ctx.Err(); err != nil {
114 | return err
115 | }
116 |
117 | if resp.StatusCode < 200 || resp.StatusCode >= 300 {
118 | return decodeError(resp, params.ErrorDecoder)
119 | }
120 |
121 | // Mutate the response parameter in-place.
122 | if params.Response != nil {
123 | if writer, ok := params.Response.(io.Writer); ok {
124 | _, err = io.Copy(writer, resp.Body)
125 | } else {
126 | err = json.NewDecoder(resp.Body).Decode(params.Response)
127 | }
128 | if err != nil {
129 | if err == io.EOF {
130 | if params.ResponseIsOptional {
131 | // The response is optional, so we should ignore the
132 | // io.EOF error
133 | return nil
134 | }
135 | return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response)
136 | }
137 | return err
138 | }
139 | }
140 |
141 | return nil
142 | }
143 |
144 | // buildURL constructs the final URL by appending the given query parameters (if any).
145 | func buildURL(
146 | url string,
147 | queryParameters url.Values,
148 | ) string {
149 | if len(queryParameters) == 0 {
150 | return url
151 | }
152 | if strings.ContainsRune(url, '?') {
153 | url += "&"
154 | } else {
155 | url += "?"
156 | }
157 | url += queryParameters.Encode()
158 | return url
159 | }
160 |
161 | // newRequest returns a new *http.Request with all of the fields
162 | // required to issue the call.
163 | func newRequest(
164 | ctx context.Context,
165 | url string,
166 | method string,
167 | endpointHeaders http.Header,
168 | request interface{},
169 | bodyProperties map[string]interface{},
170 | ) (*http.Request, error) {
171 | requestBody, err := newRequestBody(request, bodyProperties)
172 | if err != nil {
173 | return nil, err
174 | }
175 | req, err := http.NewRequestWithContext(ctx, method, url, requestBody)
176 | if err != nil {
177 | return nil, err
178 | }
179 | req = req.WithContext(ctx)
180 | req.Header.Set(contentTypeHeader, contentType)
181 | for name, values := range endpointHeaders {
182 | req.Header[name] = values
183 | }
184 | return req, nil
185 | }
186 |
187 | // newRequestBody returns a new io.Reader that represents the HTTP request body.
188 | func newRequestBody(request interface{}, bodyProperties map[string]interface{}) (io.Reader, error) {
189 | if isNil(request) {
190 | if len(bodyProperties) == 0 {
191 | return nil, nil
192 | }
193 | requestBytes, err := json.Marshal(bodyProperties)
194 | if err != nil {
195 | return nil, err
196 | }
197 | return bytes.NewReader(requestBytes), nil
198 | }
199 | if body, ok := request.(io.Reader); ok {
200 | return body, nil
201 | }
202 | requestBytes, err := MarshalJSONWithExtraProperties(request, bodyProperties)
203 | if err != nil {
204 | return nil, err
205 | }
206 | return bytes.NewReader(requestBytes), nil
207 | }
208 |
209 | // decodeError decodes the error from the given HTTP response. Note that
210 | // it's the caller's responsibility to close the response body.
211 | func decodeError(response *http.Response, errorDecoder ErrorDecoder) error {
212 | if errorDecoder != nil {
213 | // This endpoint has custom errors, so we'll
214 | // attempt to unmarshal the error into a structured
215 | // type based on the status code.
216 | return errorDecoder(response.StatusCode, response.Body)
217 | }
218 | // This endpoint doesn't have any custom error
219 | // types, so we just read the body as-is, and
220 | // put it into a normal error.
221 | bytes, err := io.ReadAll(response.Body)
222 | if err != nil && err != io.EOF {
223 | return err
224 | }
225 | if err == io.EOF {
226 | // The error didn't have a response body,
227 | // so all we can do is return an error
228 | // with the status code.
229 | return core.NewAPIError(response.StatusCode, nil)
230 | }
231 | return core.NewAPIError(response.StatusCode, errors.New(string(bytes)))
232 | }
233 |
234 | // isNil is used to determine if the request value is equal to nil (i.e. an interface
235 | // value that holds a nil concrete value is itself non-nil).
236 | func isNil(value interface{}) bool {
237 | return value == nil || reflect.ValueOf(value).IsNil()
238 | }
239 |
--------------------------------------------------------------------------------
/internal/caller_test.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "net/http"
11 | "net/http/httptest"
12 | "net/url"
13 | "strconv"
14 | "testing"
15 |
16 | "github.com/getzep/zep-go/v2/core"
17 | "github.com/stretchr/testify/assert"
18 | "github.com/stretchr/testify/require"
19 | )
20 |
21 | // TestCase represents a single test case.
22 | type TestCase struct {
23 | description string
24 |
25 | // Server-side assertions.
26 | givePathSuffix string
27 | giveMethod string
28 | giveResponseIsOptional bool
29 | giveHeader http.Header
30 | giveErrorDecoder ErrorDecoder
31 | giveRequest *Request
32 | giveQueryParams url.Values
33 | giveBodyProperties map[string]interface{}
34 |
35 | // Client-side assertions.
36 | wantResponse *Response
37 | wantError error
38 | }
39 |
40 | // Request a simple request body.
41 | type Request struct {
42 | Id string `json:"id"`
43 | }
44 |
45 | // Response a simple response body.
46 | type Response struct {
47 | Id string `json:"id"`
48 | ExtraBodyProperties map[string]interface{} `json:"extraBodyProperties,omitempty"`
49 | QueryParameters url.Values `json:"queryParameters,omitempty"`
50 | }
51 |
52 | // NotFoundError represents a 404.
53 | type NotFoundError struct {
54 | *core.APIError
55 |
56 | Message string `json:"message"`
57 | }
58 |
59 | func TestCall(t *testing.T) {
60 | tests := []*TestCase{
61 | {
62 | description: "GET success",
63 | giveMethod: http.MethodGet,
64 | giveHeader: http.Header{
65 | "X-API-Status": []string{"success"},
66 | },
67 | giveRequest: &Request{
68 | Id: "123",
69 | },
70 | wantResponse: &Response{
71 | Id: "123",
72 | },
73 | },
74 | {
75 | description: "GET success with query",
76 | givePathSuffix: "?limit=1",
77 | giveMethod: http.MethodGet,
78 | giveHeader: http.Header{
79 | "X-API-Status": []string{"success"},
80 | },
81 | giveRequest: &Request{
82 | Id: "123",
83 | },
84 | wantResponse: &Response{
85 | Id: "123",
86 | QueryParameters: url.Values{
87 | "limit": []string{"1"},
88 | },
89 | },
90 | },
91 | {
92 | description: "GET not found",
93 | giveMethod: http.MethodGet,
94 | giveHeader: http.Header{
95 | "X-API-Status": []string{"fail"},
96 | },
97 | giveRequest: &Request{
98 | Id: strconv.Itoa(http.StatusNotFound),
99 | },
100 | giveErrorDecoder: newTestErrorDecoder(t),
101 | wantError: &NotFoundError{
102 | APIError: core.NewAPIError(
103 | http.StatusNotFound,
104 | errors.New(`{"message":"ID \"404\" not found"}`),
105 | ),
106 | },
107 | },
108 | {
109 | description: "POST empty body",
110 | giveMethod: http.MethodPost,
111 | giveHeader: http.Header{
112 | "X-API-Status": []string{"fail"},
113 | },
114 | giveRequest: nil,
115 | wantError: core.NewAPIError(
116 | http.StatusBadRequest,
117 | errors.New("invalid request"),
118 | ),
119 | },
120 | {
121 | description: "POST optional response",
122 | giveMethod: http.MethodPost,
123 | giveHeader: http.Header{
124 | "X-API-Status": []string{"success"},
125 | },
126 | giveRequest: &Request{
127 | Id: "123",
128 | },
129 | giveResponseIsOptional: true,
130 | },
131 | {
132 | description: "POST API error",
133 | giveMethod: http.MethodPost,
134 | giveHeader: http.Header{
135 | "X-API-Status": []string{"fail"},
136 | },
137 | giveRequest: &Request{
138 | Id: strconv.Itoa(http.StatusInternalServerError),
139 | },
140 | wantError: core.NewAPIError(
141 | http.StatusInternalServerError,
142 | errors.New("failed to process request"),
143 | ),
144 | },
145 | {
146 | description: "POST extra properties",
147 | giveMethod: http.MethodPost,
148 | giveHeader: http.Header{
149 | "X-API-Status": []string{"success"},
150 | },
151 | giveRequest: new(Request),
152 | giveBodyProperties: map[string]interface{}{
153 | "key": "value",
154 | },
155 | wantResponse: &Response{
156 | ExtraBodyProperties: map[string]interface{}{
157 | "key": "value",
158 | },
159 | },
160 | },
161 | {
162 | description: "GET extra query parameters",
163 | giveMethod: http.MethodGet,
164 | giveHeader: http.Header{
165 | "X-API-Status": []string{"success"},
166 | },
167 | giveQueryParams: url.Values{
168 | "extra": []string{"true"},
169 | },
170 | giveRequest: &Request{
171 | Id: "123",
172 | },
173 | wantResponse: &Response{
174 | Id: "123",
175 | QueryParameters: url.Values{
176 | "extra": []string{"true"},
177 | },
178 | },
179 | },
180 | {
181 | description: "GET merge extra query parameters",
182 | givePathSuffix: "?limit=1",
183 | giveMethod: http.MethodGet,
184 | giveHeader: http.Header{
185 | "X-API-Status": []string{"success"},
186 | },
187 | giveRequest: &Request{
188 | Id: "123",
189 | },
190 | giveQueryParams: url.Values{
191 | "extra": []string{"true"},
192 | },
193 | wantResponse: &Response{
194 | Id: "123",
195 | QueryParameters: url.Values{
196 | "limit": []string{"1"},
197 | "extra": []string{"true"},
198 | },
199 | },
200 | },
201 | }
202 | for _, test := range tests {
203 | t.Run(test.description, func(t *testing.T) {
204 | var (
205 | server = newTestServer(t, test)
206 | client = server.Client()
207 | )
208 | caller := NewCaller(
209 | &CallerParams{
210 | Client: client,
211 | },
212 | )
213 | var response *Response
214 | err := caller.Call(
215 | context.Background(),
216 | &CallParams{
217 | URL: server.URL + test.givePathSuffix,
218 | Method: test.giveMethod,
219 | Headers: test.giveHeader,
220 | BodyProperties: test.giveBodyProperties,
221 | QueryParameters: test.giveQueryParams,
222 | Request: test.giveRequest,
223 | Response: &response,
224 | ResponseIsOptional: test.giveResponseIsOptional,
225 | ErrorDecoder: test.giveErrorDecoder,
226 | },
227 | )
228 | if test.wantError != nil {
229 | assert.EqualError(t, err, test.wantError.Error())
230 | return
231 | }
232 | require.NoError(t, err)
233 | assert.Equal(t, test.wantResponse, response)
234 | })
235 | }
236 | }
237 |
238 | func TestMergeHeaders(t *testing.T) {
239 | t.Run("both empty", func(t *testing.T) {
240 | merged := MergeHeaders(make(http.Header), make(http.Header))
241 | assert.Empty(t, merged)
242 | })
243 |
244 | t.Run("empty left", func(t *testing.T) {
245 | left := make(http.Header)
246 |
247 | right := make(http.Header)
248 | right.Set("X-API-Version", "0.0.1")
249 |
250 | merged := MergeHeaders(left, right)
251 | assert.Equal(t, "0.0.1", merged.Get("X-API-Version"))
252 | })
253 |
254 | t.Run("empty right", func(t *testing.T) {
255 | left := make(http.Header)
256 | left.Set("X-API-Version", "0.0.1")
257 |
258 | right := make(http.Header)
259 |
260 | merged := MergeHeaders(left, right)
261 | assert.Equal(t, "0.0.1", merged.Get("X-API-Version"))
262 | })
263 |
264 | t.Run("single value override", func(t *testing.T) {
265 | left := make(http.Header)
266 | left.Set("X-API-Version", "0.0.0")
267 |
268 | right := make(http.Header)
269 | right.Set("X-API-Version", "0.0.1")
270 |
271 | merged := MergeHeaders(left, right)
272 | assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version"))
273 | })
274 |
275 | t.Run("multiple value override", func(t *testing.T) {
276 | left := make(http.Header)
277 | left.Set("X-API-Versions", "0.0.0")
278 |
279 | right := make(http.Header)
280 | right.Add("X-API-Versions", "0.0.1")
281 | right.Add("X-API-Versions", "0.0.2")
282 |
283 | merged := MergeHeaders(left, right)
284 | assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions"))
285 | })
286 |
287 | t.Run("disjoint merge", func(t *testing.T) {
288 | left := make(http.Header)
289 | left.Set("X-API-Tenancy", "test")
290 |
291 | right := make(http.Header)
292 | right.Set("X-API-Version", "0.0.1")
293 |
294 | merged := MergeHeaders(left, right)
295 | assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy"))
296 | assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version"))
297 | })
298 | }
299 |
300 | // newTestServer returns a new *httptest.Server configured with the
301 | // given test parameters.
302 | func newTestServer(t *testing.T, tc *TestCase) *httptest.Server {
303 | return httptest.NewServer(
304 | http.HandlerFunc(
305 | func(w http.ResponseWriter, r *http.Request) {
306 | assert.Equal(t, tc.giveMethod, r.Method)
307 | assert.Equal(t, contentType, r.Header.Get(contentTypeHeader))
308 | for header, value := range tc.giveHeader {
309 | assert.Equal(t, value, r.Header.Values(header))
310 | }
311 |
312 | request := new(Request)
313 |
314 | bytes, err := io.ReadAll(r.Body)
315 | if tc.giveRequest == nil {
316 | require.Empty(t, bytes)
317 | w.WriteHeader(http.StatusBadRequest)
318 | _, err = w.Write([]byte("invalid request"))
319 | require.NoError(t, err)
320 | return
321 | }
322 | require.NoError(t, err)
323 | require.NoError(t, json.Unmarshal(bytes, request))
324 |
325 | switch request.Id {
326 | case strconv.Itoa(http.StatusNotFound):
327 | notFoundError := &NotFoundError{
328 | APIError: &core.APIError{
329 | StatusCode: http.StatusNotFound,
330 | },
331 | Message: fmt.Sprintf("ID %q not found", request.Id),
332 | }
333 | bytes, err = json.Marshal(notFoundError)
334 | require.NoError(t, err)
335 |
336 | w.WriteHeader(http.StatusNotFound)
337 | _, err = w.Write(bytes)
338 | require.NoError(t, err)
339 | return
340 |
341 | case strconv.Itoa(http.StatusInternalServerError):
342 | w.WriteHeader(http.StatusInternalServerError)
343 | _, err = w.Write([]byte("failed to process request"))
344 | require.NoError(t, err)
345 | return
346 | }
347 |
348 | if tc.giveResponseIsOptional {
349 | w.WriteHeader(http.StatusOK)
350 | return
351 | }
352 |
353 | extraBodyProperties := make(map[string]interface{})
354 | require.NoError(t, json.Unmarshal(bytes, &extraBodyProperties))
355 | delete(extraBodyProperties, "id")
356 |
357 | response := &Response{
358 | Id: request.Id,
359 | ExtraBodyProperties: extraBodyProperties,
360 | QueryParameters: r.URL.Query(),
361 | }
362 | bytes, err = json.Marshal(response)
363 | require.NoError(t, err)
364 |
365 | _, err = w.Write(bytes)
366 | require.NoError(t, err)
367 | },
368 | ),
369 | )
370 | }
371 |
372 | // newTestErrorDecoder returns an error decoder suitable for tests.
373 | func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error {
374 | return func(statusCode int, body io.Reader) error {
375 | raw, err := io.ReadAll(body)
376 | require.NoError(t, err)
377 |
378 | var (
379 | apiError = core.NewAPIError(statusCode, errors.New(string(raw)))
380 | decoder = json.NewDecoder(bytes.NewReader(raw))
381 | )
382 | if statusCode == http.StatusNotFound {
383 | value := new(NotFoundError)
384 | value.APIError = apiError
385 | require.NoError(t, decoder.Decode(value))
386 |
387 | return value
388 | }
389 | return apiError
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/internal/error_decoder.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io"
9 |
10 | "github.com/getzep/zep-go/v2/core"
11 | )
12 |
13 | // ErrorDecoder decodes *http.Response errors and returns a
14 | // typed API error (e.g. *core.APIError).
15 | type ErrorDecoder func(statusCode int, body io.Reader) error
16 |
17 | // ErrorCodes maps HTTP status codes to error constructors.
18 | type ErrorCodes map[int]func(*core.APIError) error
19 |
20 | // NewErrorDecoder returns a new ErrorDecoder backed by the given error codes.
21 | func NewErrorDecoder(errorCodes ErrorCodes) ErrorDecoder {
22 | return func(statusCode int, body io.Reader) error {
23 | raw, err := io.ReadAll(body)
24 | if err != nil {
25 | return fmt.Errorf("failed to read error from response body: %w", err)
26 | }
27 | apiError := core.NewAPIError(
28 | statusCode,
29 | errors.New(string(raw)),
30 | )
31 | newErrorFunc, ok := errorCodes[statusCode]
32 | if !ok {
33 | // This status code isn't recognized, so we return
34 | // the API error as-is.
35 | return apiError
36 | }
37 | customError := newErrorFunc(apiError)
38 | if err := json.NewDecoder(bytes.NewReader(raw)).Decode(customError); err != nil {
39 | // If we fail to decode the error, we return the
40 | // API error as-is.
41 | return apiError
42 | }
43 | return customError
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/internal/error_decoder_test.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "net/http"
7 | "testing"
8 |
9 | "github.com/getzep/zep-go/v2/core"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestErrorDecoder(t *testing.T) {
14 | decoder := NewErrorDecoder(
15 | ErrorCodes{
16 | http.StatusNotFound: func(apiError *core.APIError) error {
17 | return &NotFoundError{APIError: apiError}
18 | },
19 | })
20 |
21 | tests := []struct {
22 | description string
23 | giveStatusCode int
24 | giveBody string
25 | wantError error
26 | }{
27 | {
28 | description: "unrecognized status code",
29 | giveStatusCode: http.StatusInternalServerError,
30 | giveBody: "Internal Server Error",
31 | wantError: core.NewAPIError(http.StatusInternalServerError, errors.New("Internal Server Error")),
32 | },
33 | {
34 | description: "not found with valid JSON",
35 | giveStatusCode: http.StatusNotFound,
36 | giveBody: `{"message": "Resource not found"}`,
37 | wantError: &NotFoundError{
38 | APIError: core.NewAPIError(http.StatusNotFound, errors.New(`{"message": "Resource not found"}`)),
39 | Message: "Resource not found",
40 | },
41 | },
42 | {
43 | description: "not found with invalid JSON",
44 | giveStatusCode: http.StatusNotFound,
45 | giveBody: `Resource not found`,
46 | wantError: core.NewAPIError(http.StatusNotFound, errors.New("Resource not found")),
47 | },
48 | }
49 |
50 | for _, tt := range tests {
51 | t.Run(tt.description, func(t *testing.T) {
52 | assert.Equal(t, tt.wantError, decoder(tt.giveStatusCode, bytes.NewReader([]byte(tt.giveBody))))
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/internal/extra_properties.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "reflect"
8 | "strings"
9 | )
10 |
11 | // MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property.
12 | func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) {
13 | return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value})
14 | }
15 |
16 | // MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties.
17 | func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) {
18 | bytes, err := json.Marshal(marshaler)
19 | if err != nil {
20 | return nil, err
21 | }
22 | if len(extraProperties) == 0 {
23 | return bytes, nil
24 | }
25 | keys, err := getKeys(marshaler)
26 | if err != nil {
27 | return nil, err
28 | }
29 | for _, key := range keys {
30 | if _, ok := extraProperties[key]; ok {
31 | return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key)
32 | }
33 | }
34 | extraBytes, err := json.Marshal(extraProperties)
35 | if err != nil {
36 | return nil, err
37 | }
38 | if isEmptyJSON(bytes) {
39 | if isEmptyJSON(extraBytes) {
40 | return bytes, nil
41 | }
42 | return extraBytes, nil
43 | }
44 | result := bytes[:len(bytes)-1]
45 | result = append(result, ',')
46 | result = append(result, extraBytes[1:len(extraBytes)-1]...)
47 | result = append(result, '}')
48 | return result, nil
49 | }
50 |
51 | // ExtractExtraProperties extracts any extra properties from the given value.
52 | func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) {
53 | val := reflect.ValueOf(value)
54 | for val.Kind() == reflect.Ptr {
55 | if val.IsNil() {
56 | return nil, fmt.Errorf("value must be non-nil to extract extra properties")
57 | }
58 | val = val.Elem()
59 | }
60 | if err := json.Unmarshal(bytes, &value); err != nil {
61 | return nil, err
62 | }
63 | var extraProperties map[string]interface{}
64 | if err := json.Unmarshal(bytes, &extraProperties); err != nil {
65 | return nil, err
66 | }
67 | for i := 0; i < val.Type().NumField(); i++ {
68 | key := jsonKey(val.Type().Field(i))
69 | if key == "" || key == "-" {
70 | continue
71 | }
72 | delete(extraProperties, key)
73 | }
74 | for _, key := range exclude {
75 | delete(extraProperties, key)
76 | }
77 | if len(extraProperties) == 0 {
78 | return nil, nil
79 | }
80 | return extraProperties, nil
81 | }
82 |
83 | // getKeys returns the keys associated with the given value. The value must be a
84 | // a struct or a map with string keys.
85 | func getKeys(value interface{}) ([]string, error) {
86 | val := reflect.ValueOf(value)
87 | if val.Kind() == reflect.Ptr {
88 | val = val.Elem()
89 | }
90 | if !val.IsValid() {
91 | return nil, nil
92 | }
93 | switch val.Kind() {
94 | case reflect.Struct:
95 | return getKeysForStructType(val.Type()), nil
96 | case reflect.Map:
97 | var keys []string
98 | if val.Type().Key().Kind() != reflect.String {
99 | return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value)
100 | }
101 | for _, key := range val.MapKeys() {
102 | keys = append(keys, key.String())
103 | }
104 | return keys, nil
105 | default:
106 | return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value)
107 | }
108 | }
109 |
110 | // getKeysForStructType returns all the keys associated with the given struct type,
111 | // visiting embedded fields recursively.
112 | func getKeysForStructType(structType reflect.Type) []string {
113 | if structType.Kind() == reflect.Pointer {
114 | structType = structType.Elem()
115 | }
116 | if structType.Kind() != reflect.Struct {
117 | return nil
118 | }
119 | var keys []string
120 | for i := 0; i < structType.NumField(); i++ {
121 | field := structType.Field(i)
122 | if field.Anonymous {
123 | keys = append(keys, getKeysForStructType(field.Type)...)
124 | continue
125 | }
126 | keys = append(keys, jsonKey(field))
127 | }
128 | return keys
129 | }
130 |
131 | // jsonKey returns the JSON key from the struct tag of the given field,
132 | // excluding the omitempty flag (if any).
133 | func jsonKey(field reflect.StructField) string {
134 | return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty")
135 | }
136 |
137 | // isEmptyJSON returns true if the given data is empty, the empty JSON object, or
138 | // an explicit null.
139 | func isEmptyJSON(data []byte) bool {
140 | return len(data) <= 2 || bytes.Equal(data, []byte("null"))
141 | }
142 |
--------------------------------------------------------------------------------
/internal/extra_properties_test.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | type testMarshaler struct {
13 | Name string `json:"name"`
14 | BirthDate time.Time `json:"birthDate"`
15 | CreatedAt time.Time `json:"created_at"`
16 | }
17 |
18 | func (t *testMarshaler) MarshalJSON() ([]byte, error) {
19 | type embed testMarshaler
20 | var marshaler = struct {
21 | embed
22 | BirthDate string `json:"birthDate"`
23 | CreatedAt string `json:"created_at"`
24 | }{
25 | embed: embed(*t),
26 | BirthDate: t.BirthDate.Format("2006-01-02"),
27 | CreatedAt: t.CreatedAt.Format(time.RFC3339),
28 | }
29 | return MarshalJSONWithExtraProperty(marshaler, "type", "test")
30 | }
31 |
32 | func TestMarshalJSONWithExtraProperties(t *testing.T) {
33 | tests := []struct {
34 | desc string
35 | giveMarshaler interface{}
36 | giveExtraProperties map[string]interface{}
37 | wantBytes []byte
38 | wantError string
39 | }{
40 | {
41 | desc: "invalid type",
42 | giveMarshaler: []string{"invalid"},
43 | giveExtraProperties: map[string]interface{}{"key": "overwrite"},
44 | wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`,
45 | },
46 | {
47 | desc: "invalid key type",
48 | giveMarshaler: map[int]interface{}{42: "value"},
49 | giveExtraProperties: map[string]interface{}{"key": "overwrite"},
50 | wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`,
51 | },
52 | {
53 | desc: "invalid map overwrite",
54 | giveMarshaler: map[string]interface{}{"key": "value"},
55 | giveExtraProperties: map[string]interface{}{"key": "overwrite"},
56 | wantError: `cannot add extra property "key" because it is already defined on the type`,
57 | },
58 | {
59 | desc: "invalid struct overwrite",
60 | giveMarshaler: new(testMarshaler),
61 | giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"},
62 | wantError: `cannot add extra property "birthDate" because it is already defined on the type`,
63 | },
64 | {
65 | desc: "invalid struct overwrite embedded type",
66 | giveMarshaler: new(testMarshaler),
67 | giveExtraProperties: map[string]interface{}{"name": "bob"},
68 | wantError: `cannot add extra property "name" because it is already defined on the type`,
69 | },
70 | {
71 | desc: "nil",
72 | giveMarshaler: nil,
73 | giveExtraProperties: nil,
74 | wantBytes: []byte(`null`),
75 | },
76 | {
77 | desc: "empty",
78 | giveMarshaler: map[string]interface{}{},
79 | giveExtraProperties: map[string]interface{}{},
80 | wantBytes: []byte(`{}`),
81 | },
82 | {
83 | desc: "no extra properties",
84 | giveMarshaler: map[string]interface{}{"key": "value"},
85 | giveExtraProperties: map[string]interface{}{},
86 | wantBytes: []byte(`{"key":"value"}`),
87 | },
88 | {
89 | desc: "only extra properties",
90 | giveMarshaler: map[string]interface{}{},
91 | giveExtraProperties: map[string]interface{}{"key": "value"},
92 | wantBytes: []byte(`{"key":"value"}`),
93 | },
94 | {
95 | desc: "single extra property",
96 | giveMarshaler: map[string]interface{}{"key": "value"},
97 | giveExtraProperties: map[string]interface{}{"extra": "property"},
98 | wantBytes: []byte(`{"key":"value","extra":"property"}`),
99 | },
100 | {
101 | desc: "multiple extra properties",
102 | giveMarshaler: map[string]interface{}{"key": "value"},
103 | giveExtraProperties: map[string]interface{}{"one": 1, "two": 2},
104 | wantBytes: []byte(`{"key":"value","one":1,"two":2}`),
105 | },
106 | {
107 | desc: "nested properties",
108 | giveMarshaler: map[string]interface{}{"key": "value"},
109 | giveExtraProperties: map[string]interface{}{
110 | "user": map[string]interface{}{
111 | "age": 42,
112 | "name": "alice",
113 | },
114 | },
115 | wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`),
116 | },
117 | {
118 | desc: "multiple nested properties",
119 | giveMarshaler: map[string]interface{}{"key": "value"},
120 | giveExtraProperties: map[string]interface{}{
121 | "metadata": map[string]interface{}{
122 | "ip": "127.0.0.1",
123 | },
124 | "user": map[string]interface{}{
125 | "age": 42,
126 | "name": "alice",
127 | },
128 | },
129 | wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`),
130 | },
131 | {
132 | desc: "custom marshaler",
133 | giveMarshaler: &testMarshaler{
134 | Name: "alice",
135 | BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
136 | CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
137 | },
138 | giveExtraProperties: map[string]interface{}{
139 | "extra": "property",
140 | },
141 | wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`),
142 | },
143 | }
144 | for _, tt := range tests {
145 | t.Run(tt.desc, func(t *testing.T) {
146 | bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties)
147 | if tt.wantError != "" {
148 | require.EqualError(t, err, tt.wantError)
149 | assert.Nil(t, tt.wantBytes)
150 | return
151 | }
152 | require.NoError(t, err)
153 | assert.Equal(t, tt.wantBytes, bytes)
154 |
155 | value := make(map[string]interface{})
156 | require.NoError(t, json.Unmarshal(bytes, &value))
157 | })
158 | }
159 | }
160 |
161 | func TestExtractExtraProperties(t *testing.T) {
162 | t.Run("none", func(t *testing.T) {
163 | type user struct {
164 | Name string `json:"name"`
165 | }
166 | value := &user{
167 | Name: "alice",
168 | }
169 | extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value)
170 | require.NoError(t, err)
171 | assert.Nil(t, extraProperties)
172 | })
173 |
174 | t.Run("non-nil pointer", func(t *testing.T) {
175 | type user struct {
176 | Name string `json:"name"`
177 | }
178 | value := &user{
179 | Name: "alice",
180 | }
181 | extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value)
182 | require.NoError(t, err)
183 | assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties)
184 | })
185 |
186 | t.Run("nil pointer", func(t *testing.T) {
187 | type user struct {
188 | Name string `json:"name"`
189 | }
190 | var value *user
191 | _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value)
192 | assert.EqualError(t, err, "value must be non-nil to extract extra properties")
193 | })
194 |
195 | t.Run("non-zero value", func(t *testing.T) {
196 | type user struct {
197 | Name string `json:"name"`
198 | }
199 | value := user{
200 | Name: "alice",
201 | }
202 | extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value)
203 | require.NoError(t, err)
204 | assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties)
205 | })
206 |
207 | t.Run("zero value", func(t *testing.T) {
208 | type user struct {
209 | Name string `json:"name"`
210 | }
211 | var value user
212 | extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value)
213 | require.NoError(t, err)
214 | assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties)
215 | })
216 |
217 | t.Run("exclude", func(t *testing.T) {
218 | type user struct {
219 | Name string `json:"name"`
220 | }
221 | value := &user{
222 | Name: "alice",
223 | }
224 | extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age")
225 | require.NoError(t, err)
226 | assert.Nil(t, extraProperties)
227 | })
228 | }
229 |
--------------------------------------------------------------------------------
/internal/http.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/url"
7 | )
8 |
9 | // HTTPClient is an interface for a subset of the *http.Client.
10 | type HTTPClient interface {
11 | Do(*http.Request) (*http.Response, error)
12 | }
13 |
14 | // ResolveBaseURL resolves the base URL from the given arguments,
15 | // preferring the first non-empty value.
16 | func ResolveBaseURL(values ...string) string {
17 | for _, value := range values {
18 | if value != "" {
19 | return value
20 | }
21 | }
22 | return ""
23 | }
24 |
25 | // EncodeURL encodes the given arguments into the URL, escaping
26 | // values as needed.
27 | func EncodeURL(urlFormat string, args ...interface{}) string {
28 | escapedArgs := make([]interface{}, 0, len(args))
29 | for _, arg := range args {
30 | escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg)))
31 | }
32 | return fmt.Sprintf(urlFormat, escapedArgs...)
33 | }
34 |
35 | // MergeHeaders merges the given headers together, where the right
36 | // takes precedence over the left.
37 | func MergeHeaders(left, right http.Header) http.Header {
38 | for key, values := range right {
39 | if len(values) > 1 {
40 | left[key] = values
41 | continue
42 | }
43 | if value := right.Get(key); value != "" {
44 | left.Set(key, value)
45 | }
46 | }
47 | return left
48 | }
49 |
--------------------------------------------------------------------------------
/internal/query.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "net/url"
7 | "reflect"
8 | "strings"
9 | "time"
10 |
11 | "github.com/google/uuid"
12 | )
13 |
14 | var (
15 | bytesType = reflect.TypeOf([]byte{})
16 | queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem()
17 | timeType = reflect.TypeOf(time.Time{})
18 | uuidType = reflect.TypeOf(uuid.UUID{})
19 | )
20 |
21 | // QueryEncoder is an interface implemented by any type that wishes to encode
22 | // itself into URL values in a non-standard way.
23 | type QueryEncoder interface {
24 | EncodeQueryValues(key string, v *url.Values) error
25 | }
26 |
27 | // QueryValues encodes url.Values from request objects.
28 | //
29 | // Note: This type is inspired by Google's query encoding library, but
30 | // supports far less customization and is tailored to fit this SDK's use case.
31 | //
32 | // Ref: https://github.com/google/go-querystring
33 | func QueryValues(v interface{}) (url.Values, error) {
34 | values := make(url.Values)
35 | val := reflect.ValueOf(v)
36 | for val.Kind() == reflect.Ptr {
37 | if val.IsNil() {
38 | return values, nil
39 | }
40 | val = val.Elem()
41 | }
42 |
43 | if v == nil {
44 | return values, nil
45 | }
46 |
47 | if val.Kind() != reflect.Struct {
48 | return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
49 | }
50 |
51 | err := reflectValue(values, val, "")
52 | return values, err
53 | }
54 |
55 | // reflectValue populates the values parameter from the struct fields in val.
56 | // Embedded structs are followed recursively (using the rules defined in the
57 | // Values function documentation) breadth-first.
58 | func reflectValue(values url.Values, val reflect.Value, scope string) error {
59 | typ := val.Type()
60 | for i := 0; i < typ.NumField(); i++ {
61 | sf := typ.Field(i)
62 | if sf.PkgPath != "" && !sf.Anonymous {
63 | // Skip unexported fields.
64 | continue
65 | }
66 |
67 | sv := val.Field(i)
68 | tag := sf.Tag.Get("url")
69 | if tag == "" || tag == "-" {
70 | continue
71 | }
72 |
73 | name, opts := parseTag(tag)
74 | if name == "" {
75 | name = sf.Name
76 | }
77 |
78 | if scope != "" {
79 | name = scope + "[" + name + "]"
80 | }
81 |
82 | if opts.Contains("omitempty") && isEmptyValue(sv) {
83 | continue
84 | }
85 |
86 | if sv.Type().Implements(queryEncoderType) {
87 | // If sv is a nil pointer and the custom encoder is defined on a non-pointer
88 | // method receiver, set sv to the zero value of the underlying type
89 | if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) {
90 | sv = reflect.New(sv.Type().Elem())
91 | }
92 |
93 | m := sv.Interface().(QueryEncoder)
94 | if err := m.EncodeQueryValues(name, &values); err != nil {
95 | return err
96 | }
97 | continue
98 | }
99 |
100 | // Recursively dereference pointers, but stop at nil pointers.
101 | for sv.Kind() == reflect.Ptr {
102 | if sv.IsNil() {
103 | break
104 | }
105 | sv = sv.Elem()
106 | }
107 |
108 | if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType {
109 | values.Add(name, valueString(sv, opts, sf))
110 | continue
111 | }
112 |
113 | if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
114 | if sv.Len() == 0 {
115 | // Skip if slice or array is empty.
116 | continue
117 | }
118 | for i := 0; i < sv.Len(); i++ {
119 | value := sv.Index(i)
120 | if isStructPointer(value) && !value.IsNil() {
121 | if err := reflectValue(values, value.Elem(), name); err != nil {
122 | return err
123 | }
124 | } else {
125 | values.Add(name, valueString(value, opts, sf))
126 | }
127 | }
128 | continue
129 | }
130 |
131 | if sv.Kind() == reflect.Struct {
132 | if err := reflectValue(values, sv, name); err != nil {
133 | return err
134 | }
135 | continue
136 | }
137 |
138 | values.Add(name, valueString(sv, opts, sf))
139 | }
140 |
141 | return nil
142 | }
143 |
144 | // valueString returns the string representation of a value.
145 | func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string {
146 | for v.Kind() == reflect.Ptr {
147 | if v.IsNil() {
148 | return ""
149 | }
150 | v = v.Elem()
151 | }
152 |
153 | if v.Type() == timeType {
154 | t := v.Interface().(time.Time)
155 | if format := sf.Tag.Get("format"); format == "date" {
156 | return t.Format("2006-01-02")
157 | }
158 | return t.Format(time.RFC3339)
159 | }
160 |
161 | if v.Type() == uuidType {
162 | u := v.Interface().(uuid.UUID)
163 | return u.String()
164 | }
165 |
166 | if v.Type() == bytesType {
167 | b := v.Interface().([]byte)
168 | return base64.StdEncoding.EncodeToString(b)
169 | }
170 |
171 | return fmt.Sprint(v.Interface())
172 | }
173 |
174 | // isEmptyValue checks if a value should be considered empty for the purposes
175 | // of omitting fields with the "omitempty" option.
176 | func isEmptyValue(v reflect.Value) bool {
177 | type zeroable interface {
178 | IsZero() bool
179 | }
180 |
181 | if !v.IsZero() {
182 | if z, ok := v.Interface().(zeroable); ok {
183 | return z.IsZero()
184 | }
185 | }
186 |
187 | switch v.Kind() {
188 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
189 | return v.Len() == 0
190 | case reflect.Bool:
191 | return !v.Bool()
192 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
193 | return v.Int() == 0
194 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
195 | return v.Uint() == 0
196 | case reflect.Float32, reflect.Float64:
197 | return v.Float() == 0
198 | case reflect.Interface, reflect.Ptr:
199 | return v.IsNil()
200 | case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer:
201 | return false
202 | }
203 |
204 | return false
205 | }
206 |
207 | // isStructPointer returns true if the given reflect.Value is a pointer to a struct.
208 | func isStructPointer(v reflect.Value) bool {
209 | return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct
210 | }
211 |
212 | // tagOptions is the string following a comma in a struct field's "url" tag, or
213 | // the empty string. It does not include the leading comma.
214 | type tagOptions []string
215 |
216 | // parseTag splits a struct field's url tag into its name and comma-separated
217 | // options.
218 | func parseTag(tag string) (string, tagOptions) {
219 | s := strings.Split(tag, ",")
220 | return s[0], s[1:]
221 | }
222 |
223 | // Contains checks whether the tagOptions contains the specified option.
224 | func (o tagOptions) Contains(option string) bool {
225 | for _, s := range o {
226 | if s == option {
227 | return true
228 | }
229 | }
230 | return false
231 | }
232 |
--------------------------------------------------------------------------------
/internal/query_test.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestQueryValues(t *testing.T) {
12 | t.Run("empty optional", func(t *testing.T) {
13 | type nested struct {
14 | Value *string `json:"value,omitempty" url:"value,omitempty"`
15 | }
16 | type example struct {
17 | Nested *nested `json:"nested,omitempty" url:"nested,omitempty"`
18 | }
19 |
20 | values, err := QueryValues(&example{})
21 | require.NoError(t, err)
22 | assert.Empty(t, values)
23 | })
24 |
25 | t.Run("empty required", func(t *testing.T) {
26 | type nested struct {
27 | Value *string `json:"value,omitempty" url:"value,omitempty"`
28 | }
29 | type example struct {
30 | Required string `json:"required" url:"required"`
31 | Nested *nested `json:"nested,omitempty" url:"nested,omitempty"`
32 | }
33 |
34 | values, err := QueryValues(&example{})
35 | require.NoError(t, err)
36 | assert.Equal(t, "required=", values.Encode())
37 | })
38 |
39 | t.Run("allow multiple", func(t *testing.T) {
40 | type example struct {
41 | Values []string `json:"values" url:"values"`
42 | }
43 |
44 | values, err := QueryValues(
45 | &example{
46 | Values: []string{"foo", "bar", "baz"},
47 | },
48 | )
49 | require.NoError(t, err)
50 | assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode())
51 | })
52 |
53 | t.Run("nested object", func(t *testing.T) {
54 | type nested struct {
55 | Value *string `json:"value,omitempty" url:"value,omitempty"`
56 | }
57 | type example struct {
58 | Required string `json:"required" url:"required"`
59 | Nested *nested `json:"nested,omitempty" url:"nested,omitempty"`
60 | }
61 |
62 | nestedValue := "nestedValue"
63 | values, err := QueryValues(
64 | &example{
65 | Required: "requiredValue",
66 | Nested: &nested{
67 | Value: &nestedValue,
68 | },
69 | },
70 | )
71 | require.NoError(t, err)
72 | assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode())
73 | })
74 |
75 | t.Run("url unspecified", func(t *testing.T) {
76 | type example struct {
77 | Required string `json:"required" url:"required"`
78 | NotFound string `json:"notFound"`
79 | }
80 |
81 | values, err := QueryValues(
82 | &example{
83 | Required: "requiredValue",
84 | NotFound: "notFound",
85 | },
86 | )
87 | require.NoError(t, err)
88 | assert.Equal(t, "required=requiredValue", values.Encode())
89 | })
90 |
91 | t.Run("url ignored", func(t *testing.T) {
92 | type example struct {
93 | Required string `json:"required" url:"required"`
94 | NotFound string `json:"notFound" url:"-"`
95 | }
96 |
97 | values, err := QueryValues(
98 | &example{
99 | Required: "requiredValue",
100 | NotFound: "notFound",
101 | },
102 | )
103 | require.NoError(t, err)
104 | assert.Equal(t, "required=requiredValue", values.Encode())
105 | })
106 |
107 | t.Run("datetime", func(t *testing.T) {
108 | type example struct {
109 | DateTime time.Time `json:"dateTime" url:"dateTime"`
110 | }
111 |
112 | values, err := QueryValues(
113 | &example{
114 | DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC),
115 | },
116 | )
117 | require.NoError(t, err)
118 | assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode())
119 | })
120 |
121 | t.Run("date", func(t *testing.T) {
122 | type example struct {
123 | Date time.Time `json:"date" url:"date" format:"date"`
124 | }
125 |
126 | values, err := QueryValues(
127 | &example{
128 | Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC),
129 | },
130 | )
131 | require.NoError(t, err)
132 | assert.Equal(t, "date=1994-03-16", values.Encode())
133 | })
134 |
135 | t.Run("optional time", func(t *testing.T) {
136 | type example struct {
137 | Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"`
138 | }
139 |
140 | values, err := QueryValues(
141 | &example{},
142 | )
143 | require.NoError(t, err)
144 | assert.Empty(t, values.Encode())
145 | })
146 |
147 | t.Run("omitempty with non-pointer zero value", func(t *testing.T) {
148 | type enum string
149 |
150 | type example struct {
151 | Enum enum `json:"enum,omitempty" url:"enum,omitempty"`
152 | }
153 |
154 | values, err := QueryValues(
155 | &example{},
156 | )
157 | require.NoError(t, err)
158 | assert.Empty(t, values.Encode())
159 | })
160 |
161 | t.Run("object array", func(t *testing.T) {
162 | type object struct {
163 | Key string `json:"key" url:"key"`
164 | Value string `json:"value" url:"value"`
165 | }
166 | type example struct {
167 | Objects []*object `json:"objects,omitempty" url:"objects,omitempty"`
168 | }
169 |
170 | values, err := QueryValues(
171 | &example{
172 | Objects: []*object{
173 | {
174 | Key: "hello",
175 | Value: "world",
176 | },
177 | {
178 | Key: "foo",
179 | Value: "bar",
180 | },
181 | },
182 | },
183 | )
184 | require.NoError(t, err)
185 | assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode())
186 | })
187 | }
188 |
--------------------------------------------------------------------------------
/internal/retrier.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "crypto/rand"
5 | "math/big"
6 | "net/http"
7 | "time"
8 | )
9 |
10 | const (
11 | defaultRetryAttempts = 2
12 | minRetryDelay = 500 * time.Millisecond
13 | maxRetryDelay = 5000 * time.Millisecond
14 | )
15 |
16 | // RetryOption adapts the behavior the *Retrier.
17 | type RetryOption func(*retryOptions)
18 |
19 | // RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do).
20 | type RetryFunc func(*http.Request) (*http.Response, error)
21 |
22 | // WithMaxAttempts configures the maximum number of attempts
23 | // of the *Retrier.
24 | func WithMaxAttempts(attempts uint) RetryOption {
25 | return func(opts *retryOptions) {
26 | opts.attempts = attempts
27 | }
28 | }
29 |
30 | // Retrier retries failed requests a configurable number of times with an
31 | // exponential back-off between each retry.
32 | type Retrier struct {
33 | attempts uint
34 | }
35 |
36 | // NewRetrier constructs a new *Retrier with the given options, if any.
37 | func NewRetrier(opts ...RetryOption) *Retrier {
38 | options := new(retryOptions)
39 | for _, opt := range opts {
40 | opt(options)
41 | }
42 | attempts := uint(defaultRetryAttempts)
43 | if options.attempts > 0 {
44 | attempts = options.attempts
45 | }
46 | return &Retrier{
47 | attempts: attempts,
48 | }
49 | }
50 |
51 | // Run issues the request and, upon failure, retries the request if possible.
52 | //
53 | // The request will be retried as long as the request is deemed retriable and the
54 | // number of retry attempts has not grown larger than the configured retry limit.
55 | func (r *Retrier) Run(
56 | fn RetryFunc,
57 | request *http.Request,
58 | errorDecoder ErrorDecoder,
59 | opts ...RetryOption,
60 | ) (*http.Response, error) {
61 | options := new(retryOptions)
62 | for _, opt := range opts {
63 | opt(options)
64 | }
65 | maxRetryAttempts := r.attempts
66 | if options.attempts > 0 {
67 | maxRetryAttempts = options.attempts
68 | }
69 | var (
70 | retryAttempt uint
71 | previousError error
72 | )
73 | return r.run(
74 | fn,
75 | request,
76 | errorDecoder,
77 | maxRetryAttempts,
78 | retryAttempt,
79 | previousError,
80 | )
81 | }
82 |
83 | func (r *Retrier) run(
84 | fn RetryFunc,
85 | request *http.Request,
86 | errorDecoder ErrorDecoder,
87 | maxRetryAttempts uint,
88 | retryAttempt uint,
89 | previousError error,
90 | ) (*http.Response, error) {
91 | if retryAttempt >= maxRetryAttempts {
92 | return nil, previousError
93 | }
94 |
95 | // If the call has been cancelled, don't issue the request.
96 | if err := request.Context().Err(); err != nil {
97 | return nil, err
98 | }
99 |
100 | response, err := fn(request)
101 | if err != nil {
102 | return nil, err
103 | }
104 |
105 | if r.shouldRetry(response) {
106 | defer response.Body.Close()
107 |
108 | delay, err := r.retryDelay(retryAttempt)
109 | if err != nil {
110 | return nil, err
111 | }
112 |
113 | time.Sleep(delay)
114 |
115 | return r.run(
116 | fn,
117 | request,
118 | errorDecoder,
119 | maxRetryAttempts,
120 | retryAttempt+1,
121 | decodeError(response, errorDecoder),
122 | )
123 | }
124 |
125 | return response, nil
126 | }
127 |
128 | // shouldRetry returns true if the request should be retried based on the given
129 | // response status code.
130 | func (r *Retrier) shouldRetry(response *http.Response) bool {
131 | return response.StatusCode == http.StatusTooManyRequests ||
132 | response.StatusCode == http.StatusRequestTimeout ||
133 | response.StatusCode >= http.StatusInternalServerError
134 | }
135 |
136 | // retryDelay calculates the delay time in milliseconds based on the retry attempt.
137 | func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) {
138 | // Apply exponential backoff.
139 | delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt)
140 |
141 | // Do not allow the number to exceed maxRetryDelay.
142 | if delay > maxRetryDelay {
143 | delay = maxRetryDelay
144 | }
145 |
146 | // Apply some itter by randomizing the value in the range of 75%-100%.
147 | max := big.NewInt(int64(delay / 4))
148 | jitter, err := rand.Int(rand.Reader, max)
149 | if err != nil {
150 | return 0, err
151 | }
152 |
153 | delay -= time.Duration(jitter.Int64())
154 |
155 | // Never sleep less than the base sleep seconds.
156 | if delay < minRetryDelay {
157 | delay = minRetryDelay
158 | }
159 |
160 | return delay, nil
161 | }
162 |
163 | type retryOptions struct {
164 | attempts uint
165 | }
166 |
--------------------------------------------------------------------------------
/internal/retrier_test.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "io"
7 | "net/http"
8 | "net/http/httptest"
9 | "testing"
10 | "time"
11 |
12 | "github.com/getzep/zep-go/v2/core"
13 | "github.com/stretchr/testify/assert"
14 | "github.com/stretchr/testify/require"
15 | )
16 |
17 | type RetryTestCase struct {
18 | description string
19 |
20 | giveAttempts uint
21 | giveStatusCodes []int
22 | giveResponse *Response
23 |
24 | wantResponse *Response
25 | wantError *core.APIError
26 | }
27 |
28 | func TestRetrier(t *testing.T) {
29 | tests := []*RetryTestCase{
30 | {
31 | description: "retry request succeeds after multiple failures",
32 | giveAttempts: 3,
33 | giveStatusCodes: []int{
34 | http.StatusServiceUnavailable,
35 | http.StatusServiceUnavailable,
36 | http.StatusOK,
37 | },
38 | giveResponse: &Response{
39 | Id: "1",
40 | },
41 | wantResponse: &Response{
42 | Id: "1",
43 | },
44 | },
45 | {
46 | description: "retry request fails if MaxAttempts is exceeded",
47 | giveAttempts: 3,
48 | giveStatusCodes: []int{
49 | http.StatusRequestTimeout,
50 | http.StatusRequestTimeout,
51 | http.StatusRequestTimeout,
52 | http.StatusOK,
53 | },
54 | wantError: &core.APIError{
55 | StatusCode: http.StatusRequestTimeout,
56 | },
57 | },
58 | {
59 | description: "retry durations increase exponentially and stay within the min and max delay values",
60 | giveAttempts: 4,
61 | giveStatusCodes: []int{
62 | http.StatusServiceUnavailable,
63 | http.StatusServiceUnavailable,
64 | http.StatusServiceUnavailable,
65 | http.StatusOK,
66 | },
67 | },
68 | {
69 | description: "retry does not occur on status code 404",
70 | giveAttempts: 2,
71 | giveStatusCodes: []int{http.StatusNotFound, http.StatusOK},
72 | wantError: &core.APIError{
73 | StatusCode: http.StatusNotFound,
74 | },
75 | },
76 | {
77 | description: "retries occur on status code 429",
78 | giveAttempts: 2,
79 | giveStatusCodes: []int{http.StatusTooManyRequests, http.StatusOK},
80 | },
81 | {
82 | description: "retries occur on status code 408",
83 | giveAttempts: 2,
84 | giveStatusCodes: []int{http.StatusRequestTimeout, http.StatusOK},
85 | },
86 | {
87 | description: "retries occur on status code 500",
88 | giveAttempts: 2,
89 | giveStatusCodes: []int{http.StatusInternalServerError, http.StatusOK},
90 | },
91 | }
92 |
93 | for _, tc := range tests {
94 | t.Run(tc.description, func(t *testing.T) {
95 | var (
96 | test = tc
97 | server = newTestRetryServer(t, test)
98 | client = server.Client()
99 | )
100 |
101 | t.Parallel()
102 |
103 | caller := NewCaller(
104 | &CallerParams{
105 | Client: client,
106 | },
107 | )
108 |
109 | var response *Response
110 | err := caller.Call(
111 | context.Background(),
112 | &CallParams{
113 | URL: server.URL,
114 | Method: http.MethodGet,
115 | Request: &Request{},
116 | Response: &response,
117 | MaxAttempts: test.giveAttempts,
118 | ResponseIsOptional: true,
119 | },
120 | )
121 |
122 | if test.wantError != nil {
123 | require.IsType(t, err, &core.APIError{})
124 | expectedErrorCode := test.wantError.StatusCode
125 | actualErrorCode := err.(*core.APIError).StatusCode
126 | assert.Equal(t, expectedErrorCode, actualErrorCode)
127 | return
128 | }
129 |
130 | require.NoError(t, err)
131 | assert.Equal(t, test.wantResponse, response)
132 | })
133 | }
134 | }
135 |
136 | // newTestRetryServer returns a new *httptest.Server configured with the
137 | // given test parameters, suitable for testing retries.
138 | func newTestRetryServer(t *testing.T, tc *RetryTestCase) *httptest.Server {
139 | var index int
140 | timestamps := make([]time.Time, 0, len(tc.giveStatusCodes))
141 |
142 | return httptest.NewServer(
143 | http.HandlerFunc(
144 | func(w http.ResponseWriter, r *http.Request) {
145 | timestamps = append(timestamps, time.Now())
146 | if index > 0 && index < len(expectedRetryDurations) {
147 | // Ensure that the duration between retries increases exponentially,
148 | // and that it is within the minimum and maximum retry delay values.
149 | actualDuration := timestamps[index].Sub(timestamps[index-1])
150 | expectedDurationMin := expectedRetryDurations[index-1] * 75 / 100
151 | expectedDurationMax := expectedRetryDurations[index-1] * 125 / 100
152 | assert.True(
153 | t,
154 | actualDuration >= expectedDurationMin && actualDuration <= expectedDurationMax,
155 | "expected duration to be in range [%v, %v], got %v",
156 | expectedDurationMin,
157 | expectedDurationMax,
158 | actualDuration,
159 | )
160 | assert.LessOrEqual(
161 | t,
162 | actualDuration,
163 | maxRetryDelay,
164 | "expected duration to be less than the maxRetryDelay (%v), got %v",
165 | maxRetryDelay,
166 | actualDuration,
167 | )
168 | assert.GreaterOrEqual(
169 | t,
170 | actualDuration,
171 | minRetryDelay,
172 | "expected duration to be greater than the minRetryDelay (%v), got %v",
173 | minRetryDelay,
174 | actualDuration,
175 | )
176 | }
177 |
178 | request := new(Request)
179 | bytes, err := io.ReadAll(r.Body)
180 | require.NoError(t, err)
181 | require.NoError(t, json.Unmarshal(bytes, request))
182 | require.LessOrEqual(t, index, len(tc.giveStatusCodes))
183 |
184 | statusCode := tc.giveStatusCodes[index]
185 | w.WriteHeader(statusCode)
186 |
187 | if tc.giveResponse != nil && statusCode == http.StatusOK {
188 | bytes, err = json.Marshal(tc.giveResponse)
189 | require.NoError(t, err)
190 | _, err = w.Write(bytes)
191 | require.NoError(t, err)
192 | }
193 |
194 | index++
195 | },
196 | ),
197 | )
198 | }
199 |
200 | // expectedRetryDurations holds an array of calculated retry durations,
201 | // where the index of the array should correspond to the retry attempt.
202 | //
203 | // Values are calculated based off of `minRetryDelay + minRetryDelay*i*i`, with
204 | // a max and min value of 5000ms and 500ms respectively.
205 | var expectedRetryDurations = []time.Duration{
206 | 500 * time.Millisecond,
207 | 1000 * time.Millisecond,
208 | 2500 * time.Millisecond,
209 | 5000 * time.Millisecond,
210 | 5000 * time.Millisecond,
211 | }
212 |
--------------------------------------------------------------------------------
/internal/stringer.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import "encoding/json"
4 |
5 | // StringifyJSON returns a pretty JSON string representation of
6 | // the given value.
7 | func StringifyJSON(value interface{}) (string, error) {
8 | bytes, err := json.MarshalIndent(value, "", " ")
9 | if err != nil {
10 | return "", err
11 | }
12 | return string(bytes), nil
13 | }
14 |
--------------------------------------------------------------------------------
/internal/time.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "encoding/json"
5 | "time"
6 | )
7 |
8 | const dateFormat = "2006-01-02"
9 |
10 | // DateTime wraps time.Time and adapts its JSON representation
11 | // to conform to a RFC3339 date (e.g. 2006-01-02).
12 | //
13 | // Ref: https://ijmacd.github.io/rfc3339-iso8601
14 | type Date struct {
15 | t *time.Time
16 | }
17 |
18 | // NewDate returns a new *Date. If the given time.Time
19 | // is nil, nil will be returned.
20 | func NewDate(t time.Time) *Date {
21 | return &Date{t: &t}
22 | }
23 |
24 | // NewOptionalDate returns a new *Date. If the given time.Time
25 | // is nil, nil will be returned.
26 | func NewOptionalDate(t *time.Time) *Date {
27 | if t == nil {
28 | return nil
29 | }
30 | return &Date{t: t}
31 | }
32 |
33 | // Time returns the Date's underlying time, if any. If the
34 | // date is nil, the zero value is returned.
35 | func (d *Date) Time() time.Time {
36 | if d == nil || d.t == nil {
37 | return time.Time{}
38 | }
39 | return *d.t
40 | }
41 |
42 | // TimePtr returns a pointer to the Date's underlying time.Time, if any.
43 | func (d *Date) TimePtr() *time.Time {
44 | if d == nil || d.t == nil {
45 | return nil
46 | }
47 | if d.t.IsZero() {
48 | return nil
49 | }
50 | return d.t
51 | }
52 |
53 | func (d *Date) MarshalJSON() ([]byte, error) {
54 | if d == nil || d.t == nil {
55 | return nil, nil
56 | }
57 | return json.Marshal(d.t.Format(dateFormat))
58 | }
59 |
60 | func (d *Date) UnmarshalJSON(data []byte) error {
61 | var raw string
62 | if err := json.Unmarshal(data, &raw); err != nil {
63 | return err
64 | }
65 |
66 | parsedTime, err := time.Parse(dateFormat, raw)
67 | if err != nil {
68 | return err
69 | }
70 |
71 | *d = Date{t: &parsedTime}
72 | return nil
73 | }
74 |
75 | // DateTime wraps time.Time and adapts its JSON representation
76 | // to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z).
77 | //
78 | // Ref: https://ijmacd.github.io/rfc3339-iso8601
79 | type DateTime struct {
80 | t *time.Time
81 | }
82 |
83 | // NewDateTime returns a new *DateTime.
84 | func NewDateTime(t time.Time) *DateTime {
85 | return &DateTime{t: &t}
86 | }
87 |
88 | // NewOptionalDateTime returns a new *DateTime. If the given time.Time
89 | // is nil, nil will be returned.
90 | func NewOptionalDateTime(t *time.Time) *DateTime {
91 | if t == nil {
92 | return nil
93 | }
94 | return &DateTime{t: t}
95 | }
96 |
97 | // Time returns the DateTime's underlying time, if any. If the
98 | // date-time is nil, the zero value is returned.
99 | func (d *DateTime) Time() time.Time {
100 | if d == nil || d.t == nil {
101 | return time.Time{}
102 | }
103 | return *d.t
104 | }
105 |
106 | // TimePtr returns a pointer to the DateTime's underlying time.Time, if any.
107 | func (d *DateTime) TimePtr() *time.Time {
108 | if d == nil || d.t == nil {
109 | return nil
110 | }
111 | if d.t.IsZero() {
112 | return nil
113 | }
114 | return d.t
115 | }
116 |
117 | func (d *DateTime) MarshalJSON() ([]byte, error) {
118 | if d == nil || d.t == nil {
119 | return nil, nil
120 | }
121 | return json.Marshal(d.t.Format(time.RFC3339))
122 | }
123 |
124 | func (d *DateTime) UnmarshalJSON(data []byte) error {
125 | var raw string
126 | if err := json.Unmarshal(data, &raw); err != nil {
127 | return err
128 | }
129 |
130 | parsedTime, err := time.Parse(time.RFC3339, raw)
131 | if err != nil {
132 | return err
133 | }
134 |
135 | *d = DateTime{t: &parsedTime}
136 | return nil
137 | }
138 |
--------------------------------------------------------------------------------
/option/request_option.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package option
4 |
5 | import (
6 | core "github.com/getzep/zep-go/v2/core"
7 | http "net/http"
8 | url "net/url"
9 | )
10 |
11 | // RequestOption adapts the behavior of an indivdual request.
12 | type RequestOption = core.RequestOption
13 |
14 | // WithBaseURL sets the base URL, overriding the default
15 | // environment, if any.
16 | func WithBaseURL(baseURL string) *core.BaseURLOption {
17 | return &core.BaseURLOption{
18 | BaseURL: baseURL,
19 | }
20 | }
21 |
22 | // WithHTTPClient uses the given HTTPClient to issue the request.
23 | func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption {
24 | return &core.HTTPClientOption{
25 | HTTPClient: httpClient,
26 | }
27 | }
28 |
29 | // WithHTTPHeader adds the given http.Header to the request.
30 | func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption {
31 | return &core.HTTPHeaderOption{
32 | // Clone the headers so they can't be modified after the option call.
33 | HTTPHeader: httpHeader.Clone(),
34 | }
35 | }
36 |
37 | // WithBodyProperties adds the given body properties to the request.
38 | func WithBodyProperties(bodyProperties map[string]interface{}) *core.BodyPropertiesOption {
39 | copiedBodyProperties := make(map[string]interface{}, len(bodyProperties))
40 | for key, value := range bodyProperties {
41 | copiedBodyProperties[key] = value
42 | }
43 | return &core.BodyPropertiesOption{
44 | BodyProperties: copiedBodyProperties,
45 | }
46 | }
47 |
48 | // WithQueryParameters adds the given query parameters to the request.
49 | func WithQueryParameters(queryParameters url.Values) *core.QueryParametersOption {
50 | copiedQueryParameters := make(url.Values, len(queryParameters))
51 | for key, values := range queryParameters {
52 | copiedQueryParameters[key] = values
53 | }
54 | return &core.QueryParametersOption{
55 | QueryParameters: copiedQueryParameters,
56 | }
57 | }
58 |
59 | // WithMaxAttempts configures the maximum number of retry attempts.
60 | func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption {
61 | return &core.MaxAttemptsOption{
62 | MaxAttempts: attempts,
63 | }
64 | }
65 |
66 | // WithAPIKey sets the apiKey auth request header.
67 | func WithAPIKey(apiKey string) *core.APIKeyOption {
68 | return &core.APIKeyOption{
69 | APIKey: apiKey,
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/pointer.go:
--------------------------------------------------------------------------------
1 | package zep
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | // Bool returns a pointer to the given bool value.
10 | func Bool(b bool) *bool {
11 | return &b
12 | }
13 |
14 | // Byte returns a pointer to the given byte value.
15 | func Byte(b byte) *byte {
16 | return &b
17 | }
18 |
19 | // Complex64 returns a pointer to the given complex64 value.
20 | func Complex64(c complex64) *complex64 {
21 | return &c
22 | }
23 |
24 | // Complex128 returns a pointer to the given complex128 value.
25 | func Complex128(c complex128) *complex128 {
26 | return &c
27 | }
28 |
29 | // Float32 returns a pointer to the given float32 value.
30 | func Float32(f float32) *float32 {
31 | return &f
32 | }
33 |
34 | // Float64 returns a pointer to the given float64 value.
35 | func Float64(f float64) *float64 {
36 | return &f
37 | }
38 |
39 | // Int returns a pointer to the given int value.
40 | func Int(i int) *int {
41 | return &i
42 | }
43 |
44 | // Int8 returns a pointer to the given int8 value.
45 | func Int8(i int8) *int8 {
46 | return &i
47 | }
48 |
49 | // Int16 returns a pointer to the given int16 value.
50 | func Int16(i int16) *int16 {
51 | return &i
52 | }
53 |
54 | // Int32 returns a pointer to the given int32 value.
55 | func Int32(i int32) *int32 {
56 | return &i
57 | }
58 |
59 | // Int64 returns a pointer to the given int64 value.
60 | func Int64(i int64) *int64 {
61 | return &i
62 | }
63 |
64 | // Rune returns a pointer to the given rune value.
65 | func Rune(r rune) *rune {
66 | return &r
67 | }
68 |
69 | // String returns a pointer to the given string value.
70 | func String(s string) *string {
71 | return &s
72 | }
73 |
74 | // Uint returns a pointer to the given uint value.
75 | func Uint(u uint) *uint {
76 | return &u
77 | }
78 |
79 | // Uint8 returns a pointer to the given uint8 value.
80 | func Uint8(u uint8) *uint8 {
81 | return &u
82 | }
83 |
84 | // Uint16 returns a pointer to the given uint16 value.
85 | func Uint16(u uint16) *uint16 {
86 | return &u
87 | }
88 |
89 | // Uint32 returns a pointer to the given uint32 value.
90 | func Uint32(u uint32) *uint32 {
91 | return &u
92 | }
93 |
94 | // Uint64 returns a pointer to the given uint64 value.
95 | func Uint64(u uint64) *uint64 {
96 | return &u
97 | }
98 |
99 | // Uintptr returns a pointer to the given uintptr value.
100 | func Uintptr(u uintptr) *uintptr {
101 | return &u
102 | }
103 |
104 | // UUID returns a pointer to the given uuid.UUID value.
105 | func UUID(u uuid.UUID) *uuid.UUID {
106 | return &u
107 | }
108 |
109 | // Time returns a pointer to the given time.Time value.
110 | func Time(t time.Time) *time.Time {
111 | return &t
112 | }
113 |
114 | // MustParseDate attempts to parse the given string as a
115 | // date time.Time, and panics upon failure.
116 | func MustParseDate(date string) time.Time {
117 | t, err := time.Parse("2006-01-02", date)
118 | if err != nil {
119 | panic(err)
120 | }
121 | return t
122 | }
123 |
124 | // MustParseDateTime attempts to parse the given string as a
125 | // datetime time.Time, and panics upon failure.
126 | func MustParseDateTime(datetime string) time.Time {
127 | t, err := time.Parse(time.RFC3339, datetime)
128 | if err != nil {
129 | panic(err)
130 | }
131 | return t
132 | }
133 |
--------------------------------------------------------------------------------
/user.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package zep
4 |
5 | import (
6 | json "encoding/json"
7 | fmt "fmt"
8 | internal "github.com/getzep/zep-go/v2/internal"
9 | )
10 |
11 | type CreateUserRequest struct {
12 | // The email address of the user.
13 | Email *string `json:"email,omitempty" url:"-"`
14 | // Optional instruction to use for fact rating.
15 | FactRatingInstruction *FactRatingInstruction `json:"fact_rating_instruction,omitempty" url:"-"`
16 | // The first name of the user.
17 | FirstName *string `json:"first_name,omitempty" url:"-"`
18 | // The last name of the user.
19 | LastName *string `json:"last_name,omitempty" url:"-"`
20 | // The metadata associated with the user.
21 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"`
22 | // The unique identifier of the user.
23 | UserID string `json:"user_id" url:"-"`
24 | }
25 |
26 | type UserListOrderedRequest struct {
27 | // Page number for pagination, starting from 1
28 | PageNumber *int `json:"-" url:"pageNumber,omitempty"`
29 | // Number of users to retrieve per page
30 | PageSize *int `json:"-" url:"pageSize,omitempty"`
31 | }
32 |
33 | type User struct {
34 | CreatedAt *string `json:"created_at,omitempty" url:"created_at,omitempty"`
35 | DeletedAt *string `json:"deleted_at,omitempty" url:"deleted_at,omitempty"`
36 | Email *string `json:"email,omitempty" url:"email,omitempty"`
37 | FactRatingInstruction *FactRatingInstruction `json:"fact_rating_instruction,omitempty" url:"fact_rating_instruction,omitempty"`
38 | FirstName *string `json:"first_name,omitempty" url:"first_name,omitempty"`
39 | ID *int `json:"id,omitempty" url:"id,omitempty"`
40 | LastName *string `json:"last_name,omitempty" url:"last_name,omitempty"`
41 | // Deprecated
42 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"`
43 | ProjectUUID *string `json:"project_uuid,omitempty" url:"project_uuid,omitempty"`
44 | // Deprecated
45 | SessionCount *int `json:"session_count,omitempty" url:"session_count,omitempty"`
46 | // Deprecated
47 | UpdatedAt *string `json:"updated_at,omitempty" url:"updated_at,omitempty"`
48 | UserID *string `json:"user_id,omitempty" url:"user_id,omitempty"`
49 | UUID *string `json:"uuid,omitempty" url:"uuid,omitempty"`
50 |
51 | extraProperties map[string]interface{}
52 | rawJSON json.RawMessage
53 | }
54 |
55 | func (u *User) GetCreatedAt() *string {
56 | if u == nil {
57 | return nil
58 | }
59 | return u.CreatedAt
60 | }
61 |
62 | func (u *User) GetDeletedAt() *string {
63 | if u == nil {
64 | return nil
65 | }
66 | return u.DeletedAt
67 | }
68 |
69 | func (u *User) GetEmail() *string {
70 | if u == nil {
71 | return nil
72 | }
73 | return u.Email
74 | }
75 |
76 | func (u *User) GetFactRatingInstruction() *FactRatingInstruction {
77 | if u == nil {
78 | return nil
79 | }
80 | return u.FactRatingInstruction
81 | }
82 |
83 | func (u *User) GetFirstName() *string {
84 | if u == nil {
85 | return nil
86 | }
87 | return u.FirstName
88 | }
89 |
90 | func (u *User) GetID() *int {
91 | if u == nil {
92 | return nil
93 | }
94 | return u.ID
95 | }
96 |
97 | func (u *User) GetLastName() *string {
98 | if u == nil {
99 | return nil
100 | }
101 | return u.LastName
102 | }
103 |
104 | func (u *User) GetMetadata() map[string]interface{} {
105 | if u == nil {
106 | return nil
107 | }
108 | return u.Metadata
109 | }
110 |
111 | func (u *User) GetProjectUUID() *string {
112 | if u == nil {
113 | return nil
114 | }
115 | return u.ProjectUUID
116 | }
117 |
118 | func (u *User) GetSessionCount() *int {
119 | if u == nil {
120 | return nil
121 | }
122 | return u.SessionCount
123 | }
124 |
125 | func (u *User) GetUpdatedAt() *string {
126 | if u == nil {
127 | return nil
128 | }
129 | return u.UpdatedAt
130 | }
131 |
132 | func (u *User) GetUserID() *string {
133 | if u == nil {
134 | return nil
135 | }
136 | return u.UserID
137 | }
138 |
139 | func (u *User) GetUUID() *string {
140 | if u == nil {
141 | return nil
142 | }
143 | return u.UUID
144 | }
145 |
146 | func (u *User) GetExtraProperties() map[string]interface{} {
147 | return u.extraProperties
148 | }
149 |
150 | func (u *User) UnmarshalJSON(data []byte) error {
151 | type unmarshaler User
152 | var value unmarshaler
153 | if err := json.Unmarshal(data, &value); err != nil {
154 | return err
155 | }
156 | *u = User(value)
157 | extraProperties, err := internal.ExtractExtraProperties(data, *u)
158 | if err != nil {
159 | return err
160 | }
161 | u.extraProperties = extraProperties
162 | u.rawJSON = json.RawMessage(data)
163 | return nil
164 | }
165 |
166 | func (u *User) String() string {
167 | if len(u.rawJSON) > 0 {
168 | if value, err := internal.StringifyJSON(u.rawJSON); err == nil {
169 | return value
170 | }
171 | }
172 | if value, err := internal.StringifyJSON(u); err == nil {
173 | return value
174 | }
175 | return fmt.Sprintf("%#v", u)
176 | }
177 |
178 | type UserListResponse struct {
179 | RowCount *int `json:"row_count,omitempty" url:"row_count,omitempty"`
180 | TotalCount *int `json:"total_count,omitempty" url:"total_count,omitempty"`
181 | Users []*User `json:"users,omitempty" url:"users,omitempty"`
182 |
183 | extraProperties map[string]interface{}
184 | rawJSON json.RawMessage
185 | }
186 |
187 | func (u *UserListResponse) GetRowCount() *int {
188 | if u == nil {
189 | return nil
190 | }
191 | return u.RowCount
192 | }
193 |
194 | func (u *UserListResponse) GetTotalCount() *int {
195 | if u == nil {
196 | return nil
197 | }
198 | return u.TotalCount
199 | }
200 |
201 | func (u *UserListResponse) GetUsers() []*User {
202 | if u == nil {
203 | return nil
204 | }
205 | return u.Users
206 | }
207 |
208 | func (u *UserListResponse) GetExtraProperties() map[string]interface{} {
209 | return u.extraProperties
210 | }
211 |
212 | func (u *UserListResponse) UnmarshalJSON(data []byte) error {
213 | type unmarshaler UserListResponse
214 | var value unmarshaler
215 | if err := json.Unmarshal(data, &value); err != nil {
216 | return err
217 | }
218 | *u = UserListResponse(value)
219 | extraProperties, err := internal.ExtractExtraProperties(data, *u)
220 | if err != nil {
221 | return err
222 | }
223 | u.extraProperties = extraProperties
224 | u.rawJSON = json.RawMessage(data)
225 | return nil
226 | }
227 |
228 | func (u *UserListResponse) String() string {
229 | if len(u.rawJSON) > 0 {
230 | if value, err := internal.StringifyJSON(u.rawJSON); err == nil {
231 | return value
232 | }
233 | }
234 | if value, err := internal.StringifyJSON(u); err == nil {
235 | return value
236 | }
237 | return fmt.Sprintf("%#v", u)
238 | }
239 |
240 | type UserNodeResponse struct {
241 | Node *EntityNode `json:"node,omitempty" url:"node,omitempty"`
242 |
243 | extraProperties map[string]interface{}
244 | rawJSON json.RawMessage
245 | }
246 |
247 | func (u *UserNodeResponse) GetNode() *EntityNode {
248 | if u == nil {
249 | return nil
250 | }
251 | return u.Node
252 | }
253 |
254 | func (u *UserNodeResponse) GetExtraProperties() map[string]interface{} {
255 | return u.extraProperties
256 | }
257 |
258 | func (u *UserNodeResponse) UnmarshalJSON(data []byte) error {
259 | type unmarshaler UserNodeResponse
260 | var value unmarshaler
261 | if err := json.Unmarshal(data, &value); err != nil {
262 | return err
263 | }
264 | *u = UserNodeResponse(value)
265 | extraProperties, err := internal.ExtractExtraProperties(data, *u)
266 | if err != nil {
267 | return err
268 | }
269 | u.extraProperties = extraProperties
270 | u.rawJSON = json.RawMessage(data)
271 | return nil
272 | }
273 |
274 | func (u *UserNodeResponse) String() string {
275 | if len(u.rawJSON) > 0 {
276 | if value, err := internal.StringifyJSON(u.rawJSON); err == nil {
277 | return value
278 | }
279 | }
280 | if value, err := internal.StringifyJSON(u); err == nil {
281 | return value
282 | }
283 | return fmt.Sprintf("%#v", u)
284 | }
285 |
286 | type UpdateUserRequest struct {
287 | // The email address of the user.
288 | Email *string `json:"email,omitempty" url:"-"`
289 | // Optional instruction to use for fact rating.
290 | FactRatingInstruction *FactRatingInstruction `json:"fact_rating_instruction,omitempty" url:"-"`
291 | // The first name of the user.
292 | FirstName *string `json:"first_name,omitempty" url:"-"`
293 | // The last name of the user.
294 | LastName *string `json:"last_name,omitempty" url:"-"`
295 | // The metadata to update
296 | Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"`
297 | }
298 |
--------------------------------------------------------------------------------
/user/client.go:
--------------------------------------------------------------------------------
1 | // This file was auto-generated by Fern from our API Definition.
2 |
3 | package user
4 |
5 | import (
6 | context "context"
7 | v2 "github.com/getzep/zep-go/v2"
8 | core "github.com/getzep/zep-go/v2/core"
9 | internal "github.com/getzep/zep-go/v2/internal"
10 | option "github.com/getzep/zep-go/v2/option"
11 | http "net/http"
12 | os "os"
13 | )
14 |
15 | type Client struct {
16 | baseURL string
17 | caller *internal.Caller
18 | header http.Header
19 | }
20 |
21 | func NewClient(opts ...option.RequestOption) *Client {
22 | options := core.NewRequestOptions(opts...)
23 | if options.APIKey == "" {
24 | options.APIKey = os.Getenv("ZEP_API_KEY")
25 | }
26 | return &Client{
27 | baseURL: options.BaseURL,
28 | caller: internal.NewCaller(
29 | &internal.CallerParams{
30 | Client: options.HTTPClient,
31 | MaxAttempts: options.MaxAttempts,
32 | },
33 | ),
34 | header: options.ToHeader(),
35 | }
36 | }
37 |
38 | // Adds a user.
39 | func (c *Client) Add(
40 | ctx context.Context,
41 | request *v2.CreateUserRequest,
42 | opts ...option.RequestOption,
43 | ) (*v2.User, error) {
44 | options := core.NewRequestOptions(opts...)
45 | baseURL := internal.ResolveBaseURL(
46 | options.BaseURL,
47 | c.baseURL,
48 | "https://api.getzep.com/api/v2",
49 | )
50 | endpointURL := baseURL + "/users"
51 | headers := internal.MergeHeaders(
52 | c.header.Clone(),
53 | options.ToHeader(),
54 | )
55 | headers.Set("Content-Type", "application/json")
56 | errorCodes := internal.ErrorCodes{
57 | 400: func(apiError *core.APIError) error {
58 | return &v2.BadRequestError{
59 | APIError: apiError,
60 | }
61 | },
62 | 500: func(apiError *core.APIError) error {
63 | return &v2.InternalServerError{
64 | APIError: apiError,
65 | }
66 | },
67 | }
68 |
69 | var response *v2.User
70 | if err := c.caller.Call(
71 | ctx,
72 | &internal.CallParams{
73 | URL: endpointURL,
74 | Method: http.MethodPost,
75 | Headers: headers,
76 | MaxAttempts: options.MaxAttempts,
77 | BodyProperties: options.BodyProperties,
78 | QueryParameters: options.QueryParameters,
79 | Client: options.HTTPClient,
80 | Request: request,
81 | Response: &response,
82 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
83 | },
84 | ); err != nil {
85 | return nil, err
86 | }
87 | return response, nil
88 | }
89 |
90 | // Returns all users.
91 | func (c *Client) ListOrdered(
92 | ctx context.Context,
93 | request *v2.UserListOrderedRequest,
94 | opts ...option.RequestOption,
95 | ) (*v2.UserListResponse, error) {
96 | options := core.NewRequestOptions(opts...)
97 | baseURL := internal.ResolveBaseURL(
98 | options.BaseURL,
99 | c.baseURL,
100 | "https://api.getzep.com/api/v2",
101 | )
102 | endpointURL := baseURL + "/users-ordered"
103 | queryParams, err := internal.QueryValues(request)
104 | if err != nil {
105 | return nil, err
106 | }
107 | if len(queryParams) > 0 {
108 | endpointURL += "?" + queryParams.Encode()
109 | }
110 | headers := internal.MergeHeaders(
111 | c.header.Clone(),
112 | options.ToHeader(),
113 | )
114 | errorCodes := internal.ErrorCodes{
115 | 400: func(apiError *core.APIError) error {
116 | return &v2.BadRequestError{
117 | APIError: apiError,
118 | }
119 | },
120 | 500: func(apiError *core.APIError) error {
121 | return &v2.InternalServerError{
122 | APIError: apiError,
123 | }
124 | },
125 | }
126 |
127 | var response *v2.UserListResponse
128 | if err := c.caller.Call(
129 | ctx,
130 | &internal.CallParams{
131 | URL: endpointURL,
132 | Method: http.MethodGet,
133 | Headers: headers,
134 | MaxAttempts: options.MaxAttempts,
135 | BodyProperties: options.BodyProperties,
136 | QueryParameters: options.QueryParameters,
137 | Client: options.HTTPClient,
138 | Response: &response,
139 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
140 | },
141 | ); err != nil {
142 | return nil, err
143 | }
144 | return response, nil
145 | }
146 |
147 | // Returns a user.
148 | func (c *Client) Get(
149 | ctx context.Context,
150 | // The user_id of the user to get.
151 | userID string,
152 | opts ...option.RequestOption,
153 | ) (*v2.User, error) {
154 | options := core.NewRequestOptions(opts...)
155 | baseURL := internal.ResolveBaseURL(
156 | options.BaseURL,
157 | c.baseURL,
158 | "https://api.getzep.com/api/v2",
159 | )
160 | endpointURL := internal.EncodeURL(
161 | baseURL+"/users/%v",
162 | userID,
163 | )
164 | headers := internal.MergeHeaders(
165 | c.header.Clone(),
166 | options.ToHeader(),
167 | )
168 | errorCodes := internal.ErrorCodes{
169 | 404: func(apiError *core.APIError) error {
170 | return &v2.NotFoundError{
171 | APIError: apiError,
172 | }
173 | },
174 | 500: func(apiError *core.APIError) error {
175 | return &v2.InternalServerError{
176 | APIError: apiError,
177 | }
178 | },
179 | }
180 |
181 | var response *v2.User
182 | if err := c.caller.Call(
183 | ctx,
184 | &internal.CallParams{
185 | URL: endpointURL,
186 | Method: http.MethodGet,
187 | Headers: headers,
188 | MaxAttempts: options.MaxAttempts,
189 | BodyProperties: options.BodyProperties,
190 | QueryParameters: options.QueryParameters,
191 | Client: options.HTTPClient,
192 | Response: &response,
193 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
194 | },
195 | ); err != nil {
196 | return nil, err
197 | }
198 | return response, nil
199 | }
200 |
201 | // Deletes a user.
202 | func (c *Client) Delete(
203 | ctx context.Context,
204 | // User ID
205 | userID string,
206 | opts ...option.RequestOption,
207 | ) (*v2.SuccessResponse, error) {
208 | options := core.NewRequestOptions(opts...)
209 | baseURL := internal.ResolveBaseURL(
210 | options.BaseURL,
211 | c.baseURL,
212 | "https://api.getzep.com/api/v2",
213 | )
214 | endpointURL := internal.EncodeURL(
215 | baseURL+"/users/%v",
216 | userID,
217 | )
218 | headers := internal.MergeHeaders(
219 | c.header.Clone(),
220 | options.ToHeader(),
221 | )
222 | errorCodes := internal.ErrorCodes{
223 | 404: func(apiError *core.APIError) error {
224 | return &v2.NotFoundError{
225 | APIError: apiError,
226 | }
227 | },
228 | 500: func(apiError *core.APIError) error {
229 | return &v2.InternalServerError{
230 | APIError: apiError,
231 | }
232 | },
233 | }
234 |
235 | var response *v2.SuccessResponse
236 | if err := c.caller.Call(
237 | ctx,
238 | &internal.CallParams{
239 | URL: endpointURL,
240 | Method: http.MethodDelete,
241 | Headers: headers,
242 | MaxAttempts: options.MaxAttempts,
243 | BodyProperties: options.BodyProperties,
244 | QueryParameters: options.QueryParameters,
245 | Client: options.HTTPClient,
246 | Response: &response,
247 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
248 | },
249 | ); err != nil {
250 | return nil, err
251 | }
252 | return response, nil
253 | }
254 |
255 | // Updates a user.
256 | func (c *Client) Update(
257 | ctx context.Context,
258 | // User ID
259 | userID string,
260 | request *v2.UpdateUserRequest,
261 | opts ...option.RequestOption,
262 | ) (*v2.User, error) {
263 | options := core.NewRequestOptions(opts...)
264 | baseURL := internal.ResolveBaseURL(
265 | options.BaseURL,
266 | c.baseURL,
267 | "https://api.getzep.com/api/v2",
268 | )
269 | endpointURL := internal.EncodeURL(
270 | baseURL+"/users/%v",
271 | userID,
272 | )
273 | headers := internal.MergeHeaders(
274 | c.header.Clone(),
275 | options.ToHeader(),
276 | )
277 | headers.Set("Content-Type", "application/json")
278 | errorCodes := internal.ErrorCodes{
279 | 400: func(apiError *core.APIError) error {
280 | return &v2.BadRequestError{
281 | APIError: apiError,
282 | }
283 | },
284 | 404: func(apiError *core.APIError) error {
285 | return &v2.NotFoundError{
286 | APIError: apiError,
287 | }
288 | },
289 | 500: func(apiError *core.APIError) error {
290 | return &v2.InternalServerError{
291 | APIError: apiError,
292 | }
293 | },
294 | }
295 |
296 | var response *v2.User
297 | if err := c.caller.Call(
298 | ctx,
299 | &internal.CallParams{
300 | URL: endpointURL,
301 | Method: http.MethodPatch,
302 | Headers: headers,
303 | MaxAttempts: options.MaxAttempts,
304 | BodyProperties: options.BodyProperties,
305 | QueryParameters: options.QueryParameters,
306 | Client: options.HTTPClient,
307 | Request: request,
308 | Response: &response,
309 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
310 | },
311 | ); err != nil {
312 | return nil, err
313 | }
314 | return response, nil
315 | }
316 |
317 | // Deprecated: Use Get User Edges instead.
318 | func (c *Client) GetFacts(
319 | ctx context.Context,
320 | // The user_id of the user to get.
321 | userID string,
322 | opts ...option.RequestOption,
323 | ) (*v2.FactsResponse, error) {
324 | options := core.NewRequestOptions(opts...)
325 | baseURL := internal.ResolveBaseURL(
326 | options.BaseURL,
327 | c.baseURL,
328 | "https://api.getzep.com/api/v2",
329 | )
330 | endpointURL := internal.EncodeURL(
331 | baseURL+"/users/%v/facts",
332 | userID,
333 | )
334 | headers := internal.MergeHeaders(
335 | c.header.Clone(),
336 | options.ToHeader(),
337 | )
338 | errorCodes := internal.ErrorCodes{
339 | 404: func(apiError *core.APIError) error {
340 | return &v2.NotFoundError{
341 | APIError: apiError,
342 | }
343 | },
344 | 500: func(apiError *core.APIError) error {
345 | return &v2.InternalServerError{
346 | APIError: apiError,
347 | }
348 | },
349 | }
350 |
351 | var response *v2.FactsResponse
352 | if err := c.caller.Call(
353 | ctx,
354 | &internal.CallParams{
355 | URL: endpointURL,
356 | Method: http.MethodGet,
357 | Headers: headers,
358 | MaxAttempts: options.MaxAttempts,
359 | BodyProperties: options.BodyProperties,
360 | QueryParameters: options.QueryParameters,
361 | Client: options.HTTPClient,
362 | Response: &response,
363 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
364 | },
365 | ); err != nil {
366 | return nil, err
367 | }
368 | return response, nil
369 | }
370 |
371 | // Returns a user's node.
372 | func (c *Client) GetNode(
373 | ctx context.Context,
374 | // The user_id of the user to get the node for.
375 | userID string,
376 | opts ...option.RequestOption,
377 | ) (*v2.UserNodeResponse, error) {
378 | options := core.NewRequestOptions(opts...)
379 | baseURL := internal.ResolveBaseURL(
380 | options.BaseURL,
381 | c.baseURL,
382 | "https://api.getzep.com/api/v2",
383 | )
384 | endpointURL := internal.EncodeURL(
385 | baseURL+"/users/%v/node",
386 | userID,
387 | )
388 | headers := internal.MergeHeaders(
389 | c.header.Clone(),
390 | options.ToHeader(),
391 | )
392 | errorCodes := internal.ErrorCodes{
393 | 404: func(apiError *core.APIError) error {
394 | return &v2.NotFoundError{
395 | APIError: apiError,
396 | }
397 | },
398 | 500: func(apiError *core.APIError) error {
399 | return &v2.InternalServerError{
400 | APIError: apiError,
401 | }
402 | },
403 | }
404 |
405 | var response *v2.UserNodeResponse
406 | if err := c.caller.Call(
407 | ctx,
408 | &internal.CallParams{
409 | URL: endpointURL,
410 | Method: http.MethodGet,
411 | Headers: headers,
412 | MaxAttempts: options.MaxAttempts,
413 | BodyProperties: options.BodyProperties,
414 | QueryParameters: options.QueryParameters,
415 | Client: options.HTTPClient,
416 | Response: &response,
417 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
418 | },
419 | ); err != nil {
420 | return nil, err
421 | }
422 | return response, nil
423 | }
424 |
425 | // Returns all sessions for a user.
426 | func (c *Client) GetSessions(
427 | ctx context.Context,
428 | // User ID
429 | userID string,
430 | opts ...option.RequestOption,
431 | ) ([]*v2.Session, error) {
432 | options := core.NewRequestOptions(opts...)
433 | baseURL := internal.ResolveBaseURL(
434 | options.BaseURL,
435 | c.baseURL,
436 | "https://api.getzep.com/api/v2",
437 | )
438 | endpointURL := internal.EncodeURL(
439 | baseURL+"/users/%v/sessions",
440 | userID,
441 | )
442 | headers := internal.MergeHeaders(
443 | c.header.Clone(),
444 | options.ToHeader(),
445 | )
446 | errorCodes := internal.ErrorCodes{
447 | 500: func(apiError *core.APIError) error {
448 | return &v2.InternalServerError{
449 | APIError: apiError,
450 | }
451 | },
452 | }
453 |
454 | var response []*v2.Session
455 | if err := c.caller.Call(
456 | ctx,
457 | &internal.CallParams{
458 | URL: endpointURL,
459 | Method: http.MethodGet,
460 | Headers: headers,
461 | MaxAttempts: options.MaxAttempts,
462 | BodyProperties: options.BodyProperties,
463 | QueryParameters: options.QueryParameters,
464 | Client: options.HTTPClient,
465 | Response: &response,
466 | ErrorDecoder: internal.NewErrorDecoder(errorCodes),
467 | },
468 | ); err != nil {
469 | return nil, err
470 | }
471 | return response, nil
472 | }
473 |
--------------------------------------------------------------------------------