Coder Social home page Coder Social logo

meilisearch / meilisearch-swift Goto Github PK

View Code? Open in Web Editor NEW
90.0 90.0 26.0 1.88 MB

Swift client for the Meilisearch API

Home Page: https://www.meilisearch.com

License: MIT License

Swift 99.33% Shell 0.14% Ruby 0.50% Dockerfile 0.03%
client sdk swift vapor

meilisearch-swift's Introduction

meilisearch-swift

Meilisearch Swift

GitHub Workflow Status License Bors enabled

โšก The Meilisearch API client written for Swift ๐ŸŽ

Meilisearch Swift is the Meilisearch API client for Swift developers.

Meilisearch is an open-source search engine. Learn more about Meilisearch.

Table of Contents

๐Ÿ“– Documentation

For more information about this API see our Swift documentation.

For more information about Meilisearch see our Documentation or our API References.

โšก Supercharge your Meilisearch experience

Say goodbye to server deployment and manual updates with Meilisearch Cloud. Get started with a 14-day free trial! No credit card required.

๐Ÿ”ง Installation

With Cocoapods

CocoaPods is a dependency manager for Cocoa projects.

Meilisearch-Swift is available through CocoaPods. To install it, add the following line to your Podfile:

pod 'MeiliSearch'

Then, run the following command:

pod install

This will download the latest version of Meilisearch pod and prepare the xcworkspace.

With the Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.

Once you have your Swift package set up, adding Meilisearch-Swift as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/meilisearch/meilisearch-swift.git", from: "0.16.0")
]

Run Meilisearch

There are many easy ways to download and run a Meilisearch instance.

For example, using the curl command in your Terminal:

#Install Meilisearch
curl -L https://install.meilisearch.com | sh

# Launch Meilisearch
./meilisearch --master-key=masterKey

NB: you can also download Meilisearch from Homebrew or APT or even run it using Docker.

๐ŸŽฌ Getting started

To do a simple search using the client, you can create a Swift script like this:

Add documents

    import MeiliSearch

    // Create a new client instance of Meilisearch.
    // Note: You must provide a fully qualified URL including scheme.
    let client = try! MeiliSearch(host: "http://localhost:7700")

    struct Movie: Codable, Equatable {
        let id: Int
        let title: String
        let genres: [String]
    }

    let movies: [Movie] = [
        Movie(id: 1, title: "Carol", genres: ["Romance", "Drama"]),
        Movie(id: 2, title: "Wonder Woman", genres: ["Action", "Adventure"]),
        Movie(id: 3, title: "Life of Pi", genres: ["Adventure", "Drama"]),
        Movie(id: 4, title: "Mad Max: Fury Road", genres: ["Adventure", "Science Fiction"]),
        Movie(id: 5, title: "Moana", genres: ["Fantasy", "Action"]),
        Movie(id: 6, title: "Philadelphia", genres: ["Drama"])
    ]

    let semaphore = DispatchSemaphore(value: 0)

    // An index is where the documents are stored.
    // The uid is the unique identifier to that index.
    let index = client.index("movies")

    // If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.
    index.addDocuments(
        documents: movies,
        primaryKey: nil
    ) { result in
        switch result {
        case .success(let task):
            print(task) // => Task(uid: 0, status: Task.Status.enqueued, ...)
        case .failure(let error):
            print(error.localizedDescription)
        }
        semaphore.signal()
      }
    semaphore.wait()

With the uid of the task, you can check the status (enqueued, canceled, processing, succeeded or failed) of your documents addition using the update endpoint.

Basic Search

do {
  // Call the search function and wait for the result.
  let result: SearchResult<Movie> = try await client.index("movies").search(SearchParameters(query: "philoudelphia"))
  dump(result)
} catch {
  print(error.localizedDescription)
}

Output:

 MeiliSearch.SearchResult<SwiftWork.(unknown context at $10d9e7f3c).Movie>
  โ–ฟ hits: 1 element
    โ–ฟ SwiftWork.(unknown context at $10d9e7f3c).Movie
      - id: 6
      - title: "Philadelphia"
      โ–ฟ genres: 1 element
        - "Drama"
  - offset: 0
  - limit: 20
  - estimatedTotalHits: 1
  - facetDistribution: nil
  โ–ฟ processingTimeMs: Optional(1)
    - some: 1
  โ–ฟ query: Optional("philoudelphia")
    - some: "philoudelphia"

