diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore index 1c30ae7..645e43e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,26 @@ +# The directory Mix will write compiled artifacts to. /_build/ + +# If you run "mix test --cover", coverage assets end up here. /cover/ + +# The directory Mix downloads your dependencies sources to. /deps/ + +# Where third-party dependencies like ExDoc output generated docs. /doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. /.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). *.ez -search-*.tar -.elixir_ls \ No newline at end of file + +# Ignore package tarball (built via "mix hex.build"). +algolia-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c3db50e..0ee8422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + ## v0.8.0 (2018-11-17) - * Allow extra HTTP headers to be passed along Algolia requests - * Add support for [Delete By](https://www.algolia.com/doc/api-reference/api-methods/delete-by/) request - * Add support for [Get Logs](https://www.algolia.com/doc/api-reference/api-methods/get-logs/) request - * Fix docs for default parameter +* Allow extra HTTP headers to be passed along Algolia requests +* Add support for [Delete By](https://www.algolia.com/doc/api-reference/api-methods/delete-by/) + request +* Add support for [Get Logs](https://www.algolia.com/doc/api-reference/api-methods/get-logs/) + request +* Fix docs for default parameter diff --git a/LICENSE b/LICENSE index 941858e..d9a10c0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,176 @@ -2012-2016 (c) Sikan He + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - http://www.apache.org/licenses/LICENSE-2.0 + 1. Definitions. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md index 5e04df3..c3a90e6 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,81 @@ -## Algolia +# Algolia [![Build Status](https://semaphoreci.com/api/v1/sikanhe/algolia-elixir/branches/master/badge.svg)](https://semaphoreci.com/sikanhe/algolia-elixir) [![Inline docs](http://inch-ci.org/github/sikanhe/algolia-elixir.svg?branch=master)](http://inch-ci.org/github/sikanhe/algolia-elixir) +[![Module Version](https://img.shields.io/hexpm/v/algolia.svg)](https://hex.pm/packages/algolia) +[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/algolia/) +[![Total Download](https://img.shields.io/hexpm/dt/algolia.svg)](https://hex.pm/packages/algolia) +[![License](https://img.shields.io/hexpm/l/algolia.svg)](https://github.com/sikanhe/algolia-elixir/blob/master/LICENSE) +[![Last Updated](https://img.shields.io/github/last-commit/sikanhe/algolia-elixir.svg)](https://github.com/sikanhe/algolia-elixir/commits/master) -This is the elixir implementation of Algolia search API, it is purely functional +This is the Elixir implementation of Algolia search API, it is purely +functional. -Add to your dependencies +## Installation +Add `:algolia` to your dependencies: ```elixir - defp deps do - [{:algolia, "~> 0.8.0"}] - end +defp deps do + [ + {:algolia, "~> 0.8.0"} + ] +end ``` -(Pre-Elixir-1.4) Add :algolia to your applications +Add `:algolia` to your applications for Elixir version before 1.4: ```elixir - def application do - [applications: [:algolia]] - end +def application do + [ + applications: [:algolia] + ] +end ``` ## Configuration -#### Using environment variables: +### Using environment variables: - ALGOLIA_APPLICATION_ID=YOUR_APPLICATION_ID - ALGOLIA_API_KEY=YOUR_API_KEY +```bash +ALGOLIA_APPLICATION_ID=YOUR_APPLICATION_ID +ALGOLIA_API_KEY=YOUR_API_KEY +``` -#### Using config: +### Using config: - config :algolia, - application_id: YOUR_APPLICATION_ID, - api_key: YOUR_API_KEY +NOTE: You must use `ADMIN API_KEY` instead of `SEARCH API_KEY` to enable write +access. -*NOTE: You must use ADMIN API_KEY instead of SEARCH API_KEY to enable write access* +```elixir +config :algolia, + application_id: YOUR_APPLICATION_ID, + api_key: YOUR_API_KEY +``` ## The Client -You don't need to initiate an index with this client unlike other OO Algolia clients. -However, Most of the client search/write functions all use the syntax +You don't need to initiate an index with this client unlike other OO Algolia +clients. However, Most of the client search/write functions all use the +syntax: - operation(index, args....) - -So you can easy emulate the index.function() syntax using piping +```elixir +operation(index, args....) +``` - "my_index" |> operation(args) +So you can easy emulate the `index.function()` syntax using piping: +```elixir +"my_index" |> operation(args) +``` ### Return values -All functions are serialized into maps before returning these responses +All functions are serialized into maps before returning these responses: - - `{:ok, response}` - - `{:error, error_code, response}` - - `{:error, "Cannot connect to Algolia"}`: The client implements retry + * `{:ok, response}` + * `{:error, error_code, response}` + * `{:error, "Cannot connect to Algolia"}`: The client implements retry strategy on all Algolia hosts with increasing timeout, It should only return this error when it has tried all 4 hosts. [**More Details here**](https://www.algolia.com/doc/rest#quick-reference). @@ -67,13 +87,13 @@ All functions are serialized into maps before returning these responses #### Searching an index ```elixir - "my_index" |> search("some query") +"my_index" |> search("some query") ``` With Options ```elixir - "my_index" |> search("some query", [attributesToRetrieve: "firstname", hitsPerPage: 20]) +"my_index" |> search("some query", [attributesToRetrieve: "firstname", hitsPerPage: 20]) ``` See all available search options [**here**](https://www.algolia.com/doc/rest#full-text-search-parameters) @@ -81,9 +101,9 @@ See all available search options [**here**](https://www.algolia.com/doc/rest#ful #### Multiple queries at once ```elixir - multi([%{index_name => "my_index1", query: "search query"}, - %{index_name => "my_index2", query: "another query", hitsPerPage: 3,}, - %{index_name => "my_index3", query: "3rd query", tagFilters: "promotion"}]) +multi([%{index_name => "my_index1", query: "search query"}, + %{index_name => "my_index2", query: "another query", hitsPerPage: 3,}, + %{index_name => "my_index3", query: "3rd query", tagFilters: "promotion"}]) ``` You can specify a strategy to optimize your multiple queries @@ -91,138 +111,137 @@ You can specify a strategy to optimize your multiple queries - `stop_if_enough_matches`: Execute the sequence of queries until the number of hits is reached by the sum of hits. ```elixir - multi([query1, query2], strategy: :stop_if_enough_matches) +multi([query1, query2], strategy: :stop_if_enough_matches) ``` ### Saving -All `save_*` operations will overrides the object at the objectID +All `save_*` operations will overrides the object at the objectID. Save a single object to index without specifying objectID, must have objectID -inside object, or use the `id_attribute` option (see below) +inside object, or use the `id_attribute` option (see below): ```elixir - "my_index" |> save_object(%{objectID: "1"}) +"my_index" |> save_object(%{objectID: "1"}) ``` -Save a single object with a given objectID +Save a single object with a given objectID: ```elixir - "my_index" |> save_object(%{title: "hello"}, "12345") +"my_index" |> save_object(%{title: "hello"}, "12345") ``` -Save multiple objects to an index +Save multiple objects to an index: ```elixir - "my_index" |> save_objects([%{objectID: "1"}, %{objectID: "2"}]) +"my_index" |> save_objects([%{objectID: "1"}, %{objectID: "2"}]) ``` ### Updating -Partially updates a single object +Partially updates a single object: ```elixir - "my_index" |> partial_update_object(%{title: "hello"}, "12345") +"my_index" |> partial_update_object(%{title: "hello"}, "12345") ``` -Update multiple objects, must have objectID in each object, or use the `id_attribute` option (see below) - +Update multiple objects, must have objectID in each object, or use the +`id_attribute` option, see below: ```elixir - "my_index" |> partial_update_objects([%{objectID: "1"}, %{objectID: "2"}]) +"my_index" |> partial_update_objects([%{objectID: "1"}, %{objectID: "2"}]) ``` Partial update by default creates a new object if an object does not exist at the -objectID, you can turn this off by passing `false` to the `:upsert?` option +objectID, you can turn this off by passing `false` to the `:upsert?` option: ```elixir - "my_index" |> partial_update_object(%{title: "hello"}, "12345", upsert?: false) - "my_index" |> partial_update_objects([%{id: "1"}, %{id: "2"}], id_attribute: :id, upsert?: false) +"my_index" |> partial_update_object(%{title: "hello"}, "12345", upsert?: false) +"my_index" |> partial_update_objects([%{id: "1"}, %{id: "2"}], id_attribute: :id, upsert?: false) ``` - ### Bonus for this Elixir client only: `id_attribute` option -All write functions such as `save_object` and `partial_update_object` comes with an `id_attribute` option that lets the you specifying an objectID from an existing field in the object, so you do not -have to generate it yourself +All write functions such as `save_object` and `partial_update_object` comes +with an `id_attribute` option that lets the you specifying an objectID from an +existing field in the object, so you do not have to generate it yourself: ```elixir - "my_index" |> save_object(%{id: "2"}, id_attribute: :id) +"my_index" |> save_object(%{id: "2"}, id_attribute: :id) ``` -It also works for batch operations, such as `save_objects` and `partial_update_objects` +It also works for batch operations, such as `save_objects` and +`partial_update_objects`: ```elixir - "my_index" |> save_objects([%{id: "1"}, %{id: "2"}], id_attribute: :id) +"my_index" |> save_objects([%{id: "1"}, %{id: "2"}], id_attribute: :id) ``` -However, this cannot be used together with an ID specifying argument together +However, this cannot be used together with an ID specifying argument together: ```elixir - "my_index" |> save_object(%{id: "1234"}, "1234", id_attribute: :id) - > Error +"my_index" |> save_object(%{id: "1234"}, "1234", id_attribute: :id) +> Error ``` ### Wait for task -All write operations can be waited on by simply piping the response into wait/1 +All write operations can be waited on by simply piping the response into `wait/1`: ```elixir - "my_index" |> save_object(%{id: "123"}) |> wait +"my_index" |> save_object(%{id: "123"}) |> wait ``` - -Since the client polls the server to check for publishing status, -You can specify a time between each tick of the poll, the default is 1000 ms +Since the client polls the server to check for publishing status, You can +specify a time between each tick of the poll, the default is 1000 ms: ```elixir - "my_index" |> save_object(%{id: "123"}) |> wait(2_000) +"my_index" |> save_object(%{id: "123"}) |> wait(2_000) ``` -You can also use the underlying wait_task function explicitly - +You can also use the underlying wait_task function explicitly: ```elixir - {:ok, %{"taskID" => task_id, "indexName" => index}} - = "my_index" |> save_object(%{id: "123"} + {:ok, %{"taskID" => task_id, "indexName" => index}} + = "my_index" |> save_object(%{id: "123"} - wait(index, task_id) + wait(index, task_id) ``` -or with option +or with option: ```elixir - wait(index, task_id, 2_000) +wait(index, task_id, 2_000) ``` ### Index related operations #### Listing all indexes - ```elixir - list_indexes() - ``` +```elixir +list_indexes() +``` #### move_index/2 -Moves an index to a new one +Moves an index to a new one: ```elixir - move_index(source_index, destination_index) +move_index(source_index, destination_index) ``` #### copy_index/2 -Copies an index to a new one +Copies an index to a new one: ```elixir - copy_index(source_index, destination_index) +copy_index(source_index, destination_index) ``` #### Clear an index ```elixir - clear_index(index) +clear_index(index) ``` ### Settings @@ -230,10 +249,10 @@ Copies an index to a new one #### get_settings/1 ```elixir - get_settings(index) +get_settings(index) ``` -Example response +Example response: ```elixir {:ok, @@ -262,13 +281,13 @@ Example response #### set_settings/2 ```elixir - set_settings(index, %{"hitsPerPage" => 20}) +set_settings(index, %{"hitsPerPage" => 20}) - > %{"updatedAt" => "2013-08-21T13:20:18.960Z", - "taskID" => 10210332. - "indexName" => "my_index"} +> %{"updatedAt" => "2013-08-21T13:20:18.960Z", + "taskID" => 10210332. + "indexName" => "my_index"} ``` -### TODOS: +## TODOS - [x] get_object - [x] save_object @@ -289,3 +308,18 @@ Example response - [ ] add_user_key - [ ] update_user_key - [ ] delete_user_key + +## Copyright and License + +Copyright (c) 2016 Sikan He + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/lib/algolia.ex b/lib/algolia.ex index 647da6a..a2d40f5 100644 --- a/lib/algolia.ex +++ b/lib/algolia.ex @@ -1,6 +1,6 @@ defmodule Algolia do @moduledoc """ - Elixir implementation of Algolia search API, using Hackney for http requests + Elixir implementation of Algolia search API, using `Hackney` for HTTP requests. """ alias Algolia.Paths @@ -76,7 +76,7 @@ defmodule Algolia do end @doc """ - Search a single index + Search a single index. """ def search(index, query, opts \\ []) do {request_options, opts} = Keyword.pop(opts, :request_options) @@ -87,7 +87,7 @@ defmodule Algolia do end @doc """ - Search for facet values + Search for facet values. Enables you to search through the values of a facet attribute, selecting only a **subset of those values that meet a given criteria**. @@ -131,6 +131,7 @@ defmodule Algolia do "processingTimeMS" => 42 } } + """ @spec search_for_facet_values(binary, binary, binary, map) :: {:ok, map} | {:error, code :: integer, message :: binary} @@ -195,7 +196,7 @@ defmodule Algolia do end @doc """ - Get an object in an index by objectID + Get an object in an index by objectID. """ def get_object(index, object_id, opts \\ []) do path = Paths.object(index, object_id) @@ -206,7 +207,7 @@ defmodule Algolia do end @doc """ - Add an Object + Add an Object. An attribute can be chosen as the objectID. """ @@ -224,7 +225,7 @@ defmodule Algolia do end @doc """ - Add multiple objects + Add multiple objects. An attribute can be chosen as the objectID. """ @@ -240,7 +241,7 @@ defmodule Algolia do @doc """ Save a single object, without objectID specified, must have objectID as - a field + a field. """ def save_object(index, object, opts \\ []) @@ -276,7 +277,7 @@ defmodule Algolia do end @doc """ - Save multiple objects + Save multiple objects. """ def save_objects(index, objects, opts \\ [id_attribute: :objectID]) when is_list(objects) do id_attribute = opts[:id_attribute] || :objectID @@ -288,7 +289,7 @@ defmodule Algolia do end @doc """ - Partially updates an object, takes option upsert: true or false + Partially updates an object, takes option upsert: true or false. """ def partial_update_object(index, object, object_id, opts \\ [upsert?: true]) do body = Jason.encode!(object) @@ -300,7 +301,7 @@ defmodule Algolia do end @doc """ - Partially updates multiple objects + Partially updates multiple objects. """ def partial_update_objects(index, objects, opts \\ [upsert?: true, id_attribute: :objectID]) do id_attribute = opts[:id_attribute] || :objectID @@ -368,7 +369,7 @@ defmodule Algolia do end @doc """ - Delete a object by its objectID + Delete a object by its objectID. """ def delete_object(index, object_id, opts \\ []) @@ -385,7 +386,7 @@ defmodule Algolia do end @doc """ - Delete multiple objects + Delete multiple objects. """ def delete_objects(index, object_ids, opts \\ []) do object_ids @@ -412,6 +413,7 @@ defmodule Algolia do iex> Algolia.delete_by("index", filters: ["score < 30"]) {:ok, %{"indexName" => "index", "taskId" => 42, "deletedAt" => "2018-10-30T15:33:13.556Z"}} + """ def delete_by(index, opts) when is_list(opts) do {request_options, opts} = Keyword.pop(opts, :request_options) @@ -446,14 +448,14 @@ defmodule Algolia do defp validate_delete_by_opts!(opts), do: opts @doc """ - List all indexes + List all indexes. """ def list_indexes do send_request(:read, %{method: :get, path: Paths.indexes()}) end @doc """ - Deletes the index + Deletes the index. """ def delete_index(index) do :write @@ -462,7 +464,7 @@ defmodule Algolia do end @doc """ - Clears all content of an index + Clears all content of an index. """ def clear_index(index) do :write @@ -471,7 +473,7 @@ defmodule Algolia do end @doc """ - Set the settings of a index + Set the settings of a index. """ def set_settings(index, settings) do body = Jason.encode!(settings) @@ -483,7 +485,7 @@ defmodule Algolia do end @doc """ - Get the settings of a index + Get the settings of a index. """ def get_settings(index) do :read @@ -492,7 +494,7 @@ defmodule Algolia do end @doc """ - Moves an index to new one + Moves an index to new one. """ def move_index(src_index, dst_index) do body = Jason.encode!(%{operation: "move", destination: dst_index}) @@ -503,7 +505,7 @@ defmodule Algolia do end @doc """ - Copies an index to a new one + Copies an index to a new one. """ def copy_index(src_index, dst_index) do body = Jason.encode!(%{operation: "copy", destination: dst_index}) @@ -540,8 +542,9 @@ defmodule Algolia do end @doc """ - Wait for a task for an index to complete - returns :ok when it's done + Wait for a task for an index to complete. + + Returns `:ok` when it's done. """ def wait_task(index, task_id, time_before_retry \\ 1000) do case send_request(:write, %{method: :get, path: Paths.task(index, task_id)}) do @@ -558,8 +561,10 @@ defmodule Algolia do end @doc """ - Convinient version of wait_task/4, accepts a response to be waited on - directly. This enables piping a operation directly into wait_task + Convenient version of `wait_task/4`, accepts a response to be waited on + directly. + + This enables piping a operation directly into `wait_task/4`. """ def wait(response = {:ok, %{"indexName" => index, "taskID" => task_id}}, time_before_retry) do with :ok <- wait_task(index, task_id, time_before_retry), do: response diff --git a/mix.exs b/mix.exs index ea4bec3..d9b371f 100644 --- a/mix.exs +++ b/mix.exs @@ -1,37 +1,57 @@ defmodule Algolia.Mixfile do use Mix.Project + @source_url "https://github.com/sikanhe/algolia-elixir" + @version "0.8.0" + def project do [ app: :algolia, - version: "0.8.0", - description: "Elixir implementation of Algolia Search API", + version: @version, elixir: "~> 1.5", package: package(), deps: deps(), - docs: [extras: ["README.md"], main: "readme"] + docs: docs() ] end def package do [ + description: "Elixir implementation of Algolia Search API", maintainers: ["Sikan He"], - licenses: ["Apache 2.0"], - links: %{"GitHub" => "https://github.com/sikanhe/algolia-elixir"} + licenses: ["Apache-2.0"], + links: %{ + "Changelog" => "https://hexdocs.pm/algolia/changelog.html", + "GitHub" => @source_url + } ] end def application do - [applications: [:logger, :hackney]] + [ + applications: [:logger, :hackney] + ] end defp deps do [ {:hackney, "~> 1.9 or ~> 1.10"}, {:jason, "~> 1.0"}, - # Docs - {:ex_doc, "~> 0.19", only: :dev}, - {:inch_ex, ">= 0.0.0", only: :dev} + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:inch_ex, ">= 0.0.0", only: :dev, runtime: false} + ] + end + + defp docs do + [ + extras: [ + "CHANGELOG.md": [title: "Changelog"], + LICENSE: [title: "License"], + "README.md": [title: "Overview"] + ], + main: "readme", + source_url: @source_url, + formatters: ["html"] ] end end diff --git a/mix.lock b/mix.lock index b48dea7..077591c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,17 +1,19 @@ %{ - "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, + "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm", "fdc6066ceeccb3aa14049ab6edf0b9af3b64ae1b0db2a92d5c52146f373bbb1c"}, + "earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm", "f8b8820099caf0d5e72ae6482d2b0da96f213cbbe2b5b2191a37966e119eaa27"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"}, + "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, + "hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e38f4a7937b6dfc5fa87403ece26b1826bc81838f09ac57fabf2f7a9885fe818"}, + "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fc1a2f7340c422650504b1662f28fdf381f34cbd30664e8491744e52c9511d40"}, + "inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "7123ca0450686a61416a06cd38e26af18fd0f8c1cff5214770a957c6e0724338"}, + "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b96c400e04b7b765c0854c05a4966323e90c0d11fee0483b1567cda079abb205"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm", "7a4c8e1115a2732a67d7624e28cf6c9f30c66711a9e92928e745c255887ba465"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm", "4f8805eb5c8a939cf2359367cb651a3180b27dfb48444846be2613d79355d65e"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm", "da1d9bef8a092cc7e1e51f1298037a5ddfb0f657fe862dfe7ba4c5807b551c29"}, } diff --git a/test/algolia_test.exs b/test/algolia_test.exs index 103191f..419ed91 100644 --- a/test/algolia_test.exs +++ b/test/algolia_test.exs @@ -4,19 +4,26 @@ defmodule AlgoliaTest do import Algolia @indexes [ - "test", "test_1", "test_2", "test_3", "test_4", - "multi_test_1", "multi_test_2", + "test", + "test_1", + "test_2", + "test_3", + "test_4", + "multi_test_1", + "multi_test_2", "delete_by_test_1", - "move_index_test_src", "move_index_test_dst", - "copy_index_src", "copy_index_dst" + "move_index_test_src", + "move_index_test_dst", + "copy_index_src", + "copy_index_dst" ] @settings_test_index "settings_test" setup_all do - on_exit fn -> + on_exit(fn -> delete_index(@settings_test_index) - end + end) @indexes |> Enum.map(&clear_index/1) @@ -29,19 +36,21 @@ defmodule AlgoliaTest do |> add_object(%{text: "hello"}) |> wait - assert {:ok, %{"text" => "hello"}} = - get_object("test_1", object_id) + assert {:ok, %{"text" => "hello"}} = get_object("test_1", object_id) end test "add multiple objects" do assert {:ok, %{"objectIDs" => ids}} = - "test_1" - |> add_objects([%{text: "add multiple test"}, %{text: "add multiple test"}, %{text: "add multiple test"}]) - |> wait + "test_1" + |> add_objects([ + %{text: "add multiple test"}, + %{text: "add multiple test"}, + %{text: "add multiple test"} + ]) + |> wait for id <- ids do - assert {:ok, %{"text" => "add multiple test"}} = - get_object("test_1", id) + assert {:ok, %{"text" => "add multiple test"}} = get_object("test_1", id) end end @@ -50,8 +59,9 @@ defmodule AlgoliaTest do end test "wait task" do - :rand.seed(:exs1024, :erlang.timestamp) - object_id = :rand.uniform(1000000) |> to_string + :rand.seed(:exs1024, :erlang.timestamp()) + object_id = :rand.uniform(1_000_000) |> to_string + {:ok, %{"objectID" => ^object_id, "taskID" => task_id}} = save_object("test_1", %{}, object_id) @@ -61,8 +71,8 @@ defmodule AlgoliaTest do end test "save one object, and then read it, using wait_task pipeing" do - :rand.seed(:exs1024, :erlang.timestamp) - id = :rand.uniform(1000000) |> to_string + :rand.seed(:exs1024, :erlang.timestamp()) + id = :rand.uniform(1_000_000) |> to_string {:ok, %{"objectID" => object_id}} = save_object("test_1", %{}, id) @@ -87,26 +97,27 @@ defmodule AlgoliaTest do end test "search single index" do - :rand.seed(:exs1024, :erlang.timestamp) - count = :rand.uniform 10 - docs = Enum.map(1..count, &(%{id: &1, test: "search_single_index"})) + :rand.seed(:exs1024, :erlang.timestamp()) + count = :rand.uniform(10) + docs = Enum.map(1..count, &%{id: &1, test: "search_single_index"}) - {:ok, _} = save_objects("test_3", docs, id_attribute: :id) |> wait + {:ok, _} = save_objects("test_3", docs, id_attribute: :id) |> wait {:ok, %{"hits" => hits1}} = search("test_3", "search_single_index") assert length(hits1) === count end test "search with list opts" do - :rand.seed(:exs1024, :erlang.timestamp) - count = :rand.uniform 10 - docs = Enum.map(1..count, &(%{id: &1, test: "search with list opts"})) + :rand.seed(:exs1024, :erlang.timestamp()) + count = :rand.uniform(10) + docs = Enum.map(1..count, &%{id: &1, test: "search with list opts"}) {:ok, _} = save_objects("test_3", docs, id_attribute: :id) |> wait opts = [ responseFields: ["hits", "nbPages"] ] + {:ok, response} = search("test_3", "search_with_list_opts", opts) assert response["hits"] @@ -115,7 +126,7 @@ defmodule AlgoliaTest do end test "search > 1 pages" do - docs = Enum.map(1..40, &(%{id: &1, test: "search_more_than_one_pages"})) + docs = Enum.map(1..40, &%{id: &1, test: "search_more_than_one_pages"}) {:ok, _} = save_objects("test_3", docs, id_attribute: :id) |> wait @@ -127,7 +138,7 @@ defmodule AlgoliaTest do end test "search multiple indexes" do - :rand.seed(:exs1024, :erlang.timestamp) + :rand.seed(:exs1024, :erlang.timestamp()) indexes = ["multi_test_1", "multi_test_2"] @@ -141,7 +152,7 @@ defmodule AlgoliaTest do for {index, count} <- fixture_list do hits = results - |> Enum.find(fn(result) -> result["index"] == index end) + |> Enum.find(fn result -> result["index"] == index end) |> Map.fetch!("hits") assert length(hits) == count @@ -165,15 +176,23 @@ defmodule AlgoliaTest do {:ok, %{"facetHits" => hits}} = search_for_facet_values("test_4", "family", "Dip") assert [ - %{"count" => 2, "highlighted" => "Diplaziopsidaceae", "value" => "Diplaziopsidaceae"}, - %{"count" => 1, "highlighted" => "Dipteridaceae", "value" => "Dipteridaceae"} - ] == hits + %{ + "count" => 2, + "highlighted" => "Diplaziopsidaceae", + "value" => "Diplaziopsidaceae" + }, + %{ + "count" => 1, + "highlighted" => "Dipteridaceae", + "value" => "Dipteridaceae" + } + ] == hits end defp generate_fixtures_for_index(index) do - :rand.seed(:exs1024, :erlang.timestamp) + :rand.seed(:exs1024, :erlang.timestamp()) count = :rand.uniform(3) - objects = Enum.map(1..count, &(%{objectID: &1, test: "search_multiple_indexes"})) + objects = Enum.map(1..count, &%{objectID: &1, test: "search_multiple_indexes"}) save_objects(index, objects) |> wait(3_000) {index, length(objects)} end @@ -197,8 +216,8 @@ defmodule AlgoliaTest do id = "partially_update_object_upsert_true" assert {:ok, _} = - partial_update_object("test_2", %{}, id) - |> wait + partial_update_object("test_2", %{}, id) + |> wait {:ok, object} = get_object("test_2", id) assert object["objectID"] == id @@ -208,8 +227,8 @@ defmodule AlgoliaTest do id = "partial_update_upsert_false" assert {:ok, _} = - partial_update_object("test_3", %{update: "updated"}, id, upsert?: false) - |> wait + partial_update_object("test_3", %{update: "updated"}, id, upsert?: false) + |> wait assert {:error, 404, _} = get_object("test_3", id) end @@ -218,20 +237,22 @@ defmodule AlgoliaTest do objects = [%{id: "partial_update_multiple_1"}, %{id: "partial_update_multiple_2"}] assert {:ok, _} = - partial_update_objects("test_3", objects, id_attribute: :id) - |> wait + partial_update_objects("test_3", objects, id_attribute: :id) + |> wait assert {:ok, _} = get_object("test_3", "partial_update_multiple_1") assert {:ok, _} = get_object("test_3", "partial_update_multiple_2") end test "partially update multiple objects, upsert is false" do - objects = [%{id: "partial_update_multiple_1_no_upsert"}, - %{id: "partial_update_multiple_2_no_upsert"}] + objects = [ + %{id: "partial_update_multiple_1_no_upsert"}, + %{id: "partial_update_multiple_2_no_upsert"} + ] assert {:ok, _} = - partial_update_objects("test_3", objects, id_attribute: :id, upsert?: false) - |> wait + partial_update_objects("test_3", objects, id_attribute: :id, upsert?: false) + |> wait assert {:error, 404, _} = get_object("test_3", "partial_update_multiple_1_no_upsert") assert {:error, 404, _} = get_object("test_3", "partial_update_multiple_2_no_upsert") @@ -253,6 +274,7 @@ defmodule AlgoliaTest do test "delete multiple objects" do objects = [%{id: "delete_multipel_objects_1"}, %{id: "delete_multipel_objects_2"}] + {:ok, %{"objectIDs" => object_ids}} = save_objects("test_1", objects, id_attribute: :id) |> wait @@ -305,10 +327,12 @@ defmodule AlgoliaTest do attributesToIndex = ~w(foo bar baz) assert {:ok, _} = - @settings_test_index - |> set_settings(%{attributesToIndex: attributesToIndex}) - |> wait - assert {:ok, %{"attributesToIndex" => ^attributesToIndex}} = get_settings(@settings_test_index) + @settings_test_index + |> set_settings(%{attributesToIndex: attributesToIndex}) + |> wait + + assert {:ok, %{"attributesToIndex" => ^attributesToIndex}} = + get_settings(@settings_test_index) end test "move index" do