Skip to content

Commit

Permalink
Merge #425
Browse files Browse the repository at this point in the history
425: [BREAKING] Tasks Parity r=curquiza a=Sherlouk

Closes #363
Closes #366
Closes #368
Closes #254

## User/Client Facing

* Introduces Cancel Tasks API
* Introduces Delete Tasks API
* Provides compiler-safe access to task types
  * Utilising a future-proof solution avoiding errors should new task types be added without Swift support
* Provides compiler-safe access to task result details
  * Utilising a future-proof solution avoiding errors should new details be added without Swift support
* Grouped task related models into own directory
* Uses `Date` objects instead of `String` for dates within task models
* Added missing fields to existing task models
* Fixed error caused by global tasks
* Uses compiler-safe types in task query to improve safety
* Adds `total` to task results response (replacing #412)
* Removed all instances of `try!` (force try) to prevent crashes in test runner

## Breaking Changes

⚠️  This is a breaking change. Even with something as simple as moving to a enum for TaskType requires changes to clients. This PR looks to bring all task changes together meaning that clients can upgrade to this new and safe API design in one go.

* `Task.Status` has a new value `.canceled` (handle in any `switch` cases)
* `TaskType` replaces a previous String based API (use `.description` to access String)
* `TasksQuery` uses compiled types instead of String APIs (example: `"indexUpdate"` is now `.indexUpdate`)
* `enqueuedAt` now uses Date instead of ISO-8601 String

## Parity

Reviewing documentation available on MeiliSearch's website, this PR brings the `Tasks` API up to **100% parity** with existing features. It resolves many errors caused by the previous design (both architecturally but also instability in the current release), and provides future-proofness against further scope within the tasks API.

## Personal

As I am developing my own Swift based application for monitoring MeiliSearch (and it's tasks), this branch has been extremely helpful for me in exploring jobs and being able to cancel them and performing my own housekeeping.

The type safety has been delightful and actually detected a bug in my original implementation which had a typo in a task name.

Co-authored-by: James Sherlock <15193942+Sherlouk@users.noreply.github.com>
  • Loading branch information
meili-bors[bot] and Sherlouk authored Oct 11, 2023
2 parents 25e5c5b + 4bcee63 commit 978cd07
Show file tree
Hide file tree
Showing 35 changed files with 886 additions and 533 deletions.
33 changes: 21 additions & 12 deletions .code-samples.meilisearch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ async_guide_filter_by_ids_1: |-
}
}
async_guide_filter_by_statuses_1: |-
client.getTasks(params: TasksQuery(statuses: ["failed", "canceled"])) { result in
client.getTasks(params: TasksQuery(statuses: [.failed, .canceled])) { result in
switch result {
case .success(let taskResult):
print(taskResult)
Expand All @@ -142,7 +142,7 @@ async_guide_filter_by_statuses_1: |-
}
}
async_guide_filter_by_types_1: |-
client.getTasks(params: TasksQuery(types: ["dumpCreation", "indexSwap"])) { result in
client.getTasks(params: TasksQuery(types: [.dumpCreation, .indexSwap])) { result in
switch result {
case .success(let taskResult):
print(taskResult)
Expand Down Expand Up @@ -417,15 +417,6 @@ search_post_1: |-
print(error)
}
}
get_task_by_index_1: |-
client.index("movies").getTask(taskUid: 1) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
get_task_1: |-
client.getTask(taskUid: 1) { (result) in
switch result {
Expand All @@ -436,14 +427,32 @@ get_task_1: |-
}
}
get_all_tasks_1: |-
client.getTasks() { (result) in
client.getTasks { (result) in
switch result {
case .success(let tasks):
print(tasks)
case .failure(let error):
print(error)
}
}
delete_tasks_1: |-
client.deleteTasks(filter: DeleteTasksQuery(uids: [1, 2])) { (result) in
switch result {
case .success(let taskInfo):
print(taskInfo)
case .failure(let error):
print(error)
}
}
cancel_tasks_1: |-
client.cancelTasks(filter: CancelTasksQuery(uids: [1, 2])) { (result) in
switch result {
case .success(let taskInfo):
print(taskInfo)
case .failure(let error):
print(error)
}
}
get_one_key_1: |-
client.getKey(keyOrUid: "6062abda-a5aa-4414-ac91-ecd7944c0f8d") { result in
switch result {
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ To do a simple search using the client, you can create a Swift script like this:
) { result in
switch result {
case .success(let task):
print(task) // => Task(uid: 0, status: "enqueued", ...)
print(task) // => Task(uid: 0, status: Task.Status.enqueued, ...)
case .failure(let error):
print(error.localizedDescription)
}
Expand Down
28 changes: 28 additions & 0 deletions Sources/MeiliSearch/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,34 @@ public struct MeiliSearch {
self.tasks.getTasks(params: params, completion)
}

/**
Cancel any number of enqueued or processing tasks, stopping them from continuing to run

- parameter filter: The filter in which chooses which tasks will be canceled
- parameter completion: The completion closure is used to notify when the server
completes the query request, it returns a `Result` object that contains `TaskInfo`
value. If the request was successful or `Error` if a failure occurred.
*/
public func cancelTasks(
filter: CancelTasksQuery,
completion: @escaping (Result<TaskInfo, Swift.Error>) -> Void) {
self.tasks.cancelTasks(filter, completion)
}

/**
Delete a finished (succeeded, failed, or canceled) task

- parameter filter: The filter in which chooses which tasks will be deleted
- parameter completion: The completion closure is used to notify when the server
completes the query request, it returns a `Result` object that contains `TaskInfo`
value. If the request was successful or `Error` if a failure occurred.
*/
public func deleteTasks(
filter: DeleteTasksQuery,
completion: @escaping (Result<TaskInfo, Swift.Error>) -> Void) {
self.tasks.deleteTasks(filter, completion)
}

// MARK: Keys

/**
Expand Down
4 changes: 4 additions & 0 deletions Sources/MeiliSearch/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import Foundation
public extension MeiliSearch {
// MARK: Error
struct MSErrorResponse: Decodable, Encodable, Equatable {
/// A human-readable description of the error
public let message: String
/// The error code (https://www.meilisearch.com/docs/reference/errors/error_codes)
public let code: String
/// The error type (https://www.meilisearch.com/docs/reference/errors/overview#errors)
public let type: String
/// A link to the relevant section of the documentation
public let link: String?
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/MeiliSearch/Keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct Keys {
return
}
do {
let decoder = JSONDecoder()
let decoder = Constants.customJSONDecoder
let key: Key = try decoder.decode(Key.self, from: data)
completion(.success(key))
} catch {
Expand Down
131 changes: 0 additions & 131 deletions Sources/MeiliSearch/Model/Task.swift

This file was deleted.

65 changes: 65 additions & 0 deletions Sources/MeiliSearch/Model/Task/Task.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Foundation

/**
`Task` instances represent the current transaction status, use the `uid` value to
verify the status of your transaction.
*/
public struct Task: Decodable, Equatable {
/// Unique ID for the current `Task`.
public let uid: Int

/// Unique identifier of the targeted index
public let indexUid: String?

/// Returns if the task has been successful or not.
public let status: Status

/// Type of the task.
public let type: TaskType

/// Details of the task.
public let details: Details?

/// Duration of the task process.
public let duration: String?

/// Date when the task has been enqueued.
public let enqueuedAt: Date

/// Date when the task has been started.
public let startedAt: Date?

/// Date when the task has been finished, regardless of status.
public let finishedAt: Date?

/// ID of the the `Task` which caused this to be canceled.
public let canceledBy: Int?

/// Error information in case of failed update.
public let error: MeiliSearch.MSErrorResponse?

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TaskType.self, forKey: .type)

self.uid = try container.decode(Int.self, forKey: .uid)
self.indexUid = try container.decodeIfPresent(String.self, forKey: .indexUid)
self.status = try container.decode(Status.self, forKey: .status)
self.type = type
self.duration = try container.decodeIfPresent(String.self, forKey: .duration)
self.enqueuedAt = try container.decode(Date.self, forKey: .enqueuedAt)
self.startedAt = try container.decodeIfPresent(Date.self, forKey: .startedAt)
self.finishedAt = try container.decodeIfPresent(Date.self, forKey: .finishedAt)
self.canceledBy = try container.decodeIfPresent(Int.self, forKey: .canceledBy)
self.error = try container.decodeIfPresent(MeiliSearch.MSErrorResponse.self, forKey: .error)

// we ignore errors thrown by `superDecoder` to handle cases where no details are provided by the API
// for example when the type is `snapshotCreation`.
let detailsDecoder = try? container.superDecoder(forKey: .details)
self.details = try Details(decoder: detailsDecoder, type: type)
}

enum CodingKeys: String, CodingKey {
case uid, indexUid, status, type, details, duration, enqueuedAt, startedAt, finishedAt, canceledBy, error
}
}
Loading

0 comments on commit 978cd07

Please sign in to comment.