Since Meilisearch is typo-tolerant, the movie philadelphia is a valid search response to philoudelphia.

Note: All package APIs support closure-based results for backwards compatibility. Newer async/await variants are being added under issue 332.

Custom Search With Filters

If you want to enable filtering, you must add your attributes to the filterableAttributes index setting.

index.updateFilterableAttributes(["id", "genres"]) { result in
    // Handle Result in Closure
}

You only need to perform this operation once.

Note that MeiliSearch will rebuild your index whenever you update filterableAttributes. Depending on the size of your dataset, this might take time. You can track the process using the update status.

Then, you can perform the search:

let searchParameters = SearchParameters(
    query: "wonder",
    filter: "id > 1 AND genres = Action"
)

let response: Searchable<Meteorite> = try await index.search(searchParameters)
{
  "hits": [
    {
      "id": 2,
      "title": "Wonder Woman",
      "genres": ["Action","Adventure"]
    }
  ],
  "offset": 0,
  "limit": 20,
  "nbHits": 1,
  "processingTimeMs": 0,
  "query": "wonder"
}

๐Ÿค– Compatibility with Meilisearch

This package guarantees compatibility with version v1.x of Meilisearch, but some features may not be present. Please check the issues for more info.

๐Ÿ’ก Learn more

The following sections in our main documentation website may interest you:

โš™๏ธ Contributing

Any new contribution is more than welcome in this project!

If you want to know more about the development workflow or want to contribute, please visit our contributing guidelines for detailed instructions!

๐Ÿ“œ Demos

To try out a demo you will need to go to its directory in Demos/. In that directory you can either:

  • Open the SwiftPM project in Xcode and press run, or
  • Run swift build or swift run in the terminal.

Vapor

Please check the Vapor Demo source code here.

Perfect

Please check the Perfect Demo source code here.


Meilisearch provides and maintains many SDKs and Integration tools like this one. We want to provide everyone with an amazing search experience for any kind of project. If you want to contribute, make suggestions, or just know what's going on right now, visit us in the integration-guides repository.

meilisearch-swift's People

Contributors

alallema avatar aronbudinszky avatar berwyn avatar bidoubiwa avatar bors[bot] avatar brunoocasali avatar curquiza avatar damien-rivet avatar dependabot[bot] avatar dichotommy avatar dishant10 avatar edinapap avatar ednofedulo avatar eskombro avatar fatihyildizhan avatar felix-gohla avatar hard-coder05 avatar igorchernyshov avatar mannuch avatar matboivin avatar meili-bors[bot] avatar meili-bot avatar mgregoire254 avatar nicolasvienot avatar ppamorim avatar sherlouk avatar zt4ff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

meilisearch-swift's Issues

Instantiate the MeiliSearch client

The MeiliSearch method that instantiates the client:

  • should take a mandatory parameter: the host URL
  • should not do an HTTP call to the server

Distinct attribute missing in settings

The settings model is missing distinctAttribute

// model/settings.swift
    public init(
      rankingRules: [String],
      searchableAttributes: [String],
      displayedAttributes: [String],
      stopWords: [String],
      synonyms: [String: [String]],
      acceptNewFields: Bool) {
      self.rankingRules = rankingRules
      self.searchableAttributes = searchableAttributes
      self.displayedAttributes = displayedAttributes
      self.stopWords = stopWords
      self.synonyms = synonyms
      self.acceptNewFields = acceptNewFields
    }

Change master branch to main

Let's be allies and make this change that means a lot.

Here is a blog post that explain a little more why it's important, and how to easily do it. It will be a bit more complicated with automation, but we still should do it!

Simplify the facetsDistribution decoder?

In MeiliSearch/Model/SearchResult.swift file, here is the current decoder and the big part of the code for the facetsDistribution decoder:

extension SearchResult {

    /// Decodes the JSON to a `SearchParameters` object, sets the default value if required.
    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        hits = (try values.decodeIfPresent([T].self, forKey: .hits)) ?? []
        offset = try values.decode(Int.self, forKey: .offset)
        limit = try values.decode(Int.self, forKey: .limit)
        nbHits = try values.decode(Int.self, forKey: .nbHits)
        exhaustiveNbHits = try values.decodeIfPresent(Bool.self, forKey: .exhaustiveNbHits)
        exhaustiveFacetsCount = try values.decodeIfPresent(Bool.self, forKey: .exhaustiveFacetsCount)
        processingTimeMs = try values.decodeIfPresent(Int.self, forKey: .processingTimeMs)
        query = try values.decode(String.self, forKey: .query)

