├── .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 | Zep Logo 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 | Chat on Discord 19 | Twitter Follow 20 | Go Reference 21 | Go Report Card 22 | CI 26 | 27 | CI 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 | --------------------------------------------------------------------------------