Skip to content

Commit 73df279

Browse files
Initial commit for task end points (#691)
1 parent d7dc45d commit 73df279

File tree

6 files changed

+531
-0
lines changed

6 files changed

+531
-0
lines changed

v2/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Change Log
22

33
## [master](https://github.com/arangodb/go-driver/tree/master) (N/A)
4+
- Add tasks endpoints to v2
45

56
## [2.1.3](https://github.com/arangodb/go-driver/tree/v2.1.3) (2025-02-21)
67
- Switch to Go 1.22.11

v2/arangodb/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ type Client interface {
3636
ClientAdmin
3737
ClientAsyncJob
3838
ClientFoxx
39+
ClientTasks
3940
}

v2/arangodb/client_impl.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func newClient(connection connection.Connection) *client {
3939
c.clientAdmin = newClientAdmin(c)
4040
c.clientAsyncJob = newClientAsyncJob(c)
4141
c.clientFoxx = newClientFoxx(c)
42+
c.clientTask = newClientTask(c)
4243

4344
c.Requests = NewRequests(connection)
4445

@@ -56,6 +57,7 @@ type client struct {
5657
*clientAdmin
5758
*clientAsyncJob
5859
*clientFoxx
60+
*clientTask
5961

6062
Requests
6163
}

v2/arangodb/tasks.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// DISCLAIMER
2+
//
3+
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//
17+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
18+
19+
package arangodb
20+
21+
import (
22+
"context"
23+
)
24+
25+
// ClientTasks defines the interface for managing tasks in ArangoDB.
26+
type ClientTasks interface {
27+
// Task retrieves an existing task by its ID.
28+
// If no task with the given ID exists, a NotFoundError is returned.
29+
Task(ctx context.Context, databaseName string, id string) (Task, error)
30+
31+
// Tasks returns a list of all tasks on the server.
32+
Tasks(ctx context.Context, databaseName string) ([]Task, error)
33+
34+
// CreateTask creates a new task with the specified options.
35+
CreateTask(ctx context.Context, databaseName string, options TaskOptions) (Task, error)
36+
37+
// If a task with the given ID already exists, a Conflict error is returned.
38+
CreateTaskWithID(ctx context.Context, databaseName string, id string, options TaskOptions) (Task, error)
39+
40+
// RemoveTask deletes an existing task by its ID.
41+
RemoveTask(ctx context.Context, databaseName string, id string) error
42+
}
43+
44+
// TaskOptions contains options for creating a new task.
45+
type TaskOptions struct {
46+
// ID is an optional identifier for the task.
47+
ID *string `json:"id,omitempty"`
48+
// Name is an optional name for the task.
49+
Name *string `json:"name,omitempty"`
50+
51+
// Command is the JavaScript code to be executed.
52+
Command *string `json:"command"`
53+
54+
// Params are optional parameters passed to the command.
55+
Params interface{} `json:"params,omitempty"`
56+
57+
// Period is the interval (in seconds) at which the task runs periodically.
58+
// If zero, the task runs once after the offset.
59+
Period *int64 `json:"period,omitempty"`
60+
61+
// Offset is the delay (in milliseconds) before the task is first executed.
62+
Offset *float64 `json:"offset,omitempty"`
63+
}
64+
65+
// Task provides access to a single task on the server.
66+
type Task interface {
67+
// ID returns the ID of the task.
68+
ID() *string
69+
70+
// Name returns the name of the task.
71+
Name() *string
72+
73+
// Command returns the JavaScript code of the task.
74+
Command() *string
75+
76+
// Params returns the parameters of the task.
77+
Params(result interface{}) error
78+
79+
// Period returns the period (in seconds) of the task.
80+
Period() *int64
81+
82+
// Offset returns the offset (in milliseconds) of the task.
83+
Offset() *float64
84+
}

v2/arangodb/tasks_impl.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
// DISCLAIMER
2+
//
3+
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//
17+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
18+
//
19+
20+
package arangodb
21+
22+
import (
23+
"context"
24+
"encoding/json"
25+
"fmt"
26+
"net/http"
27+
"net/url"
28+
29+
"github.com/pkg/errors"
30+
31+
"github.com/arangodb/go-driver/v2/arangodb/shared"
32+
"github.com/arangodb/go-driver/v2/connection"
33+
)
34+
35+
type clientTask struct {
36+
client *client
37+
}
38+
39+
// newClientTask initializes a new task client with the given database name.
40+
func newClientTask(client *client) *clientTask {
41+
return &clientTask{
42+
client: client,
43+
}
44+
}
45+
46+
// will check all methods in ClientTasks are implemented with the clientTask struct.
47+
var _ ClientTasks = &clientTask{}
48+
49+
type taskResponse struct {
50+
ID string `json:"id,omitempty"`
51+
Name string `json:"name,omitempty"`
52+
Command string `json:"command,omitempty"`
53+
Params json.RawMessage `json:"params,omitempty"`
54+
Period int64 `json:"period,omitempty"`
55+
Offset float64 `json:"offset,omitempty"`
56+
}
57+
58+
func newTask(client *client, resp *taskResponse) Task {
59+
return &task{
60+
client: client,
61+
id: &resp.ID,
62+
name: &resp.Name,
63+
command: &resp.Command,
64+
params: resp.Params,
65+
period: &resp.Period,
66+
offset: &resp.Offset,
67+
}
68+
}
69+
70+
type task struct {
71+
client *client
72+
id *string
73+
name *string
74+
command *string
75+
params json.RawMessage
76+
period *int64
77+
offset *float64
78+
}
79+
80+
// Task interface implementation for the task struct.
81+
func (t *task) ID() *string {
82+
return t.id
83+
}
84+
85+
func (t *task) Name() *string {
86+
return t.name
87+
}
88+
89+
func (t *task) Command() *string {
90+
return t.command
91+
}
92+
93+
func (t *task) Params(result interface{}) error {
94+
if t.params == nil {
95+
return nil
96+
}
97+
return json.Unmarshal(t.params, result)
98+
}
99+
100+
func (t *task) Period() *int64 {
101+
return t.period
102+
}
103+
104+
func (t *task) Offset() *float64 {
105+
return t.offset
106+
}
107+
108+
// Tasks retrieves all tasks from the specified database.
109+
// Retuns a slice of Task objects representing the tasks in the database.
110+
func (c *clientTask) Tasks(ctx context.Context, databaseName string) ([]Task, error) {
111+
urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks")
112+
response := make([]taskResponse, 0) // Direct array response
113+
resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response)
114+
if err != nil {
115+
return nil, errors.WithStack(err)
116+
}
117+
switch code := resp.Code(); code {
118+
case http.StatusOK:
119+
// Convert the response to Task objects
120+
result := make([]Task, len(response))
121+
for i, task := range response {
122+
result[i] = newTask(c.client, &task)
123+
}
124+
return result, nil
125+
default:
126+
// Attempt to get error details from response headers or body
127+
return nil, shared.NewResponseStruct().AsArangoErrorWithCode(code)
128+
}
129+
}
130+
131+
// Task retrieves a specific task by its ID from the specified database.
132+
// If the task does not exist, it returns an error.
133+
// If the task exists, it returns a Task object.
134+
func (c *clientTask) Task(ctx context.Context, databaseName string, id string) (Task, error) {
135+
urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(id))
136+
response := struct {
137+
taskResponse `json:",inline"`
138+
shared.ResponseStruct `json:",inline"`
139+
}{}
140+
141+
resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response)
142+
if err != nil {
143+
return nil, errors.WithStack(err)
144+
}
145+
switch code := resp.Code(); code {
146+
case http.StatusOK:
147+
return newTask(c.client, &response.taskResponse), nil
148+
default:
149+
return nil, response.AsArangoError()
150+
}
151+
}
152+
153+
// validateTaskOptions checks if required fields in TaskOptions are set.
154+
func validateTaskOptions(options *TaskOptions) error {
155+
if options == nil {
156+
return errors.New("TaskOptions must not be nil")
157+
}
158+
if options.Command == nil {
159+
return errors.New("TaskOptions.Command must not be empty")
160+
}
161+
return nil
162+
}
163+
164+
// CreateTask creates a new task with the specified options in the given database.
165+
// If the task already exists (based on ID), it will update the existing task.
166+
// If the task does not exist, it will create a new task.
167+
// The options parameter contains the task configuration such as name, command, parameters, period, and offset.
168+
// The ID field in options is optional; if provided, it will be used as the task identifier.
169+
func (c *clientTask) CreateTask(ctx context.Context, databaseName string, options TaskOptions) (Task, error) {
170+
if err := validateTaskOptions(&options); err != nil {
171+
return nil, errors.WithStack(err)
172+
}
173+
var urlEndpoint string
174+
if options.ID != nil {
175+
urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(*options.ID))
176+
} else {
177+
urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks")
178+
}
179+
// Prepare the request body
180+
createRequest := struct {
181+
ID *string `json:"id,omitempty"`
182+
Name *string `json:"name,omitempty"`
183+
Command *string `json:"command,omitempty"`
184+
Params json.RawMessage `json:"params,omitempty"`
185+
Period *int64 `json:"period,omitempty"`
186+
Offset *float64 `json:"offset,omitempty"`
187+
}{}
188+
189+
if options.ID != nil {
190+
createRequest.ID = options.ID
191+
}
192+
if options.Name != nil {
193+
createRequest.Name = options.Name
194+
}
195+
if options.Command != nil {
196+
createRequest.Command = options.Command
197+
}
198+
if options.Period != nil {
199+
createRequest.Period = options.Period
200+
}
201+
if options.Offset != nil {
202+
createRequest.Offset = options.Offset
203+
}
204+
205+
if options.Params != nil {
206+
// Marshal Params into JSON
207+
// This allows for complex parameters to be passed as JSON objects
208+
// and ensures that the Params field is correctly formatted.
209+
raw, err := json.Marshal(options.Params)
210+
if err != nil {
211+
return nil, errors.WithStack(err)
212+
}
213+
createRequest.Params = raw
214+
}
215+
216+
response := struct {
217+
shared.ResponseStruct `json:",inline"`
218+
taskResponse `json:",inline"`
219+
}{}
220+
221+
resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &response, &createRequest)
222+
if err != nil {
223+
return nil, errors.WithStack(err)
224+
}
225+
226+
switch code := resp.Code(); code {
227+
case http.StatusCreated, http.StatusOK:
228+
return newTask(c.client, &response.taskResponse), nil
229+
default:
230+
return nil, response.AsArangoError()
231+
}
232+
}
233+
234+
// RemoveTask deletes an existing task by its ID from the specified database.
235+
// If the task is successfully removed, it returns nil.
236+
// If the task does not exist or there is an error during the removal, it returns an error.
237+
// The ID parameter is the identifier of the task to be removed.
238+
// The databaseName parameter specifies the database from which the task should be removed.
239+
// It constructs the URL endpoint for the task API and calls the DELETE method to remove the task
240+
func (c *clientTask) RemoveTask(ctx context.Context, databaseName string, id string) error {
241+
urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(id))
242+
243+
resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, nil)
244+
if err != nil {
245+
return err
246+
}
247+
248+
switch code := resp.Code(); code {
249+
case http.StatusAccepted, http.StatusOK:
250+
return nil
251+
default:
252+
return shared.NewResponseStruct().AsArangoErrorWithCode(code)
253+
}
254+
}
255+
256+
// CreateTaskWithID creates a new task with the specified ID and options.
257+
// If a task with the given ID already exists, it returns a Conflict error.
258+
// If the task does not exist, it creates a new task with the provided options.
259+
func (c *clientTask) CreateTaskWithID(ctx context.Context, databaseName string, id string, options TaskOptions) (Task, error) {
260+
// Check if task already exists
261+
existingTask, err := c.Task(ctx, databaseName, id)
262+
if err == nil && existingTask != nil {
263+
return nil, &shared.ArangoError{
264+
Code: http.StatusConflict,
265+
ErrorMessage: fmt.Sprintf("Task with ID %s already exists", id),
266+
}
267+
}
268+
269+
// Set the ID and call CreateTask
270+
options.ID = &id
271+
return c.CreateTask(ctx, databaseName, options)
272+
}

0 commit comments

Comments
 (0)