        //Behemoth ancient code below needed to dynamically decode the JSON
        if values.contains(.facetsDistribution) {
            let nested: KeyedDecodingContainer<StringKey> = try values.nestedContainer(keyedBy: StringKey.self, forKey: .facetsDistribution)
            var dic: [String: [String: Int]] = Dictionary(minimumCapacity: nested.allKeys.count)
            try nested.allKeys.forEach { (key: KeyedDecodingContainer<StringKey>.Key) in
                let facet: KeyedDecodingContainer<StringKey> = try nested.nestedContainer(keyedBy: StringKey.self, forKey: key)
                var inner: [String: Int] = Dictionary(minimumCapacity: facet.allKeys.count)
                try facet.allKeys.forEach { (innerKey: KeyedDecodingContainer<StringKey>.Key) in
                    inner[innerKey.stringValue] = try facet.decode(Int.self, forKey: innerKey)
                }
                dic[key.stringValue] = inner
            }
            facetsDistribution = dic
        } else {
            facetsDistribution = nil
        }

    }

Maybe we could find a way to improve this by making it at least more readable if it's possible ๐Ÿ™‚

Create and fill sample file to display SWIFT examples in MeiliSearch Documentation

Code Samples for MeiliSearch documentation

Introduction

As MeiliSearch grows so does its SDK's. More and more SDK's rises from the ground and they all deserve to be as well documented as the core engine itself.

The most common way for a user to understand MeiliSearch is to go to its official documentation. As of yesterday all examples in the documentation were made with cURL. Unfortunately, most of our users do not communicate with MeiliSearch directly with cURL. Which forces them to search for the specific references somewhere else (in the readme's, in the sdk's code itself,..). This makes for unnecessary friction.

Goal

We want our documentation to include all SDK's

As a first step we want all examples to be made using the most possible SDK's. As did Stripe and Algolia.

sdk_sample

examples with curl, javascript and soon enough this SDK too!

To achieve this it is expected from this SDK to create a sample file containing all the code samples needed by the documentation.

These are the steps to follow:

  • Create your sample file
  • Fill your sample file
  • Add your samples to the documentation

Create your sample file

The sample file is a yaml file added at the root of each MeiliSearch SDK.
Sample files are created based on the sample-template.yaml file.

sample-template file:

get_one_index_1: |-
list_all_indexes_1: |-
create_an_index_1: |-
...

This template is accessible publicly here or in the public directory : .vuepress/public/sample-template.yaml of the documentation.

The name of the file should be .code-samples.meilisearch.yaml

Fill your sample file

After creating the sample file with the content of the sample template you should have a file containing all the sampleId's.

get_one_index_1: |-
list_all_indexes_1: |-
create_an_index_1: |-
...

By the name of the different sampleId you should know where that code sample will be added to the documentation.

For example, create_an_index_1 is the first example in the API References of the index creation.

Using the already existing cURL example in the documentation you should see what is expected from that sample. It is very important that you look at the already existing samples in the documentation as it gives you the parameters to use in your sample to match with the rest of the documentation.

Good sample based on documentation:

create_an_index_1: |-
  client.createIndex({ uid: 'movies' })

Bad sample that does not match with the response.

create_an_index_1: |-
  client.createIndex({ uid: 'otherName' })

Each sample is expected to be written in the respective SDK language.

Javascript example:

get_one_index_1: |-
  client.getIndex('movies').show()
list_all_indexes_1: |-
  client.listIndexes()
create_an_index_1: |-
  client.createIndex({ uid: 'movies' })
  ...

The complete cURL sample file is available at the root of the documentation repository.
Every other SDK sample file should be available at the root of their respective repository.

Formatting Exception

There is one exception to the formatting rule.
When the sampleId finishes with _md it means it is expected to be written in markdown format.

JavaScript sample id with _md extension:
yaml-js-example

Add your samples to the documentation

Once your sample file is filled with the code samples, you will need to create a pull request on the documentation repository.

Open the following file in your IDE:
.vuepress/code-samples/sdks.json

And add your sample file to the list:

[
  ...
  {
    "language": "sdk-language",
    "label": "sdk-label",
    "url": "url to yaml file"
  }
]

The language key expect a supported language for the code highlight.

The label key is the name of the tab. While label and language could have been the same, it created some conflict (i.e: bash and cURL).

The url is the raw link to access your sample file. It should look like this:
https://raw.githubusercontent.com/[PROJECT]/[REPO]/.code-samples.meilisearch.yaml

Provide the library on Cocoapods

There is no way to use this library in Cocoapods based projects. The PR #62 adds a wrapper around the Swift project without the needed of changes in the code structure from SwiftPM.

Cannot change default SearchParameters

Problem

After looking through the documentation and the code, I did not find any way to change the default SearchParameters.
There is a static function for creating a query with default parameters, but I do not know how to e.g. query with a specific offset.
Am I missing something here? ๐Ÿ˜Š

Solution

One easy fix was to make the initialiser of SearchParameters public. If this is not desired, the fields could be variables instead of constants, so you could change the fields after using the query function.

Primary key should be optionnal in documents addition routes

Problem

The primaryKey parameter is not optionnal and is misleading since giving it a value of "" like the tests did makes MeiliSearch believe that the primaryKey's name is indeed "".
Because of that, documents are not added in these cases:

self.client.addDocuments(
            UID: uid,
            documents: documents,
            primaryKey: ""
        )

MeiliSearch returns the following error:

    "error": "document id is missing",

To let MeiliSearch infer the primary key one should give nil as a value to the primaryKey

self.client.addDocuments(
            UID: uid,
            documents: documents,
            primaryKey: nil
        )

Which is a lot to write and is not scalable with new parameters that could come in the next versions of MeiliSearch.

Solutions

In order to not have to write primaryKey each time document are added the parameter should become optionnal.

Implementation

This is done by adding a default value in the function parameters:

func addOrReplace(
        _ UID: String,
        _ document: Data,
        _ primaryKey: String? = nil ,
        _ completion: @escaping (Result<Update, Swift.Error>) -> Void) {
func addOrUpdate(
        _ UID: String,
        _ document: Data,
        _ primaryKey: String? = nil,
        _ completion: @escaping (Result<Update, Swift.Error>) -> Void) {
public func addDocuments(
        UID: String,
        documents: Data,
        primaryKey: String? = nil,
        _ completion: @escaping (Result<Update, Swift.Error>) -> Void) {
        self.documents.addOrReplace(
            UID,
            documents,
            primaryKey,
            completion)
    }
   public func updateDocuments(
        UID: String,
        documents: Data,
        primaryKey: String? = nil,
        _ completion: @escaping (Result<Update, Swift.Error>) -> Void) 

tests

  • Tests without Primary Key
  • Tests with Primary Key

I would like your opinion on my suggestion @ppamorim

primary Key parameter missing on index creation

Problem

The primary key should be an optionnal parameter given on index creation.

See create index reference documentation

$ curl \
  -X POST 'http://localhost:7700/indexes' \
  --data '{
    "uid": "movies",
    "primaryKey": "movie_id"
  }'

As per it works today, it does not have that parameter:

public func createIndex(
        UID: String,
        _ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
        self.indexes.create(UID, completion)
    }

    public func getOrCreateIndex(
        UID: String,
        _ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
        self.indexes.getOrCreate(UID, completion)
    }

Solution

We should add primaryKey as an optionnal parameter with a default value of nil in the following routes:

  • createIndex
  • getOrCreateIndex

UpdateIndex should return the updated index

The updateIndex method is missing a return with the updated index.

in create index this return is added this way:

case .success(let data):

                Indexes.decodeJSON(data, completion)

Add facets support

While doing #11 I noticed that the project was missing the support of faceted search. There is a PR ready for this issue waiting to be merged.

Benchmark

It would be nice to have a benchmark to see the overhead and also to check if the SDK is correctly implemented to handle parallel requests.

Create a better error handler

When doing tests the only error I recieve whenever there is an error is the following:

failed - The operation couldnโ€™t be completed. (MeiliSearch.MSError error 1.)

I have to look at meiliSearch logs to understand the problem.

The error handler should return the full body that meiliSearch provides.

More precision (by @curquiza)

We should add a basic error handler with these 2 custom exception (or error):

  • MeiliSearchApiException: MeiliSearch (HTTP) sends back a 4xx or a 5xx. There are most of the SDKs errors.
  • MeiliSearchCommunicationException: problem with communication, for example, the MeiliSearch instance is unreachable.

FYI: Even if it's not documented yet, MeiliSearch API has a great error handler, and returns this kind of JSON when an error is found:

Capture dโ€™eฬcran 2020-07-02 aฬ€ 10 18 21

It could be great to let the user access message, errorCode, errorType and errorLink.

UpdateSetting() method erase settings not updated

Description
updateSettings seems to override all attributes that are not updated.

Expected behavior
Settings before using updateSettings:

{"rankingRules":["typo","words","proximity","attribute","wordsPosition","exactness"],"distinctAttribute":null,"searchableAttributes":["*"],"displayedAttributes":["*"],"stopWords":[],"synonyms":{},"attributesForFaceting":[]}

Current behavior
Settings after using updateSettings this way:

client.updateSetting(UID: "movies", Setting(distinctAttribute: nil))
{"rankingRules":[],"distinctAttribute":"nil","searchableAttributes":["*"],"displayedAttributes":["*"],"stopWords":[],"synonyms":{},"attributesForFaceting":[]}

Environment:

  • OS: Apple OS X
  • MeiliSearch version: v.0.20.0
  • meilisearch-swift version: v0.7.0

Trying out the demos

Hello,
I'm trying to make the demo's work.
I have build the project using swift build. Then I went inside the PerfectDemo folder and following the README I tried:

swift run

This error is raised:

PerfectDemo git:(master) โœ— swift run
error: Error Domain=NSCocoaErrorDomain Code=260 "The folder โ€œmeilisearch-swiftโ€ doesnโ€™t exist." UserInfo={NSFilePath=/Users/charlottevermandel/MeiliSearch/meilisearch-swift/meilisearch-swift, NSUserStringVariant=(
    Folder
), NSUnderlyingError=0x7feac3d100b0 {Error Domain=NSOSStatusErrorDomain Code=-43 "fnfErr: File not found"}}

I probably just forgot to do a specific command but I cannot find which one.

Remove specific indexAlreadyExist error.

Problem

There is a class called CreateError that created an error called indexAlreadyExists.

This is used in the getOrCreateIndex method to determine if an index already exists:

    func getOrCreate(_ UID: String, _ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
        self.create(UID) { result in
            switch result {
            case .success(let index):
                completion(.success(index))

            case .failure(let error):
                switch error {
                case CreateError.indexAlreadyExists: // "index already exists" should be used using error.errorCode
                    self.get(UID, completion)
                default:
                    completion(.failure(error))
                }
            }
        }
    }
/**
Error type for all functions included in the `Indexes` struct.
*/
public enum CreateError: Swift.Error, Equatable {

    // MARK: Values

    /// Case the `Index` already exists in the Meilisearch instance, this error will return.
    case indexAlreadyExists

    // MARK: Codable

    static func decode(_ error: MSError) -> Swift.Error {
        let underlyingError: NSError = error.underlying as NSError
        if let msErrorResponse: MSErrorResponse = error.data {
            if underlyingError.code == 400 && msErrorResponse.errorType == "invalid_request_error" && msErrorResponse.errorCode == "index_already_exists" {
                return CreateError.indexAlreadyExists
            }
            return error

        }
        return error
    }
}

Solution

We should be able to do something like this without having to go through another error class.

    func getOrCreate(_ UID: String, _ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
        self.create(UID) { result in
            switch result {
            case .success(let index):
                completion(.success(index))
            case .failure(let error):
                switch error {
                case error.errorCode == "index_already_exists":
                    self.get(UID, completion)
                default:
                    completion(.failure(error))
                }
            }
        }
    }

Linux support

Thanks for the great library! I wasn't able to find any mention of Linux in README and there's no Linux build set up on CI either. Is Linux supported in any way or are there any plans to support it in your roadmap?

NIO Support

I started using MeiliSearch on my server-side Swift project before I came across this repository. I made a thin NIO-based wrapper that makes it quite easy to fit any server-side project.

Taking a closer look at this repo, it seems like you've chosen to not depend on NIO, which is understandable. I was wondering if you'd be open to consider having a NIO add-on directly in this repo or in a separate one. I'd be happy to contribute my code if that's something that would interest you.

Name should be optional in Index struct

Problem

When creating an Index object the name parameter is mandatory. This should be optional.

Solution

  • Make the parameter definition to optional: public let name: String?
  • Give a default value and make the parameter optional in the parameters of the init method
  • Move the name parameter after the uid parameter in the init method
import Foundation

/**
 `Index` instances is an entity that gathers a set of documents with its own settings.
 It can be comparable to a table in SQL, or a collection in MongoDB.
 */
public struct Index: Codable, Equatable {

    // MARK: Properties

    /// The index UID.
    public let UID: String

    /// The index name.
    public let name: String?

    /// The data when the index was created.
    public let createdAt: Date?

    /// The data when the index was last updated.
    public let updatedAt: Date?

    /// The primary key configured for the index.
    public let primaryKey: String?

    // MARK: Initializers

    init(
        UID: String,
        name: String? = "",
        createdAt: Date? = nil,
        updatedAt: Date? = nil,
        primaryKey: String? = nil) {
        self.name = name
        self.UID = UID
        self.createdAt = createdAt
        self.updatedAt = updatedAt
        self.primaryKey = primaryKey
    }

    enum CodingKeys: String, CodingKey {
        case name
        case UID = "uid"
        case createdAt
        case updatedAt
        case primaryKey
    }

}

UpdateIndex should return the updated index.

The updateIndex method is missing a return with the updated index.

in create index this return is added this way:

case .success(let data):
     Indexes.decodeJSON(data, completion)

Whereas in update nothing is returned.

case .success:
                completion(.success(()))

Solution

update in indexex.swift should return the index on completion.

Deploy the docs only on master

Currently, when we do a PR pointing to master, the GH Action runs the Publish Jazzy Docs task in the build job (around 5min).
This task should be in a separate GHA to run only on master.

Update Index does not work and is missing primary key

The following method: updateIndex() which is prototypes this way:

    public func updateIndex(
        UID: String,
        name: String,
        _ completion: @escaping (Result<(), Swift.Error>) -> Void) {
        self.indexes.update(UID, name, completion)
    }

Has two problems

1. It does not work

In the following we try to update an index with a new name

let updateExpectation = XCTestExpectation(description: "Update movie index")
        self.client.updateIndex(UID: self.uid, name: "random") { result in
            switch result {
            case .success(let index):
                print(index)
                updateExpectation.fulfill()
            case .failure:
                XCTFail("Failed to update movie index")
            }
        }

This results in the following error:

{"message":"Unsupported media type","errorCode":"unsupported_media_type","errorType":"invalid_request_error","errorLink":"https://docs.meilisearch.com/errors#unsupported_media_type"}

2. It is missing primary key

The primary key should be settable while updating the index. See also #35
As explained in the documentation

$ curl \
  -X PUT 'http://localhost:7700/indexes/movie_review' \
  --data '{
      "primaryKey" : "movie_review_id"
  }'

Wrong parameter on getDocuments route

Problem

The getDocuments route on meilisearch has 3 optionnal parameters:

  • offset ( default: 0 )
  • limit ( default: 20 )
  • attributesToRetrieve ( default: ["*"] )

The implementation in the swift library only has limit and made it mandatory.

Solution

We should add the two missing parameters as optionnal with their respective default value

  • offset
  • attributesToRetrieve

and make limit an optionnal parameter with a default value of 20

This is possible in two ways:

Make every query parameter a function parameter

public func getDocuments<T>(
        UID: String,
        limit: Int? = 20,
        offset: Int? = 20,
        attributesToRetrieve: String[]? = ["*"]
        _ completion: @escaping (Result<[T], Swift.Error>) -> Void)
        where T: Codable, T: Equatable {
...
    }

Make a getDocumentsParameter type

Using a parameter object

struct getDocumentsParameter: Codable {
    limit: Int?
    offset: Int?
    attributesToRetrieve: String[]?
    init(
      limit: Int? = 20,
      offset: Int? = 20,
      attributesToRetrieve: String[]? = ["*"]) {
        self.limit = limit
        self.offset = offset
       self.attributesToRetrieve = attributesToRetrieve
    }
}
public func getDocuments<T>(
        UID: String,
       _parameters: getDocumentsParameter?,
        _ completion: @escaping (Result<[T], Swift.Error>) -> Void)
        where T: Codable, T: Equatable {
...
    }

I prefer option 2. Which one do you prefer @ppamorim ?

Missing Dumps route

As described in the documentation, Meilisearch can be triggered to create dumps. This SDK doesn't have support to this at the moment.

When asking for the document identifier, only string is accepted but Int should be accepted too

Problem

When using a route like getDocument, one of the parameter is identifier. This parameter is of type string. Problem is, often the id in documents are of type Int.

Because of that the user has to change types before using that route.

Example

In the following example I have to change my type from Int to String to my my request work.

private struct Movie: Codable, Equatable {
    let id: Int
    let title: String
    let comment: String?

    enum CodingKeys: String, CodingKey {
        case id
        case title
        case comment
    }
    init(id: Int, title: String, comment: String? = nil) {
        self.id = id
        self.title = title
        self.comment = comment
    }

}

let movie = Movie(id: 1, title: "test", comment: "no comment")
client.getDocument(UID: "movies", identifier: String(movie.id))

Solution

We should accept both types.

public func getDocument<T>(
        UID: String,
        identifier: String | Int,
        _ completion: @escaping (Result<T, Swift.Error>) -> Void)
        where T: Codable, T: Equatable {
        self.documents.get(UID, identifier, completion)
    }

Add swiftlint in the CI

We should add a linter check in the CI.

  • update the CI
  • update the CONTRIBUTING.md if another linter that swiftlint is chosen

Create integrations tests for all MeiliSearch http routes

  • Client tests #32
  • Indexes tests #32
  • Documents tests #32
  • add Settings integrations tests
    • attributes for faceting tests
    • displayed attributes tests
    • distinct attributes tests
    • ranking rules tests
    • searchable attributes tests
    • settings route tests
    • stop words tests
    • synonyms tests
  • update route tests
  • search route tests
    • limit tests
    • offset tests
    • attributes to crop tests
    • cropLength tests
    • matches tests
    • attributesToHighlight tests
    • attributesToRetrieve tests
    • filters tests
    • facetsDistribution tests
    • facetFilters tests

Add bors

Title is self-explanatory. I am missing bors try here.

Should not ask user to jsonEncode when using the SDK

Problem

When using the SDK, before each call an object must be encoded in JSON. But the returned object are already decoded into the expected type. This lacks in coherence

Exemple

In the following example I had to JSONEncode my movie into the Data type before adding it to the document parameter of addDocuments.
When In the second request I use the method getOneDocument, I recieve my movie back not as a Movie type and not as a Data type.

It should either be Data to Data, or Swift type to Swift type.

    func testAddAndGetOneDocuments() {
        let movie: Movie = Movie(id: 10, title: "test", comment: "test movie")
        let documents: Data = try! JSONEncoder().encode([movie])

        let expectation = XCTestExpectation(description: "Add or replace Movies document")

        self.client.addDocuments(
            UID: uid,
            documents: documents,
            primaryKey: nil
        ) { result in
            switch result {
            case .success(let update):
                XCTAssertEqual(Update(updateId: 0), update)
                expectation.fulfill()
            case .failure(let error):
                XCTFail(error.localizedDescription)
            }
        }
        self.wait(for: [expectation], timeout: 1.0)
        sleep(1)
        let getExpectation = XCTestExpectation(description: "Add or replace Movies document")
        self.client.getDocument(
            UID: uid,
            identifier: "10"
        ) { (result: Result<Movie, Swift.Error>) in

            switch result {
            case .success(let returnedMovie):
                print("HELLO")
                print(returnedMovie)
                XCTAssertEqual(movie, returnedMovie)
                getExpectation.fulfill()
            case .failure(let error):
                XCTFail(error.localizedDescription)
            }
        }
        self.wait(for: [getExpectation], timeout: 3.0)
    }

Solution

I prefer that the user sends its data using its specific type (in this case Movie). Swift will most likely be used for front end matter so while I understand that it makes it more complex to add a "big json" to meilisearch, i'm not sure any user will use swift to add documents to meilisearch (maybe i'm wrong). And in the rare cases it does, the data will come from whatever form the user completed and thus the data will be in a given format (i.e: Movie) and not in a jsonString.

In this case, the function prototype will look like this:

public func addDocuments<T>(
        UID: String,
        documents: T,
        primaryKey: String?,
        _ completion: @escaping (Result<Update, Swift.Error>) -> Void) {
        self.documents.addOrReplace(
            UID,
            documents,
            primaryKey,
            completion)
    }

Cannot updateDocuments

Update documents fails by throwing the following error:

documentsTests.swift:214: error: -[MeiliSearchTests.DocumentsTests testUpdateDocuments] : failed - The operation couldnโ€™t be completed. (MeiliSearch.MSError error 1.)

The same tests using addDocuments works.

Any idea where it might come from?

Examples

    func testUpdateDocuments() {
        // FAILS
        let movie: Movie = Movie(id: 10, title: "test", comment: "test movie")
        let documents: Data = try! JSONEncoder().encode([movie])
        
        let expectation = XCTestExpectation(description: "Add or update Movies document")

        self.client.updateDocuments(
            UID: uid,
            documents: documents,
            primaryKey: nil
        ) { result in
            switch result {
            case .success(let update):
                XCTAssertEqual(Update(updateId: 0), update)
                expectation.fulfill()
            case .failure(let error):
                XCTFail(error.localizedDescription)
            }
        }
        self.wait(for: [expectation], timeout: 1.0)
        sleep(1)
        let getExpectation = XCTestExpectation(description: "Add or update Movies document")
        self.client.getDocument(
            UID: uid,
            identifier: "10"
        ) { (result: Result<Movie, Swift.Error>) in

            switch result {
            case .success(let returnedMovie):
                print("HELLO")
                print(returnedMovie)
                XCTAssertEqual(movie, returnedMovie)
                getExpectation.fulfill()
            case .failure(let error):
                XCTFail(error.localizedDescription)
            }
        }
        self.wait(for: [getExpectation], timeout: 3.0)
    }

getDistinctAttribute returning value with double comma

Summary

When calling the function getDistinctAttribute the result returns an invalid string that contains a double quote, like this:

"product_id"

Note the additional comma, when represented by Swift it prints as ""product_id"". The value it should return is product_id instead.

Origin

The problem comes from this line, Meilisearch returns "product_id" and Swift understand this as the full string.

let distinctAttribute: String = String(
decoding: data, as: UTF8.self)

Solution

I can't find any ideal way to parse this value, the only way I found, bit smelly, is to drop the first and last characters of the string and accept it as is.

Help

@curquiza @qdequele @bidoubiwa Any idea of what can be done? Is my solution good enough (I don't like it)?

Deletebatch has wrong api URL

Wrong URL

The delete batch route has the following api route:
POST /indexes/:index_uid/documents/delete-batch

See delete batch in the official documentation

In the code, the path is missing the /documents/ part

self.request.post(api: "/indexes/\(UID)/delete-batch", data) { result in

            switch result {
            case .success(let data):

                Documents.decodeJSON(data, completion: completion)

            case .failure(let error):
                print(error)
                completion(.failure(error))
            }

        }

Solution

Add the missing part

self.request.post(api: "/indexes/\(UID)/documents/delete-batch", data) { result in

            switch result {
            case .success(let data):

                Documents.decodeJSON(data, completion: completion)

            case .failure(let error):
                print(error)
                completion(.failure(error))
            }

        }

Run MeiliSearch Swift test against a real MeiliSearch server

As for my understanding, every single MeiliSearch test in this repository is being run on a mocked server, replacing/overriding the functions, and getting a mocked answer. Wouldn't it be better if we run a docker container with MeiliSearch's latest version and run the tests by making directly the requests to an actual MeiliSearch instance? What do you think @ppamorim ?

Push pod to cocoapods with GHA

Add a GHA to push to cocoapods with meili-bot credentials

Should be done after the merge of #118

Should be done by an internal Meili team member.

No requests works when using apiKey

Even by providing the right apiKey, when a apikey is set on the MeiliSearch server no requests made from the swift client works.

It is hard to tell why as there is no error handler but after finding the right spot it appears its because the apikey is not send to the server:

MSErrorResponse(message: "You must have an authorization token", errorCode: "missing_authorization_header", errorType: "authentication_error", errorLink: Optional("https://docs.meilisearch.com/errors#missing_authorization_header"))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.